Files
dendritic/.github/instructions/soteria-host.instructions.md
T
2026-07-01 00:03:48 -05:00

9.1 KiB

description, name, applyTo
description name applyTo
Use when modifying the Soteria host definition or host-local secrets under modules/hosts/soteria. Covers host wiring and SSH cert mechanics specific to Soteria. Soteria Host Instructions modules/hosts/soteria/**/*.nix, modules/hosts/soteria/secrets.yaml

Soteria Host Instructions

Use this instruction when changing Soteria host wiring in modules/hosts/soteria.

Host Intent And Shape

  • Treat Soteria as a NixOS service node for Forgejo, RESTic server, mTLS, and a paired Home Manager profile for john.
  • Keep host identity aligned: hostname = "soteria", networking.hostName = hostname, and flake.nixosConfigurations."${hostname}".
  • Keep host-local secrets in modules/hosts/soteria/secrets.yaml and wire with sops.defaultSopsFile = ./secrets.yaml.
  • Keep host-specific Home Manager wiring in flake.modules.homeManager.soteria, and keep mysops.hostSecretFile pointing at the Soteria secrets file.

Required Module Composition

When editing modules/hosts/soteria/default.nix, preserve this composition unless the task explicitly changes architecture:

  • Import nixos.step-ssh-host to enable host SSH cert integration.
  • Import nixos.janus-ca so Step client trust material and defaults remain available.
  • Keep step-ssh-host.hostname = hostname; so cert principals match host naming.

SSH Cert Mechanics On Soteria

Soteria SSH certificate behavior is the result of multiple modules. Keep this flow intact:

  1. soteria/default.nix imports nixos.step-ssh-host and sets step-ssh-host.hostname = "soteria" (via hostname).
  2. modules/services/step-ca/ssh-host.nix:
    • Enables ssh.certificates.enable = true.
    • Requires sops.secrets."janus/admin_jwk" (provisioner credential) from Soteria's secrets.yaml.
    • Defines cert paths at /etc/ssh/ssh_host_ed25519_key and /etc/ssh/ssh_host_ed25519_key-cert.pub.
    • Exposes ssh-host-cert-renew and ssh-host-cert-check helper binaries.
    • Schedules step-ssh-host-renew.timer every 4h with jitter.
  3. modules/services/ssh.nix consumes ssh.certificates.enable and configures OpenSSH to:
    • Set TrustedUserCAKeys = /etc/ssh/ssh_user_ca.pub.
    • Set HostCertificate = /etc/ssh/ssh_host_ed25519_key-cert.pub.
    • Install the trusted user CA file into /etc/ssh/ssh_user_ca.pub.
  4. nixos.janus-ca provides Step CA trust bootstrapping (/etc/step-ca/defaults.json and linked root CA material), allowing Step CLI operations to trust and reach the CA endpoint.

Current implementation note:

  • step-ssh-host-renew.service currently checks renewal state via step ssh needs-renewal and logs status. It does not directly invoke ssh-host-cert-renew in the service script. Preserve this behavior unless the task explicitly asks to change renewal execution semantics.

SOPS/Secrets Mechanics On Soteria

Soteria secret handling spans host-local secrets, NixOS secret materialization, and Home Manager secret tooling. Keep this flow intact:

  1. modules/hosts/soteria/default.nix imports nixos.mysops and sets sops.defaultSopsFile = ./secrets.yaml.
  2. The SOPS file modules/hosts/soteria/secrets.yaml is the canonical encrypted source for this host's system secrets.
  3. Shared modules imported by Soteria declare required secret entries under sops.secrets and consume them through config.sops.secrets.<name>.path:
    • modules/services/step-ca/ssh-host.nix declares and consumes janus/admin_jwk as the Step provisioner password file.
    • modules/features/forgejo.nix declares and consumes forgejo/secret_key, forgejo/internal_token, forgejo/jwt_secret, and forgejo/lfs_jwt_secret.
  4. Secret ownership is module-defined and must stay aligned with service users:
    • Forgejo secrets are owned by config.services.forgejo.user.
    • Step SSH provisioner secret is locked to root ownership and 0400 mode.
  5. flake.modules.homeManager.soteria imports homeManager.mysops and sets mysops.hostSecretFile to the same Soteria host secret file path. This drives helper tooling such as edit-secrets while Home Manager keeps its own default SOPS context from modules/programs/sops.nix.

Current implementation note:

  • restic_password exists in modules/hosts/soteria/secrets.yaml, but it is not currently consumed by the Soteria NixOS module graph.
  • Changing, deleting, or renaming seemingly unused keys in secrets.yaml should be treated as a compatibility change and confirmed by the task.

Restic REST Server On Soteria

Soteria's RESTic REST server is defined directly in the host module, not through the shared nixos.restic-server helper. Trace it this way:

  1. modules/hosts/soteria/default.nix configures services.restic.server inline with:
    • enable = true
    • privateRepos = true
    • listenAddress = "0.0.0.0:8000"
    • extraFlags = [ "--no-auth" "--tls" "--tls-cert=${config.mtls.certFile}" "--tls-key=${config.mtls.keyFile}" ]
  2. The host opens the matching TCP port separately via networking.firewall.allowedTCPPorts = [ 8000 ].
  3. TLS for the REST server is provided by the same mTLS certificate/key files used elsewhere on the host:
    • --tls-cert=${config.mtls.certFile}
    • --tls-key=${config.mtls.keyFile}
  4. The mTLS renewal hook includes restic-rest-server.service in mtls.renew.reloadUnits, so certificate rotation reloads the REST server alongside Forgejo.
  5. loginText.extraServiceStatus exposes the running unit as restic-rest-server.service, which is the service name to keep in mind for status and reload behavior.

Storage Location

  • Soteria does not set services.restic.server.dataDir in its host definition.
  • Because Soteria does not import nixos.restic-server, it also does not inherit the repo helper's /mnt/restic override from modules/features/restic.nix.
  • That means repository storage follows the underlying NixOS services.restic.server default data directory unless the host explicitly sets dataDir later.
  • privateRepos = true constrains repo exposure behavior, but it does not define storage location by itself.

Current implementation note:

  • The REST server is TLS-protected but currently passed --no-auth, so client access control relies on transport/security model rather than rest-server password auth.
  • If you want Soteria to use /mnt/restic, either set services.restic.server.dataDir = "/mnt/restic" in the host or switch to the shared nixos.restic-server module and then reconcile port/TLS differences.

Forgejo Implementation On Soteria

Soteria's Forgejo setup is split between host-local option values and the shared nixos.forgejo module. Trace it this way:

  1. modules/hosts/soteria/default.nix imports nixos.forgejo and enables it with:
    • forgejo.enable = true
    • forgejo.root_url = "https://forgejo.john-stream.com"
    • forgejo.https = true
    • forgejo.port = 443
  2. modules/features/forgejo.nix defines forgejo.port as the controlling option for the web listener. Its default is 3000, but Soteria overrides it to 443.
  3. The shared module wires that option into multiple places:
    • services.forgejo.settings.server.HTTP_PORT = cfg.port
    • networking.firewall.allowedTCPPorts = [ cfg.port ] when openFirewall = true
    • If cfg.port < 1024, the Forgejo systemd unit gets CAP_NET_BIND_SERVICE so it can bind a privileged port.
  4. HTTPS on Soteria is terminated directly by Forgejo using mTLS-managed files:
    • services.forgejo.settings.server.PROTOCOL = "https"
    • KEY_FILE = config.mtls.keyFile
    • CERT_FILE = config.mtls.certFile

Storage Locations

  • This repo does not override Forgejo's primary state directory in the Soteria host or in modules/features/forgejo.nix.
  • That means Forgejo storage follows the underlying NixOS services.forgejo defaults, exposed in this config as config.services.forgejo.stateDir.
  • PostgreSQL storage is also left at the underlying module default and is referenced as config.services.postgresql.dataDir.
  • The cleanup helpers in modules/features/forgejo.nix confirm these are the intended storage anchors because they remove exactly config.services.forgejo.stateDir and config.services.postgresql.dataDir.
  • Forgejo backup dumps are generated by forgejo-dump.service using --work-path ${config.services.forgejo.dump.backupDir}, so dump staging/output follows the Forgejo module's backup directory setting rather than a Soteria-specific path override.

Current implementation note:

  • If you need to change Forgejo storage location on Soteria, do it by setting the underlying Forgejo or PostgreSQL service directory options explicitly, not by changing cleanup scripts alone.
  • If you need to change the external Forgejo endpoint, update forgejo.root_url, forgejo.port, and the Soteria mTLS SAN list together.

Change Safety Rules

  • Do not rename the secret key janus/admin_jwk without updating all consumers.
  • Do not change SSH host key/cert filenames unless OpenSSH HostKey/HostCertificate paths are updated together.
  • If changing hostnames or domains, update cert principals in step-ssh-host and corresponding SSH client targets together.
  • If adding or removing nixos.step-ssh-host, explain impact on SSH cert issuance, renewal, and trust in the change summary.