Truxnell Nix-Config Pattern Analysis¶
Date: 2025-01-02
Overview¶
This document captures the analysis of design patterns from truxnell/nix-config that could benefit our repository, focusing on DRY principles, secrets management, and backup configurations.
Key Patterns Identified¶
1. Profile-Based Architecture for DRY¶
truxnell uses a clever separation of hardware and role profiles that significantly reduces duplication:
# In flake.nix, hosts are composed like:
"daedalus" = mkNixosConfig {
hostname = "daedalus";
hardwareModules = [ ./nixos/profiles/hw-generic-x86.nix ];
profileModules = [
./nixos/profiles/role-server.nix
./nixos/profiles/role-dev.nix
];
};
Benefits: - Reuse common hardware configs across similar machines - Mix and match roles (server, dev, workstation) without duplicating config - Cleaner host definitions in flake.nix
2. Helper Functions for Services¶
Their lib.mySystem.mkService function standardizes container deployments:
# Creates standardized container configuration with:
# - Automatic Traefik label generation
# - Security hardening options
# - Persistence folder management
# - User/group creation
mkService = options: {
virtualisation.oci-containers.containers.${options.app} = {
image = "${options.container.image}";
user = "${user}:${group}";
environment = { TZ = options.timeZone; };
labels = mkTraefikLabels { ... };
extraOptions = containerExtraOptions;
};
};
3. Advanced Backup System¶
Current Gap: Our repository has no backup system configured.
truxnell's approach: - ZFS snapshots at 2AM before backups (ensures consistency) - Per-service restic backups to both local and remote - Automatic systemd service/timer creation - Built-in warnings for disabled backups
# Per-service backup configuration
services.restic.backups = mkIf cfg.backup (
config.lib.mySystem.mkRestic {
inherit app user;
paths = [ appFolder ];
appFolder = appFolder;
}
);
Key implementation details:
- Snapshot creation: zfs snapshot rpool/local/root@restic_nightly_snap
- Mount snapshot: mount -t zfs rpool/local/root@restic_nightly_snap /mnt/nightly_backup
- Restic backs up from snapshot (not live filesystem)
- Both local (NAS) and remote (Cloudflare R2) destinations
4. Security Improvements¶
Per-Machine Age Keys¶
truxnell derives age keys from SSH host keys:
# .sops.yaml
keys:
- &luna age1lj5vmr02qkudvv2xedfj5tq8x93gllgpr6tzylwdlt7lud4tfv5qfqsd5u
- &rydev age17edew3aahg3t5nte5g0a505sn96vnj8g8gqse8q06ccrrn2n3uysyshu2c
This is more secure than our current shared key approach.
Container Security Hardening¶
containerExtraOptions =
lib.optionals (caps.privileged) ["--privileged"]
++ lib.optionals (caps.readOnly) ["--read-only"]
++ lib.optionals (caps.noNewPrivileges) ["--security-opt=no-new-privileges"]
++ lib.optionals (caps.dropAll) ["--cap-drop=ALL"];
5. NixOS Warnings for Operational Safety¶
They add warnings for critical misconfigurations:
warnings = [
(mkIf (!cfg.backup && config.mySystem.purpose != "Development")
"WARNING: Backups for ${app} are disabled!")
];
Implementation Roadmap¶
Phase 1: Profile Architecture (1-2 days)¶
- Create
modules/profiles/directory - Extract hardware-specific configs:
hw-x86_64-vm.nix(for luna, rydev)hw-aarch64-darwin.nix(for rymac)- Create role profiles:
role-server.nix(common server settings)role-workstation.nix(desktop/laptop settings)role-dev.nix(development tools)- Refactor host configs to use these profiles
Phase 2: Service Helpers (2-3 days)¶
- Create
lib/service-helpers.nixwith: mkPodmanService- Similar to their mkService but for our Podman/Caddy stackmkCaddyVirtualHost- Standardize Caddy config generation- Security defaults for containers
- Refactor existing services to use these helpers
Phase 3: Backup Infrastructure (3-4 days)¶
- Implement
lib/backup-helpers.nixwithmkResticfunction - Add restic module configuration in
modules/nixos/services/restic/ - Configure per-service backups for:
- 1Password Connect
- Attic
- UniFi/Omada controllers
- Other critical services
- Set up both local (to NAS) and remote (B2/R2) destinations
- Create restore documentation and taskfiles
Phase 4: Security Hardening (2-3 days)¶
- Migrate to per-machine age keys:
- Generate age keys from SSH host keys
- Update
.sops.yamlwith per-host keys - Re-encrypt all secrets
- Add container security options:
- Drop all capabilities by default
- Read-only containers where possible
- No new privileges
- Implement systemd hardening for services
Phase 5: Operational Excellence (1-2 days)¶
- Add NixOS warnings for:
- Disabled backups
- Disabled monitoring
- Missing security configurations
- Create restore taskfiles similar to their approach
- Document backup/restore procedures
- Add backup monitoring/alerting
Priority Recommendations¶
- Backup system (Critical - currently missing entirely)
- Profile architecture (High - major DRY improvement)
- Service helpers (Medium - consistency and security)
- Per-machine secrets (Medium - security improvement)
- Operational warnings (Low - nice to have)
Comparison with Current Architecture¶
What We Have¶
- Unified mkNixosSystem/mkDarwinSystem builders
- SOPS secrets management (but with shared keys)
- Podman container infrastructure
- Caddy reverse proxy with DNS integration
What We're Missing¶
- Backup system (biggest gap)
- Profile-based configuration
- Service helper functions
- Per-machine security keys
- Operational warnings
What We Do Better¶
- DNS integration is more sophisticated
- Binary cache (Attic) setup
- Cross-platform Darwin support
- DNS record aggregation from Caddy configs
Next Steps¶
- Start with backup implementation (most critical)
- Implement profile architecture (biggest DRY win)
- Create service helpers (improve consistency)
- Enhance security posture
- Add operational safeguards
Code Examples to Reference¶
mkRestic Helper (from truxnell)¶
lib.mySystem.mkRestic = options: {
"${options.app}-local" = {
pruneOpts = [
"--keep-last 3"
"--keep-daily 7"
"--keep-weekly 5"
"--keep-monthly 12"
];
timerConfig = {
OnCalendar = "02:05";
Persistent = true;
RandomizedDelaySec = "1h";
};
paths = map (x: "${config.mySystem.system.resticBackup.mountPath}/${x}") options.paths;
passwordFile = config.sops.secrets."services/restic/password".path;
repository = "${config.mySystem.system.resticBackup.local.location}/${options.appFolder}";
};
"${options.app}-remote" = {
# Similar config for remote backups
};
};
Profile Usage Pattern¶
# Host configuration becomes much cleaner:
mkNixosConfig {
hostname = "luna";
hardwareModules = [ ./profiles/hw-x86_64-server.nix ];
profileModules = [
./profiles/role-server.nix
./profiles/role-container-host.nix
];
}
This pattern analysis provides a roadmap for improving our Nix configuration while maintaining what we've already built well.