From bedc94cbc0e01c41b43b7925e8a2fef0a2eaa476 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 30 Mar 2026 17:26:21 -0400 Subject: [PATCH] gitea: add actions runner and CI/CD deploy workflow - enable gitea actions - add native host runner (nix:host label, capacity 1) - add gitea-runner system user with persisted state - add agenix-encrypted CI secrets (deploy key, git-crypt key, runner token) - authorize CI deploy key for root SSH - add build-and-deploy workflow triggered on push to main --- .gitea/workflows/deploy.yml | 48 +++++++++++++++++++++++++++++ configuration.nix | 9 ++++++ modules/age-secrets.nix | 23 ++++++++++++++ modules/impermanence.nix | 1 + secrets/ci-deploy-key.age | Bin 0 -> 633 bytes secrets/git-crypt-key-dotfiles.age | Bin 0 -> 370 bytes secrets/gitea-runner-token.age | Bin 0 -> 269 bytes services/gitea-actions-runner.nix | 46 +++++++++++++++++++++++++++ services/gitea.nix | 1 + services/ssh.nix | 5 ++- 10 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 secrets/ci-deploy-key.age create mode 100644 secrets/git-crypt-key-dotfiles.age create mode 100644 secrets/gitea-runner-token.age create mode 100644 services/gitea-actions-runner.nix diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..439abc2 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,48 @@ +name: Build and Deploy +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: nix + steps: + - uses: https://github.com/actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build NixOS configuration + run: | + nix build .#nixosConfigurations.muffin.config.system.build.toplevel -L + + - name: Deploy via deploy-rs + run: | + eval $(ssh-agent -s) + ssh-add /run/agenix/ci-deploy-key + nix run github:serokell/deploy-rs -- .#muffin --ssh-opts="-o StrictHostKeyChecking=no" + + - name: Health check + run: | + sleep 10 + ssh -i /run/agenix/ci-deploy-key -o StrictHostKeyChecking=no root@server-public \ + "systemctl is-active gitea && systemctl is-active caddy && systemctl is-active continuwuity && systemctl is-active coturn" + + - name: Notify success + if: success() + run: | + curl -sf -X POST \ + "https://ntfy.sigkill.computer/deployments" \ + -H "Title: [muffin] Deploy succeeded" \ + -H "Priority: default" \ + -H "Tags: white_check_mark" \ + -d "server-config deployed from commit ${GITHUB_SHA::8}" + + - name: Notify failure + if: failure() + run: | + curl -sf -X POST \ + "https://ntfy.sigkill.computer/deployments" \ + -H "Title: [muffin] Deploy FAILED" \ + -H "Priority: urgent" \ + -H "Tags: rotating_light" \ + -d "server-config deploy failed at commit ${GITHUB_SHA::8}" diff --git a/configuration.nix b/configuration.nix index 9af329f..1a002e1 100644 --- a/configuration.nix +++ b/configuration.nix @@ -26,6 +26,7 @@ ./services/caddy.nix ./services/immich.nix ./services/gitea.nix + ./services/gitea-actions-runner.nix ./services/minecraft.nix ./services/wg.nix @@ -249,6 +250,14 @@ users.groups.${service_configs.media_group} = { }; + users.users.gitea-runner = { + isSystemUser = true; + group = "gitea-runner"; + home = "/var/lib/gitea-runner"; + description = "Gitea Actions CI runner"; + }; + users.groups.gitea-runner = { }; + users.users.${username} = { isNormalUser = true; extraGroups = [ diff --git a/modules/age-secrets.nix b/modules/age-secrets.nix index 81cb4be..6e527bf 100644 --- a/modules/age-secrets.nix +++ b/modules/age-secrets.nix @@ -128,5 +128,28 @@ group = "continuwuity"; }; + # CI deploy SSH key + ci-deploy-key = { + file = ../secrets/ci-deploy-key.age; + mode = "0400"; + owner = "gitea-runner"; + group = "gitea-runner"; + }; + + # Git-crypt symmetric key for dotfiles repo + git-crypt-key-dotfiles = { + file = ../secrets/git-crypt-key-dotfiles.age; + mode = "0400"; + owner = "gitea-runner"; + group = "gitea-runner"; + }; + + # Gitea Actions runner registration token + gitea-runner-token = { + file = ../secrets/gitea-runner-token.age; + mode = "0400"; + owner = "gitea-runner"; + group = "gitea-runner"; + }; }; } diff --git a/modules/impermanence.nix b/modules/impermanence.nix index 86d36f0..0eee73c 100644 --- a/modules/impermanence.nix +++ b/modules/impermanence.nix @@ -24,6 +24,7 @@ # ZFS cache directory - persisting the directory instead of the file # avoids "device busy" errors when ZFS atomically updates the cache "/etc/zfs" + "/var/lib/gitea-runner" ]; files = [ diff --git a/secrets/ci-deploy-key.age b/secrets/ci-deploy-key.age new file mode 100644 index 0000000000000000000000000000000000000000..da1e327136fcefdef7b976b52a5a8676802beb72 GIT binary patch literal 633 zcmZQ@_Y83kiVO&0*q9+y{cGa(H<~F=?`o}F=p{JY;ap12`4cafG0v;=S*YamD`|!DIbII4gEc-M$R<1)=lsRrif2GgNtQ+cn3oB|Tg=!XUXi{IR{eso8UwG@{GdGyj zROi%r8Q68rnYlsxbl0N0X@8sNo_!c!u=j`NdHn1}l|B2NC!9I)<8%k&3V!0HbR(>rQ@7?<8#&QOzhvsC-CKM(XT;un9k-#{){jN^?7v34 z?Vrx_OqrxJ(Rh|mpSqmTH2#H5DJKqG3Y`|I{Xt7_#>IHm`-1gB)^%b(vahdjel=4# zYxaKm!#AXFn-xbbs$cv}h4IvdS%!uzg6=m9zfRp;{U?8eSY_GOBKtz?LnfR@&%c>j z^+tQ&x@*?L4+Tp3G8UGfz1@~SH9;@l`+i%=C!TAIS1ftLJblsKcdxyF=1h6%#pwIV zYSy-C9N(I!JZcT$eUbU^k>Hi3CQ~y*CoS16?6voeiT>4Jcc(C=tzcZ0?V2hgbM*T< zhX?=bte;8O<-Ya5@pp>ftujUxc;0LSfbr%r6mG;UTD zyQaNev)Va-N7dHMjn!#6s(13r|A21sSJUclroVf~D!(N+_fsn04W1<{D&L58 z&P!4HzV1U<`uz)=n%a)_9%^FxQl>aPs80UP0t=m`-#P#0c9n7@yX6T@&8PqXJ}x_c literal 0 HcmV?d00001 diff --git a/secrets/git-crypt-key-dotfiles.age b/secrets/git-crypt-key-dotfiles.age new file mode 100644 index 0000000000000000000000000000000000000000..1483fad1f44e3c4a6a9569f6c3bb2e17868b22e4 GIT binary patch literal 370 zcmZQ@_Y83kiVO&0_}Y^$$@pN;+nhxrw{jm|n33LaY(t{Bw~vp}w;5}uvV7%G{Dy6+{kyqL|h-}K`Xzl(b(9JiRiAdk_`5{LRYi6mNnhG3Rw_LNeseIR-&pzqAQGJ`P z-F~-Wu5J3g>DE-!%7fEXKfctN)9QS6zxM6`+oDNkA8x$gZSpqoS%16ZyN7(+;~ps5 g@-$?5if*`kQ6lQLQ-O~6R*x!g*Gge+)?<6J0q%#mtN;K2 literal 0 HcmV?d00001 diff --git a/secrets/gitea-runner-token.age b/secrets/gitea-runner-token.age new file mode 100644 index 0000000000000000000000000000000000000000..8f687a8a5cdb7bf7a6195fc6dea78a2f21ef4e13 GIT binary patch literal 269 zcmZQ@_Y83kiVO&02+h=rK6P#0)2^`HAJ6AS&e+uz6=byOUhneV<=#Gvo~Hk6W6{4P zar}v^dEuKS*HpdMEjH7VG1>F|;F*;FUMGUC*py9eD=A%hAc|XU)pVxo2JSXfPk+xa zJ@w_ag6_fdeh$aC*z~5Jbj*HUasElp(ImsOHTCmuzk0mgB|YikOWW)@2dAYm8E;$s zzklJDH&aelC&@mQH;_wylDjee;5p8eUG3ff6B}8Zl<&zF9=fsni24_%-4Dx_RqvMy z7r5Lh#-e?BnY5wdtDST5xKqQ~XIb>mK3~e*cG*E;mVDKmjAhF|3go?z+pG8~@}9W0 f^(47amG4@c{=V?Pyza!@ZOZ3%@OHglvUmaj)L@9! literal 0 HcmV?d00001 diff --git a/services/gitea-actions-runner.nix b/services/gitea-actions-runner.nix new file mode 100644 index 0000000..686063a --- /dev/null +++ b/services/gitea-actions-runner.nix @@ -0,0 +1,46 @@ +{ + config, + lib, + pkgs, + service_configs, + ... +}: +{ + services.gitea-actions-runner.instances.muffin = { + enable = true; + name = "muffin"; + url = config.services.gitea.settings.server.ROOT_URL; + tokenFile = config.age.secrets.gitea-runner-token.path; + labels = [ "nix:host" ]; + hostPackages = with pkgs; [ + bash + coreutils + curl + gawk + git + git-crypt + gnugrep + gnused + jq + nix + nodejs + openssh + ]; + settings = { + runner = { + capacity = 1; + timeout = "3h"; + }; + }; + }; + + # Override DynamicUser to use our static gitea-runner user + systemd.services."gitea-runner-muffin" = { + serviceConfig = { + DynamicUser = lib.mkForce false; + User = "gitea-runner"; + Group = "gitea-runner"; + }; + environment.GIT_SSH_COMMAND = "ssh -i /run/agenix/ci-deploy-key -o StrictHostKeyChecking=no"; + }; +} diff --git a/services/gitea.nix b/services/gitea.nix index dff1abe..907abe5 100644 --- a/services/gitea.nix +++ b/services/gitea.nix @@ -37,6 +37,7 @@ }; # only I shall use gitea service.DISABLE_REGISTRATION = true; + actions.ENABLED = true; }; }; diff --git a/services/ssh.nix b/services/ssh.nix index d5b0730..e0f2a4f 100644 --- a/services/ssh.nix +++ b/services/ssh.nix @@ -31,5 +31,8 @@ # used for deploying configs to server users.users.root.openssh.authorizedKeys.keys = - config.users.users.${username}.openssh.authorizedKeys.keys; + config.users.users.${username}.openssh.authorizedKeys.keys + ++ [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC5ZYN6idL/w/mUIfPOH1i+Q/SQXuzAMQUEuWpipx1Pc ci-deploy@muffin" + ]; }