Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Installation Guide

This guide covers various installation scenarios for the nix-config repository.

Windows Subsystem for Linux (WSL)

Prerequisites

  • Windows 10 version 2004 and higher (Build 19041 and higher) or Windows 11
  • WSL 2 enabled
  • Administrator access to Windows

Installation Steps

1. Install WSL 2

# Run in PowerShell as Administrator
wsl --install

# If WSL is already installed, ensure you're using WSL 2
wsl --set-default-version 2

2. Setup NixOS for WSL

Download and install NixOS-WSL via NixOS-WSL:

# Download the latest NixOS-WSL tarball
# Import the NixOS-WSL distribution
wsl --import NixOS .\NixOS\ nixos-wsl.tar.gz --version 2

# Start the NixOS instance
wsl -d NixOS

3. Configure NixOS-WSL

After starting your NixOS-WSL instance:

# Clone this repository
sudo git clone https://github.com/DaRacci/nix-config.git /etc/nixos

# Apply the WSL configuration
sudo nixos-rebuild switch --flake /etc/nixos#winix

4. WSL-Specific Features

The WSL configuration includes:

  • SSH agent relay between Windows and WSL
  • Hardware acceleration support for development
  • Remote desktop capabilities
  • Optimized for headless operation

Native NixOS Installation

Prerequisites

  • NixOS installation media
  • Target hardware
  • Network connectivity
  • Backup of important data

Installation Process

1. Boot from NixOS Installation Media

  • Download NixOS ISO from nixos.org
  • Create bootable USB/DVD
  • Boot from installation media

2. Network Configuration

# For WiFi connections
sudo systemctl start wpa_supplicant
wpa_cli
> add_network
> set_network 0 ssid "YourSSID"
> set_network 0 psk "YourPassword"
> enable_network 0
> quit

# Verify connectivity
ping nixos.org

3. Disk Setup

Follow standard NixOS installation procedures for disk partitioning and filesystem setup as described in the NixOS manual.

4. Generate Hardware Configuration

# Generate hardware configuration
nixos-generate-config --root /mnt

# Copy to your host configuration
mkdir -p /mnt/etc/nixos/hosts/{device-type}/{hostname}
cp /mnt/etc/nixos/hardware-configuration.nix /mnt/etc/nixos/hosts/{device-type}/{hostname}/hardware.nix

# Clone this repository
cd /mnt/etc/nixos
git clone https://github.com/DaRacci/nix-config.git .

5. Customize Host Configuration

Edit hosts/{device-type}/{hostname}/default.nix and hardware.nix according to your needs.

6. Install NixOS

# Install with your specific host configuration
nixos-install --flake .#{hostname}

# Set root password when prompted

7. Post-Installation

# Reboot into new system
reboot

# After reboot, ensure configuration is applied
sudo nixos-rebuild switch --flake /etc/nixos#{hostname}

Existing NixOS System Migration

From Traditional NixOS Configuration

1. Backup Current Configuration

# Backup current configuration (adjust path if using flakes)
sudo cp -r /etc/nixos /etc/nixos.backup

2. Clone This Repository

# Clone to a working directory
git clone https://github.com/DaRacci/nix-config.git /tmp/nix-config
sudo cp -r /tmp/nix-config/* /etc/nixos/

3. Create Host Configuration

# Create your host directory
sudo mkdir -p /etc/nixos/hosts/{device-type}/{hostname}

# Migrate your hardware configuration
sudo cp /etc/nixos.backup/hardware-configuration.nix /etc/nixos/hosts/{device-type}/{hostname}/hardware.nix

# Create default.nix based on your old configuration
# Edit to follow the new structure

4. Test and Apply

# Test the new configuration
sudo nixos-rebuild build --flake .#{hostname}

# Apply if build succeeds
sudo nixos-rebuild switch --flake .#{hostname}

IO Guardian - Database Availability System

The IO Guardian system ensures that services across the infrastructure are aware of the availability of centralized databases (PostgreSQL and Redis) hosted on config.server.ioPrimaryHost. It provides graceful startup and shutdown coordination between the database host and dependent services on other servers.

Overview

The system consists of two components:

  1. Guardian Server (runs on client servers)

    • WebSocket server that listens for commands from the coordinator
    • Executes drain/undrain commands by controlling io-databases.target
  2. Guardian Client (runs on the IO Host)

    • WebSocket client that connects to all guardian servers
    • Sends undrain command after databases are online (start dependent services)
    • Sends drain command before database shutdown (stop dependent services)

How It Works

System Startup

  1. Client servers boot and run wait-for-io-databases.service
  2. This service waits (with retries) until PostgreSQL and Redis on the IO Host are reachable
  3. Once databases are confirmed available, the service completes
  4. The io-databases.target is now ready to be activated
  5. When the IO Hosts io-database-coordinator.service starts, it sends undrain to all clients
  6. Clients start io-databases.target, which starts all dependent services

Database Shutdown (Graceful Drain)

  1. When io-database-coordinator.service stops (before databases stop)
  2. It connects to all guardian servers via WebSocket
  3. Sends drain command to each server
  4. Guardian servers stop io-databases.target
  5. Dependent services stop gracefully before databases go down

Database Startup (Undrain)

  1. When databases come online on the IO Host
  2. io-database-coordinator.service starts
  3. It sends undrain command to all guardian servers
  4. Guardian servers start io-databases.target
  5. All dependent services start

Security

Communication is secured using a Pre-Shared Key (PSK) that must be at least 32 characters. All WebSocket connections must authenticate with this key before commands are accepted.

Generating the PSK

Generate a new PSK using OpenSSL:

openssl rand -base64 32

Adding the Secret

Add the generated PSK to hosts/server/secrets.yaml:

IO_GUARDIAN_PSK: <your-generated-key>

Then encrypt the file:

sops --encrypt --in-place hosts/server/secrets.yaml

Configuration

Port

The guardian WebSocket server listens on port 9876 by default. This port is automatically opened to local subnets on servers with database dependencies.

Dependent Services

Dependent Services will be automatically populated with service names where there is a systemd.service.<name> defined from the names in server.database.postgres or server.database.redis.

To manually add a service bind to the database availability target, add it to the server.database.dependentServices option:

{
  server.database.dependentServices = [
    "my-service"
    "another-service"
  ];
}

Services listed here will:

  • Start only when io-databases.target is active
  • Stop when io-databases.target stops
  • Restart when the target restarts

Systemd Units

On Client Servers

UnitTypeDescription
io-guardian.servicesimpleWebSocket server for receiving commands
io-databases.targettargetRepresents “databases are online”
wait-for-io-databases.serviceoneshotWaits for databases at boot (runs once)

On nixio

UnitTypeDescription
io-database-coordinator.serviceoneshotSends undrain on start, drain on stop

Troubleshooting

Checking Guardian Status

On client servers:

systemctl status io-guardian.service
systemctl status io-databases.target
systemctl status wait-for-io-databases.service
journalctl -u io-guardian.service -f

On IO Hosts:

systemctl status io-database-coordinator.service
journalctl -u io-database-coordinator.service

Manual Commands

To manually start dependent services on a client:

systemctl start io-databases.target

To manually stop dependent services:

systemctl stop io-databases.target

Common Issues

Guardian server won’t start:

  • Check that IO_GUARDIAN_PSK secret is properly configured
  • Verify the sops decryption is working: cat /run/secrets/IO_GUARDIAN_PSK

Services not starting after boot:

  • Check wait service logs: journalctl -u wait-for-io-databases.service
  • Verify network connectivity to an IO Host on ports 5432 (Postgres) and 6379 (Redis)
  • Ensure an IO Hosts coordinator has sent the undrain command

Authentication failures in logs:

  • Ensure the same PSK is deployed to all servers
  • Re-encrypt secrets if the key was changed

Protocol Reference

The guardian uses a simple JSON-based WebSocket protocol:

Authentication

// Client sends:
{"type": "auth", "key": "<psk>"}

// Server responds:
{"type": "auth", "status": "ok", "message": "Authentication successful"}
// or
{"type": "auth", "status": "error", "message": "Invalid key"}

Commands

// Coordinator sends:
{"type": "command", "action": "drain"}
// or
{"type": "command", "action": "undrain"}
// or
{"type": "command", "action": "ping"}

// Server responds:
{"type": "response", "action": "<action>", "status": "ok", "message": "..."}
// or
{"type": "response", "action": "<action>", "status": "error", "message": "..."}

Server Cluster Monitoring

The monitoring module provides a comprehensive observability stack for the server cluster using Prometheus (metrics), Loki (logs), and Grafana (visualization). All components are configured as reusable NixOS modules with automatic cross-host discovery.

Overview

The system consists of three layers:

  1. Exporters (run on all servers)

    • node_exporter for system-level metrics (CPU, memory, disk, network, per-process stats)
    • Grafana Alloy for shipping journald logs and Caddy access logs to Loki
    • Caddy access logs are parsed as JSON at ingest time so detected_level, logger, and status are available in Loki
    • Ingest-time log parsing for journal stdout entries and Caddy access logs to infer detected_level and normalize common timestamp formats
    • Application-specific exporters (Caddy, PostgreSQL, Redis) enabled automatically
  2. Collectors (run on the monitoring primary host)

    • Prometheus for metrics aggregation with 90-day retention
    • Loki for log aggregation with 90-day retention
    • Alertmanager for alert routing and notifications
  3. Visualization (runs on the monitoring primary host)

    • Grafana with provisioned datasources and dashboards
    • Native Kanidm OAuth2 authentication

Architecture

┌─────────────────────────────────────────────────────┐
│                    nixmon (Monitoring Primary)        │
│  ┌──────────┐  ┌──────┐  ┌─────────┐  ┌──────────┐ │
│  │Prometheus │  │ Loki │  │ Grafana │  │Alertmgr  │ │
│  │  :9090    │  │:3100 │  │  :3000  │  │  :9093   │ │
│  └────┬──┬──┘  └──┬───┘  └─────────┘  └────┬─────┘ │
│       │  │        │                         │       │
│  ┌────┘  │   ┌────┘        ┌────────────────┘       │
│  │ scrape│   │ push        │ webhooks               │
├──┼───────┼───┼─────────────┼────────────────────────┤
│  ▼       ▼   ▼             ▼                        │
│  All servers:          Home Assistant / Nextcloud    │
│  - node_exporter :9100                              │
│  - alloy → Loki                                     │
│  - caddy metrics :2019 (if proxy configured)        │
│  - postgres_exporter :9187 (if postgres configured) │
│  - redis_exporter :9121 (if redis configured)       │
│  - pve_exporter :9221 (nixmon only, Proxmox API)    │
└─────────────────────────────────────────────────────┘

Configuration

Enabling Monitoring

Monitoring is enabled by default on all servers (server.monitoring.enable = true). The monitoring primary host is configured via the allocations.server.monitoringPrimaryHost option, currently set to nixmon.

Options Reference

All options live under server.monitoring:

OptionTypeDefaultDescription
enablebooltrueEnable monitoring for this server
retention.metricsstring"90d"Prometheus TSDB retention period
retention.logsstring"90d"Loki log retention period
exporters.node.enablebooltrueEnable node_exporter
exporters.caddy.enableboolautoEnable Caddy metrics (auto if proxy configured)
exporters.postgres.enableboolautoEnable PostgreSQL exporter (auto on IO host)
exporters.redis.enableboolautoEnable Redis exporter (auto on IO host)
logs.enablebooltrueEnable Alloy log shipping
collector.enableboolautoEnable collectors (auto on monitoring host)
collector.grafana.kanidm.enablebooltrueEnable Kanidm OAuth2 for Grafana
collector.alerting.enablebooltrueEnable Alertmanager
collector.alerting.homeAssistant.enableboolfalseEnable Home Assistant webhook alerting
collector.alerting.nextcloudTalk.enableboolfalseEnable Nextcloud Talk webhook alerting
collector.proxmox.enablebooltrueEnable Proxmox VE metrics collection

Auto-Detection

The module automatically detects and enables exporters based on host role:

  • Caddy exporter: Enabled when server.proxy.virtualHosts is non-empty
  • PostgreSQL exporter: Enabled on the IO primary host when postgres databases are configured
  • Redis exporter: Enabled on the IO primary host when redis instances are configured
  • Caddy access logs: Enabled when Caddy metrics/logs are enabled; each access log file under /var/log/caddy-access-* is shipped to Loki and parsed as JSON
  • node_exporter process collector: Enabled on all servers via the processes collector to expose per-process stats
  • Collector services: Enabled only on the monitoring primary host

Secrets

The monitoring module requires the following secrets in hosts/server/nixmon/secrets.yaml:

MONITORING:
    GRAFANA:
        SECRET_KEY: <random-secret-key>
        OAUTH_SECRET: <kanidm-oauth2-secret>
    HOME_ASSISTANT:
        WEBHOOK_URL: <ha-webhook-url>
    NEXTCLOUD_TALK:
        WEBHOOK_URL: <nc-talk-webhook-url>
PROXMOX:
    USER: <proxmox-user-at-realm>
    TOKEN_ID: <proxmox-token-name>
    TOKEN_SECRET: <proxmox-token-secret>

Generating Secrets

Generate the Grafana secret key:

cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 48

The MONITORING/GRAFANA/OAUTH_SECRET must match the value in hosts/server/nixcloud/secrets.yaml under KANIDM/OAUTH2/GRAFANA_SECRET (the Kanidm provisioning side).

Caddy Virtual Hosts

The module configures three virtual hosts on nixmon:

ServiceSubdomainAccess
Grafanagrafana.<domain>Public
Prometheusprometheus.<domain>LAN
Lokiloki.<domain>LAN

These are defined in hosts/server/nixmon/default.nix and collected by the IO primary host’s Caddy configuration.

Alert Rules

The following alerts are configured by default:

AlertConditionSeverity
HostDownup{job="node"} == 0 for 2 minutesCritical
DiskSpaceCriticalRoot filesystem < 10% free for 5 minutesCritical
HighCPUUsageCPU usage > 90% for 5 minutesWarning
HighMemoryUsageMemory usage > 90% for 5 minutesWarning
ServiceDownup{job!="node"} == 0 for 2 minutesCritical

Alerts are routed to:

  • Home Assistant: All critical and warning alerts via webhook (requires collector.alerting.homeAssistant.enable = true)
  • Nextcloud Talk: Critical alerts only via webhook (requires collector.alerting.nextcloudTalk.enable = true)

Module Structure

modules/nixos/server/monitoring/
├── default.nix              # Entry point, imports sub-modules
├── options.nix              # All server.monitoring.* options
├── collector/
│   ├── default.nix          # Imports collector sub-modules
│   ├── prometheus.nix       # Prometheus server + scrape targets
│   ├── loki.nix             # Loki server + storage config
│   ├── grafana.nix          # Grafana + Kanidm OAuth2
│   ├── alerting.nix         # Alertmanager + alert rules
│   └── dashboards.nix       # Dashboard provisioning
├── exporters/
│   ├── default.nix          # Imports exporter sub-modules
│   ├── node.nix             # node_exporter
│   ├── caddy.nix            # Caddy metrics
│   ├── postgres.nix         # PostgreSQL exporter
│   └── redis.nix            # Redis exporter
├── logs/
│   └── alloy.nix            # Alloy log shipping
└── integrations/
    └── proxmox.nix          # PVE exporter for Proxmox API

Troubleshooting

Checking Service Status

On the monitoring host (nixmon):

systemctl status prometheus.service
systemctl status loki.service
systemctl status grafana.service
systemctl status prometheus-alertmanager.service
systemctl status prometheus-pve-exporter.service

On any server:

systemctl status prometheus-node-exporter.service
systemctl status alloy.service

Verifying Metrics Collection

Check Prometheus targets are up:

curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {instance: .labels.instance, health: .health}'

Verifying Log Collection

Alloy applies ingest-time parsing for journal stdout logs and Caddy access logs before forwarding to Loki:

  • Caddy access logs are read as JSON, not plain text

  • Legacy timestamps in form YYYY/MM/DD HH:MM:SS are parsed and used as event timestamps

  • ISO-8601 timestamps with a log level prefix are parsed and normalized

  • detected_level defaults to info when the source log line does not provide one

  • Caddy JSON fields level, ts, logger, and status are extracted into Loki labels and timestamps

  • Caddy access logs are read from /var/log/caddy-access-*.log and use the timestamp and level prefix in each line when present

node_exporter also enables the processes collector, which exposes per-process metrics such as CPU and memory usage for running processes.

Check Alloy is shipping logs:

journalctl -u alloy.service -f

Query Loki directly:

curl -s 'http://localhost:3100/loki/api/v1/labels' | jq

Common Issues

Grafana OAuth login fails:

  • Verify GRAFANA_OAUTH_SECRET in nixmon matches KANIDM/OAUTH2/GRAFANA_SECRET in nixcloud
  • Check Kanidm provisioning has the grafana OAuth2 client configured
  • Verify DNS resolves auth.<domain> correctly

Prometheus targets showing as down:

  • Check firewall rules allow traffic on exporter ports from the monitoring host
  • Verify the exporter service is running on the target host
  • Check network connectivity between nixmon and the target host

Proxmox metrics missing:

  • Verify proxmox/token_id and proxmox/token_secret are valid
  • Check PVE API is accessible from nixmon: curl -k https://pve.<domain>/api2/json
  • Review PVE exporter logs: journalctl -u prometheus-pve-exporter.service

Creating New Users

To add a new user configuration:

1. Create User Directory

mkdir -p home/newuser

2. Create User Configuration Files

Create host-specific configurations in home/newuser/{hostname}.nix:

{ pkgs, lib, ... }:
{
  imports = [
    # Import shared configurations
    ./features/cli              # Common CLI tools
    ./features/desktop/common   # Desktop environment basics
  ];

  # User-specific configuration
  home = {
    username = "newuser";
    homeDirectory = "/home/newuser";
    stateVersion = "25.05";
  };

  # Add user-specific packages and configuration
  programs = {
    git = {
      userName = "Your Name";
      userEmail = "your.email@domain.com";
    };
  };
}

Create feature modules in home/newuser/features/:

mkdir -p home/newuser/features/{cli,desktop,development}

The auto-discovery system will automatically link users to hosts if:

  • A file home/{username}/{hostname}.nix exists
  • The hostname matches an existing host configuration

4. Test User Configuration

# Build home-manager configuration
home-manager build --flake .#newuser@hostname

# Switch to new configuration
home-manager switch --flake .#newuser@hostname

Creating New Hosts

To add a new host to your configuration:

1. Create Host Directory Structure

# For a new desktop host named "mydesktop"
mkdir -p hosts/desktop/mydesktop

# For a new server host named "myserver"
mkdir -p hosts/server/myserver

# For a new laptop host named "mylaptop"
mkdir -p hosts/laptop/mylaptop

2. Create Required Configuration Files

Create hosts/{device-type}/{hostname}/default.nix:

{ self, pkgs, ... }:
{
  imports = [
    # Hardware configuration (required)
    ./hardware.nix

    # Optional: device-specific modules
    # "${self}/hosts/shared/optional/containers.nix"
    # "${self}/modules/nixos/custom-module.nix"
  ];

  # Host-specific configuration
  host = {
    device.isHeadless = false; # Set to true for servers
  };

  # Add your system configuration here
  # networking.hostName is automatically set from directory name
}

Create hosts/{device-type}/{hostname}/hardware.nix:

{ inputs, ... }:
{
  imports = [
    # Include relevant hardware modules
    inputs.nixos-hardware.nixosModules.common-cpu-amd
    inputs.nixos-hardware.nixosModules.common-pc-ssd

    # For laptops, also include:
    # inputs.nixos-hardware.nixosModules.common-pc-laptop
  ];

  # Boot configuration
  boot.loader = {
    systemd-boot.enable = true;
    efi.canTouchEfiVariables = true;
  };

  # Filesystem configuration (use disko for declarative disk setup)
  fileSystems."/" = {
    device = "/dev/disk/by-label/nixos";
    fsType = "ext4";
  };

  # Add hardware-specific configuration
}

3. Add Hardware Acceleration (Optional)

If your host supports hardware acceleration, add it to the acceleration lists in flake.nix:

accelerationHosts = {
  cuda = [
    "your-new-host"  # Add here for CUDA support
  ];
  rocm = [
    "your-amd-host"  # Add here for ROCm support
  ];
};

4. Build and Test

# Build the configuration (don't switch yet)
sudo nixos-rebuild build --flake .#your-new-host

# Test the configuration
sudo nixos-rebuild test --flake .#your-new-host

# Switch to the new configuration
sudo nixos-rebuild switch --flake .#your-new-host

Using a Nix Package or NixOS Module from a Separate Fork of Nixpkgs

This guide will show you how to use a Nix package or NixOS module from a separate fork of nixpkgs.

Step 1: Define the Forked Repository

In your Nix file, define the forked repository using fetchFromGitHub function:

nixpkgs.overlays = [
  (self: super: {
    <your-package> = (import
      (pkgs.fetchzip (
        let owner = "<owner>"; branch = "<branch>"; in {
          url = "https://github.com/${owner}/nixpkgs/archive/${branch}.tar.gz";
          # Change to 52 zeros when archive needs to be redownloaded.
          sha256 = "<sha256>";
        }
      ))
      { overlays = [ ]; config = super.config; }).<your-package>;
  })
];

In this example, replace <your-package>, <owner>, <branch>, and <sha256> with the actual values from the forked repository.

Step 2: Use Packages or Modules from the Forked Repository

Now you can use packages or modules from the forked repository in your Nix expressions. For example, if you want to use a package from the forked repository, you can refer to it using the <your-package> attribute. Here’s an example:

{
  environment.systemPackages = with pkgs; [
    <your-pckage>
  ];
}

In this example, replace <your-package with the actual name of the package you want to use.

Declarative Gnome Dconf

Description

When changing GNOME or GNOME extension settings, it is recommended to use dconf2nix and cherry pick its output. This allows for easy configuration using the GUI, but requires copying the settings back into the respective dconf settings in home-manager to save them.


DConf Locations

The locations for where to save DConf settings to is:

  • Base.nix for standard GNOME DConf Settings.
  • Extensions.nix for Extensions DConf Settings
  • Per User Settings should be saved in the format of home/${username}/desktop/gnome.nix

Getting the Output

dconf2nix will be installed as part of this flakes dev shell.

Running the following will output the current dconf settings into a temporary file so you can Cherry Pick your changes.

dconf dump / | dconf2nix > dconf.nix

Using a Package/Module from a Fork

Modules Overview

Purpose

This section provides an overview of the custom NixOS and Home-Manager modules defined in this repository. These modules allow for modular and reusable configurations across different hosts and users.

Entry Points

NixOS Modules

This section covers all NixOS modules provided by this flake.

NixOS Services

This section documents the custom NixOS service modules available in this configuration. These modules provide specialised integrations and monitoring capabilities.

AI Agent

AI Agent

Autonomous AI Agent service powered by Zeroclaw, providing intelligent task automation with security controls for code review and development tasks.

Special Options

  • services.ai-agent.enable: Enable the autonomous AI Agent service.

Allowed Domains

The AI Agent is configured to access the following domains for development, coding, and general information tasks:

  • Version Control & Collaboration: github.com, gitlab.com, codeberg.org, git.sr.ht, raw.githubusercontent.com
  • Programming Languages & Frameworks: rust-lang.org, golang.org, registry.npmjs.org, pypi.org, developer.mozilla.org, cppreference.com
  • Documentation & Reference: docs.rs, crates.io, devdocs.io, learn.microsoft.com, w3.org, gnu.org, curl.se, man.archlinux.org
  • General Information: wikipedia.org
  • Package Management & Containers: docker.io, hub.docker.com
  • Development Tools: stackoverflow.com
  • NixOS Ecosystem: nixos.org

Allowed Commands

The AI Agent has automatic approval for the following common and safe bash utilities and developer tools:

  • File Operations: cat, diff, fd, file, find, head, od, strings, tail, tree, wc
  • Text Processing & Search: awk, cut, echo, grep, jq, printf, rg, sed, sort, tr, uniq
  • Archiving & Compression: gzip, gunzip, tar
  • Hashing & Encoding: base64, md5sum, sha256sum
  • System Information: date, pwd, uname, whoami
  • Network & Connectivity: curl, dig, ping, wget
  • Text Editors & Pagers: less, more, nano, vim
  • Rust Development: cargo, cargo-build, cargo-check, cargo-test, rustc, rustfmt, rustup
  • Nix Tools: nix, nix-build, nix-env, nix-flake, nix-fmt, nix-shell
  • C/C++ Development: clang, cmake, g++, gcc
  • Version Control: git, hg, jj
  • Language Runtimes: go, lua, node, npm, pip, poetry, python, python3, ruby
  • Build Tools: make
  • Debugging & Analysis: gdb, lldb, ltrace, strace, valgrind

Specialized Agents

The AI Agent service includes 15 specialized agents designed for different development tasks. Each agent is optimized for specific roles:

Deep Work & Architecture

  • sisyphus: Deep architectural analysis and foundational problem solving (Claude Opus 4.6)
  • metis: Strategic planning, best practices, and architectural wisdom (Claude Opus 4.6)
  • architect: System design, architecture decisions, and scalability planning (Claude Opus 4.6)

Code Understanding & Exploration

  • atlas: Code exploration, pattern discovery, and codebase navigation (GPT-5.2 Codex)
  • prometheus: Code analysis, refactoring planning, and optimization strategies (GPT-5.2 Codex)
  • explorer: Quick code exploration and debugging assistance (GPT-5 Mini)

Implementation & Building

  • hephaestus: Implementation, building, and crafting solutions (GPT-5.2 Codex)
  • validator: Test design, QA strategy, and reliability verification (GPT-5.2)

Quality & Security

  • critic: Code review, quality analysis, and best practice enforcement (Claude Opus 4.6)
  • guardian: Security analysis, vulnerability assessment, and hardening (Claude Opus 4.6)

Operations & Infrastructure

  • devops: Infrastructure, deployment, and operational excellence (Claude Opus 4.6)

Knowledge & Communication

  • oracle: General technical inquiry and consultation (GPT-5.2)
  • librarian: Documentation lookup, API reference, and quick answers (GPT-5 Mini)
  • scribe: Documentation writing, README creation, and technical communication (GPT-5 Mini)

General Purpose

  • default: General purpose agent for miscellaneous tasks (Claude Opus 4.6)

Usage Example

{ ... }: {
  services.ai-agent = {
    enable = true;
  };
}

Operational Notes

The AI Agent service runs with automatic approval for safe operations (file_read, memory_recall, web_fetch, web_search) as well as a curated set of non-destructive bash commands. It enforces security controls including OTP gating for sensitive domains (banking, finance, medical, government, identity providers) and an emergency stop capability. The service is configured to operate only within designated workspace boundaries and maintains a local SQLite memory backend with automatic saving. It runs a heartbeat check every 15 minutes to ensure operational health.

Huntress

Huntress

Managed EDR (Endpoint Detection and Response) platform that protects systems by detecting malicious footholds used by attackers.

Special Options

  • services.huntress.accountKeyFile: Path to a file containing the Huntress account key.
  • services.huntress.organisationKeyFile: Path to a file containing the Huntress organisation key.

Usage Example

{ config, ... }: {
  services.huntress = {
    enable = true;
    accountKeyFile = config.sops.secrets.huntress_account_key.path;
    organisationKeyFile = config.sops.secrets.huntress_org_key.path;
  };
}

Operational Notes

The agent configuration is generated at /etc/huntress/agent_config.yaml during the service’s preStart phase. It merges the provided account and organisation keys using yaml-merge. The keys are securely loaded into the service using systemd LoadCredential.

MCPO

MCPO (Model Context Protocol Orchestrator)

Orchestrates Model Context Protocol (MCP) servers, providing a centralized way to manage and expose multiple MCP servers.

Special Options

  • services.mcpo.configuration: An attribute set defining the MCP servers to orchestrate.
  • services.mcpo.apiTokenFile: Optional path to a file containing an API token for the service.
  • services.mcpo.extraPackages: Additional packages to include in the service’s PATH.
  • services.mcpo.helpers: Read-only attribute set of helper functions for common server types (e.g., npxServer, uvxServer).

Usage Example

{ config, ... }: {
  services.mcpo = {
    enable = true;
    configuration = {
      everything = config.services.mcpo.helpers.npxServer "@modelcontextprotocol/server-everything";
    };
  };
}

Operational Notes

MCPO runs as a DynamicUser with a state directory at /var/lib/mcpo. The configuration is rendered via sops.templates and loaded into the service via systemd credentials. The service’s PATH includes bash, nodejs, and uv by default to support various MCP server types.

Metrics

Metrics & Hacompanion

Comprehensive metrics collection and integration with Home Assistant via hacompanion.

Special Options

  • services.metrics.hacompanion.enable: Enable the Home Assistant Companion service.
  • services.metrics.hacompanion.sensor.<name>.enable: Enable specific built-in sensors (e.g., cpu_temp, memory, uptime).
  • services.metrics.hacompanion.script: Define custom scripts to expose as sensors or switches in Home Assistant.
  • services.metrics.hacompanion.storage: Configure monitoring for storage devices and ZFS pools.
  • services.metrics.upgradeStatus.enable: Enable a specialized sensor for tracking NixOS upgrade status.

Usage Example

{ ... }: {
  services.metrics.hacompanion = {
    enable = true;
    sensor.cpu_temp.enable = true;
    sensor.memory.enable = true;
    storage.main = {
      name = "Main OS Drive";
      sensors.used = true;
    };
  };
}

Operational Notes

Hacompanion uses a generated TOML configuration file and securely loads the Home Assistant API token from sops.secrets.HACOMPANION_ENV. The upgradeStatus feature can also integrate with Uptime Kuma to provide heartbeat notifications for successful system upgrades.

Tailscale

Tailscale

Extensions to the standard NixOS Tailscale module, providing easier tag management.

Special Options

  • services.tailscale.tags: A list of tags to advertise for this device. These tags are automatically prefixed with tag: when passed to tailscale up.

Usage Example

{ ... }: {
  services.tailscale = {
    enable = true;
    tags = [ "server" "internal" ];
  };
}

Operational Notes

This module simplifies the application of Tailscale tags by automatically constructing the --advertise-tags flag. Ensure that the device has the necessary permissions in your Tailscale ACLs to apply the requested tags.

Core Module

Documents shared NixOS core modules used across hosts.

Purpose

modules/nixos/core/ contains reusable host-level defaults and feature modules.

It also defines top-level baseline options under core.* that control this shared behavior for most hosts.

Top-Level Options

core.enable

Typebool
Defaulttrue

Master switch for shared core baseline.

core.audio.enable

Typebool
Default!config.host.device.isHeadless

Enable shared audio stack on non-headless hosts by default.

core.bluetooth.enable

Typebool
Default!config.host.device.isHeadless

Enable Bluetooth support on non-headless hosts by default.

core.network.enable

Typebool
Default!config.host.device.isVirtual

Enable shared network baseline on non-virtual hosts by default.

Baseline Behaviour

When core.enable is true, module applies shared defaults from modules/nixos/core/default.nix:

  • sets services.dbus.implementation = "broker",
  • enables PipeWire audio stack and disables PulseAudio when core.audio.enable is on,
  • enables Bluetooth stack, Blueman, and persisted Bluetooth state when core.bluetooth.enable is on,
  • enables NetworkManager and adds network to shared default groups when core.network.enable is on, and
  • on non-headless hosts, adds video and i2c groups and enables dleyna, gnome-keyring, udisks2, colord, xserver.updateDbusEnvironment, and polkit.

Audio baseline also enables security.rtkit, adds audio, pipewire, and rtkit groups, installs udev rules for rtc0 and hpet, and sets PAM limits for realtime audio workloads.

Bluetooth baseline unblocks rfkill during activation and persists /var/lib/bluetooth.

Key Pages

Usage Example

{ ... }: {
  core = {
    enable = true;
    audio.enable = true;
    bluetooth.enable = true;
    network.enable = true;
  };
}

Notes

These modules are imported through modules/nixos/core/default.nix. Most feature pages document their own core.<name> option namespaces, while some baseline modules such as Nix apply unconditionally once imported.

Activation

Reports system generation changes during NixOS activation.

  • Entry point: modules/nixos/core/activation.nix

Overview

This module adds activation-time diff reporting with nvd. During activation it compares previous and new system generations and prints package and closure changes.


Options

core.activation.enable

Typebool
Defaultconfig.core.enable

Enable activation diff reporting.


Behaviour

When enabled, module installs system.activationScripts.report-changes that:

  • finds previous and newest system profile links under /nix/var/nix/profiles,
  • resolves both links to store paths, and
  • runs nvd diff between them.

If no previous generation exists yet, script does nothing.


Usage Example

{ ... }: {
  core.activation.enable = true;
}

Operational Notes

  • Diff output is informational only. Script ends with || true, so activation does not fail if nvd diff returns non-zero.
  • Default follows top-level core.enable, so most hosts get generation diff reporting automatically.

Auto Upgrade

Schedules automatic NixOS upgrades from flake host outputs.

  • Entry point: modules/nixos/core/auto-upgrade.nix

Overview

This module configures system.autoUpgrade to rebuild host from github:DaRacci/nix-config#<host>. It also applies resource limits to nixos-upgrade.service so scheduled upgrades run with lower CPU and IO priority.


Options

core.auto-upgrade.enable

Typebool
Defaulttrue

Enable automatic upgrade configuration.

core.auto-upgrade.hostName

Typestring
Defaultconfig.networking.hostName

Host output name used in flake reference github:DaRacci/nix-config#<hostName>.


Behaviour

When enabled, module configures:

  • system.autoUpgrade.dates = "04:00",
  • randomizedDelaySec = "45min" to spread out upgrade times across hosts,
  • flags --refresh, --accept-flake-config, and --no-update-lock-file,
  • service resource controls for nixos-upgrade.service.

Auto-upgrade itself only turns on when flake has self.rev, meaning repository is in clean revisioned state.


Usage Example

{ ... }: {
  core.auto-upgrade = {
    enable = true;
    hostName = "my-host";
  };
}

Operational Notes

  • Dirty working trees or non-revisioned evaluations leave system.autoUpgrade.enable = false.
  • Module always points upgrades at GitHub flake source, not local checkout.
  • Resource limits set CPUWeight = 20, CPUQuota = 65%, and IOWeight = 20 on upgrade service.

Containers

Enables Docker-based container runtime defaults.

  • Entry point: modules/nixos/core/containers.nix

Overview

This module turns on Docker as primary container backend and configures OCI containers to use Docker. It also enables weekly image pruning and persists Docker state directories.


Options

core.containers.enable

Typebool
Defaultdisabled

Enable shared container runtime configuration.


Behaviour

When enabled, module:

  • enables virtualisation.docker,
  • sets virtualisation.docker.package = pkgs.docker,
  • enables CDI support with daemon.settings.features.cdi = true,
  • enables weekly docker autoPrune,
  • sets virtualisation.oci-containers.backend = "docker",
  • adds docker to core.defaultGroups, and
  • persists Docker state under /var/lib/docker.

Persisted directories include overlay2, image, volumes, containers, containerd, and buildkit.


Usage Example

{ ... }: {
  core.containers.enable = true;
}

Operational Notes

  • Module intentionally prefers Docker because current workloads still need features not covered by Podman or podman-compose.
  • Users receive Docker access through shared core.defaultGroups handling.

Display Manager

Configures display manager for graphical sessions on desktop and laptop hosts.

  • Entry point: modules/nixos/core/display-manager.nix

Overview

This module sets up greetd with tuigreet as default display manager. It is automatically enabled on hosts where host.device.isHeadless = false.

When session packages are present in services.displayManager.sessionPackages, module also passes both Wayland and X session directories to tuigreet.


Options

core.display-manager.enable

Typebool
Default!config.host.device.isHeadless

Enable display manager configuration. Automatically disabled on headless hosts.


Behaviour

When enabled, greetd starts with tuigreet providing terminal-based greeter that:

  • shows current time with --time,
  • remembers last logged-in user with --remember,
  • remembers last selected session with --remember-session, and
  • adds --sessions and --xsessions only when services.displayManager.sessionPackages is non-empty.

Greeter cache is persisted to /var/cache/tuigreet through host.persistence.directories.


Usage Example

{ ... }: {
  services.displayManager.sessionPackages = [
    pkgs.hyprland
  ];

  core.display-manager.enable = true;
}

Operational Notes

  • greetd runs as greeter user.
  • Both Wayland (wayland-sessions) and X11 (xsessions) session paths are built dynamically from installed session packages, so adding new session package is enough to make it appear in greeter.

Gaming

Enables gaming, VR, and Steam-focused desktop features.

  • Entry point: modules/nixos/core/gaming.nix

Overview

This module configures desktop gaming stack around Steam, 32-bit graphics support, Android ADB tools, and WiVRn streaming. It also adds firewall rules, udev rules for common gaming devices, and optional Decky Loader lifecycle integration.


Options

core.gaming.enable

Typebool
Defaultdisabled

Enable gaming feature set.


Behaviour

When enabled, module:

  • adds adbusers to core.defaultGroups,
  • enables hardware.steam-hardware,
  • enables 32-bit graphics support,
  • installs pkgs.android-tools,
  • enables Steam with Steam Deck style launch arguments,
  • enables programs.steam.extest,
  • adds pkgs.xwayland-run and pkgs.xwininfo as Steam extra packages,
  • adds pkgs.proton-ge-bin as compatibility package,
  • opens Steam Remote Play and local transfer firewall rules,
  • enables services.wivrn with autoStart, highPriority, steam.importOXRRuntimes, firewall access, and JSON config, and
  • installs udev rules for PlayStation controller, Oculus Quest, and tty ACM devices.

It also overlays gamescope-session to use 4K resolution and wider refresh limits, and opens firewall ports UDP 41492, 9943, 9944 plus TCP 8082, 9943, 9944, and 24070.


Decky Loader Integration

If config.jovian.decky-loader.enable is true, module additionally:

  • prevents decky-loader.service from auto-starting at boot,
  • adds Polkit rule so active local user can start and stop decky-loader.service, and
  • when Home Manager is present, installs user service that polls Steam PID file, starts Decky Loader once Steam is running, and stops it after Steam exits.

Usage Example

{ ... }: {
  core.gaming.enable = true;
}

Operational Notes

  • Module assumes desktop-class host with graphics stack and Steam support.
  • WiVRn config uses NVENC H.265 encoder entries and enables pkgs.wayvr as application.
  • Some extra behavior only appears when related modules already exist, such as Jovian Decky Loader and Home Manager.

Generators

Configures image and container generator support for shared NixOS hosts.

  • Entry point: modules/nixos/core/generators.nix

Overview

This module imports nixos-generators formats and exposes core.generators options for generator-specific setup.

Current focus is Proxmox LXC image generation. When enabled for virtual hosts, module adds activation logic that prompts for SSH host private key on first boot, validates it with ssh-keygen, and stores it under /persist/etc/ssh/ssh_host_ed25519_key so later secret management can install it into /etc/ssh.


Options

core.generators.enable

Typebool
Defaultconfig.core.enable

Enable shared generator configuration and import generator formats from nixos-generators.

core.generators.proxmoxLXC.enable

Typebool
Defaultcfg.enable && config.host.device.isVirtual

Enable Proxmox LXC generator integration. Adds first-boot SSH private key prompt and Proxmox LXC format configuration.

core.generators.proxmoxLXC.sedPath

Typepath
DefaultgetExe' pkgs.busybox "sed"

Path to sed binary used to extract pasted OpenSSH private key block from terminal input.

core.generators.proxmoxLXC.sshKeygenPath

Typepath
DefaultgetExe' pkgs.openssh "ssh-keygen"

Path to ssh-keygen binary used to validate pasted private key and derive matching public key.

core.generators.proxmoxLXC.clearPath

Typepath
DefaultgetExe' pkgs.busybox "clear"

Path to clear binary used between failed interactive prompt attempts.


Assertions and Behaviour

When core.generators.proxmoxLXC.enable = true, module asserts that image contains /etc/ssh/ssh_host_ed25519_key.pub.

If activation runs without controlling terminal, prompt is skipped and activation exits cleanly. If terminal exists, module loops until pasted private key:

  • contains valid OpenSSH private key block,
  • passes ssh-keygen -y, and
  • matches public key already present at /etc/ssh/ssh_host_ed25519_key.pub.

Usage Example

{ ... }: {
  core.generators = {
    enable = true;

    proxmoxLXC = {
      enable = true;
    };
  };
}

Operational Notes

  • nixos-generators formats are imported unconditionally by module, but runtime configuration only applies when core.generators.enable is on.
  • Proxmox LXC flow is designed for images where public host key is baked into image but private key must be supplied interactively after boot.
  • Stored private key lives in /persist, so persistence and later secret deployment must be configured for target host.

Groups

Applies shared extra group membership to all declared users.

  • Entry point: modules/nixos/core/groups.nix

Overview

This module defines core.defaultGroups, shared list of Unix groups appended to every configured standard user. Other core modules use this option to grant access to subsystems like audio, networking, Docker, printing, and virtualisation.


Options

core.defaultGroups

Typelist of string
Default[]

Additional groups added to all users by default.


Behaviour

When core.defaultGroups is non-empty, module rewrites users.users entries so each declared user receives extraGroups = mkAfter cfg.defaultGroups.

This means module appends shared groups after any user-specific group configuration instead of replacing it.


Usage Example

{ ... }: {
  core.defaultGroups = [
    "audio"
    "network"
    "lp"
  ];
}

Operational Notes

  • This module has no enable flag. It activates whenever core.defaultGroups contains values.
  • Group assignment applies to every user passed through module argument users.

Locale

Sets shared timezone and locale defaults.

  • Entry point: modules/nixos/core/locale.nix

Overview

This module provides opinionated regional defaults for timezone and locale. It sets Australia/Sydney timezone and enables Australian and US English UTF-8 locales.


Options

core.locale.enable

Typebool
Defaulttrue

Enable locale baseline configuration.


Behaviour

When enabled, module sets:

  • time.timeZone = "Australia/Sydney",
  • i18n.defaultLocale = "en_AU.UTF-8", and
  • i18n.supportedLocales = [ "en_AU.UTF-8/UTF-8" "en_US.UTF-8/UTF-8" ].

All values use mkDefault, so host-specific configuration can still override them.


Usage Example

{ ... }: {
  core.locale.enable = true;
}

Operational Notes

  • Module is enabled by default.
  • Because settings use mkDefault, this module acts as baseline rather than hard override.

Nix

Defines shared Nix daemon, cache, and registry defaults.

  • Entry point: modules/nixos/core/nix.nix

Overview

This module establishes global Nix configuration for hosts in this flake. It sets overlays, system.stateVersion, trusted users, experimental features, binary caches, garbage collection, and registry-derived nixPath.

It also provisions cache push secret and attic-watch-store service for automatic uploads to remote Attic cache.


Options

This module does not define core.* options. It applies shared baseline configuration directly.


Behaviour

Module configures:

  • overlays from inputs.angrr and inputs.nix4vscode,
  • system.stateVersion from state.version file in flake root,
  • trusted Nix users root and @wheel,
  • nix.settings.auto-optimise-store = mkForce true,
  • experimental features nix-command, flakes, and pipe-operator,
  • substituters, trusted substituters, and trusted public keys for cache.nixos.org, nix-community, and cache.racci.dev,
  • daily automatic Nix GC, and
  • nix.nixPath derived from config.nix.registry.

It also enables services.angrr to retain recent system profiles and creates systemd.services.attic-watch-store that waits for network-online.target, restarts on failure, logs into Attic with SOPS-managed CACHE_PUSH_KEY, and watches store for uploads.


Operational Notes

  • Because module has no enable flag, this is always active and applied to all hosts.
  • attic-watch-store depends on sops.secrets.CACHE_PUSH_KEY from hosts/secrets.yaml.
  • services.angrr keeps system profiles for 14 days, latest 3 generations, current system, and booted system.

OpenSSH

Configures opinionated SSH server and client defaults.

  • Entry point: modules/nixos/core/openssh.nix

Overview

This module enables OpenSSH with ed25519-only host keys, disables password authentication, and generates known-host entries for all configured NixOS hosts in flake outputs.

It also wires SOPS-managed host private key into sshd and publishes matching public key under /etc/ssh/ssh_host_ed25519_key.pub.


Options

core.openssh.enable

Typebool
Defaulttrue

Enable opinionated OpenSSH server and client configuration.


Behaviour

When enabled, module:

  • enables services.openssh,
  • disables password authentication,
  • sets PermitRootLogin = "prohibit-password",
  • sets GatewayPorts = "clientspecified",
  • configures services.openssh.hostKeys from config.sops.secrets.SSH_PRIVATE_KEY.path,
  • publishes current host public key at /etc/ssh/ssh_host_ed25519_key.pub,
  • enables security.pam.sshAgentAuth,
  • adds current host public host key to users.users.root.openssh.authorizedKeys.keyFiles, and
  • generates programs.ssh.knownHosts entries for every host in outputs.nixosConfigurations.

Client configuration also restricts host key algorithms and accepted public key types to ssh-ed25519.


Usage Example

{ ... }: {
  core.openssh.enable = true;
}

Operational Notes

  • Module expects matching host public key file to exist in flake for each host.
  • Current host gets localhost as extra known-host alias in generated SSH client config.
  • Root authorization here uses host key material from flake, not per-user login keys.
  • Private host key comes from SOPS secret SSH_PRIVATE_KEY, so core.sops integration usually pairs with this module.

Printing

Enables shared printer support for workstation-class NixOS hosts.

  • Entry point: modules/nixos/core/printing.nix

Overview

This module enables CUPS printing support on non-server, non-virtual hosts and installs common printer drivers used in this configuration.

When active, it turns on services.printing, adds HP and Gutenprint drivers, and includes Brother MFC-L3770CDW driver packages.


Options

core.printing.enable

Typebool
Defaultconfig.host.device.role != "server" && !config.host.device.isVirtual

Enable printing support. Default keeps printing off for servers and virtual machines.


Behaviour

When both config.core.enable and core.printing.enable are true, module:

  • enables services.printing,
  • installs printer drivers from pkgs.hplip, pkgs.gutenprint, pkgs.gutenprint-bin, pkgs.cups-filters, pkgs.mfcl3770cdwlpr, and pkgs.mfcl3770cdwcupswrapper, and
  • adds lp to core.defaultGroups.

Usage Example

{ ... }: {
  core.printing.enable = true;
}

Operational Notes

  • Module does not activate unless top-level core.enable is also enabled.
  • Default is tuned for physical desktop or laptop systems where local or network printer access is expected.
  • core.defaultGroups = [ "lp" ] ensures standard users can access printer devices through shared default group handling.

Remote Access

Provides optional remote desktop and game-streaming capabilities for desktop hosts.

  • Entry point: modules/nixos/core/remote.nix

Overview

This module exposes core.remote with two independent sub-features:

Sub-featureImplementationPurpose
Remote DesktopxrdpFull desktop access over RDP
StreamingSunshineLow-latency game or desktop streaming

Options

core.remote.enable

Typebool
Defaultdisabled

Master switch. Nothing in this module activates unless this is true.

core.remote.remoteDesktop.enable

Typebool
Defaultdisabled

Enable xrdp-based remote desktop access.

core.remote.remoteDesktop.startCommand

Typestring
Default"gnome-session"

Command xrdp uses as defaultWindowManager.

core.remote.streaming.enable

Typebool
Defaultdisabled

Enable Sunshine streaming server.


Behaviour

When core.remote.enable = true:

  • remoteDesktop.enable turns on services.xrdp, sets defaultWindowManager, and opens firewall for RDP.
  • streaming.enable turns on services.sunshine, enables auto-start, opens firewall, and sets capSysAdmin = true.
  • if Home Manager is present, streaming also persists .config/sunshine through shared Home Manager module.

Hyprland Integration

When both core.remote.streaming.enable and programs.hyprland.enable are true, module additionally:

  • sets services.sunshine.settings.output_name = "3",
  • adds two Sunshine application entries named Shared Desktop and Exclusive Desktop,
  • creates headless output at login with hyprctl output create headless, and
  • keeps HEADLESS-2 disabled until Sunshine prep commands enable it.
ApplicationBehaviour
Shared DesktopEnables HEADLESS-2 at client resolution and leaves physical monitors active.
Exclusive DesktopEnables HEADLESS-2, saves active physical monitor state to $XDG_STATE_HOME/hyprland-disabled-monitors-pre-sunshine.json, disables physical monitors, then restores them on disconnect.

Usage Examples

Streaming only

{ ... }: {
  core.remote = {
    enable = true;
    streaming.enable = true;
  };
}

RDP only

{ ... }: {
  core.remote = {
    enable = true;
    remoteDesktop = {
      enable = true;
      startCommand = "hyprland";
    };
  };
}

Operational Notes

  • Two sub-features are independent. You can enable streaming without RDP, or RDP without streaming.
  • Sunshine persistence and Hyprland settings are only added when Home Manager is present in system configuration.
  • Hyprland-specific Sunshine application entries are only added when both streaming and Hyprland are enabled.

Security

Applies shared host security defaults.

  • Entry point: modules/nixos/core/security.nix

Overview

This module enables baseline security features such as sudo-rs, TPM2 support, Polkit, kernel protection flags, and open-file limits for users.


Options

core.security.enable

Typebool
Defaulttrue

Enable shared security baseline.

core.security.userLimit

Typeunsigned integer
Default32768

Maximum number of open files per user. Applied to PAM login limits and user systemd default limit.


Behaviour

When enabled, module:

  • sets security.protectKernelImage = true,
  • enables security.polkit,
  • disables classic sudo and enables security.sudo-rs.execWheelOnly,
  • enables security.tpm2,
  • sets PAM nofile limit for all users to core.security.userLimit,
  • sets systemd.user.extraConfig = "DefaultLimitNOFILE=<limit>", and
  • sets kernel sysctl fs.file-max = 65536.

Usage Example

{ ... }: {
  core.security = {
    enable = true;
    userLimit = 65536;
  };
}

Operational Notes

  • Module leaves security.lockKernelModules = false even while enabling other hardening defaults.
  • userLimit affects both PAM sessions and user systemd services, keeping file descriptor limits aligned.

SOPS

Configures shared SOPS and age decryption defaults.

  • Entry point: modules/nixos/core/sops.nix

Overview

This module imports sops-nix, sets host default secrets file, derives age SSH key paths from persisted host SSH keys, and declares managed SSH private key secret for OpenSSH.


Options

core.sops.enable

Typebool
Defaultconfig.core.enable

Enable automatic SOPS configuration.

core.sops.hostSecretsFile

Typepath
Default"${hostDirectory}/secrets.yaml"

Path to host-specific SOPS secrets file inside flake.


Behaviour

When enabled, module:

  • imports inputs.sops-nix.nixosModules.sops unless function argument importExternals = false,
  • sets sops.defaultSopsFile to core.sops.hostSecretsFile,
  • builds sops.age.sshKeyPaths from persisted host SSH key path first, then appends any configured ed25519 OpenSSH host keys, and
  • declares sops.secrets.SSH_PRIVATE_KEY at /etc/ssh/ssh_host_ed25519_key with sshd.service restart hook.

Usage Example

{ ... }: {
  core.sops.hostSecretsFile = ./secrets.yaml;
}

Operational Notes

  • Default age key path includes ${config.host.persistence.root}/etc/ssh/ssh_host_ed25519_key.
  • Module filters config.services.openssh.hostKeys to ed25519 keys before adding them to sops.age.sshKeyPaths.
  • core.openssh typically consumes sops.secrets.SSH_PRIVATE_KEY declared here.

Stylix

Applies shared system theme defaults with Stylix.

  • Entry point: modules/nixos/core/stylix.nix

Overview

This module imports Stylix and enables dark Tokyo Night theming on non-headless hosts by default.


Options

core.stylix.enable

Typebool
Default!config.host.device.isHeadless

Enable Stylix configuration. Default disables theming on headless hosts.


Behaviour

When enabled, module:

  • imports inputs.stylix.nixosModules.stylix unless function argument importExternals = false,
  • sets stylix.enable = true,
  • sets stylix.polarity = "dark", and
  • uses Tokyo Night dark Base16 scheme from tinted-schemes input.

Usage Example

{ ... }: {
  core.stylix.enable = true;
}

Operational Notes

  • Module is intended for graphical hosts.
  • Theme source is ${inputs.stylix.inputs.tinted-schemes}/base16/tokyo-night-dark.yaml.

Virtualisation

Configures libvirt, VFIO passthrough, bridge networking, and guest isolation helpers.

  • Entry point: modules/nixos/core/virtualisation.nix

Overview

This module enables libvirt with QEMU, VFIO GPU passthrough, Looking Glass shared memory, bridge networking, custom OVMF firmware metadata, and libvirt hook helpers for selected guests.

It also generates helper scripts that change AllowedCPUs on host slices while selected guests run, detach and reattach passthrough GPUs for -single guests, and block host sleep while libvirt domains are active.


Options

core.virtualisation.enable

Typebool
Defaultdisabled

Master switch for virtualisation support.

core.virtualisation.vmUsers

Typelist of string
Default[]

Users that receive kvm and libvirtd group membership for VM management.

core.virtualisation.isolatedGuests

Typelist of string
Default[ "win11" "win11-gaming" ]

Guest names that receive generated libvirt hook directories for CPU isolation helpers. Each guest also gets -single hook variants that add GPU detach and attach helpers.

core.virtualisation.bridgeInterface

Typestring
Default"br0"

Bridge interface exposed to libvirt guests.

core.virtualisation.externalInterface

Typestring
Default"eth0"

Physical interface attached to core.virtualisation.bridgeInterface.

core.virtualisation.cpuCores

Typeint >= 4
Default24

Total CPU core or thread count used by isolation helpers.

core.virtualisation.gpu.video

Typestring
Default"10de:1b06"

PCI ID for passthrough GPU video device.

core.virtualisation.gpu.audio

Typestring
Default"10de:1bef"

PCI ID for passthrough GPU audio device.


Behaviour

When enabled, module:

  • imports external virtualisation helpers from crtified.modules.virtualisation.nix and ../desktop/vfio.nix,
  • enables virtualisation.libvirtd, Spice USB redirection, and services.spice-autorandr,
  • enables VFIO with IOMMUType = "amd", disableEFIfb = true, and configured GPU devices,
  • configures Looking Glass shared memory file looking-glass owned by racci:qemu-libvirtd,
  • adds virt-manager, virtiofsd, virtio-win, and win-spice to system packages,
  • sets LIBVIRT_DEFAULT_URI = qemu:///system,
  • creates bridge networking with DHCP on bridgeInterface and externalInterface enslaved into bridge,
  • adds kvmfr kernel module package and modprobe config static_size_mb=128, and
  • installs udev rule for /dev/kvmfr access.

Module also persists libvirt and swtpm state under host.persistence.directories.


Isolation and Hook Helpers

For each guest in core.virtualisation.isolatedGuests, module creates libvirt hook entries that:

  • restrict host user.slice, system.slice, and init.scope CPU sets during guest startup,
  • restore full CPU set when guest stops,
  • for <guest>-single, detach GPU and stop display-related services before launch, and
  • reattach GPU, reload drivers, restart saved services, and rebind VT consoles after shutdown.

It also creates libvirt-nosleep@<guest> service that uses systemd-inhibit to block sleep while guest is running.


Firmware and Persistence

Module extends libvirt startup to populate /run/libvirt/nix-ovmf with secure-boot and Microsoft-enrolled OVMF firmware files, then publishes matching firmware JSON metadata under /var/lib/qemu/firmware.

Persisted paths include:

  • /var/lib/libvirt/qemu
  • /var/lib/libvirt/images
  • /var/lib/libvirt/swtpm
  • /var/lib/libvirt/secrets
  • /var/lib/swtpm-localca

Usage Example

{ ... }: {
  core.virtualisation = {
    enable = true;
    vmUsers = [ "racci" ];
    isolatedGuests = [ "win11-gaming" ];
    bridgeInterface = "br0";
    externalInterface = "enp6s0";
    cpuCores = 24;

    gpu = {
      video = "10de:1b06";
      audio = "10de:1bef";
    };
  };
}

Operational Notes

  • core.virtualisation.cpuCores is validated by both option type and assertion, so values below 4 fail evaluation.
  • vmUsers is opt-in. Only listed users receive kvm and libvirtd access.
  • Hook generation assumes guest naming convention where <name>-single means single-GPU passthrough workflow.

WSL

Adds Windows Subsystem for Linux specific integration and fixes.

  • Entry point: modules/nixos/core/wsl.nix

Overview

This module configures WSL-focused defaults such as default user, Windows interop, graphics library paths, nix-ld, and Start Menu launcher syncing.


Options

core.wsl.enable

Typebool
Defaultdisabled

Enable WSL-specific configuration.

core.wsl.user

Typestring
Defaultnone

Default user used for WSL integration.


Behaviour

When enabled, module:

  • sets users.allowNoPasswordLogin = true,
  • installs pkgs.wslu,
  • enables programs.nix-ld with C toolchain library for Remote WSL compatibility,
  • sets session variables for WSL graphics and library paths,
  • enables hardware.graphics and adds config.hardware.graphics.package, config.hardware.graphics.package32, and pkgs.libvdpau-va-gl, and
  • when NVIDIA graphics are present, appends CUDA and NVIDIA library paths.

If wsl module exists in option tree, module also:

  • enables wsl.enable,
  • sets wsl.defaultUser = core.wsl.user,
  • enables Start Menu launchers and Windows driver usage,
  • enables Windows interop and PATH appending,
  • exposes dirname, readlink, and uname through wsl.extraBin for VS Code Remote WSL compatibility, and
  • copies per-user Home Manager applications and icons into /usr/share during activation so launchers appear in Windows Start Menu.

Usage Example

{ ... }: {
  core.wsl = {
    enable = true;
    user = "racci";
  };
}

Operational Notes

  • core.wsl.user is required when WSL integration is enabled.
  • Extra binaries dirname, readlink, and uname are exposed for VS Code Remote WSL compatibility.
  • Some behavior is conditional on separate WSL module being available in options.

Server Module

The Server module provides a cluster-aware configuration for server hosts in the flake. It must be explicitly enabled using the server.enable option.

Purpose

The primary purpose of this module is to establish a shared environment for servers in the cluster, defining a coordinator node (ioPrimaryHost) and providing helper functions for inter-server communication and attribute collection.

Entry Point

  • modules/nixos/server/default.nix

Special Options and Behaviors

The main configuration entry point is server.enable. Once enabled, it sets up the server-specific baseline:

  • Journald Persistence: Configured with a 14-day retention period and storage limits.
  • Pre-Switch Checks: Runs dix on system activation to report changes between generations.
  • server.ioPrimaryHost: Specifies the hostname of the coordinator host for the cluster. This host runs primary database instances, the reverse proxy, and storage master nodes. This option is typically set on the coordinator host and used by other servers in the cluster for synchronization.

Example Usage

To use the server module, it must be explicitly enabled in the host configuration.

# hosts/server/nixmon/default.nix
{
  server = {
    enable = true;
    # Set to the hostname of the cluster's coordinator node
    ioPrimaryHost = "nixio";
  };
}

Operational Notes

  • This module provides many helper functions (like getAllAttrsFunc, collectAllAttrs, etc.) that are used by submodules to gather configuration data from other servers in the cluster.
  • These helpers allow for dynamic configuration based on the state of other cluster nodes, such as building a global dashboard or a reverse proxy configuration.
  • The ioPrimaryHost is a critical component of the cluster, as many services (like Dashy or MinIO) rely on it as the central point of coordination.

Server Dashboard Module

The Server Dashboard module provides an integrated dashboard for monitoring and accessing services within the server cluster.

Purpose

The dashboard module integrates with Dashy and collects dashboard sections from all servers in the cluster to display on the ioPrimaryHost.

Entry Point

  • modules/nixos/server/dashboard.nix

Special Options and Behaviors

The module provides options under server.dashboard to define the section for each server:

  • server.dashboard.name: The name of the section in the dashboard, defaulting to the capitalized hostname.
  • server.dashboard.icon: An optional icon for the section.
  • server.dashboard.items: A set of dashboard items (with title, icon, and url) to be displayed.
  • server.dashboard.displayData: Arbitrary JSON data to be passed to the dashboard configuration.

Example Usage

Configure the dashboard section for a server:

# hosts/server/nixserv/default.nix
{
  server.dashboard = {
    name = "Services";
    icon = "fas fa-server";
    items = {
      "Grafana" = {
        title = "Grafana Dashboard";
        icon = "fas fa-chart-line";
        url = "https://grafana.example.com";
      };
    };
  };
}

Operational Notes

  • This module uses getAllAttrsFunc to gather server.dashboard configurations from all servers in the cluster.
  • The aggregated configuration is only applied to the ioPrimaryHost, which runs the primary Dashy instance.
  • This allows each server to define its own dashboard items, which are then automatically collected and displayed on a single unified dashboard.

Server Network Module

The Server Network module provides a declarative way to manage network configurations and firewall rules across the server cluster.

Purpose

The network module coordinates network subnet definitions and firewall rules, allowing for centralized configuration of subnets and automatic propagation of these settings to other servers in the cluster.

Entry Point

  • modules/nixos/server/network.nix

Special Options and Behaviors

The main configuration options are under server.network:

  • server.network.subnets: A list of subnet definitions, each with:
    • dns: The DNS server for the subnet.
    • domain: The domain name for the subnet.
    • ipv4, ipv6: Configuration options (like CIDR and ARPA) for the subnet’s IP range.
  • server.network.openPortsForSubnet: Defines TCP and UDP ports to be opened on the firewall for each defined subnet.

Example Usage

Configure a subnet and open ports on a server:

# hosts/server/nixio/default.nix
{
  server.network = {
    subnets = [
      {
        dns = "192.168.1.1";
        domain = "lan.example.com";
        ipv4.cidr = "192.168.1.0/24";
      }
    ];
    openPortsForSubnet = {
      tcp = [ 80 443 ];
    };
  };
}

Operational Notes

  • This module uses getIOPrimaryHostAttr to fetch the server.network.subnets configuration from the ioPrimaryHost.
  • This ensures that all servers in the cluster are aware of the network structure defined on the coordinator host.
  • The module automatically generates iptables and ip6tables rules for the specified ports, allowing traffic only from the defined subnets.
  • These rules are added to the nixos-fw chain and are managed through the networking.firewall.extraCommands and networking.firewall.extraStopCommands options.

Server Distributed Builds Module

The Server Distributed Builds module provides a declarative way to manage distributed builds across the server cluster.

Purpose

The distributed builds module allows for distributed building of Nix derivations using remote build machines, providing a coordinator host and several build machines to distribute the build load.

Entry Point

  • modules/nixos/server/distributed-builds.nix

Special Options and Behaviors

The module provides options under server.distributedBuilder:

  • server.distributedBuilder.builderUser: The user to use when connecting to remote build daemons (default: builder).
  • server.distributedBuilder.builders: A list of hostnames of remote build daemons to connect to for distributed builds.

Example Usage

Configure a build server and a host to use it for distributed builds:

# hosts/server/nixserv/default.nix (build server)
{
  server.distributedBuilder = {
    builders = [ "nixserv" ];
  };
}

# hosts/server/nixdev/default.nix (host using build server)
{
  server.distributedBuilder = {
    builders = [ "nixserv" ];
  };
}

Operational Notes

  • This module coordinates the creation of a system user (builder) on the build server and adds the necessary SSH keys to allow other hosts to connect.
  • On the hosts using the build server, the module automatically configures nix.distributedBuilds and sets up the build machines using nix.buildMachines.
  • The builder user is automatically added to nix.settings.trusted-users on the build server.
  • The module uses self.nixosConfigurations to dynamically discover the system architecture of the build machines.
  • For more information on distributed builds in Nix, see the NixOS Manual.

Database Submodule

The database submodule provides a managed interface for PostgreSQL and Redis across the server infrastructure. It centralizes database configuration on the primary database host (config.server.ioPrimaryHost) while allowing client services to declaratively request databases.

The module is implemented across several files in modules/nixos/server/database/:

  • default.nix: Core options and connection management.
  • postgres.nix: PostgreSQL-specific provisioning and secrets.
  • redis.nix: Redis-specific ID mappings and security.
  • guardian.nix: Lifecycle synchronization and the IO Guardian.

Purpose

This submodule automates:

  • Provisioning of PostgreSQL databases and roles.
  • Management of Redis database IDs via static mappings.
  • Synchronization of service lifecycle with database availability using the IO Guardian.
  • Automated password handling via SOPS secrets.

Entry Points

  • server.database.postgres: Manage PostgreSQL databases and users (in postgres.nix).
  • server.database.redis: Manage Redis database instances (in redis.nix).
  • server.database.host: Centralized host address for database connections (in default.nix).
  • server.database.dependentServices: Lifecycle coordination for dependent services (in guardian.nix).

Key Options and Behaviors

Connection Management

The server.database.host option determines how services connect to databases. On the primary database host (ioPrimaryHost), it defaults to localhost. On all other hosts, it defaults to the value of config.server.ioPrimaryHost.

PostgreSQL Management

When a service defines a database in server.database.postgres:

  • Automatic Provisioning: The IO Host automatically creates the database and a role with the same name.
  • Password Management: A SOPS secret is expected at POSTGRES/<DB_NAME_UPPER>_PASSWORD. Database names containing hyphens (-) replace them with underscores (_) when constructing the secret path. The system automatically sets this password for the role during the postgresql-setup service.
  • Aggregated Configuration: The IO Host collects all PostgreSQL requirements from across the entire flake to ensure all necessary extensions and initial scripts are loaded.

Redis Management

Redis management uses a similar aggregation pattern:

  • Database IDs: Because Redis uses numeric IDs (0-15), the system uses a static mapping file (redis-mappings.json) on the IO Host to ensure consistent ID assignment across the fleet.
  • Password Management: A shared password for the primary Redis instance is managed via REDIS/PASSWORD in SOPS.
  • Tooling: Use the update-redis-mappings command on the IO Host to update the mapping file when adding new Redis clients.

Per-Module Examples

Connection Configuration (default.nix)

You can override the default database host (e.g., if using a custom tunnel or local proxy):

{
  server.database.host = "10.0.0.50";
}

PostgreSQL Example (postgres.nix)

Requesting a PostgreSQL database for a service:

{
  server.database.postgres."my-app" = {
    # database and user will be 'my-app'
    # Password expected at sops secret: POSTGRES/MY_APP_PASSWORD
  };
}

Redis Example (redis.nix)

Requesting a Redis database:

{
  server.database.redis.myapp = {
    # prefix will be 'myapp'
    # database_id is assigned from redis-mappings.json
  };
}

Guardian Dependency Example (guardian.nix)

Manually adding services to the database lifecycle coordination:

{
  server.database.dependentServices = [
    "custom-backend.service"
    "worker-node" # .service suffix is added automatically
  ];
}

Operational Notes

IO Guardian Coordination

Lifecycle management is handled by the IO Guardian.

  • On Clients: Services that use these database modules are automatically bound to io-databases.target. This ensures they only start when the remote databases are reachable and stop before the databases go offline.
  • On IO Primary Host: The io-database-coordinator service manages the drain and undrain signals sent to clients during system startup and shutdown.

IO Primary Host Behavior

The host designated as the IO Primary Host (config.server.ioPrimaryHost) is responsible for running the actual database engines. It aggregates all database requirements from every host in the flake and applies them locally.

Storage

The storage module manages persistent storage abstractions, specifically for mounting S3-compatible buckets from MinIO as local filesystems.

Purpose

This submodule provides a declarative way to mount remote storage buckets. It handles the underlying FUSE configuration and credential mapping automatically.

Key Options and Behaviors

Bucket Mounts

The bucketMounts option uses s3fs-fuse to mount buckets from https://minio.racci.dev.

  • Credential Management: It automatically looks for sops secrets with the pattern S3FS_AUTH/<NAME_IN_UPPERCASE>. These secrets should contain the credentials in the ACCESS_KEY_ID:SECRET_ACCESS_KEY format.
  • Mount Points: Buckets are mounted at /mnt/buckets/<bucket-name> unless a different mountLocation is specified.
  • Ownership and Permissions: You can control the mount ownership using uid and gid. The umask option (defaulting to 022) controls the default file and directory permissions.

Example

The following example mounts a “media” bucket and sets specific ownership.

{
  server.storage.bucketMounts.media = {
    uid = 1000;
    gid = 1000;
    umask = 007;
  };
}

Operational Notes

  • s3fs-fuse: This module uses the s3fs package. It relies on FUSE, so it requires programs.fuse.userAllowOther = true which the module enables automatically when mounts are defined.
  • Network Dependency: Mounts use the _netdev option to ensure they are only attempted after the network is up.
  • Credential Format: Ensure that your sops secrets provide the exact string format required by s3fs.
  • MinIO Endpoint: The module is currently configured to use https://minio.racci.dev.

References

Proxy Submodule

The Proxy submodule provides a unified interface for exposing internal services through Caddy. It handles virtual host configuration, automatic SSL via ACME, OAuth2 authentication with Kanidm, and public exposure through Cloudflared tunnels.

Purpose

This module abstracts the complexity of reverse proxying by allowing services to define their proxy requirements within their own module configuration. It automatically coordinates between backend hosts and the primary IO host to ensure ports are open and traffic is correctly routed.

Entry Points

The primary configuration is managed through:

  • server.proxy.domain: The root domain for all services (e.g., example.com).
  • server.proxy.virtualHosts: An attribute set of service configurations.

Key Options and Behaviors

Global Options

  • server.proxy.domain: Defines the base domain. All virtual hosts default to <name>.<domain> unless overridden.
  • server.proxy.kanidmContexts: Defines shared OAuth2 configurations that can be reused across multiple virtual hosts.
    • scopes: Default scopes are ["openid" "email" "profile" "groups"].
    • tokenLifetime: Default lifetime is 3600 seconds.

Virtual Host Options

  • aliases: A list of additional hostnames (relative to server.proxy.domain) that route to this service.
  • public: If true, the service is added to the Cloudflared tunnel ingress for public access.
  • ports: List of ports to open on the backend host to allow traffic from the IO primary host.
  • kanidm: Configures OAuth2 protection. If enabled, the module generates Caddy security blocks and handles Kanidm client provisioning.
    • bypassPaths: List of path patterns (e.g., /api/*) that should bypass authentication.
    • allowGroups: List of groups allowed access. Note: This cannot be empty if Kanidm is enabled.
  • l4: Configures Layer 4 forwarding using the Caddy L4 plugin. This opens both TCP and UDP ports on the IO primary host.
  • extraConfig: Injected directly into the Caddy handle block. localhost references are automatically replaced with the backend host’s address.

Per-Module Examples

default.nix - Logic and Helpers

This file contains the internal logic for resolving OAuth contexts and mapping local addresses to backend hostnames.

# Example: How contextToEnvPrefix transforms names for environment variables
contextToEnvPrefix "my-service" # Returns "MY_SERVICE"

options.nix - Option Definitions

Defines the structure of virtual hosts and shared contexts.

server.proxy.kanidmContexts.admin-apps = {
  authDomain = "auth.internal.example.com";
  allowGroups = [ "admins@auth.example.com" ];
};

server.proxy.virtualHosts.grafana = {
  public = true;
  kanidm = {
    context = "admin-apps";
    allowGroups = [ "grafana-users@auth.example.com" ];
    bypassPaths = [ "/health" ];
  };
  extraConfig = "reverse_proxy localhost:3000";
};

config.nix - Caddy Integration

Handles the generation of services.caddy.virtualHosts and ACME certificate requests.

# Generated Caddy block for a vhost with Kanidm
grafana.example.com {
    import default
    import public

    @bypass_auth_grafana path /health
    handle @bypass_auth_grafana {
        reverse_proxy 10.0.0.5:3000
    }

    route /auth/* {
        authenticate with grafana_portal
    }
    handle {
        authorize with grafana_policy
        reverse_proxy 10.0.0.5:3000
    }
}

kanidm.nix - Authentication Security

Generates the Caddy security block, including identity providers, portals, and authorization policies.

security {
    oauth identity provider admin-apps {
        realm admin-apps
        client_id "admin-apps"
        client_secret {env.OAUTH_ADMIN_APPS_CLIENT_SECRET}
        metadata_url https://auth.internal.example.com/oauth2/openid/admin-apps/.well-known/openid-configuration
    }
    # ... portals and policies
}

extensions.nix - System Integration

Connects the proxy to the dashboard, Cloudflared tunnels, and automates Kanidm client provisioning.

# Automatic Kanidm provisioning based on proxy config
services.kanidm.provision.systems.oauth2.admin-apps = {
    displayName = "Admin Apps";
    originUrl = [ "https://grafana.example.com/auth/oauth2/admin-apps/authorization-code-callback" ];
    # ...
};

Operational Notes

Caddy Integration

The module assumes the existence of a default Caddy snippet for common headers and security settings. When public is enabled, it also expects a public snippet.

Dashboard Integration

Services defined in server.proxy.virtualHosts are automatically added to the server dashboard with default titles and icons derived from the host name.

Kanidm OAuth2 Context

Authentication requires specific secrets per context, managed via sops-nix:

  1. KANIDM/OAUTH2/<UPPER_CONTEXT>_SECRET: Provisioning secret for Kanidm systems.
  2. OAUTH_<PREFIX>_CLIENT_SECRET: The OAuth2 client secret for Caddy.
  3. <PREFIX>_SHARED_KEY: A shared key used by Caddy to sign and verify authentication tokens.

These are automatically managed if Kanidm provisioning is enabled on the same host.

Layer 4 Forwarding

L4 forwarding uses the caddy.layer4 plugin. It is primarily used for non-HTTP traffic like database connections or SSH.

Public services are routed through the Cloudflared tunnel with ID 8d42e9b2-3814-45ea-bbb5-9056c8f017e2. Ensure this tunnel is correctly configured on the IO host.

References

Server SSH Module

The Server SSH module provides a rich interactive environment for root users upon login. It automatically transitions interactive root sessions into a dedicated development shell, ensuring consistent tooling and a powerful shell experience across server environments.

Purpose

The SSH submodule enhances administrative access by providing a session-only environment tailored for server management. It removes the need for manual setup of common tools and aliases by automatically entering a pre-configured nix-shell when a root user logs in interactively over SSH.

Key Options and Behaviors

Auto-entry Logic (ssh/default.nix)

The module modifies /etc/bashrc to detect interactive root logins via SSH. It evaluates several conditions before launching the session shell:

  • User must be root (EUID=0).
  • Session must be via SSH (SSH_CONNECTION present).
  • Session must be interactive (stdin is a TTY).
  • No active session shell detected (SSH_NIX_SHELL unset).
  • User has not opted out via NIX_SKIP_SHELL.

The module also configures OpenSSH to accept the NIX_SKIP_SHELL environment variable from clients, allowing remote users to bypass the auto-shell entry when necessary.

Session Environment (ssh/shell.nix)

The default session shell is a nix-shell environment containing:

  • Modern Shells: Fish shell with Starship prompt, Zoxide navigation, and Carapace completions.
  • Enhanced Tooling: Replacements for standard utilities such as bat (cat), fd (find), ripgrep (grep), and procs (ps).
  • System Diagnostics: Tools like btop, doggo, gping, inxi, and hyfetch.

The shellHook in shell.nix starts an interactive Fish session and immediately exits the nix-shell wrapper once the Fish session concludes.

Per-Module Examples

Enabling the SSH Shell

Enable the auto-shell behavior in your host configuration:

{
  server.sshShell.enable = true;
}

Customizing the Shell File

Override the shell definition file if you require a different set of tools:

{
  server.sshShell.shellFile = ./my-custom-shell.nix;
}

Operational Notes

Opt-Out Behavior

If you need to log in as root without entering the specialized shell, set the NIX_SKIP_SHELL environment variable on your local machine before connecting:

NIX_SKIP_SHELL=1 ssh root@your-server

This is particularly useful for automated scripts or troubleshooting scenarios where the standard Bash environment is preferred.

Guard Mechanism

The auto-entry script uses the SSH_NIX_SHELL environment variable to prevent recursive shell entries. If nix-shell fails to start, the system falls back to the default shell and provides a warning message.

References

Flake Allocations

The flake allocations module defines cross-host configuration options at the flake level. Rather than configuring each NixOS system independently, allocations let you declare cluster-wide concerns — like which machines have GPUs, which server coordinates I/O, and which servers act as distributed builders — in a single place.

How It Works

The allocation system has three layers:

  1. Option Definitions (modules/flake/allocations.nix) — Declares the available allocation options.
  2. Configuration (flake/nixos/flake-module.nix) — Sets the actual values for those options.
  3. Apply Modules (modules/flake/apply/) — Propagates allocation values into each NixOS or Home-Manager configuration via specialArgs.

Data Flow

allocations.nix          flake-module.nix            apply/system.nix
┌──────────────┐   ┌──────────────────────┐   ┌───────────────────────┐
│ Define opts  │──▶│ Set values           │──▶│ Map to NixOS options  │
│ (types,      │   │ (which host has what)│   │ per system via        │
│  defaults)   │   │                      │   │ specialArgs           │
└──────────────┘   └──────────────────────┘   └───────────────────────┘

When mkSystem builds a NixOS configuration, it receives the allocations attribute set and passes it as a specialArgs argument. The apply module then conditionally maps those allocations to NixOS module options based on the host’s device type.

Options

allocations.accelerators

Maps hostnames to their available hardware accelerators (cuda, rocm). Used by the builder system to configure nixpkgs with the correct cudaSupport / rocmSupport flags per host.

allocations.accelerators = {
  nixmi = [ "cuda" ];
  nixai = [ ];
};

Hosts not listed default to no accelerators. The builder (lib/builders/default.nix) reads allocations.accelerators.${hostname} and sets the corresponding nixpkgs config flags.

allocations.hostTypes

Read-only attribute set mapping device types to their hostnames. Auto-populated from getHostsByType, which scans hosts/ directory structure.

# Automatically resolves to something like:
allocations.hostTypes = {
  server = [ "nixio" "nixserv" "nixmon" ];
  desktop = [ "nixmi" ];
};

allocations.server.ioPrimaryCoordinator

Designates a server as the primary I/O coordinator for the cluster. This is the host that runs primary database instances, the reverse proxy, and storage master nodes.

The type is constrained to an enum of server hostnames (automatically derived from hostTypes.server).

allocations.server.ioPrimaryCoordinator = "nixio";

This value flows through apply/system.nix into server.ioPrimaryHost on each server configuration.

allocations.server.distributedBuilders

List of servers that act as remote builders for distributed builds.

allocations.server.distributedBuilders = [ "nixserv" ];

Flows into server.distributedBuilder.builders on each server configuration.

Apply Modules

The apply modules (modules/flake/apply/) bridge flake-level allocations to per-system NixOS options.

apply/system.nix

Imported by mkSystem during system construction. Receives allocations and deviceType via specialArgs. For server-type hosts, it maps:

  • allocations.server.ioPrimaryCoordinatorserver.ioPrimaryHost
  • allocations.server.distributedBuildersserver.distributedBuilder.builders

Uses optionalAttrs to only apply server-specific options when deviceType == "server", preventing errors on non-server systems.

apply/home-manager.nix

Imported by the Home-Manager builder. Currently a no-op (mkMerge []) — exists as a placeholder for future home-manager-level allocations.

Source Files

FileRole
modules/flake/allocations.nixOption definitions
modules/flake/apply/system.nixNixOS system apply
modules/flake/apply/home-manager.nixHome-Manager apply (placeholder)
flake/nixos/flake-module.nixActual configuration values
lib/builders/default.nixBuilder that consumes allocations

DIY & Making

This section documents the Home-Manager modules under purpose.diy, which provide tooling and configuration for hardware tinkering, 3D printing, and related maker activities.


Printing

The printing module installs 3D-printing software and wires up persistent storage so that settings survive reboots on impermanence-based systems.

  • Entry point: modules/home-manager/purpose/diy/printing.nix

Options

purpose.diy.printing.enable

Typebool
Defaultfalse

Enables 3D-printing support. Installs OrcaSlicer and LycheeSlicer and registers their configuration directories for persistence.


Git Sync

The gitSync sub-module adds a long-running systemd user service that watches the OrcaSlicer profile directory and automatically creates a git commit every time a profile file is added, changed, or removed. This gives you a full revision history of your slicer settings with zero manual effort.

purpose.diy.printing.gitSync.enable

Typebool
Defaultfalse

Enable the OrcaSlicer git auto-commit watcher. Requires purpose.diy.printing.enable = true.

purpose.diy.printing.gitSync.repoPath

Typestring
Default"${config.home.homeDirectory}/.config/OrcaSlicer/user/default"

Absolute path to the directory that will be managed as a git repository. The directory is initialised automatically the first time the watcher service starts, so it does not need to exist at activation time.

The default points at the standard OrcaSlicer per-user profile directory, which contains the filament/, process/, and machine/ sub-directories, so all profile types are tracked without any additional configuration.


Commit Message Convention

Commit messages are generated automatically based on the type of filesystem event and the location of the file within the repository:

EventCommit message format
File added / createdfeat(<type>): added <name>
File modifiedrefactor(<type>): updated <name>
File deletedchore(<type>): removed <name>

Where:

  • <type> is the name of the first directory component under the repo root (e.g. filament, process, machine). Files placed directly at the root level use the fallback type config.
  • <name> is the filename stripped of its extension (e.g. a file named Prusament_PLA.json yields the name Prusament_PLA).

Examples:

feat(filament): added Prusament_PLA
refactor(process): updated Standard_0.2mm_Quality
chore(machine): removed Prusa_MK4S

How It Works

  1. A systemd user service (orca-slicer-git-sync.service) is started at login and kept alive by systemd.
  2. The service uses inotifywait (from inotify-tools) in one-shot mode inside a loop to detect any filesystem event under the repo path (excluding the .git directory).
  3. After an event is received the watcher sleeps for 2 seconds to debounce rapid bursts of writes (e.g. when OrcaSlicer rewrites multiple files at once).
  4. All pending changes are then committed one file at a time, each with an individually crafted commit message.
  5. If the watched directory does not yet exist (e.g. OrcaSlicer has never been run), the service polls every 10 seconds until it appears, then initialises the repository and starts watching.

Usage Example

{ ... }: {
  purpose.diy.enable = true;

  purpose.diy.printing = {
    enable = true;

    gitSync = {
      enable = true;
      # Optional: use a custom path outside the OrcaSlicer config directory
      # repoPath = "/home/alice/slicer-profiles";
    };
  };
}

Operational Notes

  • The git repository is initialised with git init and an initial commit (chore: initial commit) the first time the service starts if no .git directory exists.
  • The service is set to restart on failure (Restart=on-failure, RestartSec=10) so transient errors do not leave settings un-tracked.
  • Because the watcher operates on the live OrcaSlicer profile directory, no separate mirroring or rsync step is needed.

Home-Manager: AI Editors & Assistants

This page documents the Home-Manager module at:

  • modules/home-manager/purpose/development/editors/ai/default.nix

It configures editor/agent tooling for AI-assisted development, centered around OpenCode and shared skill directories.


What this module sets up

When enabled, the module:

  • Ensures ~/Projects/AIFS exists at activation time.
  • Adds useful global git ignores:
    • .workspace
    • .sisyphus
  • Configures Zed to expose an OpenCode agent server (opencode acp).
  • Enables and configures programs.opencode with:
    • plugins
    • Nix formatter integration
    • LSP integrations:
      • Nix: nixd, nil
      • Config formats: marksman (Markdown), yaml-language-server, vscode-json-language-server, taplo (TOML), vscode-css-language-server, vscode-html-language-server
      • Languages: rust-analyzer, gopls, pyright, typescript-language-server, bash-language-server, lua-language-server, nushell, powershell-editor-services, dockerfile-language-server
    • command permissions policy
    • local MCP server (mcp-nixos via uvx)
  • Writes:
    • ~/.config/opencode/oh-my-opencode.json
    • ~/.config/opencode/opencode-notifier.json
  • Registers AI skills under ~/.agents/skills/<name> via home.file.
  • Persists OpenCode state directories:
    • .local/share/opencode
    • .local/state/opencode

Options

purpose.development.editors.ai.enable

Typebool
Defaultfalse

Enable AI tools and assistant/editor integrations for the user profile.


purpose.development.editors.ai.includeDefaults

Typebool
Defaulttrue

Whether to include the module’s built-in skills and agents from:

  • modules/home-manager/purpose/development/editors/ai/skills
  • modules/home-manager/purpose/development/editors/ai/agents

Set to false for a minimal setup with only base OpenCode configuration.


purpose.development.editors.ai.skills

Typelist of string
Default[]

Additional skill source paths to register globally under ~/.agents/skills.

Each entry should point to a skill directory (for example from a flake input or from this repository). The basename of each source path is used as the destination directory name.

Example:

  • "${inputs.my-skill-repo}/skills/my-skill"
  • "${self}/skills/another-skill"

Usage example

{ self, inputs, ... }: {
  purpose.development.editors.ai = {
    enable = true;
    includeDefaults = true;

    skills = [
      "${inputs.my-skill-repo}/skills/my-skill"
      "${self}/skills/another-skill"
    ];
  };
}

Notes

  • Skill links are generated under ~/.agents/skills/<basename>.
  • Default skills are discovered automatically from the module’s local skills/ directory when includeDefaults = true.
  • The module currently defines default agent discovery as well, but only skill link materialization is active in home.file output.

Home-Manager: list-ephemeral

list-ephemeral is a shell utility that helps discover ephemeral paths and generate Nix snippets for persistence. It integrates with Home-Manager to supply defaults, persisted paths, and program context.

Options

programs.list-ephemeral.enable

Enable the list-ephemeral integration and generate the runtime config at:

$XDG_CONFIG_HOME/list-ephemeral/config.json
programs.list-ephemeral.extraExcludes

Additional exclude patterns for the fd scan.

programs.list-ephemeral.extraIncludes

Additional paths to always include as candidates. Relative paths are treated as relative to $HOME.

Usage

Default TUI (fzf-based with keybindings):

list-ephemeral

TUI Keybindings

KeyAction
/Enable search mode (type to fuzzy filter)
EscapeDisable search and clear query
Ctrl-POpen program filter (gum picker)
Ctrl-XClear program filter
SpaceToggle selection and move down
Ctrl-ASelect all
Ctrl-DDeselect all
Ctrl-CQuit (standard fzf behavior)
EnterConfirm selection

Note: In browse mode (default), typing text will appear in the prompt but won’t filter results. Press / to enable search filtering.

List mode:

list-ephemeral list

Trace mode (runs a command and then opens TUI with traced ephemeral paths):

list-ephemeral trace -- <cmd> [args...]

Snippet Generation

The TUI generates Nix snippets based on path location:

  • Paths under $HOME are emitted as user.persistence.files or user.persistence.directories with paths relative to $HOME.
  • Paths outside $HOME are emitted as host.persistence.files or host.persistence.directories with absolute paths.

If the selection includes both kinds, the snippet contains both blocks.

Packages Overview

Purpose

This section documents the custom packages defined in this repository. These are packages that are either not available in nixpkgs or require custom builds.

Entry Points

  • pkgs/: Contains the package definitions, typically organized by package name.
    • alvr-bin: Binaries for ALVR that allows nvidia accelerated by using the AppImage.
    • drive-stats: Tool for monitoring and reporting drive statistics.
    • helpers: Collection of helper scripts for configuration management.
    • huntress: Integration for Huntress security agent.
    • hypr-gamemode: Script to optimize Hyprland performance for gaming.
    • io-guardian: Database lifecycle management across hosts.
    • lidarr-plugins: Lidarr plugins branch.
    • list-ephemeral: Utility to identify ephemeral paths, trace file access, and generate persistence snippets.
    • lix-woodpecker: Woodpecker CI runner.
    • mcp-sequential-thinking: MCP server for step-by-step reasoning.
    • mcp-server-amazon: MCP server for Amazon services interaction.
    • proton-mcp: MCP server for ProtonMail.
    • monocoque: Sim-racing dashboard and telemetry tool.
    • orca-slicer-zink: Orca Slicer configured to use the Zink Vulkan driver to resolve nvidia rendering issues.
    • python: Packages for home assistant python components.
    • take-control-viewer: Remote support viewer for N-able Take Control via Wine.

Key Options/Knobs

Custom packages may expose different build options depending on their derivation definition.

Common Workflows

  • Adding a Package: Create a new directory in pkgs/ with a default.nix file.
  • Using a Package: Reference the package via pkgs.<name> if the pkgs overlay is active.

Overlays Overview

Purpose

Overlays allow us to extend or modify the standard nixpkgs collection. We use them to add our custom packages, apply patches, or override package versions.

Entry Points

  • overlays/: Directory containing individual overlay definitions.
  • overlays/default.nix: The main entry point for the overlays. It composes additions (from pkgs/ and external inputs) and modifications (overrides for upstream packages).

Key Options/Knobs

Overlays themselves don’t typically have “knobs,” but they affect the available packages and their versions in the pkgs set.

Common Workflows

  • Adding an Overlay: Create a new .nix file in the overlays/ directory.
  • Applying an Overlay: Overlays are typically applied in the flake.nix configuration for NixOS or Home-Manager.

Hosts Overview

Purpose

This section covers configuration of individual host machines. Repository uses automatic discovery system to manage hosts based on device type.

Entry Points

  • hosts/: Root directory for all host configurations.
  • hosts/desktop/: Configurations for desktop systems.
  • hosts/laptop/: Configurations for laptop systems.
  • hosts/server/: Configurations for server systems.
  • hosts/shared/: Shared host configuration still used across multiple hosts.
  • hosts/secrets.yaml: Root-level encrypted secrets for host configurations.

Key Options/Knobs

Host-specific configurations live in hosts/{device-type}/{hostname}/default.nix.

Shared NixOS behavior that used to live under hosts/shared/ is being migrated into reusable modules under modules/nixos/core/. Hosts now typically enable these with top-level core.* options instead of importing host-shared files directly.

Examples:

  • core.containers.enable = true;
  • core.gaming.enable = true;
  • core.virtualisation.enable = true;
  • core.networking.tailscale.enable = true;

Global options still shared across all hosts remain in hosts/shared/global/.

Common Workflows

  • Adding new host: Create directory for host in appropriate device type category and add default.nix.
  • Modifying host: Update default.nix, associated files in host directory, or relevant module under modules/nixos/core/.

References


Decky Loader Lifecycle

When jovian.decky-loader.enable = true is set on host with core.gaming.enable = true, Decky Loader is not started automatically at boot. Instead it is managed in lock-step with Steam desktop application:

  • modules/nixos/core/gaming.nix — overrides Jovian-provided decky-loader.service to remove it from multi-user.target, suppresses noisy CSS_Loader health-check log spam via LogFilterPatterns, and adds polkit rule that permits only configured Steam user in active local session to start/stop system service without password prompt. All of this is behind lib.mkIf (config.jovian.decky-loader.enable or false) guard, so it is no-op on machines without Jovian.

  • Home-Manager shared module injected by modules/nixos/core/gaming.nix — defines decky-loader-steam-watch systemd user service, active for duration of graphical session. It polls ~/.steam/steam.pid every 3 seconds to detect Steam starting, then starts decky-loader.service, and uses tail --pid to block until Steam exits before stopping it again. Service is only enabled when osConfig.jovian.decky-loader.enable is true.

Log filtering

CSS_Loader plugin health-checks Steam’s internal web interface (port 8080) every few seconds. When Steam is not running these produce continuous journal noise of form:

[CSS_Loader] [FAIL] [css_browserhook.py:437] [Health Check] Cannot connect to host 127.0.0.1:8080 …

This is suppressed with following LogFilterPatterns entry on service (requires systemd ≥ 255):

LogFilterPatterns = "~\\[CSS_Loader\\].*\\[Health Check\\].*Cannot connect";

Lib Overview

Purpose

The lib directory contains custom Nix functions and builders used throughout the repository to simplify configuration and reduce duplication.

Entry Points

  • lib/: Root directory for lib functions.
    • attrsets.nix: Functions for manipulating and merging attribute sets.
    • default.nix: Main entry point providing the mine and builders namespaces.
    • files.nix: Utilities for filesystem operations and path handling.
    • hardware.nix: Detection and configuration helpers for hardware acceleration and drivers.
    • hypr.nix: Specialized helpers for Hyprland window manager configurations.
    • keys.nix: Management of SSH, GPG, and other cryptographic keys.
    • package.nix: Custom package definitions and derivation helpers.
    • persistence.nix: Helpers for managing path persistence in ephemeral (TempFS) environments.
    • strings.nix: String manipulation and formatting utilities.
  • lib/builders/: Contains specialized builders for system and home configurations.

Key Options/Knobs

The functions in lib take various arguments depending on their purpose. Builders typically take parameters for hostnames, user names, and modules.

Common Workflows

  • Using a Lib Function: Access functions via outputs.lib.<functionName> or by importing the relevant file.
  • Creating a Builder: Add new builder logic to lib/builders/.

RacciDev Options Search