No description https://git.neels.dev
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Neel Sani 1b9b1cea83
Some checks failed
build / evaluate (push) Failing after 8s
ci: replace custom nix action
2026-09-20 16:02:10 +00:00
.forgejo/workflows ci: replace custom nix action 2026-09-20 16:02:10 +00:00
hosts stale docker 2026-09-19 18:25:31 +00:00
modules Initial commit: nxgit forgejo deployment 2026-09-18 07:13:46 +00:00
templates Share reusable Nix CI workflow 2026-09-19 07:27:13 +00:00
.gitignore Initial commit: nxgit forgejo deployment 2026-09-18 07:13:46 +00:00
.sops.yaml Initial commit: nxgit forgejo deployment 2026-09-18 07:13:46 +00:00
flake.lock Initial commit: nxgit forgejo deployment 2026-09-18 07:13:46 +00:00
flake.nix Initial commit: nxgit forgejo deployment 2026-09-18 07:13:46 +00:00
README.md hardensec 2026-09-19 17:53:07 +00:00
secrets.yaml Initial commit: nxgit forgejo deployment 2026-09-18 07:13:46 +00:00

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 deploy key plus the [email protected] keys currently available through the SSH agent on dev-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 example git.example.com
  • nxgit.publicUrl: Forgejo ROOT_URL, for example https://git.example.com/
  • nxgit.runnerUrl: URL used by the runner to register and poll Forgejo
  • nxgit.sops: settings for sops-nix, see modules/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.