Forgejo module with fresh-install and plain-http modes, exported as nixosModules.forgejo #136

Closed
opened 2026-10-06 20:29:08 +01:00 by vnprc-agent · 1 comment

A deployment built from the public templates can run a Forgejo of its own on the hypervisor once this repo exports a Forgejo NixOS module that boots fresh, with no restored dump, and serves plain http on a private network. Part of allod/strategy#65 (slice 1 of four); the forge machine in the templates, the hypervisor bootstrap and the dev VM's flip are separate issues on their owning repos.

Primary goals:

  • One module, two switches — nixosModules.forgejo, ported from a deployment's production module, with mode = "restore" | "fresh" and tls = true | false, both defaulting to the production shape so a consumer that sets neither gets today's behaviour unchanged.
  • Fresh mode boots unattended — the first boot lets nixpkgs' own forgejo-secrets unit generate the four application secrets, generates the clone daemon's host keys, and writes the state marker that gates every Forgejo unit; restore mode keeps the marker, the secret files and restore-forgejo exactly as they are.
  • Plain http changes exactly four things — ROOT_URL, the Caddy site address, COOKIE_SECURE, and port 443 leaving the firewall set; the check evaluates both configurations side by side and asserts the difference is that list and nothing more.
  • Fail loud on a contradictory mode — restore mode without all four secret files, or fresh mode with any of them, is an evaluation error, not a machine that boots half-configured.
  • A two-node witness — a forge node in fresh plain-http mode and a client node, where the client reaches Forgejo through the advertised URL, an admin created on the forge mints a token for the agent account into a 0600 file on the client, the client creates a repository and registers its key through the API, clones it over the clone port and pushes, the administrative port accepts the operator key and refuses a second valid key, the generated secrets have the owner and mode nixpkgs gives them, and the token appears nowhere in either machine's journal or the test driver's log.

Current state and specifics

The deployment module this ports lives in a private repo and assumes a public domain with ACME and a restored dump: services.forgejo.secrets is forced to four restored files, forgejo-secrets.service is replaced by a check that the files exist, and forgejo.service and the clone daemon forgejo-sshd.service carry ConditionPathExists on a marker the operator creates after restore-forgejo. Everything else in it — the clone-only sshd on its own port with per-key forced commands into a setuid dispatcher, administrative sshd on another port, Caddy in front of Forgejo's loopback listener, fail2ban, the dump unit, the port-collision and listen-address assertions — is deployment-neutral and ports as is.

Fresh mode adds forgejo-fresh-init.service, ordered before forgejo-secrets, forgejo and forgejo-sshd, conditioned on the marker's absence: it creates the state directory, generates ed25519, ecdsa and rsa host keys for the clone daemon under the state directory, and installs the marker root-owned at mode 0600. nixpkgs 26.05 ships Forgejo 16.0.5, so fresh mode needs no package pin. The secret files nixpkgs generates land under the custom directory owned by the Forgejo user at the mode its UMask gives them; the witness measures and asserts that, rather than the restore shape's root-owned files.

The module lives at modules/forgejo/ beside modules/public-host.nix and is exported as nixosModules.forgejo; it does not import publicHost, because a forge on a private network has no ACME and the stateful posture (state gate, restore tooling, secrets that must not be regenerated) is what publicHost deliberately leaves out. The check is checks/forgejo.nix, one entry in checks/default.nix, run by allod check like the rest; it is a runNixOSTest with eval-time assertions ahead of it, following checks/static-site.nix.

Custody rule, binding on the witness and on every later slice: no password or token is ever a command-line argument, to ssh, curl, forge, jq or forgejo. The admin account takes Forgejo's random password flagged must-change; the agent account takes a random password nobody keeps; the token is minted with forgejo admin user generate-access-token --raw and travels to the client as a file the test driver copies, where curl reads the authorization header from a config on stdin. The witness proves it by grepping both journals for the token's value and the driver log for anything token-shaped, and finding nothing.

Scope

In: modules/forgejo/, its export, checks/forgejo.nix, the index entry and the README's Exports line. Out: the forge machine in inventory, profiles and secrets; forge-bootstrap on the hypervisor; the dev VM's flip to the local forge; TLS on the private network; promotion of a forge VM to a VPS. Each is its own issue under allod/strategy#65.

A deployment built from the public templates can run a Forgejo of its own on the hypervisor once this repo exports a Forgejo NixOS module that boots fresh, with no restored dump, and serves plain http on a private network. Part of allod/strategy#65 (slice 1 of four); the `forge` machine in the templates, the hypervisor bootstrap and the dev VM's flip are separate issues on their owning repos. Primary goals: - **One module, two switches** — `nixosModules.forgejo`, ported from a deployment's production module, with `mode = "restore" | "fresh"` and `tls = true | false`, both defaulting to the production shape so a consumer that sets neither gets today's behaviour unchanged. - **Fresh mode boots unattended** — the first boot lets nixpkgs' own `forgejo-secrets` unit generate the four application secrets, generates the clone daemon's host keys, and writes the state marker that gates every Forgejo unit; restore mode keeps the marker, the secret files and `restore-forgejo` exactly as they are. - **Plain http changes exactly four things** — `ROOT_URL`, the Caddy site address, `COOKIE_SECURE`, and port 443 leaving the firewall set; the check evaluates both configurations side by side and asserts the difference is that list and nothing more. - **Fail loud on a contradictory mode** — restore mode without all four secret files, or fresh mode with any of them, is an evaluation error, not a machine that boots half-configured. - **A two-node witness** — a `forge` node in fresh plain-http mode and a `client` node, where the client reaches Forgejo through the advertised URL, an admin created on the forge mints a token for the agent account into a 0600 file on the client, the client creates a repository and registers its key through the API, clones it over the clone port and pushes, the administrative port accepts the operator key and refuses a second valid key, the generated secrets have the owner and mode nixpkgs gives them, and the token appears nowhere in either machine's journal or the test driver's log. ### Current state and specifics The deployment module this ports lives in a private repo and assumes a public domain with ACME and a restored dump: `services.forgejo.secrets` is forced to four restored files, `forgejo-secrets.service` is replaced by a check that the files exist, and `forgejo.service` and the clone daemon `forgejo-sshd.service` carry `ConditionPathExists` on a marker the operator creates after `restore-forgejo`. Everything else in it — the clone-only sshd on its own port with per-key forced commands into a setuid dispatcher, administrative sshd on another port, Caddy in front of Forgejo's loopback listener, fail2ban, the dump unit, the port-collision and listen-address assertions — is deployment-neutral and ports as is. Fresh mode adds `forgejo-fresh-init.service`, ordered before `forgejo-secrets`, `forgejo` and `forgejo-sshd`, conditioned on the marker's absence: it creates the state directory, generates ed25519, ecdsa and rsa host keys for the clone daemon under the state directory, and installs the marker root-owned at mode 0600. nixpkgs 26.05 ships Forgejo 16.0.5, so fresh mode needs no package pin. The secret files nixpkgs generates land under the custom directory owned by the Forgejo user at the mode its `UMask` gives them; the witness measures and asserts that, rather than the restore shape's root-owned files. The module lives at `modules/forgejo/` beside `modules/public-host.nix` and is exported as `nixosModules.forgejo`; it does not import `publicHost`, because a forge on a private network has no ACME and the stateful posture (state gate, restore tooling, secrets that must not be regenerated) is what `publicHost` deliberately leaves out. The check is `checks/forgejo.nix`, one entry in `checks/default.nix`, run by `allod check` like the rest; it is a `runNixOSTest` with eval-time assertions ahead of it, following `checks/static-site.nix`. Custody rule, binding on the witness and on every later slice: no password or token is ever a command-line argument, to `ssh`, `curl`, `forge`, `jq` or `forgejo`. The admin account takes Forgejo's random password flagged must-change; the agent account takes a random password nobody keeps; the token is minted with `forgejo admin user generate-access-token --raw` and travels to the client as a file the test driver copies, where curl reads the authorization header from a config on stdin. The witness proves it by grepping both journals for the token's value and the driver log for anything token-shaped, and finding nothing. ### Scope In: `modules/forgejo/`, its export, `checks/forgejo.nix`, the index entry and the README's Exports line. Out: the `forge` machine in `inventory`, `profiles` and `secrets`; `forge-bootstrap` on the hypervisor; the dev VM's flip to the local forge; TLS on the private network; promotion of a forge VM to a VPS. Each is its own issue under allod/strategy#65.
Author

Landed on master as 408e1e5: the module, its export and the two-node check. The relay rewrote the commit hash from the worktree commit, so the branch was never pushed.

Landed on master as 408e1e5: the module, its export and the two-node check. The relay rewrote the commit hash from the worktree commit, so the branch was never pushed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/archetypes#136
No description provided.