# Agent Guidelines for NixOS Configuration ## Project Overview NixOS infrastructure managed with Nix Flakes. Two hosts, reusable modules, SOPS secrets, Comin auto-deployment. - **Hosts**: `cryodev-main` (x86_64 server), `cryodev-pi` (aarch64 Raspberry Pi 4) - **Modules**: Reusable NixOS modules in `modules/nixos/` - **Apps**: `create`, `deploy`, `install`, `rebuild` in `apps/` - **Templates**: `raspberry-pi`, `generic-server` for bootstrapping new hosts ## Build & Development Commands ```bash # Format code (required before committing, runs nixfmt via pre-commit) nix fmt # Run all checks (formatting, package builds, overlay builds) nix flake check # Quick evaluation test (faster than full build, use to validate changes) nix eval .#nixosConfigurations.cryodev-main.config.system.build.toplevel.name nix eval .#nixosConfigurations.cryodev-pi.config.system.build.toplevel.name # Full build nix build .#nixosConfigurations.cryodev-main.config.system.build.toplevel nix build .#nixosConfigurations.cryodev-pi.config.system.build.toplevel # Build Raspberry Pi SD image (requires binfmt on x86_64) nix build .#nixosConfigurations.cryodev-pi.config.system.build.sdImage # Update flake inputs nix flake update # Enter development shell nix develop ``` ### Deployment Both hosts use **Comin** for automatic pull-based deployment (polls the git repo). Manual deployment is only needed for initial setup or emergencies: ```bash # Deploy via deploy app (uses deploy.json, SSH port 2299, asks sudo password) nix run .#deploy -- -n cryodev-main # Manual deployment via nixos-rebuild NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ --target-host @ --sudo --ask-sudo-password ``` ### Apps ```bash nix run .#create -- -t generic-server -n # Scaffold new host nix run .#install -- -n -r # Install from NixOS ISO nix run .#deploy -- -n # Deploy to host nix run .#rebuild -- nixos # Rebuild locally ``` ## Code Style & Conventions ### Formatting - **Tool**: `nixfmt` via `git-hooks.nix` (pre-commit) - **Run**: `nix fmt` before every commit - **Indentation**: 2 spaces (enforced by formatter) ### Naming Conventions | Type | Convention | Example | |------|------------|---------| | Files | kebab-case | `hardware-configuration.nix` | | NixOS options | camelCase | `services.myService.enable` | | Let bindings | camelCase | `let myValue = ...;` | | Hosts | kebab-case | `cryodev-main`, `cryodev-pi` | | Secret paths | kebab-case with `/` | `forgejo-runner/token` | ### Module Pattern ```nix { config, lib, ... }: let cfg = config.services.myService; inherit (lib) mkDefault mkEnableOption mkIf mkOption types; in { options.services.myService = { enable = mkEnableOption "My service"; port = mkOption { type = types.port; default = 8080; description = "Port to listen on"; }; }; config = mkIf cfg.enable { assertions = [ { assertion = cfg.port > 1024; message = "Port must be > 1024"; } ]; # Implementation }; } ``` ### Key Rules - **Use `lib.mkDefault`** for all module defaults (allows host-level overrides) - **Use `constants.nix`** for domains, IPs, ports -- never hardcode these - **Use `lib.utils`** helpers: `mkReverseProxyOption`, `mkVirtualHost`, `mkUrl` - **Secrets via SOPS** only, never plaintext. Reference: `config.sops.secrets."path".path` - **Imports**: relative paths for local files, `outputs.nixosModules.*` for shared modules, `inputs.*` for external - **Assertions** for invalid configurations, `warnings` for non-critical issues - **`lib.inherit`** pattern: extract needed functions in `let` block ### Host Service Files Each service gets its own file in `hosts//services/`: ```nix # hosts/cryodev-main/services/myservice.nix { outputs, constants, ... }: { imports = [ outputs.nixosModules.myservice ]; services.myservice = { enable = true; port = constants.services.myservice.port; }; services.nginx.virtualHosts."${constants.services.myservice.fqdn}" = { forceSSL = true; enableACME = true; locations."/" = { proxyPass = "http://127.0.0.1:${toString constants.services.myservice.port}"; }; }; } ``` ### Special Args Available in Modules - `inputs` -- flake inputs (nixpkgs, sops-nix, comin, headplane, etc.) - `outputs` -- this flake's outputs (nixosModules, packages) - `constants` -- values from `constants.nix` (domain, hosts, services) - `lib` -- nixpkgs.lib extended with `lib.utils` ## Directory Structure ``` . ├── flake.nix # Entry point, inputs/outputs, mkNixosConfiguration ├── constants.nix # Central config (domains, IPs, ports) ├── hosts/ │ ├── cryodev-main/ # x86_64 server (services/, secrets.yaml, binfmt.nix) │ └── cryodev-pi/ # aarch64 RPi (services/, secrets.yaml, sd-image.nix) ├── modules/nixos/ # Reusable modules (common, forgejo, headscale, ...) ├── users/ # User definitions (steffen, ralph, benjamin) ├── apps/ # Nix apps (create, deploy, install, rebuild) ├── lib/utils.nix # Helper functions (mkUrl, mkVirtualHost, ...) ├── pkgs/ # Custom packages ├── overlays/ # Nixpkgs overlays ├── templates/ # Host templates (generic-server, raspberry-pi) ├── deploy.json # Deploy app config (hosts, SSH port) ├── .sops.yaml # SOPS encryption rules (age keys per host) ├── .forgejo/workflows/ # CI pipelines (ci.yml, deploy.yml) └── docs/ # Documentation (English) ``` ## Verification Checklist Before committing: - [ ] `nix fmt` passes - [ ] `nix flake check` passes (or at least `nix eval` works for both hosts) - [ ] New hosts added to `flake.nix` nixosConfigurations - [ ] Constants in `constants.nix`, not hardcoded - [ ] Secrets use SOPS, not plaintext - [ ] New services have their own file in `hosts//services/` - [ ] New modules registered in `modules/nixos/default.nix` - [ ] Documentation in English