feat: Gitea Actions shell runners + flake-lock workflow #18

Closed
klaatu wants to merge 0 commits from feat/gitea-runner-nix-module into main
Collaborator

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

## 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
klaatu added 1 commit 2026-04-04 06:15:13 +00:00
feat: Gitea Actions shell runners + flake-lock workflow
buildbot/nix-eval Build done. (1 warning)
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-isaiah Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-linode Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-zeke Build done.
buildbot/nix-build gitea:greg/nixos#checks.x86_64-linux.nixos-jeremiah Build done.
buildbot/nix-build Build done.
96f682830a
- 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).
Author
Collaborator

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. 🌌

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. 🌌
Owner

There are now merge conflicts and duplications with the base branch. Please update this PR on top of the base branch. @klaatu

There are now merge conflicts and duplications with the base branch. Please update this PR on top of the base branch. @klaatu
klaatu force-pushed feat/gitea-runner-nix-module from 96f682830a to de1ec998e6 2026-04-04 20:44:29 +00:00 Compare
klaatu force-pushed feat/gitea-runner-nix-module from de1ec998e6 to 81936ac524 2026-04-05 09:58:41 +00:00 Compare
Author
Collaborator

🤖 Rebase completefeat/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.

🤖 **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.
Owner

Merged manually, along with some tweaks to tighten up the loop for my instances.

Merged manually, along with some tweaks to tighten up the loop for my instances.
greg closed this pull request 2026-04-05 21:58:18 +00:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: greg/nixos#18