Rewritten from scratch with accurate project state: - Correct deployment strategy (Comin, not deploy-rs) - All 4 apps documented (create, deploy, install, rebuild) - Module pattern with inherit and assertions - Host service file pattern with constants usage - lib.utils helpers documented - Secret path naming convention - Complete directory structure with current file layout - Verification checklist including English docs requirement
6 KiB
6 KiB
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,rebuildinapps/ - Templates:
raspberry-pi,generic-serverfor bootstrapping new hosts
Build & Development Commands
# 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:
# 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 .#<hostname> \
--target-host <user>@<ip> --sudo --ask-sudo-password
Apps
nix run .#create -- -t generic-server -n <hostname> # Scaffold new host
nix run .#install -- -n <hostname> -r <REPO_URL> # Install from NixOS ISO
nix run .#deploy -- -n <hostname> # Deploy to host
nix run .#rebuild -- nixos # Rebuild locally
Code Style & Conventions
Formatting
- Tool:
nixfmtviagit-hooks.nix(pre-commit) - Run:
nix fmtbefore 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
{ 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.mkDefaultfor all module defaults (allows host-level overrides) - Use
constants.nixfor domains, IPs, ports -- never hardcode these - Use
lib.utilshelpers: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,
warningsfor non-critical issues lib.inheritpattern: extract needed functions inletblock
Host Service Files
Each service gets its own file in hosts/<host>/services/:
# 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 fromconstants.nix(domain, hosts, services)lib-- nixpkgs.lib extended withlib.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 fmtpassesnix flake checkpasses (or at leastnix evalworks for both hosts)- New hosts added to
flake.nixnixosConfigurations - Constants in
constants.nix, not hardcoded - Secrets use SOPS, not plaintext
- New services have their own file in
hosts/<host>/services/ - New modules registered in
modules/nixos/default.nix - Documentation in English