# Agent Guidelines for NixOS Configuration ## Project Overview This repository contains a NixOS configuration managed with Nix Flakes. It defines: - **Hosts**: `cryodev-main` (x86_64 server), `cryodev-pi` (aarch64 Raspberry Pi) - **Modules**: Reusable NixOS modules in `modules/nixos/` - **Packages**: Custom packages in `pkgs/` - **Templates**: `raspberry-pi`, `generic-server` for bootstrapping new hosts ## Build & Development Commands ### Prerequisites - **Nix** with Flakes enabled - **Git** ### Core Commands ```bash # Build host configuration 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 # Format code (required before committing) nix fmt # Run all checks (lint, formatting, deploy-rs validation) nix flake check # Quick evaluation test (faster than full build) nix eval .#nixosConfigurations.cryodev-main.config.system.build.toplevel.name # Update flake inputs nix flake update # Enter development shell nix develop ``` ### Deployment ```bash # Deploy all hosts via deploy app (uses deploy.json) nix run .#deploy # Deploy to cryodev-main via deploy-rs nix run github:serokell/deploy-rs -- .#cryodev-main # Manual deployment via SSH NIX_SSHOPTS="-p 2299" nixos-rebuild switch --flake .# \ --target-host @ --use-remote-sudo ``` ### Apps ```bash # Create a new host from template nix run .#create -- -t generic-server -n # Install NixOS on a new machine (run from NixOS live ISO) nix run .#install -- -n -r # Deploy to all configured hosts nix run .#deploy # Rebuild NixOS/Home Manager configuration nix run .#rebuild -- nixos ``` ## Code Style & Conventions ### Formatting - **Tool**: `nixfmt` via pre-commit hooks - **Run**: `nix fmt` before every commit - **Indentation**: 2 spaces - **Line length**: 80-100 characters (follow formatter) ### Module Structure ```nix # Standard module pattern { config, lib, pkgs, inputs, outputs, constants, ... }: let cfg = config.services.myService; in { options.services.myService = { enable = lib.mkEnableOption "My service"; port = lib.mkOption { type = lib.types.port; default = 8080; description = "Port to listen on"; }; }; config = lib.mkIf cfg.enable { # Implementation here }; } ``` ### Naming Conventions | Type | Convention | Example | |------|------------|---------| | Files | kebab-case | `hardware-configuration.nix` | | Options | camelCase | `services.myService.enable` | | Variables | camelCase | `let myValue = ...;` | | Hosts | kebab-case | `cryodev-main`, `cryodev-pi` | ### Imports ```nix # Local modules: relative paths imports = [ ./hardware.nix ./networking.nix ]; # Shared modules: via outputs imports = [ outputs.nixosModules.common ]; # External inputs imports = [ inputs.sops-nix.nixosModules.sops ]; ``` ### Constants Use `constants.nix` for domains, IPs, and ports: ```nix { constants, ... }: { services.nginx.virtualHosts."${constants.services.forgejo.fqdn}" = { ... }; } ``` ### Error Handling ```nix config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.port > 1024; message = "Port must be > 1024"; } ]; warnings = lib.optional (cfg.debug) "Debug mode enabled!"; }; ``` ### Option Conflicts Use `lib.mkDefault` for default values that can be overridden: ```nix services.nginx.enable = lib.mkDefault true; ``` ## Directory Structure ``` . ├── flake.nix # Entry point, inputs/outputs ├── constants.nix # Central config (domains, IPs, ports) ├── hosts/ │ ├── cryodev-main/ # x86_64 server │ │ ├── default.nix # Host entry point │ │ ├── hardware.nix # Hardware configuration │ │ ├── services/ # Service configurations │ │ └── secrets.yaml # SOPS-encrypted secrets │ └── cryodev-pi/ # aarch64 Raspberry Pi ├── modules/nixos/ # Reusable modules │ ├── common/ # Shared base configuration │ ├── sops/ # Secret management │ ├── forgejo/ # Git server │ ├── headscale/ # VPN control server │ └── ... ├── lib/utils.nix # Helper functions ├── apps/ # Nix apps (rebuild) ├── pkgs/ # Custom packages ├── overlays/ # Nixpkgs overlays ├── templates/ # Host templates ├── scripts/ # Helper scripts (install.sh) └── docs/ # Documentation ``` ## Key Patterns ### Adding a New Raspberry Pi Host 1. Copy template: `cp -r templates/raspberry-pi hosts/new-pi` 2. Update `hosts/new-pi/networking.nix` (hostname) 3. Add to `flake.nix`: `new-pi = mkNixosConfiguration "aarch64-linux" [ ./hosts/new-pi ];` 4. Add to `.forgejo/workflows/build-pi-image.yml` matrix 5. Push → SD image is built automatically ### SOPS Secrets - Secrets encrypted with age using SSH host keys - Config in `.sops.yaml`, secrets in `hosts//secrets.yaml` - Reference: `config.sops.secrets."path/to/secret".path` ### Special Args Available in Modules - `inputs`: Flake inputs (nixpkgs, sops-nix, etc.) - `outputs`: This flake's outputs (nixosModules, packages) - `constants`: Values from `constants.nix` - `lib`: Extended nixpkgs.lib with `lib.utils` ## Deployment Workflows | Host | Strategy | Trigger | |------|----------|---------| | `cryodev-main` | Push via deploy-rs | Forgejo Actions on push to main | | `cryodev-pi` | Pull via Comin | Automatic polling | | SD Images | Built in CI | Push to main (for Pi hosts) | ## Verification Checklist Before committing: - [ ] `nix fmt` passes - [ ] `nix flake check` passes (or at least `nix eval` works) - [ ] New hosts added to `flake.nix` - [ ] Constants in `constants.nix`, not hardcoded - [ ] Secrets use SOPS, not plaintext