Skip to content

Forgejo on the NAS

What: deploy Forgejo as a TrueNAS custom app at git.nas.fzymgc.house, reachable on the LAN and the tailnet, with a Keycloak sign-in and a Kopia backup. Design: docs/engineering/specs/2026-09-21-nas-forgejo-design.md. The decisions there are agreed and are not reopened here. Riskiest assumption: that the rootless image accepts user: 568:568 with cap_drop: [ALL] and still writes app.ini to its config mount at first start. The upstream example overrides the user, so this is expected. The first converge proves it.

1. Decisions you’ll probably want to tweak

Section titled “1. Decisions you’ll probably want to tweak”
# Decision Alternative Cost of changing later
P1 The app runs as uid 568:568, the appliance apps user, as RustFS does The image default 1000:1000 Low. Chown the dataset and change one default. Uid 568 is chosen so the same ACL walk that already serves RustFS covers this dataset.
P2 Pin 16.0.5-rootless, the newest stable tag in the registry on 2026-09-21 Pin the major 16-rootless Low. Renovate proposes each patch, which is the visibility the pin exists for.
P3 Forgejo’s built-in SSH server listens on 2222 inside the container An OpenSSH sidecar Low. The rootless image has no sshd, and the built-in server needs no host key management.
P4 The Keycloak client ships in a second PR, after the Vault grant applies One PR for both None. The grant and the client sit in different HCP workspaces, and a combined PR races them.
P5 Mirrors, Actions and self-registration are off by configuration, not by omission Leave defaults Low. Explicit keys make the scope boundary greppable.
Unknown Default Pivot signal
Does pool.dataset.create with generate_key: true return the key, or does it stay in the appliance keystore only? The keystore holds it, and the operator exports it once with pool.dataset.export_key The role’s create task returns a key field. Then the runbook step changes to “copy it from the converge output”.
Does Traefik pick the nas-internal address for a container on two networks? Yes, with the traefik.docker.network label A 502 or a Gateway Timeout from git.nas.fzymgc.house. Then read the address Traefik chose from its dashboard.
Does the Kopia sandbox see the new dataset without a container restart? No. New FILESYSTEM devices mount at container start The first backup log names /mnt/main/forgejo as missing. Restart nas-support.
Does the health check in the compose pass under REQUIRE_SIGNIN_VIEW? Yes. /api/healthz is exempt from sign-in The app reports unhealthy while the UI works. Then drop the health check.

Phases run in order. Each phase ends in a commit on this branch, except phase 3, which is its own PR, and phase 4, which is an operator session.

  • tf/vault/policy-keycloak.tf: grant create, read and update on secret/data/fzymgc-house/infrastructure/nas/forgejo/oidc and its metadata path.
  • tf/dns-records/hosts.yml: add git_nas at 192.168.20.201 and the Traefik ULA, ptr: false.
  • ansible/roles/nas-forgejo/: defaults, meta, tasks, and a compose template. The task file has three regions: dataset and directories, secret files, app reconcile.
  • ansible/nas-playbook.yml: a play on nas with tag nas-forgejo.
  • ansible/inventory/group_vars/all.yml: main/forgejo in nas_kopia_leaves.
  • ansible/inventory/host_vars/nas.yml: main/forgejo in nas_sandbox_mounts.
  • docs/operations/nas.md: a tag map row and a Forgejo section.
  • docs/reference/services.md: one row.
  • rumdl check --config docs/.rumdl.toml on the docs.
  • yamllint and ansible-lint on the role, through the pre-commit hook.
  • terraform fmt -check on the two Terraform files.
  • Push and open the PR. The operator merges.
  • tf/keycloak/forgejo.tf: client forgejo, standard flow, redirect https://git.nas.fzymgc.house/user/oauth2/keycloak/callback, secret to the /oidc path.
  • Open after the main-cluster-vault run from phase 2 shows applied.
  1. Seed the Vault secret: vault kv put secret/fzymgc-house/infrastructure/nas/forgejo secret_key=<64 random chars> internal_token=<jwt>. Forgejo prints both with forgejo generate secret SECRET_KEY and forgejo generate secret INTERNAL_TOKEN, which run from any container of the image.
  2. Converge: scripts/nas-playbook.sh --tags nas-forgejo.
  3. Export the dataset key to Vault: midclt call -j pool.dataset.export_key main/forgejo, then vault kv put secret/fzymgc-house/infrastructure/nas/forgejo-zfs-key key=<hex>.
  4. Create the admin: the docker exec command in the runbook section.
  5. Add the Keycloak authentication source at /admin/auths/new and link the identity.
  6. Restart nas-support, then scripts/nas-playbook.sh --tags nas-kopia-acls.
  7. Run the acceptance commands from the spec, on the LAN and on the tailnet.

The spec, section 8, lists the commands. Two additions:

Terminal window
scripts/nas-adhoc.sh nas -b -m command -a 'midclt call app.query [["name","=","forgejo"]]' # state RUNNING
scripts/nas-playbook.sh --tags nas-forgejo --check --diff # changed=0 on the second run