- Nix 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| hosts | ||
| modules | ||
| templates | ||
| .gitignore | ||
| .sops.yaml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| secrets.yaml | ||
nxgit
Declarative NixOS configs for a personal Forgejo instance and a separate Forgejo Actions runner.
Topology
Incus network: net-git
NixOS container: nxgit-forgejo
- Forgejo
- PostgreSQL
- Forgejo package/OCI registry
- Cloudflare Tunnel
- HTTPS Git only for now
NixOS VM: nxgit-runner
- forgejo-runner
- Docker backend
- Nix sandbox (host builds as nixbld)
- pulls CI builds from and can publish trusted builds to the Attic cache
This repository only defines the NixOS systems. It intentionally does not create Incus networks, containers, VMs, storage volumes, or deployments.
The Forgejo container imports NixOS's virtualisation/lxc-container.nix profile. It also uses systemd-networkd DHCP, matching the base Incus NixOS container image where dhcpcd does not lease correctly on the OVN-style network.
Both systems include the base image's SSH provisioning setup:
- user:
deploy - sudo: passwordless via
wheel - SSH auth: key-only
- OpenSSH firewall: enabled
- keys: the base image's
deploykey plus the[email protected]keys currently available through the SSH agent ondev-nix
Configurations
Build/evaluate the Forgejo container config:
nix build .#nixosConfigurations.forgejo-container.config.system.build.toplevel
Build/evaluate the runner VM config:
nix build .#nixosConfigurations.runner-vm.config.system.build.toplevel
Values to override
The shared defaults live in modules/nxgit-settings.nix.
Important values:
nxgit.domain: public Forgejo hostname, for examplegit.example.comnxgit.publicUrl: ForgejoROOT_URL, for examplehttps://git.example.com/nxgit.runnerUrl: URL used by the runner to register and poll Forgejonxgit.sops: settings for sops-nix, seemodules/sops.nix
Example override module:
{
nxgit = {
domain = "git.example.com";
publicUrl = "https://git.example.com/";
runnerUrl = "https://git.example.com/";
};
}
Secrets
Secrets are managed with sops-nix and
encrypted with the age key in .sops.yaml. The encrypted file is
secrets.yaml at the repository root.
Edit secrets with:
nix run nixpkgs#sops -- edit secrets.yaml
The file holds these keys:
| Key | Written to | Consumed by |
|---|---|---|
cloudflared_tunnel_token |
/run/secrets/cloudflared/nxgit-tunnel.env |
nxgit-cloudflared service |
forgejo_runner_token |
/run/secrets/forgejo-runner/token |
gitea-runner-nxgit service |
attic_jwt |
/run/secrets/atticd/server.env |
atticd service (forgejo only) |
attic_admin_token |
/run/secrets/attic/token |
runner post-build-hook (runner only) |
Secrets are stored in systemd environment file format, except the runner token which forgejo-runner v13 reads raw as a single line:
cloudflared_tunnel_token: TUNNEL_TOKEN=<token>
forgejo_runner_token: <registration token>
attic_jwt: ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64="<base64 rsa pem>"
attic_admin_token: <jwt>
The runner is registered with Forgejo via a declarative server.connections
entry (forgejo-runner v13 dropped the deprecated register command). The
connection's uuid is set with nxgit.runnerUuid in
hosts/runner-vm/configuration.nix, and the token is read from disk by the
runner itself via token_url.
SOPS host keys
sops-nix decrypts on each host using the host SSH keys from the Incus/NixOS
base image, especially /etc/ssh/ssh_host_ed25519_key. No separate
/etc/sops-nix/age.key is required unless nxgit.sops.ageKeyFile is explicitly
set.
After changing secrets.yaml, run nixos-rebuild switch on the affected host
with --ask-sudo-password.
Binary cache (Attic)
The forgejo container runs atticd on port 5000. The
runner substitutes from it on subsequent builds, so shared dependencies
compile once across all flake projects.
- Runner -> cache:
https://cache.neels.dev/cache - dev-nix -> cache:
https://cache.neels.dev/cache - Public read endpoint:
https://cache.neels.dev/cache
Forgejo and atticd both listen on 127.0.0.1 only. The Cloudflare Tunnel
routes git.neels.dev to Forgejo at http://127.0.0.1:3000 and
cache.neels.dev to Attic at http://127.0.0.1:5000, so the runner, dev-nix,
and the public all reach them through the tunnel — no direct IP access between
machines is required. The public cache is read only for unauthenticated users;
uploads require an Attic token (and now traverse the tunnel).
The cache is created once (from a host that can reach the container, or via the tunnel):
attic login nxgit https://cache.neels.dev "$(sops -d --extract '["attic_admin_token"]' secrets.yaml)"
attic cache create nxgit:cache --public --priority 41
attic cache info nxgit:cache # -> "Public Key: cache:<base64>"
Paste the public key into nxgit.atticCachePublicKey in
hosts/runner-vm/configuration.nix and redeploy the runner.
Flake CI for your projects
Copy templates/nix-ci.yaml into any flake-based project as
.forgejo/workflows/ci.yaml. It calls the shared workflow in
.forgejo/workflows/nix-ci.yaml, which builds the flake and runs nix flake check on the nixos-latest container runner with serialized Nix builds
(--max-jobs 1) and four build cores (--cores 4). Forgejo supports this
cross-repository reusable workflow because nxgit is public.
To allow trusted push workflows to publish outputs, create an Attic token with
pull and push permission for cache, then add it to the repository's Forgejo
Actions secrets as ATTIC_TOKEN. The workflow never exposes that secret to
pull requests, and skips uploading when the secret is absent.
Notes
- PostgreSQL is used instead of SQLite because Actions, package registry usage, background jobs, and future growth make it the more durable default.
- The runner uses Docker because it is the most compatible starting point for Forgejo Actions workflows.
- SSH Git is disabled in Forgejo for now; clone/push is HTTPS-only.
- The runner has no access to Forgejo's database or data directory.