From a341b9cfda7a8d164a969faf789840a45a24e6cd Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Tue, 24 Feb 2026 18:49:23 +0100 Subject: [PATCH] Implemented update functionallity --- .dockerignore | 25 ++++++++ .gitea/workflows/ci.yml | 26 +++++++++ .gitignore | 5 ++ .idea/.idea.ServiceUpdater/.idea/.gitignore | 15 +++++ .../.idea.ServiceUpdater/.idea/encodings.xml | 4 ++ .../.idea/indexLayout.xml | 8 +++ .idea/.idea.ServiceUpdater/.idea/vcs.xml | 6 ++ Dockerfile | 26 +++++++++ Program.cs | 26 +++++++++ Properties/launchSettings.json | 15 +++++ ServiceUpdater.csproj | 16 +++++ ServiceUpdater.slnx | 3 + UpdateWorker.cs | 58 +++++++++++++++++++ UpdaterConfig.cs | 5 ++ appsettings.Development.json | 8 +++ appsettings.json | 9 +++ 16 files changed, 255 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .idea/.idea.ServiceUpdater/.idea/.gitignore create mode 100644 .idea/.idea.ServiceUpdater/.idea/encodings.xml create mode 100644 .idea/.idea.ServiceUpdater/.idea/indexLayout.xml create mode 100644 .idea/.idea.ServiceUpdater/.idea/vcs.xml create mode 100644 Dockerfile create mode 100644 Program.cs create mode 100644 Properties/launchSettings.json create mode 100644 ServiceUpdater.csproj create mode 100644 ServiceUpdater.slnx create mode 100644 UpdateWorker.cs create mode 100644 UpdaterConfig.cs create mode 100644 appsettings.Development.json create mode 100644 appsettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..7b8ef73 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,26 @@ +name: Updater CI/CD + +on: + push: + tags: [ "*" ] + +jobs: + publish: + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.leon-hoppe.de \ + -u leon.hoppe --password-stdin + + - name: Build Docker image + run: | + docker build -t git.leon-hoppe.de/leon.hoppe/updater:latest . + + - name: Push Docker image + run: | + docker push git.leon-hoppe.de/leon.hoppe/updater:latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.idea/.idea.ServiceUpdater/.idea/.gitignore b/.idea/.idea.ServiceUpdater/.idea/.gitignore new file mode 100644 index 0000000..559cc3a --- /dev/null +++ b/.idea/.idea.ServiceUpdater/.idea/.gitignore @@ -0,0 +1,15 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.ServiceUpdater.iml +/modules.xml +/contentModel.xml +/projectSettingsUpdater.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.idea.ServiceUpdater/.idea/encodings.xml b/.idea/.idea.ServiceUpdater/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.ServiceUpdater/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.ServiceUpdater/.idea/indexLayout.xml b/.idea/.idea.ServiceUpdater/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.ServiceUpdater/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.ServiceUpdater/.idea/vcs.xml b/.idea/.idea.ServiceUpdater/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.ServiceUpdater/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..38a923f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +RUN apt-get update && apt-get install -y --no-install-recommends \ + clang \ + zlib1g-dev \ + libc6-dev \ + && rm -rf /var/lib/apt/lists/* +COPY . . +RUN dotnet publish ServiceUpdater.csproj \ + -c Release \ + -o /app/publish \ + -p:PublishAot=true \ + -p:StripSymbols=true \ + -p:InvariantGlobalization=true \ + -r linux-x64 \ + --self-contained true + +FROM debian:bookworm-slim AS final +WORKDIR /app +RUN apt-get update && apt-get install -y --no-install-recommends \ + docker.io \ + && rm -rf /var/lib/apt/lists/* +COPY --from=build /app/publish/ServiceUpdater /app +COPY --from=build /app/publish/appsettings.json /app +RUN chmod +x /app/ServiceUpdater +ENTRYPOINT ["/app/ServiceUpdater"] diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..8b95c99 --- /dev/null +++ b/Program.cs @@ -0,0 +1,26 @@ +using ServiceUpdater; + +var builder = WebApplication.CreateSlimBuilder(args); + +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +builder.Services.AddSingleton(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) { + app.MapOpenApi(); +} + +app.MapGet("update/{service}", async (UpdateWorker worker, string service, HttpContext context) => { + context.Response.Headers.Append("Content-Type", "text/event-stream"); + + await foreach (var line in worker.UpdateService(service)) { + await context.Response.WriteAsync(line + '\n'); + await context.Response.Body.FlushAsync(); + } +}); + +app.Run(); diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..a671c01 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "todos", + "applicationUrl": "http://localhost:5165", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ServiceUpdater.csproj b/ServiceUpdater.csproj new file mode 100644 index 0000000..7d833b9 --- /dev/null +++ b/ServiceUpdater.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + enable + enable + true + true + Linux + + + + + + + diff --git a/ServiceUpdater.slnx b/ServiceUpdater.slnx new file mode 100644 index 0000000..b57c9ac --- /dev/null +++ b/ServiceUpdater.slnx @@ -0,0 +1,3 @@ + + + diff --git a/UpdateWorker.cs b/UpdateWorker.cs new file mode 100644 index 0000000..8a6466c --- /dev/null +++ b/UpdateWorker.cs @@ -0,0 +1,58 @@ +using System.Diagnostics; + +namespace ServiceUpdater; + +public sealed class UpdateWorker(UpdaterConfig config) { + public async IAsyncEnumerable UpdateService(string serviceName) { + yield return $"Starting update for {serviceName}"; + + await foreach (var line in RunProcess(serviceName, "compose pull")) { + yield return line; + } + + yield return $"Downloaded changes for {serviceName}"; + + await foreach (var line in RunProcess(serviceName, "compose up -d --remove-orphans")) { + yield return line; + } + + yield return $"Successfully updated {serviceName}"; + } + + private async IAsyncEnumerable RunProcess(string folder, string arguments) { + var process = new Process { + StartInfo = new() { + FileName = "docker", + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = Path.Combine(config.ServicesRoot, folder) + } + }; + + process.Start(); + + var output = process.StandardOutput.ReadLineAsync(); + var error = process.StandardError.ReadLineAsync(); + + while (true) { + var completed = await Task.WhenAny(output, error); + + if (completed == output) { + var line = await output; + if (line == null) break; + yield return line; + output = process.StandardOutput.ReadLineAsync(); + } + else { + var line = await error; + if (line == null) break; + yield return "[ERR] " + line; + error = process.StandardError.ReadLineAsync(); + } + } + + await process.WaitForExitAsync(); + } +} \ No newline at end of file diff --git a/UpdaterConfig.cs b/UpdaterConfig.cs new file mode 100644 index 0000000..3bd338b --- /dev/null +++ b/UpdaterConfig.cs @@ -0,0 +1,5 @@ +namespace ServiceUpdater; + +public sealed class UpdaterConfig(IConfiguration config) { + public string ServicesRoot => config["Root"] ?? "/"; +} \ No newline at end of file diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}