## Summary
This PR implements part 1 of issue #15 (Gitea Actions):
### Changes
1. **`modules/nixos/gitea-runner.nix`** — NixOS module for `act_runner` in shell mode
- Options: `enable`, `instanceURL` (default: https://src.thehellings.com), `name` (default: hostname), `labels`, `tokenFile`
- Creates a `gitea-runner` system user and systemd service
- Auto-registers on first start using the token file
2. **Host deployments** — module enabled on:
- `jeremiah` — labels: `[self-hosted, bare-metal, host:jeremiah]`
- `isaiah` — labels: `[self-hosted, bare-metal, host:isaiah]`
- `zeke` — labels: `[self-hosted, bare-metal, host:zeke]`
- `linode` — labels: `[self-hosted, host:linode]`
3. **`.gitea/workflows/update-flake-lock.yaml`** — weekly workflow (Sunday midnight UTC)
- Runs `nix flake update` in `nixos/nix:latest` container
- Opens a PR if `flake.lock` changed, using `GITEA_TOKEN` secret
- Runs on `[self-hosted, bare-metal]` runners
### TODOs for Greg
For each host, you need to:
1. Generate a runner token in Gitea (Settings → Actions → Runners)
2. Create agenix secrets:
- `secrets/gitea-runner-jeremiah.age`
- `secrets/gitea-runner-isaiah.age`
- `secrets/gitea-runner-zeke.age`
- `secrets/gitea-runner-linode.age`
3. Uncomment the `age.secrets` and `tokenFile` lines in each host config
4. Add `GITEA_TOKEN` as a repo/org secret for the workflow
Closes part of #15
- Add modules/nixos/gitea-runner.nix: NixOS module for act_runner in
shell mode, with options for enable, instanceURL, name, labels, and
tokenFile (agenix secret path).
- Deploy gitea-runner to jeremiah, isaiah, zeke, and linode with
appropriate labels. Agenix secret placeholders left with TODOs.
- Add .gitea/workflows/update-flake-lock.yaml: weekly workflow (Sunday
midnight) that runs nix flake update and opens a PR if flake.lock
changed, using GITEA_TOKEN secret for authentication.
Closes part of #15 (NixOS shell runners + flake-lock workflow).
✅ Module properly imported in modules/nixos/default.nix
✅ExecStartPre guard (if [ ! -f .runner ]) prevents re-registration on every restart — good
✅StateDirectory = gitea-runner and dedicated system user are correct
⚠️tokenFile has no default and is not lib.mkOption ... type = lib.types.nullOr lib.types.path — meaning the module will evaluate-fail on any host where enable = false if NixOS tries to evaluate the option value. This is currently masked by lib.mkIf cfg.enable, which should protect it, but worth being aware of. Marking it lib.types.nullOr lib.types.path with default = null would be safer.
⚠️ The tokenFile lines are commented out in all four host configs, so deploying as-is will start the runner but fail to register (no token). That is fine as a staged rollout — just make sure the agenix secrets are created before uncommenting and re-deploying.
⚠️ Shell runners run workflows directly on the host (no container isolation). Labels like bare-metal are accurate, but worth noting in a comment that these runners have full host access. Consider adding PrivateTmp = true; NoNewPrivileges = true; to the systemd unit for some hardening.
update-flake-lock.yaml workflow
✅nixos/nix:latest container + experimental-features echo is the correct approach for flake commands
✅ PR-per-run with dated branch name (automated/flake-lock-YYYYMMDD) is clean
⚠️${GITHUB_SERVER_URL%/} — Gitea Actions sets GITHUB_SERVER_URL for compatibility, but double-check this actually resolves to https://src.thehellings.com in your runner environment. If not, hardcode it.
⚠️runs-on: [self-hosted, bare-metal] — this will run on Jeremiah/Isaiah/Zeke (all bare-metal). The nixos/nix:latest container image will be pulled via Docker/Podman on those hosts. Make sure container execution is enabled for act_runner (the shell runner itself does not run containers natively — act_runner in container mode is needed, or the job needs to use runs-on without a container: stanza for shell runners). This may need rethinking: shell runners can run steps directly, but the container: key tells act_runner to pull and run the whole job inside a container, which requires Docker. If these are shell-mode-only runners, drop the container: block and install nix directly on the host (it already is — these are NixOS hosts).
Recommendation on the workflow: Remove the container: block. These are NixOS hosts — nix is already available. Just run nix flake update directly in the shell runner. That also removes the Docker dependency.
Overall: Good foundation. The two items to resolve before merging:
Confirm shell runner + container: interaction (or drop the container block)
Agenix secrets need to exist before the tokenFile lines are uncommented — do not deploy with them uncommented until secrets are created
LGTM once the workflow container question is resolved. 🌌
Reviewed. Solid approach — here are my notes:
**`modules/nixos/gitea-runner.nix`**
- ✅ Module properly imported in `modules/nixos/default.nix`
- ✅ `ExecStartPre` guard (`if [ ! -f .runner ]`) prevents re-registration on every restart — good
- ✅ `StateDirectory = gitea-runner` and dedicated system user are correct
- ⚠️ `tokenFile` has no `default` and is not `lib.mkOption ... type = lib.types.nullOr lib.types.path` — meaning the module will **evaluate-fail on any host where `enable = false`** if NixOS tries to evaluate the option value. This is currently masked by `lib.mkIf cfg.enable`, which should protect it, but worth being aware of. Marking it `lib.types.nullOr lib.types.path` with `default = null` would be safer.
- ⚠️ The `tokenFile` lines are commented out in all four host configs, so deploying as-is will start the runner but fail to register (no token). That is fine as a staged rollout — just make sure the agenix secrets are created *before* uncommenting and re-deploying.
- ⚠️ Shell runners run workflows directly on the host (no container isolation). Labels like `bare-metal` are accurate, but worth noting in a comment that these runners have full host access. Consider adding `PrivateTmp = true; NoNewPrivileges = true;` to the systemd unit for some hardening.
**`update-flake-lock.yaml` workflow**
- ✅ `nixos/nix:latest` container + `experimental-features` echo is the correct approach for flake commands
- ✅ PR-per-run with dated branch name (`automated/flake-lock-YYYYMMDD`) is clean
- ⚠️ `${GITHUB_SERVER_URL%/}` — Gitea Actions sets `GITHUB_SERVER_URL` for compatibility, but double-check this actually resolves to `https://src.thehellings.com` in your runner environment. If not, hardcode it.
- ⚠️ `runs-on: [self-hosted, bare-metal]` — this will run on Jeremiah/Isaiah/Zeke (all bare-metal). The `nixos/nix:latest` container image will be pulled via Docker/Podman on those hosts. Make sure container execution is enabled for act_runner (the shell runner itself does not run containers natively — act_runner in container mode is needed, or the job needs to use `runs-on` without a `container:` stanza for shell runners). **This may need rethinking**: shell runners can run steps directly, but the `container:` key tells act_runner to pull and run the whole job inside a container, which requires Docker. If these are shell-mode-only runners, drop the `container:` block and install `nix` directly on the host (it already is — these are NixOS hosts).
**Recommendation on the workflow:** Remove the `container:` block. These are NixOS hosts — `nix` is already available. Just run `nix flake update` directly in the shell runner. That also removes the Docker dependency.
**Overall:** Good foundation. The two items to resolve before merging:
1. Confirm shell runner + `container:` interaction (or drop the container block)
2. Agenix secrets need to exist before the `tokenFile` lines are uncommented — do not deploy with them uncommented until secrets are created
LGTM once the workflow container question is resolved. 🌌
🤖Rebase complete — feat/gitea-runner-nix-module has been rebased onto current main.
Conflicts resolved:
modules/nixos/gitea-runner.nix — Two implementations existed: main had a hostname-derived label approach using services.gitea-actions-runner directly; the PR had explicit options (instanceURL, name, labels) with a custom systemd service. Resolution: Kept the PR's clean options API (instanceURL, name, labels, tokenFile) but wired them into services.gitea-actions-runner (the NixOS module from main) instead of the hand-rolled systemd service. Best of both worlds.
.gitea/workflows/update-flake-lock.yaml — Main had a correct Gitea-native workflow using KLAATU_TOKEN and proper API URLs; PR version added a ConfigureIx step but used GITHUB_* variables (wrong for Gitea). Resolution: Kept main's version (correct Gitea API integration), discarded the container/GITHUB_* approach.
All host configs (isaiah, jeremiah, zeke, linode) carry over unchanged — they use the new options API with TODO comments for agenix secrets.
🤖 **Rebase complete** — `feat/gitea-runner-nix-module` has been rebased onto current `main`.
**Conflicts resolved:**
1. **`modules/nixos/gitea-runner.nix`** — Two implementations existed: main had a hostname-derived label approach using `services.gitea-actions-runner` directly; the PR had explicit options (`instanceURL`, `name`, `labels`) with a custom systemd service. **Resolution:** Kept the PR's clean options API (`instanceURL`, `name`, `labels`, `tokenFile`) but wired them into `services.gitea-actions-runner` (the NixOS module from main) instead of the hand-rolled systemd service. Best of both worlds.
2. **`.gitea/workflows/update-flake-lock.yaml`** — Main had a correct Gitea-native workflow using `KLAATU_TOKEN` and proper API URLs; PR version added a `ConfigureIx` step but used `GITHUB_*` variables (wrong for Gitea). **Resolution:** Kept main's version (correct Gitea API integration), discarded the container/GITHUB_* approach.
All host configs (`isaiah`, `jeremiah`, `zeke`, `linode`) carry over unchanged — they use the new options API with TODO comments for agenix secrets.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
This PR implements part 1 of issue #15 (Gitea Actions):
Changes
modules/nixos/gitea-runner.nix— NixOS module foract_runnerin shell modeenable,instanceURL(default: https://src.thehellings.com),name(default: hostname),labels,tokenFilegitea-runnersystem user and systemd serviceHost deployments — module enabled on:
jeremiah— labels:[self-hosted, bare-metal, host:jeremiah]isaiah— labels:[self-hosted, bare-metal, host:isaiah]zeke— labels:[self-hosted, bare-metal, host:zeke]linode— labels:[self-hosted, host:linode].gitea/workflows/update-flake-lock.yaml— weekly workflow (Sunday midnight UTC)nix flake updateinnixos/nix:latestcontainerflake.lockchanged, usingGITEA_TOKENsecret[self-hosted, bare-metal]runnersTODOs for Greg
For each host, you need to:
secrets/gitea-runner-jeremiah.agesecrets/gitea-runner-isaiah.agesecrets/gitea-runner-zeke.agesecrets/gitea-runner-linode.ageage.secretsandtokenFilelines in each host configGITEA_TOKENas a repo/org secret for the workflowCloses part of #15
Reviewed. Solid approach — here are my notes:
modules/nixos/gitea-runner.nixmodules/nixos/default.nixExecStartPreguard (if [ ! -f .runner ]) prevents re-registration on every restart — goodStateDirectory = gitea-runnerand dedicated system user are correcttokenFilehas nodefaultand is notlib.mkOption ... type = lib.types.nullOr lib.types.path— meaning the module will evaluate-fail on any host whereenable = falseif NixOS tries to evaluate the option value. This is currently masked bylib.mkIf cfg.enable, which should protect it, but worth being aware of. Marking itlib.types.nullOr lib.types.pathwithdefault = nullwould be safer.tokenFilelines are commented out in all four host configs, so deploying as-is will start the runner but fail to register (no token). That is fine as a staged rollout — just make sure the agenix secrets are created before uncommenting and re-deploying.bare-metalare accurate, but worth noting in a comment that these runners have full host access. Consider addingPrivateTmp = true; NoNewPrivileges = true;to the systemd unit for some hardening.update-flake-lock.yamlworkflownixos/nix:latestcontainer +experimental-featuresecho is the correct approach for flake commandsautomated/flake-lock-YYYYMMDD) is clean${GITHUB_SERVER_URL%/}— Gitea Actions setsGITHUB_SERVER_URLfor compatibility, but double-check this actually resolves tohttps://src.thehellings.comin your runner environment. If not, hardcode it.runs-on: [self-hosted, bare-metal]— this will run on Jeremiah/Isaiah/Zeke (all bare-metal). Thenixos/nix:latestcontainer image will be pulled via Docker/Podman on those hosts. Make sure container execution is enabled for act_runner (the shell runner itself does not run containers natively — act_runner in container mode is needed, or the job needs to useruns-onwithout acontainer:stanza for shell runners). This may need rethinking: shell runners can run steps directly, but thecontainer:key tells act_runner to pull and run the whole job inside a container, which requires Docker. If these are shell-mode-only runners, drop thecontainer:block and installnixdirectly on the host (it already is — these are NixOS hosts).Recommendation on the workflow: Remove the
container:block. These are NixOS hosts —nixis already available. Just runnix flake updatedirectly in the shell runner. That also removes the Docker dependency.Overall: Good foundation. The two items to resolve before merging:
container:interaction (or drop the container block)tokenFilelines are uncommented — do not deploy with them uncommented until secrets are createdLGTM once the workflow container question is resolved. 🌌
There are now merge conflicts and duplications with the base branch. Please update this PR on top of the base branch. @klaatu
96f682830atode1ec998e6de1ec998e6to81936ac524🤖 Rebase complete —
feat/gitea-runner-nix-modulehas been rebased onto currentmain.Conflicts resolved:
modules/nixos/gitea-runner.nix— Two implementations existed: main had a hostname-derived label approach usingservices.gitea-actions-runnerdirectly; the PR had explicit options (instanceURL,name,labels) with a custom systemd service. Resolution: Kept the PR's clean options API (instanceURL,name,labels,tokenFile) but wired them intoservices.gitea-actions-runner(the NixOS module from main) instead of the hand-rolled systemd service. Best of both worlds..gitea/workflows/update-flake-lock.yaml— Main had a correct Gitea-native workflow usingKLAATU_TOKENand proper API URLs; PR version added aConfigureIxstep but usedGITHUB_*variables (wrong for Gitea). Resolution: Kept main's version (correct Gitea API integration), discarded the container/GITHUB_* approach.All host configs (
isaiah,jeremiah,zeke,linode) carry over unchanged — they use the new options API with TODO comments for agenix secrets.Merged manually, along with some tweaks to tighten up the loop for my instances.
Pull request closed