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

Nix Config

This is my interpretation of the perfect nix flake, this powers my desktops, laptops, servers, vms, containers, and all aspects of my computers.

Features

  • Automated Discovery: Hosts and users are automatically configured from filesystem structure
  • Automated Persistence: TempFS with persistable components using [Impermanence] or BTRFS snapshotting
  • Secret Management: Encrypted secrets in [NixOS] ([sops-nix]) and [home-manager] ([sops-nix] with [sops])
  • Automated Updates: Flake dependency updates through Github Actions using [Update-flake-lock]
  • Hardware Acceleration: Automatic hardware acceleration support detection and configuration
  • Modular Architecture: Custom modules and overlays for extensibility

Supported Configurations

  • [NixOS]-managed systems with automatic configuration discovery:
    • Desktops - Personal workstations and development environments
    • Servers - Infrastructure services and automation
    • Laptops - Portable systems with power management

Repository Structure

The repository uses an automatic discovery system that scans the filesystem to build configurations:

.
├─ home              # Root for all user homes (auto-discovered)
│  ├─── {username}   # User-specific configurations
│  └─── shared       # Shared home-manager modules
├─ hosts             # Root for all hosts (auto-discovered by device type)
│  ├─── shared       # Auto-imported modules for all hosts
│  │    ├─── global  # Core system configuration (locale, networking, etc.)
│  │    └─── optional# Optional modules for specific use cases
│  ├─── desktop      # Desktop NixOS systems
│  │    ├─── shared  # Auto-imported modules for desktops
│  │    ├─── {host}  # Individual desktop host configurations
│  ├─── laptop       # Laptop NixOS Systems
│  │    ├─── shared  # Auto-imported modules for laptops
│  │    └─── {host}  # Individual laptop host configurations
│  └─── server       # Server NixOS Systems
│       ├─── shared  # Auto-imported modules for servers
│       └─── {host}  # Individual server host configurations
├─ lib               # Extensions to nixpkgs lib and custom builders
│  └─── builders     # System and home-manager configuration builders
├─ modules           # Custom NixOS and home-manager modules
├─ overlays          # NixPkgs overlays for package modifications
├─ pkgs              # Custom packages not in nixpkgs
└─ docs              # Additional documentation

Auto-Discovery Mechanism

The flake automatically discovers:

  • Hosts: By scanning hosts/{device-type}/ directories (excluding shared/)
  • Users: By scanning home/ directories and matching with existing hosts
  • Hardware Acceleration: Support based on predefined host lists

Welcome to the docs!

Over to the left, you’ll find the sidebar. There you’ll see several sections including “User guides”, “Modules”, “Packages”, “Overlays”, “Hosts”, and “Lib”.

If you’re looking for a specific option you may find the dedicated RacciDev Option Search (at the bottom of the sidebar) has more relevant results.

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
# Replace YOUR_WSL_HOSTNAME with your actual WSL hostname
sudo nixos-rebuild switch --flake /etc/nixos#YOUR_WSL_HOSTNAME

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

Purpose

The IO Guardian system ensures that services across the infrastructure are aware of the availability of centralized databases hosted on the Database Coordinator. It provides graceful startup and shutdown coordination between the database host and dependent services on other servers.

Entry Point

Architecture / Services / Scope

The system consists of two components:

  1. Guardian Server (runs on client servers)

    • WebSocket server that listens for commands from the coordinator
    • Secures connections with a pre-shared key (PSK) from the DB_GUARDIAN_PSK secret
    • Executes drain/undrain commands by controlling db-databases.target
  2. Guardian Client (runs on the Database Coordinator)

    • 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-db-databases.service
  2. This service waits (with retries) until PostgreSQL and Redis on the Database Coordinator are reachable
  3. Once databases are confirmed available, the service completes
  4. The db-databases.target is now ready to be activated
  5. When the Database Coordinator’s db-database-coordinator.service starts, it sends undrain to all clients
  6. Clients start db-databases.target, which starts all dependent services

Database Shutdown (Graceful Drain)

  1. When db-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 db-databases.target
  5. Dependent services stop gracefully before databases go down

Database Startup (Undrain)

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

Systemd Units

On Client Servers

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

On the Database Coordinator

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

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" }

Commands

Coordinator sends one of the supported actions (drain, undrain, or ping):

{ "type": "command", "action": "drain" }

Server responds:

{ "type": "response", "action": "<action>", "status": "ok", "message": "..." }

Secrets

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.

Declared secrets

Secret keyOwnerGroupRestart unitPurpose
DB_GUARDIAN_PSKrootrootdb-guardian.serviceAuthenticate DB Guardian WebSocket API

Operational Notes / Assumptions

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. Services listed here will start only when db-databases.target is active, stop when it stops, and restart when the target restarts.

Troubleshooting

Checking Guardian Status

On client servers:

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

On the Database Coordinator:

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

Manual Commands

To manually start/stop dependent services on a client:

systemctl start db-databases.target
systemctl stop db-databases.target

Common Issues

Guardian server won’t start:

  • Check that DB_GUARDIAN_PSK secret is properly configured
  • Verify the SOPS decryption is working and the secret is non-empty: test -s /run/secrets/DB_GUARDIAN_PSK

Services not starting after boot:

  • Check wait service logs: journalctl -u wait-for-db-databases.service
  • Verify network connectivity to the Database Coordinator on ports 5432 (Postgres) and 6379 (Redis)
  • Ensure the Database 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

References

Server Cluster Monitoring — Observability Stack

Purpose

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

The system consists of three layers:

  • 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
    • Conditional Exporters: The following exporters are enabled if their corresponding services are configured on the host:
      • Caddy access logs are parsed and sent to Loki
      • fail2ban exporter available on the IO Coordinator
      • PostgreSQL exporter available on the Database Coordinator
      • Redis exporter available on the Database Coordinator
      • Proxmox exporter available on the Monitoring Coordinator
  • Collectors (run on the Monitoring Coordinator)

    • Prometheus for metrics aggregation
    • Loki for log aggregation with 90-day retention
    • Alertmanager for alert routing and notifications
    • OTLP/HTTP ingestion on otlp.<domain> with bearer-token authentication
  • Visualization (runs on the Monitoring Coordinator)

    • Grafana with provisioned datasources and dashboards

Entry Point

Architecture / Services / Scope

Configuration

Enabling Monitoring

Monitoring is enabled by default on all servers, this can be disabled with server.monitoring.enable = false.

Options

server.monitoring.collector.alerting.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable Alertmanager and alert rules.


server.monitoring.collector.alerting.homeAssistant.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Home Assistant webhook alerting.


server.monitoring.collector.alerting.nextcloudTalk.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Nextcloud Talk webhook alerting.


server.monitoring.collector.enable

Typeboolean
DefaultthisIsMonitoringPrimaryHost && cfg.enable
Exampletrue

Whether to enable monitoring collector services (Prometheus, Loki, Grafana).


server.monitoring.collector.grafana.kanidm.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable Kanidm OAuth2 authentication for Grafana.


server.monitoring.collector.otlp.bearerTokenSecret

Typestring
Default"MONITORING/OLTP/BEARER_TOKEN"

SOPS secret path used as the bearer token for OTLP/HTTP ingestion.


server.monitoring.collector.otlp.enable

Typeboolean
DefaultisThisMonitoringPrimaryHost && cfg.enable
Exampletrue

Whether to enable OTLP/HTTP ingestion via Grafana Alloy.


server.monitoring.collector.otlp.port

Typesigned integer
Default4318

Port for the OTLP/HTTP ingestion endpoint.


server.monitoring.collector.otlp.subdomain

Typestring
Default"otlp"

Subdomain used for the OTLP/HTTP ingestion endpoint.


server.monitoring.collector.proxmox.enable

Typeboolean
DefaultisThisMonitoringPrimaryHost && cfg.enable
Exampletrue

Whether to enable Proxmox VE metrics collection.


server.monitoring.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable monitoring for this server.


server.monitoring.exporters.caddy.enable

Typeboolean
Defaultcfg.enable && config.services.caddy.enable
Exampletrue

Whether to enable Caddy metrics exporter.


server.monitoring.exporters.fail2ban.enable

Typeboolean
Defaultcfg.enable && isThisIOPrimaryHost && config.server.fail2ban.enable
Exampletrue

Whether to enable fail2ban metrics exporter.


server.monitoring.exporters.node.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable node_exporter for system-level metrics.


server.monitoring.exporters.postgres.enable

Typeboolean
Defaultcfg.enable && thisIsIOPrimaryHost && hasPostgresDatabases
Exampletrue

Whether to enable PostgreSQL exporter.


server.monitoring.exporters.process.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable Process exporter for monitoring specific processes.


server.monitoring.exporters.redis.enable

Typeboolean
Defaultcfg.enable && thisIsIOPrimaryHost && hasRedisInstances
Exampletrue

Whether to enable Redis exporter.


server.monitoring.logs.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable Alloy log shipping.


server.monitoring.logs.extraConfiguration

Typestrings concatenated with "\n"
Default""

Additional configuration for the alloy log processor. This is useful for adding custom Loki stages, relabeling rules, or write targets.

Note that the default configuration for processing the system journal is always included and does not need to be specified here.


server.monitoring.retention.logs

Typestring
Default"90d"

Loki log retention period.


server.monitoring.retention.metrics

Typestring
Default"90d"

Prometheus TSDB retention period.


server.monitoring.scrapeConfigs

Typeattribute set of (submodule)
Default{ }

Declarative scrape configs for services running on this host. These are collected by the monitoring primary host and converted into Prometheus scrape configurations.


server.monitoring.scrapeConfigs.<name>.bearer_token_secret

Typenull or string
Defaultnull

SOPS secret path for bearer token authentication. When set, the secret will be created on the monitoring primary host.


server.monitoring.scrapeConfigs.<name>.host

Typestring
Defaultconfig.host.name

Host to scrape metrics from.


server.monitoring.scrapeConfigs.<name>.job_name

Typestring
Default"‹name›"

Prometheus job name for this scrape target.


server.monitoring.scrapeConfigs.<name>.metrics_path

Typestring
Default"/metrics"

HTTP path to the metrics endpoint.


server.monitoring.scrapeConfigs.<name>.port

Typesigned integer

Port the metrics endpoint listens on.


server.monitoring.scrapeConfigs.<name>.scheme

Typeone of "http", "https"
Default"http"

URL scheme for scraping.


Secrets

Declared secrets

Secret keyPurpose
MONITORING/OLTP/BEARER_TOKENBearer token for OTLP HTTP ingestion
MONITORING/GRAFANA/SECRET_KEYGrafana secret key
MONITORING/GRAFANA/OAUTH_SECRETKanidm OAuth2 secret for Grafana
MONITORING/HOME_ASSISTANT/WEBHOOK_URLHome Assistant alert webhook
MONITORING/NEXTCLOUD_TALK/WEBHOOK_URLNextcloud Talk alert webhook
PROXMOX/USERProxmox metrics user
PROXMOX/TOKEN_IDProxmox token name
PROXMOX/TOKEN_SECRETProxmox token secret

Generating Secrets

Generating secure random secrets can be done with the following command:

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

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

Caddy Virtual Hosts

The module configures four virtual hosts on Monitoring Coordinator:

ServiceSubdomainAccess
Grafanagrafana.<domain>Public
OTLPotlp.<domain>Public, bearer token required
Prometheusprometheus.<domain>LAN
Lokiloki.<domain>LAN

Grafana remains protected by the existing Kanidm-backed login flow. The OTLP ingestion endpoint is intended for machine-to-machine clients and requires an Authorization: Bearer <token> header on every request. The exposed OTLP/HTTP paths are the standard /v1/metrics and /v1/logs endpoints.

These are defined in hosts/server/nixmon/default.nix and collected by the IO Coordinator’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
│   ├── otlp.nix             # OTLP ingestion
│   ├── 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
│   └── fail2ban.nix         # fail2ban metrics exporter
├── logs/
│   └── alloy.nix            # Alloy log shipping
└── integrations/
    └── proxmox.nix          # PVE exporter for Proxmox API

Operational Notes / Assumptions

Troubleshooting

Checking Service Status

On the monitoring host (Monitoring Coordinator):

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 prometheus-fail2ban-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 MONITORING/GRAFANA/OAUTH_SECRET in the Monitoring Coordinator matches KANIDM/OAUTH2/GRAFANA_SECRET in the Identity Coordinator
  • 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 Monitoring Coordinator and the target host

Proxmox metrics missing:

  • Verify PROXMOX/TOKEN_ID and PROXMOX/TOKEN_SECRET are valid
  • Check PVE API is accessible from Monitoring Coordinator: curl -k https://pve.<domain>/api2/json
  • Review PVE exporter logs: journalctl -u prometheus-pve-exporter.service

References

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"; # Set this to the Home Manager state version from initial setup; keep it unchanged during normal NixOS or Home Manager upgrades, and only change it during a planned migration
  };

  # 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

Updating SOPS Rules

update-sops first regenerates managed sops-keys.nix, then regenerates .sops.yaml from repository layout. Host and home entries are sorted by name for stable output. Run it after adding, removing, or rotating a host key or home user.

Host Discovery

A host is discovered only when this file exists:

hosts/{device-type}/{hostname}/ssh_host_ed25519_key.pub

The script converts each OpenSSH public key with ssh-to-age and creates rules for:

  • hosts/secrets.yaml — every discovered host
  • hosts/server/secrets.yaml — every discovered server
  • hosts/{device-type}/{hostname}/ — each discovered host directory

Host-specific rules also cover nested SOPS files under that host directory.

Managed Key Map

update-sops writes sops-keys.nix as a readable inventory of discovered recipients:

{
  deployer = "age1gmc8dd4mj5q0zncy5gq4lccjlq9v84t8cqnlananmxt8g0jezv6szawll8";
  homes = {
    racci = "...";
  };
  hosts = {
    server = {
      nixauth = "...";
    };
  };
}

Host recipients come from ssh_host_ed25519_key.pub. Home recipients come from home/{username}/id_ed25519.pub. The current user’s name comes from whoami; that user’s home recipient is used as the personal recipient in every generated SOPS rule. The current user must have a home public key file.

Every directory under home/, except home/shared/, gets a rule for:

home/{username}/secrets.yaml

Home directories without id_ed25519.pub still get SOPS rules, but do not get an entry in sops-keys.nix.

Common Recipients

Every generated rule starts with two always-present recipients, in this order:

  1. current user’s home/{username}/id_ed25519.pub converted with ssh-to-age — personal age key
  2. sops-keys.nix deployer value — automated deployer age key

The updater defines this policy in get-always-present-age-keys. Host-specific rules append that host’s age recipient. Global host rules append all applicable host recipients in the same stable order used by sops-keys.nix.

Usage

Regenerate rules:

update-sops

Check for drift without writing:

update-sops --check

By default, updater changes only sops-keys.nix and .sops.yaml. To also update recipient metadata in every encrypted SOPS file under hosts/ and home/, opt in explicitly:

update-sops --update-secrets

This runs sops updatekeys --yes for each file reported as encrypted by sops filestatus, including non-YAML formats. It can re-encrypt file metadata and requires an available SOPS decryption identity. --update-secrets cannot be combined with --check.

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

Default Development Shell

The default devenv shell in this repository includes common CLI, Nix, and setup tools used across day-to-day development.

Entering the Shell

Automatic shell entry via direnv, to enable this, run:

direnv allow

Or directly with nix:

nix develop --override-input devenv-root file+file://<path-to-nix-config>/.devenv/root

Helper Commands

The shell also exposes repository helper commands from flake/dev/scripts/. rebuild-target accepts action host, or just host; when the action is omitted it defaults to switch. Extra args are forwarded to nh / nix after --accept-flake-config, and args that start with -- must be separated with a literal --:

rebuild-target build nixmi -- --fallback
rebuild-target switch nixmi

Generated .luarc.json

Entering the default shell refreshes .luarc.json in the repository root.

The shell task manages the "workspace.library" key by:

  • creating .luarc.json when missing
  • ensuring the current Hyprland stub path is present
  • removing stale entries containing share/hypr/stubs/
  • respect and preserve any other existing keys in .luarc.json that are not managed by the shell task.

Python Development Shell

This repository contains multiple Python scripts and packages. A dedicated Python development environment is provided via devenv to simplify development of these components, by providing all necessary dependencies for proper LSP support.

Entering the Python Shell

To access the Python development environment with all required dependencies:

direnv allow
devenv shell python

Or with devenv directly:

devenv shell --file devenv.nix --shell python

Included Packages

The Python shell inherits from the default development environment and adds:

Python Runtime & Tools

  • python312 - Python 3.12 interpreter
  • pip - Package installer
  • virtualenv - Virtual environment management
  • pytest - Testing framework
  • black - Code formatter
  • ruff - Fast Python linter
  • mypy - Static type checker

Python Libraries

Libraries are organized by the components they support:

Image Compression

  • pillow - Image processing
  • rich - Terminal formatting & progress bars
  • python-magic - File type detection

Memory/Knowledge Systems

  • pyyaml - YAML parsing
  • cryptography - Encryption utilities
  • anyio - Async I/O framework

I/O Guardian & Networking

  • websockets - WebSocket protocol
  • pystemd - Systemd D-Bus interface

Utilities

  • requests - HTTP library (for Lidarr plugin updates)

Development Workflow

Testing Scripts

Run pytest on project Python files:

pytest pkgs/scripts/test_image_compressor.py -v

Code Quality

Format Python code:

nix fmt -- pkgs/scripts/compressor.py

Check types with mypy:

mypy pkgs/scripts/

Running Scripts Directly

Scripts can be executed directly in the shell:

python3 pkgs/scripts/compressor.py --help
python3 docs/preprocessor/gen-options-md.py --help

Adding New Python Dependencies

To add a new Python library:

  1. Identify the python312Packages. attribute in nixpkgs
  2. Add it to the packages list in /persist/nix-config/flake/dev/devenv.nix under devenv.shells.python
  3. Run nix fmt flake/dev/devenv.nix to format
  4. Test with nix flake check
  5. Update this documentation

Example:

# In devenv.nix, add to packages list:
python312Packages.your-new-package

Troubleshooting

ModuleNotFoundError

If you see ModuleNotFoundError: No module named 'xxx', the package may not be in the python shell. Verify it’s included in the packages list in flake/dev/devenv.nix.

Python Version Mismatch

Some packages require Python 3.12. If you need a different version:

  1. Modify python312 reference in flake/dev/devenv.nix
  2. Update corresponding python312Packages references
  3. Run nix flake check to validate

Permissions Errors with pystemd

The pystemd package requires D-Bus access. Ensure you’re running within a proper devenv session, not in a sandboxed environment.

See Also

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.

Architecture / Services / Scope

Entry Points

NixOS Modules — Overview

Purpose

This section covers all NixOS modules provided by this flake, including core baseline configuration, server orchestration, custom services, and AI infrastructure.

Entry Point

  • Main file: modules/nixos/default.nix

Architecture / Services / Scope

Modules are categorized into directories based on their target:

  • core/: Shared baselines for all hosts.
  • server/: Cluster-aware server modules.
  • services/: Custom NixOS services (e.g., Dashy, Tailscale).
  • ai/: AI infrastructure daemons.

References

NixOS Services — Overview

Purpose

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

Entry Point

  • Main file: modules/nixos/services/default.nix

Architecture / Services / Scope

Nested service modules emit generated fragments for options, which are included in their respective documentation pages.

Operational Notes / Assumptions

  • Services should be toggled per-host.

References

AI Agent — Hermes Autonomous Agent

Purpose

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

Entry Point

  • Main file: ai-agent.nix
  • Upstream: Hermes Agent
  • Package: The module routes services.hermes-agent.package through the local pkgs.hermes-agent overlay, which carries a few upstream patches.

Options

services.ai-agent.apiServer.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable the OpenAI comptable endpoint.


services.ai-agent.apiServer.host

Typestring
Default"127.0.0.1"

The host/IP for the API server to bind to.


services.ai-agent.apiServer.port

Typesigned integer
Default8642

The port for the API server to listen on.


services.ai-agent.apiServer.tokenReference

Typestring
Default"AI_AGENT/API_SERVER_TOKEN"

The sops secret attribute for the API server authentication token.


services.ai-agent.containerPostStart

Typelist of (string or (submodule))
Default[ ]

Shell commands to run inside the AI agent container after startup.

A plain string runs inside the container as root via docker exec. An attrset { command = "..."; host = true; } runs on the host.

Commands to run after the AI agent container starts. Container commands get automatic retry to wait for Docker + container readiness.


services.ai-agent.dashboard.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Hermes web dashboard.


services.ai-agent.dashboard.oidc.clientId

Typestring

The OIDC client ID for dashboard authentication.


services.ai-agent.dashboard.oidc.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable OpenID Connect authentication for the dashboard.


services.ai-agent.dashboard.oidc.issuer

Typestring

The OIDC issuer URL for dashboard authentication.


services.ai-agent.dashboard.oidc.provider

Typestring
Default"self-hosted"

The OIDC plugin to use for dashboard authentication.


services.ai-agent.dashboard.oidc.scopes

Typelist of string
Default[ "openid" "profile" "email" ]

The OIDC scopes to request for dashboard authentication.


services.ai-agent.dashboard.port

Typesigned integer
Default9119

The port for the dashboard to listen on.


services.ai-agent.dashboard.publicURL

Typenull or string
Defaultnull

The public URL for the dashboard, used for generating links in notifications and similar. If not set, localhost URLs will be used.

If set, must be a valid URL starting with http:// or https://.


services.ai-agent.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable autonomous AI Agent service.


services.ai-agent.extras.browser

Typeboolean
Defaultfalse
Exampletrue

Whether to enable headless browser for web scraping and automation.


services.ai-agent.extras.plugins

Typeboolean
Defaultfalse
Exampletrue

Whether to enable extra plugins for the agent.


services.ai-agent.extras.scraper.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable web scraping and search plugins for the agent.


services.ai-agent.extras.scraper.searxEndpoint

Typenull or string
Defaultnull

The SearxNG endpoint to use for web search.


services.ai-agent.memory.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable long-term memory

When enabled this disables the builtin user profile and memory markdown features, to nudge the agent towards using the configured long-term memory provider for all memory. .


services.ai-agent.models.brains

Typestring
Default"deepseek/deepseek-v4-pro-0813"

The smartest model to use for complex reasoning and decision-making tasks.

Used for auxiliary models:


services.ai-agent.models.compression

Typestring
Default"~deepseek/deepseek-v4-flash-latest"

The model to use for compression of context, summorisation and similar tasks that don’t require reasoning. This model still needs a decently sized context window to be effective.

Used for auxiliary models:


services.ai-agent.models.primary

Typestring
Default"~deepseek/deepseek-v4-flash-latest"

The primary language model to use for the AI agent.


services.ai-agent.models.provider

Typestring
Default"openrouter"

The model provider to use.


services.ai-agent.models.simpleton

Typestring
Default"inclusionai/ling-3.0-flash"

The simpleton model to delegate tasks to that require less reasoning, basic understanding and small context windows.

Used for auxiliary models:


services.ai-agent.models.vision

Typestring
Default"xiaomi/mimo-v2.5"

The vision model to delegate image understanding tasks to.


services.ai-agent.platform.discord.allowedUsers

Typelist of string
Default[ ]

A list of Discord user IDs that the agent is allowed to interact with.


services.ai-agent.platform.discord.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Discord as a messaging channel.


services.ai-agent.platform.discord.homeChannel

Typenull or string
Defaultnull

The Discord channel ID to use as the home channel for the agent.


services.ai-agent.platform.discord.tokenReference

Typestring
Default"AI_AGENT/DISCORD_BOT_TOKEN"

The sops secret attribute for the Discord bot token.


services.ai-agent.platform.hassio.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Home Assistant as a tool and notification channel.


services.ai-agent.platform.hassio.tokenReference

Typestring
Default"AI_AGENT/HASSIO_TOKEN"

The sops secret attribute for the Home Assistant long-lived access token.


services.ai-agent.platform.hassio.url

Typestring

The URL for the Home Assistant instance, including the scheme.


services.ai-agent.platform.webhook.port

Typesigned integer
Default8654

The port for the webhook listener to listen on.


services.ai-agent.settings

TypeHermes config attrs, deep-merged with list concatenation.
Default{ }

Hermes config to merge into services.hermes-agent.settings.

This is a local option that merges correctly across feature toggles. The upstream services.hermes-agent.settings uses lib.recursiveUpdate for its config type, which replaces lists instead of concatenating them, so separate blocks would clobber each other. This option deep-merges with list concatenation and is emitted once as the final value of services.hermes-agent.settings.


services.ai-agent.voice.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable voice input and output using the TTS and STT.


services.ai-agent.voice.wyoming-stt.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable use existing Wyoming faster-whisper server for STT instead of running a separate Whisper instance.


services.ai-agent.voice.wyoming-stt.host

Typestring
Default"localhost"

The host of the Wyoming faster-whisper server.


services.ai-agent.voice.wyoming-stt.port

Typesigned integer
Default10300

The port of the Wyoming faster-whisper server.


Architecture / Services / Scope

The module enables services.hermes-agent (running inside a Docker container) and adds optional components on top:

  • Dashboard — a separate hermes-dashboard systemd service runs docker exec into the hermes-agent container to serve the dashboard under the hermes user. Environment files configured via services.hermes-agent.environmentFiles are loaded by systemd’s EnvironmentFile directive (read as root) and passed into the container via docker exec --env-file. The dashboard stays local by default and does not open a browser.
  • Voice & STT — optional voice input and output. With services.ai-agent.voice.wyoming-stt.enable, Hermes reuses an existing Wyoming faster-whisper server instead of running a separate Whisper instance: HERMES_LOCAL_STT_COMMAND is set to invoke wyoming-transcribe, which sends audio over the Wyoming protocol and returns the transcript. No second Whisper process needed.
  • OIDC Authentication — optional OpenID Connect authentication for the dashboard using a public PKCE client (no client_secret). The client ID is a public identifier — it does not need to be stored as a secret. The module generates a HERMES_DASHBOARD_OIDC_ENV environment file with the OIDC settings, loaded by the hermes-dashboard service.
  • Memory (Mnemosyne) — with services.ai-agent.memory.enable, the memory provider switches from the built-in user profile (USER.md injection) to Mnemosyne, a local SQLite-backed memory system with semantic recall (SQLite with FTS5 hybrid ranking + vector search).

Secrets

Hermes requires API keys via environment files. Configure via sops-nix:

sops = {
  secrets = {
    "AI_AGENT/OPENROUTER_API_KEY" = { };
  };
  templates."HERMES_ENV".content = ''
    OPENROUTER_API_KEY=${config.sops.placeholder."AI_AGENT/OPENROUTER_API_KEY"}
  '';
};

services.hermes-agent.environmentFiles = [ config.sops.templates."HERMES_ENV".path ];

The module itself declares secrets for the enabled optional components:

  • API server token (default AI_AGENT/API_SERVER_TOKEN) — authenticates the OpenAI-compatible API server.
  • Discord bot token (default AI_AGENT/DISCORD_BOT_TOKEN) — Discord platform.
  • Home Assistant token (default AI_AGENT/HASSIO_TOKEN) — Home Assistant platform.
  • Dashboard OIDC uses a public PKCE client, so no client secret is stored.

Operational Notes / Assumptions

Usage Example

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

Voice & STT

Enable voice input and output with services.ai-agent.voice.enable = true;. To reuse an existing Wyoming faster-whisper server instead of running a separate Whisper instance:

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

Dashboard & OIDC

Enable the web dashboard with services.ai-agent.dashboard.enable = true;, and OIDC authentication with services.ai-agent.dashboard.oidc.enable = true;:

{ ... }: {
  services.ai-agent = {
    enable = true;
    dashboard = {
      enable = true;
      publicURL = "https://dashboard.example.com";
      oidc = {
        enable = true;
        provider = "self-hosted";
        issuer = "https://auth.example.com/oauth2/openid/hermes";
        clientId = "hermes";
        scopes = [ "openid" "profile" "email" ];
      };
    };
  };
}

Memory (Mnemosyne)

Enable long-term memory with services.ai-agent.memory.enable = true;:

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

References

Huntress — Managed EDR

Purpose

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

Entry Point

Options

services.huntress.accountKeyFile

Typestring

The account key for the Huntress agent.


services.huntress.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Huntress service.


services.huntress.organisationKeyFile

Typestring

The organisation key for the Huntress agent.


services.huntress.package

Typepackage
Default<derivation huntress-0.14.74>

The Huntress package to use.


Architecture / Services / Scope

The module runs a single huntress-agent systemd service as root. The agent configuration is generated at /etc/huntress/agent_config.yaml during the service’s preStart phase: a default configuration is written on first start, after which the account and organisation keys are merged in using yaml-merge.

Secrets

  • accountKeyFile — Huntress account key, loaded into the service via systemd LoadCredential.
  • organisationKeyFile — Huntress organisation key, loaded into the service via systemd LoadCredential.

Operational Notes / Assumptions

  • Both keys are validated during preStart; the service fails to start if either is empty.
  • The merged configuration persists across restarts in /etc/huntress/agent_config.yaml.

Usage Example

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

References

MCPO — Model Context Protocol Orchestrator

Purpose

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

Entry Point

Options

services.mcpo.apiTokenFile

Typenull or absolute path
Defaultnull

Path to a file containing the API token for the mcpo service. This file will be exposed to the service through a systemd credential named “apiToken”.


services.mcpo.configuration

Typeattribute set of (submodule)
Default{ }

This option has no description.


services.mcpo.configuration.<name>.args

Typelist of string
Default[ ]

Arguments to pass to the command.


services.mcpo.configuration.<name>.command

Typenull or string
Defaultnull

Command to render the config file.


services.mcpo.configuration.<name>.headers

Typeattribute set of string
Default{ }

Headers to pass to the command.


services.mcpo.configuration.<name>.type

Typenull or one of "sse", "streamable-http"
Defaultnull

This option has no description.


services.mcpo.configuration.<name>.url

Typenull or string
Defaultnull

This option has no description.


services.mcpo.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable mcpo (Model Context Protocol Orchestrator) service.


services.mcpo.environment

Typeattribute set of string
Default{ }

Additional environment variables for the service.


services.mcpo.extraPackages

Typelist of package
Default[ ]

Additional packages to include in the service’s PATH.


services.mcpo.helpers

Typeattribute set
Default{ npxServer = <function>; npxServerWithArgs = <function>; uvxServer = <function>; uvxServerWithArgs = <function>; }

Helper functions for constructing mcpo server command blocks.


services.mcpo.package

Typepackage
Default<derivation mcpo-0.0.18>

Package providing the mcpo executable.


Architecture / Services / Scope

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; additional packages can be added with services.mcpo.extraPackages.

Secrets

  • apiTokenFile (optional) — API token exposed to the service as the systemd credential apiToken.
  • Server configuration and environment are rendered through sops templates (mcpoConfiguration, mcpoEnvironment) and consumed via LoadCredential / EnvironmentFile.

Operational Notes / Assumptions

Usage Example

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

Package Patches

  • mcpo-union-repr-compat.patch — Applied via overlay in overlays/patches/. Upstream test src/mcpo/tests/test_main.py asserts Union repr starts with "typing.Union[", but Python 3.12+ may stringify unions as str | float. Patch uses get_origin(result_type) is Union instead. Build/test compatibility only; no runtime impact.

References

Metrics & Hacompanion — Metrics Collection & Home Assistant Integration

Purpose

Comprehensive metrics collection and integration with Home Assistant via hacompanion.

Entry Point

Options

services.metrics.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Metrics collection service.


services.metrics.hacompanion.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Home Assistant Companion service.


services.metrics.hacompanion.script

Typeattribute set of (submodule)

This option has no description.


services.metrics.hacompanion.script.<name>.device_class

Typenull or one of "absolute_humidity", "apparent_power", "aqi", "area", "atmospheric_pressure", "battery", "blood_glucose_concentration", "carbon_dioxide", "carbon_monoxide", "current", "data_rate", "data_size", "date", "distance", "duration", "energy", "energy_distance", "energy_storage", "enum", "frequency", "gas", "humidity", "illuminance", "irradiance", "moisture", "monetary", "nitrogen_dioxide", "nitrogen_monoxide", "nitrous_oxide", "ozone", "ph", "pm1", "pm10", "pm25", "power", "power_factor", "precipitation", "precipitation_intensity", "pressure", "reactive_energy", "reactive_power", "signal_strength", "sound_pressure", "speed", "sulphur_dioxide", "temperature", "timestamp", "volatile_organic_compounds", "volatile_organic_compounds_parts", "voltage", "volume", "volume_flow_rate", "volume_storage", "water", "weight", "wind_direction", "wind_speed"
Defaultnull

The device class for the script in Home Assistant.


services.metrics.hacompanion.script.<name>.icon

Typestring
Default"mdi:script-text-outline"

The icon to use for the script in Home Assistant.


services.metrics.hacompanion.script.<name>.name

Typestring

The name of the script as it will appear in Home Assistant.


services.metrics.hacompanion.script.<name>.path

Typeabsolute path

The path to the script to execute.


services.metrics.hacompanion.script.<name>.type

Typeone of "sensor", "switch"
Default"sensor"

The type of the script in Home Assistant.


services.metrics.hacompanion.script.<name>.unit_of_measurement

Typenull or string
Defaultnull

The unit of measurement for the script in Home Assistant.


services.metrics.hacompanion.sensor.audio_volume.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the audio_volume sensor.


services.metrics.hacompanion.sensor.companion_running.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the companion_running sensor.


services.metrics.hacompanion.sensor.cpu_temp.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the cpu_temp sensor.


services.metrics.hacompanion.sensor.cpu_usage.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the cpu_usage sensor.


services.metrics.hacompanion.sensor.load_avg.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the load_avg sensor.


services.metrics.hacompanion.sensor.memory.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the memory sensor.


services.metrics.hacompanion.sensor.online_check.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the online_check sensor.


services.metrics.hacompanion.sensor.power.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the power sensor.


services.metrics.hacompanion.sensor.uptime.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the uptime sensor.


services.metrics.hacompanion.sensor.webcam.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable the webcam sensor.


services.metrics.hacompanion.storage

Typeattribute set of (submodule)
Default{ }

Storage devices and ZFS pools to monitor


services.metrics.hacompanion.storage.<name>.name

Typenull or string
Defaultnull

The pretty display name for this storage device in Home Assistant.


services.metrics.hacompanion.storage.<name>.sensors.avail

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable available space sensor.


services.metrics.hacompanion.storage.<name>.sensors.read

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable read speed sensor.


services.metrics.hacompanion.storage.<name>.sensors.temperature

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable temperature sensor.


services.metrics.hacompanion.storage.<name>.sensors.used

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable used space sensor.


services.metrics.hacompanion.storage.<name>.sensors.write

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable write speed sensor.


services.metrics.hacompanion.test

Typeanything
DefaulthacompanionConfig

This option has no description.


services.metrics.upgradeStatus.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Upgrade Status service.


services.metrics.upgradeStatus.uptimeKuma.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Uptime Kuma tracking for Upgrade Status.


Architecture / Services / Scope

  • hacompanion — Home Assistant companion daemon that publishes system sensors (CPU, memory, storage, uptime, and more) to Home Assistant. It uses a generated TOML configuration file and loads the Home Assistant API token from sops.secrets.HACOMPANION_ENV.
  • upgradeStatus — reports NixOS upgrade state (idle / running / failed / dirty). When upgradeStatus.uptimeKuma.enable is set, it also sends heartbeat notifications to Uptime Kuma on successful upgrades.

Secrets

  • HACOMPANION_ENV — Home Assistant API token, declared in hosts/secrets.yaml and consumed via EnvironmentFile.
  • UPGRADE_STATUS_ID — Uptime Kuma push monitor ID (host-level secrets.yaml), required when upgradeStatus.uptimeKuma.enable is set.

Operational Notes / Assumptions

  • Hacompanion runs as a DynamicUser with its state in /var/lib/hacompanion.
  • The upgradeStatus feature can integrate with Uptime Kuma to provide heartbeat notifications for successful system upgrades.

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;
    };
  };
}

References

Tailscale — Tag Management Extensions

Purpose

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

Entry Point

Options

services.tailscale.tags

Typelist of string
Default[ ]

Additional tags to advertise for this device.

Tags are used for access control and routing in Tailscale. See https://tailscale.com/kb/1018/tags/ for more information.


Architecture / Services / Scope

This module extends the standard NixOS services.tailscale module by automatically constructing the --advertise-tags flag from the configured services.tailscale.tags list.

Operational Notes / Assumptions

  • Ensure the device has the necessary permissions in your Tailscale ACLs to apply the requested tags.

Usage Example

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

References

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.

Options

core.activation.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable report diff on activation.


core.audio.enable

Typeboolean
Default!config.host.device.isHeadless
Exampletrue

Whether to enable Enable audio support.


core.auto-upgrade.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable auto-upgrade.


core.auto-upgrade.hostName

Typestring
Defaultconfig.networking.hostName

The hostName to use for auto-upgrade


core.bluetooth.enable

Typeboolean
Default!config.host.device.isHeadless
Exampletrue

Whether to enable Enable Bluetooth support.


core.containers.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable container support.


core.defaultGroups

Typelist of string
Default[ ]

Additional groups to add all users to by default.


core.display-manager.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable display manager configuration.


core.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable Enable core features.


core.gaming.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable gaming features.


core.hm-helper._1password.enableCli

Typeboolean
DefaultanyoneHasPackage pkgs._1password-cli
Exampletrue

Whether to enable Enable 1Password Cli support.


core.hm-helper._1password.enableGUI

Typeboolean
DefaultanyoneHasPackage pkgs._1password-gui
Exampletrue

Whether to enable Enable 1Password GUI support.


core.hm-helper.enable

Typeboolean
Defaultconfig ? home-manager
Exampletrue

Whether to enable Home Manager helper functions.


core.hm-helper.ff2mpv.enable

Typeboolean
DefaultanyoneHasPackage pkgs.ff2mpv-rust
Exampletrue

Whether to enable Enable ff2mpv native messaging host for Firefox..


core.hm-helper.hmUsers

Typelist of string
Default[ ]

List of Home Manager users that also exist in config.users.users.


core.hm-helper.kde-connect.enable

Typeboolean
DefaultanyoneHasOption (user: user.services.kdeconnect.enable)
Exampletrue

Whether to enable Enable KDE Connect firewall rules if any user has KDE Connect enabled..


core.hm-helper.nautilus.enable

Typeboolean
DefaultanyoneHasPackage pkgs.nautilus
Exampletrue

Whether to enable Enable Nautilus extensions and integration helpers..


core.locale.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable locale configuration.


core.network.enable

Typeboolean
Default!config.host.device.isVirtual
Exampletrue

Whether to enable Enable network support.


core.networking.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable opinionated networking defaults.


core.networking.tailscale.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable tailscale configuration.


core.openssh.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable OpenSSH server and client opinionated configuration.


core.openssh.hostPrivateKeyPath

Typestring
Default"/var/lib/provisioning/ssh/ssh_host_ed25519_key"

Canonical path of the provisioned ed25519 host private key used by OpenSSH, SOPS age decryption, and server-to-server SSH.


core.printing.enable

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

Whether to enable printing support.


core.remote.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable remote features.


core.remote.remoteDesktop

Typesubmodule
Default{ }

This option has no description.


core.remote.remoteDesktop.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable remote desktop.


core.remote.remoteDesktop.startCommand

Typestring
Default"gnome-session"

Command to start remote desktop session.


core.remote.streaming

Typesubmodule
Default{ }

This option has no description.


core.remote.streaming.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable remote streaming.


core.security.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable security features.


core.security.userLimit

Typeunsigned integer, meaning >=0
Default131072

The maximum number of open files per user.

This is used to set the limits for both PAM and systemd.


core.sops.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable SOPS auto configuration.


core.sops.hostSecretsFile

Typeabsolute path
Default"/nix/store/jq8636fkq2anq8f33kfqa816d0nrqw4m-source/hosts/secrets.yaml"

Where the SOPS secret file of this host is located in the flake.


core.stylix.enable

Typeboolean
Default!config.host.device.isHeadless
Exampletrue

Whether to enable Stylix configuration.


core.virtualisation.bridgeInterface

Typestring
Default"br0"

Bridge interface used for libvirt networking.


core.virtualisation.cpuCores

Typesigned integer
Default24

Total CPU core/thread count used for isolation helpers. Must be >= 4.


core.virtualisation.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable virtualisation support.


core.virtualisation.externalInterface

Typestring
Default"eth0"

Physical interface attached to bridge.


core.virtualisation.gpu.audio

Typestring
Default"10de:1bef"

PCI address for passthrough GPU audio device.


core.virtualisation.gpu.video

Typestring
Default"10de:1b06"

PCI address for passthrough GPU video device.


core.virtualisation.isolatedGuests

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

List of guests to apply isolation helpers to.


core.virtualisation.vmUsers

Typelist of string
Default[ ]

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


core.wsl.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable WSL specific configurations, optimisations, and fixes.


core.wsl.user

Typestring

The default user to use for WSL.


Architecture / Services / Scope

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.

Usage Example

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

References

Operational Notes / Assumptions

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.

Purpose

Provide visibility into what changed between system generations by comparing the previous and newest generation during NixOS activation, so each upgrade shows a readable package and closure diff.

Entry Point

Architecture / Services / Scope

When enabled, the module installs an activation script (system.activationScripts.report-changes) that:

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

If no previous generation exists yet, the script does nothing.

Operational Notes / Assumptions

  • Diff output is informational only; the script tolerates a non-zero nvd exit so activation never fails because of a diff error.
  • Default follows top-level core.enable, so most hosts get generation diff reporting automatically.

Auto Upgrade

Schedules automatic NixOS upgrades from flake host outputs.

Purpose

Keep hosts current by rebuilding them from the flake on a schedule, with randomized start times to spread load across hosts and resource limits so upgrades do not starve interactive workloads.

Entry Point

Options

core.auto-upgrade.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable auto-upgrade.


core.auto-upgrade.hostName

Typestring
Defaultconfig.networking.hostName

The hostName to use for auto-upgrade


Architecture / Services / Scope

When enabled, the module configures system.autoUpgrade to rebuild the host from the configured GitHub flake output for that host, using the --refresh, --accept-flake-config, and --no-update-lock-file flags, and applies CPU and IO resource limits to nixos-upgrade.service.

Operational Notes / Assumptions

  • Auto-upgrade only turns on when the flake has a revision (self.rev), meaning the repository is in a clean, revisioned state. Dirty working trees or non-revisioned evaluations leave system.autoUpgrade.enable = false.
  • Upgrades always target the GitHub flake source, not the local checkout.

Containers

Enables Docker-based container runtime defaults.

Purpose

Provide a Docker-backed container runtime for workloads that still rely on Docker-specific features, with automatic image pruning and persistent state.

Entry Point

Options

core.containers.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable container support.


Architecture / Services / Scope

When enabled, the module:

  • enables virtualisation.docker with the default Docker package,
  • enables CDI device support in the Docker daemon,
  • enables weekly automatic image pruning,
  • sets virtualisation.oci-containers.backend = "docker",
  • adds docker to core.defaultGroups, and
  • persists Docker state directories under /var/lib/docker (overlay storage, images, volumes, containers, containerd, and buildkit data).

Operational Notes / Assumptions

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

Display Manager

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

Purpose

Provide a consistent, terminal-based login experience via greetd and tuigreet on hosts with a display.

Entry Point

Options

core.display-manager.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable display manager configuration.


Architecture / Services / Scope

Enabled by default on hosts where host.device.isHeadless = false. The greetd greeter runs as the greeter user and:

  • shows the current time,
  • remembers the last logged-in user and last selected session, and
  • exposes both Wayland and X11 session directories when services.displayManager.sessionPackages is non-empty.

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

Operational Notes / Assumptions

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

Gaming — Gaming, VR, and Steam-focused desktop features

Purpose

Enable a desktop gaming stack around Steam, 32-bit graphics, Android ADB tooling, and WiVRn VR streaming, plus firewall, udev, and optional Decky Loader lifecycle integration.

Entry Point

Options

core.gaming.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable gaming features.


Architecture / Services / Scope

When enabled, module:

  • adds adbusers to core.defaultGroups,
  • enables hardware.steam-hardware and 32-bit graphics support,
  • installs android-tools,
  • enables Steam with Steam Deck style launch arguments, extest, xwayland-run/xwininfo extras, and proton-ge-bin compatibility,
  • opens Steam Remote Play and local transfer firewall rules,
  • enables WiVRn with highPriority, OpenXR runtime import, firewall access, and JSON config,
  • installs udev rules for PlayStation controller, Oculus Quest, and tty ACM devices, and
  • overlays gamescope-session for 4K resolution and wider refresh limits.

Firewall ports opened: UDP 41492, 9943, 9944 plus TCP 8082, 9943, 9944, 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.

WiVRn Socket Activation

WiVRn is activated on-demand through systemd user socket activation instead of running for the full session.

  • systemd.user.sockets.wivrn listens on %t/wivrn/comp_ipc (UNIX socket, mode 0770).
  • Socket is WantedBy=default.target, so it’s available throughout the session, but WiVRn itself only starts when a client connects.
  • Service env override: IPC_EXIT_ON_DISCONNECT=on — WiVRn exits after client disconnects.
  • Steam’s OpenXR runtime path resolves to the socket; activating a SteamVR/OpenXR game triggers socket activation.
  • Parent dir created with 0750, socket with 0770.

Operational Notes / Assumptions

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

Generators

Groups

Locale

Sets shared timezone and locale defaults.

Purpose

Provide opinionated regional defaults for timezone and locale so all hosts start from a consistent baseline without forcing per-host overrides.

Entry Point

Options

core.locale.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable locale configuration.


Architecture / Services / Scope

The module applies an Australian timezone default and enables Australian and US English UTF-8 locales.

Operational Notes / Assumptions

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

Nix

Defines shared Nix daemon, cache, and registry defaults.

Purpose

Establish global Nix configuration for every host in the flake: overlays, state version, trusted users, experimental features, binary caches, garbage collection, registry-derived nixPath, plus automatic uploads to the remote Attic cache.

Entry Point

Architecture / Services / Scope

The module applies shared baseline configuration directly (no core.* options). It:

  • installs the nix4vscode overlay,
  • sets system.stateVersion from the state.version file at the flake root,
  • configures trusted users, automatic store optimisation, experimental features, substituters and trusted public keys for the project’s binary caches, daily automatic GC, and a nixPath derived from config.nix.registry.

It also:

  • enables services.angrr to retain recent system profiles, and
  • creates systemd.services.attic-watch-store, which waits for network-online.target, restarts on failure, logs into Attic using a SOPS-managed cache push key, and watches the store for uploads.

Secrets

The module declares the SOPS secret CACHE_PUSH_KEY (from hosts/secrets.yaml) and restarts attic-watch-store.service when it changes. This key authenticates automatic uploads to the remote Attic cache.

Operational Notes / Assumptions

  • Because the module has no enable flag, it 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 a bounded set of recent system profile generations.

OpenSSH

Configures opinionated SSH server and client defaults.

Purpose

Provide a hardened, consistent SSH experience across hosts: ed25519-only host keys, no password authentication, automatically generated known-host entries, PAM ssh-agent authentication, and GatewayPorts = "clientspecified" so clients may request non-loopback forwarding binds when needed. That forwarding flexibility can expose tunnels beyond localhost if the client explicitly asks for it.

Entry Point

Options

core.openssh.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable OpenSSH server and client opinionated configuration.


core.openssh.hostPrivateKeyPath

Typestring
Default"/var/lib/provisioning/ssh/ssh_host_ed25519_key"

Canonical path of the provisioned ed25519 host private key used by OpenSSH, SOPS age decryption, and server-to-server SSH.


Architecture / Services / Scope

When enabled, the module:

  • enables services.openssh with socket activation disabled so SSH runs as a traditional always-on service (avoiding disconnects during nixos-rebuild switch over SSH),
  • disables password authentication and sets PermitRootLogin = "prohibit-password",
  • configures the ed25519 host key from core.openssh.hostPrivateKeyPath, persists that file via host.persistence.files, and publishes the matching public key at /etc/ssh/ssh_host_ed25519_key.pub,
  • enables security.pam.sshAgentAuth,
  • adds the current host’s public host key to root’s authorized keys, and
  • generates programs.ssh.knownHosts entries for every host in outputs.nixosConfigurations.

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

Operational Notes / Assumptions

  • Module expects a matching host public key file to exist in the flake for each host.
  • The current host gets localhost as an extra known-host alias in the generated SSH client config.
  • Root authorization uses host key material from the flake, not per-user login keys.
  • Socket activation is disabled (startWhenNeeded = false): the default NixOS setup spawns per-connection sshd@...service instances, and restarting them during a configuration switch disconnects active SSH sessions. An always-on service prevents remote disconnection during nixos-rebuild switch.

Printing

Enables shared printer support for workstation-class NixOS hosts.

Purpose

Provide CUPS printing and common printer drivers on non-server, non-virtual hosts so local and network printing work out of the box.

Entry Point

Options

core.printing.enable

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

Whether to enable printing support.


Architecture / Services / Scope

When both top-level core.enable and core.printing.enable are on, the module:

  • enables services.printing with HP and Gutenprint driver stacks plus Brother colour laser driver packages, and
  • adds lp to core.defaultGroups.

Operational Notes / Assumptions

  • 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.
  • lp group membership is granted through the shared core.defaultGroups handling.

Remote Access — Optional remote desktop and game-streaming capabilities for desktop hosts

Purpose

Expose the host remotely through two independent sub-features: Remote Desktop (xrdp) for full desktop access over RDP, and Streaming (Sunshine) for low-latency game or desktop streaming.

Entry Point

Options

core.remote.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable remote features.


core.remote.remoteDesktop

Typesubmodule
Default{ }

This option has no description.


core.remote.remoteDesktop.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable remote desktop.


core.remote.remoteDesktop.startCommand

Typestring
Default"gnome-session"

Command to start remote desktop session.


core.remote.streaming

Typesubmodule
Default{ }

This option has no description.


core.remote.streaming.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable remote streaming.


Architecture / Services / Scope

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

When core.remote.enable = true:

  • remoteDesktop.enable turns on services.xrdp, sets defaultWindowManager, and opens firewall for RDP.
  • streaming.enable turns on services.sunshine, opens firewall for TCP 47989, and sets capSysAdmin = true.
  • Sunshine does not run continuously. A socket-activated TCP proxy on port 47989 starts Sunshine on-demand when a client connects.
  • When the client disconnects and no new connection arrives for 5 minutes, the proxy exits and Sunshine stops.
  • 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,
  • adds two Sunshine application entries named Shared Desktop and Exclusive Desktop,
  • creates headless output at login via Home Manager, and
  • keeps HEADLESS-1 disabled by default until Sunshine prep commands enable it.

Lua Mode Handling

If Home Manager Hyprland is in Lua mode (wayland.windowManager.hyprland.configType = "lua"), the shared module uses Lua-safe equivalents:

  • Startup hook: settings.on with hyprland.start event triggers hyprctl output create headless.
  • Monitor rule: { output = "HEADLESS-1"; disabled = true; }
  • Screencopy permission: routed through wayland.windowManager.hyprland.custom-settings.permission.screenCopy, which handles both Lua and hyprlang modes automatically.
ApplicationBehaviour
Shared DesktopEnables HEADLESS-1 at client resolution and leaves physical monitors active.
Exclusive DesktopEnables HEADLESS-1, saves active physical monitor state to $XDG_STATE_HOME/hyprland-disabled-monitors-pre-sunshine.json, disables those monitors, then restores them on disconnect.

The socket proxy and Hyprland monitor helpers are shipped together in the shared sunshine-tools package so Sunshine startup and prep/undo hooks reuse one tested implementation across hosts.

On-Demand Activation & Idle Stop

Sunshine stays on its standard port family rooted at TCP/UDP 47989–47990+ (no port-family offset). External inbound TCP 47989 is firewall-redirected to internal proxy port 48989. The proxy wakes Sunshine and forwards to 127.0.0.1:47989.

StageWhat happens
Firewall redirectiptables NAT prerouting rule redirects inbound TCP :47989 to local :48989. A conntrack-based filter accept allows only redirected traffic into :48989; :48989 is not broadly exposed.
Socket activationsunshine-proxy.socket listens on TCP :48989. First connection activates sunshine-proxy.service.
Proxy startsunshine-proxy.service pulls in sunshine.service via systemd dependencies, then the packaged sunshine-proxy-wrapper helper polls until port 47989 is open and exec-s into systemd-socket-proxyd forwarding to 127.0.0.1:47989.
Active streamingSunshine handles Moonlight/Sunshine client traffic on its standard port family. The proxy relays only the initial control connection transparently. Proxy bindsTo Sunshine — if Sunshine crashes proxy goes with it.
Idle stopAfter 300s with no connection, systemd-socket-proxyd exits. Sunshine has Restart=no and StopWhenUnneeded=true with no remaining active referrer, so systemd stops it.

Dependency Model

sunshine-proxy.socket (:48989)
    ↓ activates
sunshine-proxy.service  ──bindsTo──→  sunshine.service (:47989)

No cycle: proxy starts Sunshine, proxy bindsTo Sunshine (proxy dies if Sunshine fails), Sunshine uses StopWhenUnneeded (stops when proxy exits).

Firewall Flow

External client → TCP :47989
    ↓ (NAT PREROUTING REDIRECT)
Local port :48989
    ↓ (conntrack ctorigdstport 47989 match → nixos-fw-accept)
sunshine-proxy.socket
    ↓
sunshine-proxy.service → sunshine.service (:47989)

Redirect covers only inbound network traffic. Locally-originated traffic to :47989 (e.g. from Moonlight running on the same machine) is unaffected.

Operational Notes / Assumptions

  • 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.

Caveats

  • No LAN discovery while idle. Sunshine needs to run for mDNS/SSDP advertisements to appear on LAN. While stopped (idle), clients will not auto-discover the host. Users must add the host manually by IP/hostname in Moonlight or use a previously-added host entry (Moonlight remembers known hosts).
  • TCP wake only. This proxy covers the control/initial TCP connection on 47989. Sunshine’s UDP audio/video streams (standard port range) will only work after Sunshine runs. Since Sunshine sets up its UDP sockets itself after startup, no UDP wake is needed in practice (the rendezvous happens over TCP first).
  • Delayed first connect. The first TCP connection may stall ~1–2 seconds while Sunshine starts up. Clients (Moonlight) retry or timeout gracefully.
  • Firewall. Sunshine opens its standard ports via openFirewall. The redirect only touches TCP 47989 for the wake path. No port-family offset anymore — all media/data ports remain at standard values.
  • Redirect scope. The firewall redirect applies to inbound network traffic only. Local loopback connections to :47989 bypass the redirect and reach Sunshine directly if it is already running.

Security — Shared host security defaults

Purpose

Enable baseline host security: sudo-rs, TPM2 support, Polkit, kernel protection flags, and open-file limits for users.

Entry Point

Architecture / Services / Scope

When enabled, module:

  • enables sudo-rs in place of sudo, restricted to the wheel group,
  • enables TPM2 and Polkit,
  • enables kernel image protection while leaving lockKernelModules off,
  • sets PAM and user systemd service open-file limits from core.security.userLimit, and
  • raises fs.file-max to a multiple of userLimit.

Operational Notes / Assumptions

  • 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 — Shared SOPS and age decryption defaults

Purpose

Provide a shared baseline for SOPS-managed secrets on every host: point sops-nix at the host’s secrets file and teach age to use the host’s provisioned SSH keys.

Entry Point

Architecture / Services / Scope

When enabled, module:

  • imports sops-nix (skipped when function argument importExternals = false),
  • sets sops.defaultSopsFile to core.sops.hostSecretsFile,
  • builds sops.age.sshKeyPaths from core.openssh.hostPrivateKeyPath first, then appends configured ed25519 OpenSSH host keys without duplicating the canonical path.

Operational Notes / Assumptions

  • Default age key path is core.openssh.hostPrivateKeyPath.
  • Only ed25519 entries from config.services.openssh.hostKeys are appended to the age key paths.
  • Secrets file defaults to secrets.yaml inside the host directory, overridable via core.sops.hostSecretsFile.

Stylix — Shared system theme defaults via Stylix

Purpose

Apply a consistent dark theme across graphical hosts by importing Stylix and enabling dark Tokyo Night theming by default.

Entry Point

Architecture / Services / Scope

When enabled, module:

  • imports stylix (skipped when function argument importExternals = false),
  • enables Stylix with dark polarity, and
  • selects the Tokyo Night dark Base16 scheme from the tinted-schemes input.

Operational Notes / Assumptions

  • Intended for graphical (non-headless) hosts; enabled by default there.
  • Theme source comes from the tinted-schemes input.

Virtualisation — libvirt, VFIO passthrough, bridge networking, and guest isolation helpers

Purpose

Enable libvirt/QEMU virtualisation with VFIO GPU passthrough, Looking Glass shared memory, bridge networking, custom OVMF firmware metadata, and per-guest isolation helpers that reserve host CPUs, detach GPUs, and block host sleep while guests run.

Entry Point

Architecture / Services / Scope

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 AMD IOMMU, disableEFIfb, and the configured GPU devices,
  • configures Looking Glass shared memory file owned by the libvirt group,
  • adds virt-manager, virtiofsd, virtio-win, and win-spice to system packages,
  • sets LIBVIRT_DEFAULT_URI to qemu:///system,
  • creates bridge networking with DHCP on bridgeInterface and externalInterface enslaved into the bridge,
  • adds kvmfr kernel module package and modprobe config,
  • installs udev rule for /dev/kvmfr access, and
  • 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 backed by the shared virtualisation-tools package 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. Guest-specific CPU ranges are passed through tiny generated wrappers, while shared hook logic stays in the packaged helpers.

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

Operational Notes / Assumptions

  • 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 — Windows Subsystem for Linux integration and fixes

Purpose

Make the NixOS host behave correctly inside WSL: default user, Windows interop, graphics library paths, nix-ld, and Start Menu launcher syncing.

Entry Point

Architecture / Services / Scope

Base layer (always applied when enabled):

  • allows passwordless login,
  • installs wslu,
  • enables nix-ld with a C toolchain library for VS Code Remote WSL compatibility,
  • sets session variables for WSL graphics and library paths,
  • enables hardware.graphics with the configured graphics packages and libvdpau-va-gl, and
  • when NVIDIA graphics are present, appends CUDA and NVIDIA library paths.

If the wsl module exists in the option tree, module additionally:

  • enables WSL with core.wsl.user as default 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 the Windows Start Menu.

Operational Notes / Assumptions

  • core.wsl.user is required when WSL integration is enabled.
  • Extra binaries dirname, readlink, and uname are exposed for VS Code Remote WSL compatibility.
  • Behavior is conditional on the 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 coordinator nodes for discrete roles (IO, monitoring, database, storage, identity) and providing helper functions for inter-server communication and attribute collection.

Entry Point

  • Main file: modules/nixos/server/default.nix

Options

server.dashboard.displayData

TypeJSON value
Default{ }

Display data for the section in the dashboard.


server.dashboard.icon

Typenull or string
Defaultnull

Icon for the section in the dashboard.


server.dashboard.items

Typeattribute set of (submodule)

Additional configuration for items managed by the IO Hosts dashy instance. This will be merged with the automatically generated configuration that is nested in a section with the name of the machine.


server.dashboard.items.<name>.icon

Typestring

Icon for the item.


server.dashboard.items.<name>.title

Typestring

Title of the item.


server.dashboard.items.<name>.url

Typestring

URL for the item.


server.dashboard.name

Typestring
Defaultlet withoutPrefix = removePrefix "nix" config.host.name; nixPrefixed = builtins.stringLength withoutPrefix < builtins.stringLength config.host.name; in if nixPrefixed then "Nix${lib.mine.strings.capitalise withoutPrefix}" else lib.capitalize config.host.name;

Name of the section in the dashboard.


server.database.dependentServices

Typelist of string
Default[ ]

List of systemd service names that depend on io databases. These services will be automatically bound to the io-databases.target and will stop/start when databases become unavailable/available.


server.database.host

Typestring
Defaultif isThisIOPrimaryHost then "localhost" else config.server.ioPrimaryHost

The hostname or IP address to use when connecting to managed databases.

This is “localhost” when running on the host, and when connecting from other hosts.


server.database.postgres

Typeattribute set of (submodule)
Default{ }

This option has no description.


server.database.postgres.<name>.database

Typestring
Default"‹name›"

This option has no description.


server.database.postgres.<name>.host

Typestring
Defaultconfig.server.database.host

This option has no description.


server.database.postgres.<name>.password

Typesubmodule
Default{ }

This option has no description.


server.database.postgres.<name>.password.group

Typenull or string
Defaultnull

This option has no description.


server.database.postgres.<name>.password.owner

Typenull or string
Defaultnull

This option has no description.


server.database.postgres.<name>.password.path

Typeabsolute path
Defaultconfig.sops.secrets."POSTGRES/${ toUpper config.server.database.postgres.${name}.database |> builtins.replaceStrings [ "-" ] [ "_" ] }_PASSWORD".path;

This option has no description.


server.database.postgres.<name>.port

Typesigned integer
Defaultconfig.server.database.postgres.‹name›.port

This option has no description.


server.database.postgres.<name>.user

Typestring
Default"‹name›"

This option has no description.


server.database.redis

Typeattribute set of (submodule)
Default{ }

This option has no description.


server.database.redis.<name>.database_id

Typesigned integer
DefaultstaticDbIdMappings.‹name› or (-1)

This option has no description.


server.database.redis.<name>.host

Typestring
Defaultconfig.server.database.host

This option has no description.


server.database.redis.<name>.port

Typesigned integer
Default(getIOPrimaryHostAttr "services.redis.servers")."".port

This option has no description.


server.database.redis.<name>.prefix

Typestring
Default"‹name›"

This option has no description.


server.distributedBuilds.builderUser

Typestring
Default"builder"

The user to use when connecting to remote build daemons.


server.distributedBuilds.builders

Typelist of string
Default[ ]

A list of hostnames of remote build daemons to connect to for distributed builds.


server.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable enable the server module.


server.fail2ban.enable

Typeboolean
DefaultisThisIOPrimaryHost && config.services.caddy.enable
Exampletrue

Whether to enable fail2ban intrusion detection.


server.fail2ban.exporterPort

Type16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default9191

Port for the fail2ban Prometheus exporter.


server.ioPrimaryHost

Typenull or string
Defaultnull

Which host is the primary coordinator for IO in the cluster.

This host will run the primary instances of databases, Operate the reverse proxy for handling incoming traffic, and will run the MinIO distributed storage cluster’s master node.


server.monitoring.collector.alerting.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable Alertmanager and alert rules.


server.monitoring.collector.alerting.homeAssistant.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Home Assistant webhook alerting.


server.monitoring.collector.alerting.nextcloudTalk.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Nextcloud Talk webhook alerting.


server.monitoring.collector.enable

Typeboolean
DefaultthisIsMonitoringPrimaryHost && cfg.enable
Exampletrue

Whether to enable monitoring collector services (Prometheus, Loki, Grafana).


server.monitoring.collector.grafana.kanidm.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable Kanidm OAuth2 authentication for Grafana.


server.monitoring.collector.otlp.bearerTokenSecret

Typestring
Default"MONITORING/OLTP/BEARER_TOKEN"

SOPS secret path used as the bearer token for OTLP/HTTP ingestion.


server.monitoring.collector.otlp.enable

Typeboolean
DefaultisThisMonitoringPrimaryHost && cfg.enable
Exampletrue

Whether to enable OTLP/HTTP ingestion via Grafana Alloy.


server.monitoring.collector.otlp.port

Typesigned integer
Default4318

Port for the OTLP/HTTP ingestion endpoint.


server.monitoring.collector.otlp.subdomain

Typestring
Default"otlp"

Subdomain used for the OTLP/HTTP ingestion endpoint.


server.monitoring.collector.proxmox.enable

Typeboolean
DefaultisThisMonitoringPrimaryHost && cfg.enable
Exampletrue

Whether to enable Proxmox VE metrics collection.


server.monitoring.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable monitoring for this server.


server.monitoring.exporters.caddy.enable

Typeboolean
Defaultcfg.enable && config.services.caddy.enable
Exampletrue

Whether to enable Caddy metrics exporter.


server.monitoring.exporters.fail2ban.enable

Typeboolean
Defaultcfg.enable && isThisIOPrimaryHost && config.server.fail2ban.enable
Exampletrue

Whether to enable fail2ban metrics exporter.


server.monitoring.exporters.node.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable node_exporter for system-level metrics.


server.monitoring.exporters.postgres.enable

Typeboolean
Defaultcfg.enable && thisIsIOPrimaryHost && hasPostgresDatabases
Exampletrue

Whether to enable PostgreSQL exporter.


server.monitoring.exporters.process.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable Process exporter for monitoring specific processes.


server.monitoring.exporters.redis.enable

Typeboolean
Defaultcfg.enable && thisIsIOPrimaryHost && hasRedisInstances
Exampletrue

Whether to enable Redis exporter.


server.monitoring.logs.enable

Typeboolean
Defaultcfg.enable
Exampletrue

Whether to enable Alloy log shipping.


server.monitoring.logs.extraConfiguration

Typestrings concatenated with "\n"
Default""

Additional configuration for the alloy log processor. This is useful for adding custom Loki stages, relabeling rules, or write targets.

Note that the default configuration for processing the system journal is always included and does not need to be specified here.


server.monitoring.retention.logs

Typestring
Default"90d"

Loki log retention period.


server.monitoring.retention.metrics

Typestring
Default"90d"

Prometheus TSDB retention period.


server.monitoring.scrapeConfigs

Typeattribute set of (submodule)
Default{ }

Declarative scrape configs for services running on this host. These are collected by the monitoring primary host and converted into Prometheus scrape configurations.


server.monitoring.scrapeConfigs.<name>.bearer_token_secret

Typenull or string
Defaultnull

SOPS secret path for bearer token authentication. When set, the secret will be created on the monitoring primary host.


server.monitoring.scrapeConfigs.<name>.host

Typestring
Defaultconfig.host.name

Host to scrape metrics from.


server.monitoring.scrapeConfigs.<name>.job_name

Typestring
Default"‹name›"

Prometheus job name for this scrape target.


server.monitoring.scrapeConfigs.<name>.metrics_path

Typestring
Default"/metrics"

HTTP path to the metrics endpoint.


server.monitoring.scrapeConfigs.<name>.port

Typesigned integer

Port the metrics endpoint listens on.


server.monitoring.scrapeConfigs.<name>.scheme

Typeone of "http", "https"
Default"http"

URL scheme for scraping.


server.monitoringPrimaryHost

Typenull or string
Defaultnull

Which host is the primary collector for monitoring in the cluster.

This host will run Prometheus, Loki, Grafana, and Alertmanager for centralized observability of the entire server cluster.


server.network.openPortsForSubnet.tcp

Typelist of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default[ ]

List of TCP ports to open on the firewall for each subnet.


server.network.openPortsForSubnet.udp

Typelist of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default[ ]

List of UDP ports to open on the firewall for each subnet.


server.network.subnets

Typelist of (submodule)
Default{ }

This option has no description.


server.network.subnets.*.dns

Typestring

DNS server for the subnet.


server.network.subnets.*.domain

Typestring

Domain name for the subnet.


server.network.subnets.*.ipv4

Typesubmodule
Default{ }

IPv4 configuration for the subnet.


server.network.subnets.*.ipv4.arpa

Typenull or string
Defaultnull

ARPA notation for reverse DNS lookups.


server.network.subnets.*.ipv4.cidr

Typenull or string
Defaultnull

CIDR notation for the IP range.


server.network.subnets.*.ipv6

Typesubmodule
Default{ }

IPv6 configuration for the subnet.


server.network.subnets.*.ipv6.arpa

Typenull or string
Defaultnull

ARPA notation for reverse DNS lookups.


server.network.subnets.*.ipv6.cidr

Typenull or string
Defaultnull

CIDR notation for the IP range.


server.proxy.domain

Typestring

The base domain for all virtual hosts.


server.proxy.extensions

Typeattribute set of (submodule)
Default{ }

Registry of proxy extensions. Each extension provides config functions that are injected into vhost Caddy blocks, sorted by priority.


server.proxy.extensions.<name>.config

Typefunction that evaluates to a(n) function that evaluates to a(n) function that evaluates to a(n) string

Function: vhostName -> vhostAttrSet -> hostConfig -> string. Returns Caddy directives to inject, or ‘’ for no-op. The vhostAttrSet includes the resolved extraConfig (already localhost-replaced) as _resolvedExtraConfig.


server.proxy.extensions.<name>.consumesExtraConfig

Typeboolean
Defaultfalse

Whether this extension embeds extraConfig inside its output. When true, config.nix skips the post-extension extraConfig append for this vhost.


server.proxy.extensions.<name>.enable

Typeboolean
Defaultfalse

Whether this extension is globally enabled. Each extension SHOULD auto-detect whether it has work to do and set this to true via mkDefault in its module config. User can explicitly override to force-disable (higher merge priority than mkDefault).


server.proxy.extensions.<name>.globalConfig

Typefunction that evaluates to a(n) string
Default<function>

Function: hostConfig -> string. Returns Caddy directives to inject into the top-level globalConfig block. Only called on the IO primary host. Sorted by priority across extensions.


server.proxy.extensions.<name>.priority

Typesigned integer
Default100

Lower values = earlier in Caddy config. Priority ranges: 0-49 reserved, 50-99 auth, 100-199 general, 200+ post-processing.


server.proxy.extensions.<name>.vhostModule

Typenull or module
Defaultnull

Optional module to inject into each vhost submodule. Use options.<extensionName> (relative to vhost scope) to declare per-vhost options.


server.proxy.kanidmContexts

Typeattribute set of (submodule)
Default{ }

Shared Kanidm OAuth2 context configurations.


server.proxy.kanidmContexts.<name>.allowGroups

Typelist of string
Default[ ]
Example[ "idm_all_persons@auth.racci.dev" "admins@auth.racci.dev" ]

Default list of Kanidm groups allowed to access virtualHosts using this context.


server.proxy.kanidmContexts.<name>.authDomain

Typenull or string
Defaultnull
Example"auth.example.com"

The domain where Kanidm is hosted. Defaults to auth.<server.proxy.domain> if not specified.


server.proxy.kanidmContexts.<name>.scopes

Typelist of string
Default[ "openid" "email" "profile" "groups" ]

OAuth scopes to request from Kanidm.


server.proxy.kanidmContexts.<name>.tokenLifetime

Typesigned integer
Default3600

Token lifetime in seconds for the authentication portal.


server.proxy.virtualHosts

Typeattribute set of (submodule)
Default{ }

Virtual hosts to be handled by the IO server and forwarded to the respective backend.


server.proxy.virtualHosts.<name>.aliases

Typelist of string
Default[ ]

A list of virtual host names that should be routed using this configuration. Options added here will inherit the base domain specified in <server.proxy.domain>.


server.proxy.virtualHosts.<name>.baseUrl

Typestring
Default${subdomain}.${getIOPrimaryHostAttr "server.proxy.domain"}

The base url including the configured base domain name.


server.proxy.virtualHosts.<name>.extensions

Typenull or (list of string)
Defaultnull

List of extension names to enable for this virtual host. When null (default), all globally enabled extensions apply. When set to a list, only those named extensions apply. Set to [] to disable all extensions for this vhost.


server.proxy.virtualHosts.<name>.extraConfig

Typestring
Default""

Configuration to be placed in the caddy virtualHost extraConfig.


server.proxy.virtualHosts.<name>.kanidm

Typenull or (submodule)
Defaultnull

Enable Kanidm OAuth2 authentication for this virtual host.


server.proxy.virtualHosts.<name>.kanidm.allowGroups

Typelist of string
Default[ ]
Example[ "idm_all_persons@auth.racci.dev" "admins@auth.racci.dev" ]

Default list of Kanidm groups allowed to access virtualHosts using this context.


server.proxy.virtualHosts.<name>.kanidm.authDomain

Typenull or string
Defaultnull
Example"auth.example.com"

The domain where Kanidm is hosted. Defaults to auth.<server.proxy.domain> if not specified.


server.proxy.virtualHosts.<name>.kanidm.bypassPaths

Typelist of string
Default[ ]
Example[ "/health" "/api/webhooks/*" ]

List of path patterns that should bypass authentication.


server.proxy.virtualHosts.<name>.kanidm.context

Typestring
Default"‹name›"

The OAuth context name for this virtual host.


server.proxy.virtualHosts.<name>.kanidm.scopes

Typelist of string
Default[ "openid" "email" "profile" "groups" ]

OAuth scopes to request from Kanidm.


server.proxy.virtualHosts.<name>.kanidm.tokenLifetime

Typesigned integer
Default3600

Token lifetime in seconds for the authentication portal.


server.proxy.virtualHosts.<name>.l4

Typenull or (submodule)
Defaultnull

This option has no description.


server.proxy.virtualHosts.<name>.l4.config

Typestring
Default""

Configuration for the L4 plugin.


server.proxy.virtualHosts.<name>.l4.listenPort

Type16 bit unsigned integer; between 0 and 65535 (both inclusive)

Port to listen on for L4 traffic.


server.proxy.virtualHosts.<name>.l4.protocol

Typeone of "tcp", "udp"
Default"tcp"

Protocol for L4 listener.


server.proxy.virtualHosts.<name>.listenPorts

Typenon-empty (list of 16 bit unsigned integer; between 0 and 65535 (both inclusive))
Default[ 443 ]

Port(s) to listen on for incoming traffic for this virtual host. If multiple ports are specified, the virtual host will be accessible on all of them.


server.proxy.virtualHosts.<name>.ports

Typelist of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default[ ]

Ports to be opened from the host for IO Hosts to forward traffic to.


server.proxy.virtualHosts.<name>.public

Typeboolean
Defaultfalse

When enabled this service will be accessible to the public via Cloudflared Tunnels.


server.proxy.virtualHosts.<name>.requireApiKey

Typenull or (submodule)
Defaultnull

This option has no description.


server.proxy.virtualHosts.<name>.requireApiKey.bypassPaths

Typelist of string
Default[ ]
Example[ "/health" "/api/webhooks/*" ]

List of path patterns that bypass API key authentication.


server.proxy.virtualHosts.<name>.requireApiKey.enable

Typeboolean
Defaultfalse

Enable API key authentication for this virtual host.


server.proxy.virtualHosts.<name>.useAcmeCerts

Typeboolean
Defaulttrue

Whether to generate and use ACME certificates for this virtual host. If false, you must provide your own TLS configuration in extraConfig via the caddy tls directive.


server.sshShell.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable Auto-enter a session-only devShell for root on interactive SSH logins..


server.sshShell.shellFile

Typeabsolute path
Default/nix/store/jq8636fkq2anq8f33kfqa816d0nrqw4m-source/modules/nixos/server/ssh-shell/shell.nix

Path to a single-file that defines a session-only environment. This file is evaluated by nix-shell and should import to use the system registry.


server.storage.swfsMount

Typeattribute set of (submodule)
Default{ }

Declarative storage mounts backed by MinIO or SeaweedFS.

Each entry creates a systemd-managed FUSE mount service plus an optional health-check timer.


server.storage.swfsMount.<name>.backend

Typeone of "minio", "seaweedfs"

The storage backend to mount.


server.storage.swfsMount.<name>.gid

Typenull or signed integer
Defaultnull

Group ID that should own the mounted path.


server.storage.swfsMount.<name>.healthCheck.enable

Typeboolean
Defaulttrue

Whether to monitor this mount and attempt automated recovery.


server.storage.swfsMount.<name>.healthCheck.interval

Typestring
Default"15min"

Systemd timer interval between mount health probes.


server.storage.swfsMount.<name>.healthCheck.reloadServices

Typelist of string
Default[ ]

Additional systemd services to reload after recovering this mount


server.storage.swfsMount.<name>.healthCheck.restartServices

Typelist of string
Default[ ]

Additional systemd services to restart after recovering this mount.


server.storage.swfsMount.<name>.healthCheck.timeout

Typestring
Default"30s"

Timeout applied to the mount health probe.


server.storage.swfsMount.<name>.minio.bucketName

Typestring
Default"‹name›"

The MinIO bucket to mount with s3fs.


server.storage.swfsMount.<name>.minio.credentialsFile

Typenull or string
Defaultnull
Example"/run/secrets/s3fs-credentials"

Path to the MinIO credentials file in ACCESS_KEY_ID:SECRET_ACCESS_KEY format.

When left null, the module provisions and uses the S3FS_AUTH/<NAME_IN_UPPERCASE> sops secret.


server.storage.swfsMount.<name>.minio.endpoint

Typestring
Default"https://minio.racci.dev"

The S3-compatible MinIO endpoint used by s3fs.


server.storage.swfsMount.<name>.minio.extraOptions

Typelist of string
Default[ ]

Additional -o options passed to s3fs.


server.storage.swfsMount.<name>.mountLocation

Typestring
Default"/mnt/storage/${name}"

Path where the backend should be mounted.


server.storage.swfsMount.<name>.requiredByServices

Typelist of string
Default[ ]

Systemd services that must wait for this mount before starting.


server.storage.swfsMount.<name>.seaweedfs.allowOthers

Typeboolean
Defaulttrue

Whether to allow non-owning users to access the SeaweedFS mount.


server.storage.swfsMount.<name>.seaweedfs.dirAutoCreate

Typeboolean
Defaulttrue

Whether weed mount should create the mount directory when needed.


server.storage.swfsMount.<name>.seaweedfs.extraArgs

Typelist of string
Default[ ]

Additional arguments passed directly to weed mount.


server.storage.swfsMount.<name>.seaweedfs.filer

Typestring
Default""

SeaweedFS filer address in host:port form.


server.storage.swfsMount.<name>.seaweedfs.filerPath

Typestring
Default"/"

Remote filer path to expose through the mount.


server.storage.swfsMount.<name>.seaweedfs.gidMap

Typenull or string
Defaultnull

Optional local-to-filer GID mapping string for weed mount.


server.storage.swfsMount.<name>.seaweedfs.metadataFlushSeconds

Typesigned integer
Default120

How often weed mount flushes metadata to the filer.


server.storage.swfsMount.<name>.seaweedfs.readOnly

Typeboolean
Defaultfalse

Whether the SeaweedFS mount should be read-only.


server.storage.swfsMount.<name>.seaweedfs.uidMap

Typenull or string
Defaultnull

Optional local-to-filer UID mapping string for weed mount.


server.storage.swfsMount.<name>.seaweedfs.writeBufferSizeMB

Typenull or signed integer
Defaultnull

Optional write buffer cap passed to weed mount in megabytes.


server.storage.swfsMount.<name>.uid

Typenull or signed integer
Defaultnull

User ID that should own the mounted path.


server.storage.swfsMount.<name>.umask

Typesigned integer
Default22

Umask applied to files and directories inside the mount.


Architecture / Services / Scope

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 7-day retention period, 256MB total max disk usage, and 512MB keep-free threshold. Per-file size is set to 32MB (1/8 of max use) to allow proper log rotation with ~7 archived files. All limits are defined as let variables in the module for consistency between the daemon config and the activation vacuum script. The activation script runs journalctl --vacuum on every deploy to immediately enforce the limits on existing logs.
  • Pre-Switch Checks: Runs dix on system activation to report changes between generations.
  • server.ioPrimaryHost: Specifies the hostname of the IO Coordinator. This host operates the reverse proxy for handling incoming traffic and manages IO-level coordination. This option is typically set on the coordinator host and used by other servers in the cluster for synchronization.
  • server.monitoringPrimaryHost: Specifies the hostname of the Monitoring Coordinator. This host runs Prometheus, Loki, Grafana, and Alertmanager for centralized observability across the cluster.
  • server.databasePrimaryHost: Specifies the hostname of the Database Coordinator. This host runs primary database instances for centralized data persistence across the cluster.
  • server.storagePrimaryHost: Specifies the hostname of the Storage Coordinator. This host runs primary file and block storage services for centralized data serving across the cluster.
  • server.authPrimaryHost: Specifies the hostname of the Identity Coordinator. This host runs primary authentication and authorization services for centralized identity management across the cluster.

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 / Assumptions

Generic Primary-Host Helpers

Generic helpers parameterized by any primary-host option value. These are used by submodules to check role assignment and fetch remote configuration:

  • isPrimaryHost primaryHost value: Returns true if value matches primaryHost. Accepts either a raw hostname string or an attrset with a host.name attribute.
  • isThisPrimaryHost primaryHost: Shorthand for isPrimaryHost primaryHost config — checks if the current host is the primary for a given role.
  • getPrimaryHostConfig primaryHost: Returns the NixOS configuration of the host designated as the primary for a given role. On the primary host itself this returns config locally; on other hosts it fetches the remote configuration via self.nixosConfigurations.
  • getPrimaryHostAttr primaryHost attrPath: Retrieves a specific attribute (expressed as a dot-separated path) from the primary host’s configuration.
  • getOthersWhereExcept primaryHost func: Returns a list of server hostnames (excluding the given primary host) where func returns true. Useful for discovering non-primary nodes that match certain criteria.

Backward-Compatible IO Helpers

The existing IO-specific helpers (isIOPrimaryHost, isThisIOPrimaryHost, primaryIOHostConfig, getIOPrimaryHostAttr, getOthersWhere) remain available and now delegate to the generic helpers. They behave identically but are hard-wired to server.ioPrimaryHost. New submodules should prefer the generic helpers for role-agnostic code.

Server Attribute Collection

  • 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 IO Coordinator is a critical component of the cluster, as many services (like Dashy or shared ingress) rely on it as the central point of coordination.

References

Server Dashboard — Cluster-Wide Service Dashboard

Purpose

The dashboard module integrates with Dashy and collects dashboard sections from all servers in the cluster to display on the IO Coordinator (server.ioPrimaryHost). This allows each server to define its own dashboard items, which are then automatically collected and displayed on a single unified dashboard.

Entry Point

  • Main file: modules/nixos/server/dashboard.nix

Options

server.dashboard.displayData

TypeJSON value
Default{ }

Display data for the section in the dashboard.


server.dashboard.icon

Typenull or string
Defaultnull

Icon for the section in the dashboard.


server.dashboard.items

Typeattribute set of (submodule)

Additional configuration for items managed by the IO Hosts dashy instance. This will be merged with the automatically generated configuration that is nested in a section with the name of the machine.


server.dashboard.items.<name>.icon

Typestring

Icon for the item.


server.dashboard.items.<name>.title

Typestring

Title of the item.


server.dashboard.items.<name>.url

Typestring

URL for the item.


server.dashboard.name

Typestring
Defaultlet withoutPrefix = removePrefix "nix" config.host.name; nixPrefixed = builtins.stringLength withoutPrefix < builtins.stringLength config.host.name; in if nixPrefixed then "Nix${lib.mine.strings.capitalise withoutPrefix}" else lib.capitalize config.host.name;

Name of the section in the dashboard.


Architecture / Services / Scope

  • This module uses getAllAttrsFunc to gather server.dashboard configurations from all servers in the cluster.
  • The aggregated configuration is only applied to the IO Coordinator (server.ioPrimaryHost), which runs the primary Dashy instance.

Operational Notes / Assumptions

  • Each server defines its own server.dashboard section (name, icon, and items) in its host configuration; the items are aggregated cluster-wide.

References

Server Network — Centralized Subnets and Firewall

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

  • Main file: modules/nixos/server/network.nix

Options

server.network.openPortsForSubnet.tcp

Typelist of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default[ ]

List of TCP ports to open on the firewall for each subnet.


server.network.openPortsForSubnet.udp

Typelist of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default[ ]

List of UDP ports to open on the firewall for each subnet.


server.network.subnets

Typelist of (submodule)
Default{ }

This option has no description.


server.network.subnets.*.dns

Typestring

DNS server for the subnet.


server.network.subnets.*.domain

Typestring

Domain name for the subnet.


server.network.subnets.*.ipv4

Typesubmodule
Default{ }

IPv4 configuration for the subnet.


server.network.subnets.*.ipv4.arpa

Typenull or string
Defaultnull

ARPA notation for reverse DNS lookups.


server.network.subnets.*.ipv4.cidr

Typenull or string
Defaultnull

CIDR notation for the IP range.


server.network.subnets.*.ipv6

Typesubmodule
Default{ }

IPv6 configuration for the subnet.


server.network.subnets.*.ipv6.arpa

Typenull or string
Defaultnull

ARPA notation for reverse DNS lookups.


server.network.subnets.*.ipv6.cidr

Typenull or string
Defaultnull

CIDR notation for the IP range.


Architecture / Services / Scope

  • This module uses getIOPrimaryHostAttr to fetch the server.network.subnets configuration from the IO Coordinator (server.ioPrimaryHost), ensuring all servers in the cluster are aware of the network structure defined there.
  • 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.

Operational Notes / Assumptions

  • Subnets and per-subnet open ports are declared on the IO Coordinator via server.network.subnets and server.network.openPortsForSubnet.

References

Server Distributed Builds — Remote Nix Build Distribution

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 across the server cluster.

Entry Point

  • Main file: modules/nixos/server/distributed-builds.nix

Options

server.distributedBuilds.builderUser

Typestring
Default"builder"

The user to use when connecting to remote build daemons.


server.distributedBuilds.builders

Typelist of string
Default[ ]

A list of hostnames of remote build daemons to connect to for distributed builds.


Architecture / Services / Scope

  • 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, sets up the build machines using nix.buildMachines, and points outbound SSH authentication at core.openssh.hostPrivateKeyPath.
  • 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.

Operational Notes / Assumptions

  • A host declares the build server via server.distributedBuilder.builders in its host configuration.

References

Server Database — Managed PostgreSQL and Redis

Purpose

The database submodule provides a managed interface for PostgreSQL and Redis across the server infrastructure. It centralizes database configuration on the Database Coordinator (config.server.databasePrimaryHost) while allowing client services to declaratively request databases. It 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, and automated password handling via SOPS secrets.

Entry Point

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

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

Options

server.database.dependentServices

Typelist of string
Default[ ]

List of systemd service names that depend on io databases. These services will be automatically bound to the io-databases.target and will stop/start when databases become unavailable/available.


server.database.host

Typestring
Defaultif isThisIOPrimaryHost then "localhost" else config.server.ioPrimaryHost

The hostname or IP address to use when connecting to managed databases.

This is “localhost” when running on the host, and when connecting from other hosts.


server.database.postgres

Typeattribute set of (submodule)
Default{ }

This option has no description.


server.database.postgres.<name>.database

Typestring
Default"‹name›"

This option has no description.


server.database.postgres.<name>.host

Typestring
Defaultconfig.server.database.host

This option has no description.


server.database.postgres.<name>.password

Typesubmodule
Default{ }

This option has no description.


server.database.postgres.<name>.password.group

Typenull or string
Defaultnull

This option has no description.


server.database.postgres.<name>.password.owner

Typenull or string
Defaultnull

This option has no description.


server.database.postgres.<name>.password.path

Typeabsolute path
Defaultconfig.sops.secrets."POSTGRES/${ toUpper config.server.database.postgres.${name}.database |> builtins.replaceStrings [ "-" ] [ "_" ] }_PASSWORD".path;

This option has no description.


server.database.postgres.<name>.port

Typesigned integer
Defaultconfig.server.database.postgres.‹name›.port

This option has no description.


server.database.postgres.<name>.user

Typestring
Default"‹name›"

This option has no description.


server.database.redis

Typeattribute set of (submodule)
Default{ }

This option has no description.


server.database.redis.<name>.database_id

Typesigned integer
DefaultstaticDbIdMappings.‹name› or (-1)

This option has no description.


server.database.redis.<name>.host

Typestring
Defaultconfig.server.database.host

This option has no description.


server.database.redis.<name>.port

Typesigned integer
Default(getIOPrimaryHostAttr "services.redis.servers")."".port

This option has no description.


server.database.redis.<name>.prefix

Typestring
Default"‹name›"

This option has no description.


server.database.postgres

Typeattribute set of (submodule)
Default{ }

This option has no description.


server.database.postgres.<name>.database

Typestring
Default"‹name›"

This option has no description.


server.database.postgres.<name>.host

Typestring
Defaultconfig.server.database.host

This option has no description.


server.database.postgres.<name>.password

Typesubmodule
Default{ }

This option has no description.


server.database.postgres.<name>.password.group

Typenull or string
Defaultnull

This option has no description.


server.database.postgres.<name>.password.owner

Typenull or string
Defaultnull

This option has no description.


server.database.postgres.<name>.password.path

Typeabsolute path
Defaultconfig.sops.secrets."POSTGRES/${ toUpper config.server.database.postgres.${name}.database |> builtins.replaceStrings [ "-" ] [ "_" ] }_PASSWORD".path;

This option has no description.


server.database.postgres.<name>.port

Typesigned integer
Defaultconfig.server.database.postgres.‹name›.port

This option has no description.


server.database.postgres.<name>.user

Typestring
Default"‹name›"

This option has no description.


server.database.redis

Typeattribute set of (submodule)
Default{ }

This option has no description.


server.database.redis.<name>.database_id

Typesigned integer
DefaultstaticDbIdMappings.‹name› or (-1)

This option has no description.


server.database.redis.<name>.host

Typestring
Defaultconfig.server.database.host

This option has no description.


server.database.redis.<name>.port

Typesigned integer
Default(getIOPrimaryHostAttr "services.redis.servers")."".port

This option has no description.


server.database.redis.<name>.prefix

Typestring
Default"‹name›"

This option has no description.


Architecture / Services / Scope

Connection Management

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

PostgreSQL Management

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

  • Automatic Provisioning: The Database Coordinator 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 Database Coordinator 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 Database Coordinator 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 Database Coordinator to update the mapping file when adding new Redis clients.

IO Guardian Coordination

Lifecycle management is handled by the IO Guardian. A pre-shared key for guardian communication is managed via the DB_GUARDIAN_PSK SOPS secret.

  • 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. Reachability and readiness checks are handled by the shared wait-for-io-tools package (wait-for-io and wait-for-io-databases).
  • On Database Coordinator: The io-database-coordinator service manages the drain and undrain signals sent to clients during system startup and shutdown.

Secrets

  • POSTGRES/<DB_NAME_UPPER>_PASSWORD: Per-database role password, provisioned during postgresql-setup.
  • REDIS/PASSWORD: Shared password for the primary Redis instance.
  • DB_GUARDIAN_PSK: Pre-shared key for IO Guardian communication.

Operational Notes / Assumptions

  • The host designated as the Database Coordinator (config.server.databasePrimaryHost) is responsible for running the actual database engines. It aggregates all database requirements from every host in the flake and applies them locally.
  • Client services request databases by declaring entries in server.database.postgres or server.database.redis; the submodule wires up connection info, secrets, and lifecycle binding.

References

Server Storage — Mount Abstractions and Storage Services

Purpose

The storage module manages persistent storage abstractions for the server fleet. Today that includes the server.storage.swfsMount mount abstraction and an evaluation-only SeaweedFS deployment. This area provides:

  • declarative MinIO-backed and SeaweedFS-backed mounts through server.storage.swfsMount
  • a SeaweedFS evaluation deployment on the storage primary host

Entry Point

  • Main file: modules/nixos/server/storage/default.nix
  • Supporting file: modules/nixos/server/storage/bucket.nix
  • Supporting file: modules/nixos/server/storage/seaweedfs.nix

Architecture / Services / Scope

swfsMount

The swfsMount option is the repository’s declarative storage mount interface. It is a breaking rename from server.storage.bucketMounts and each entry chooses a backend explicitly.

  • Backend Selection: Set backend = "minio" to mount a MinIO bucket through s3fs, or backend = "seaweedfs" to mount a SeaweedFS filer path through weed mount.
  • Use Scope: Use swfsMount for bucket/object-style workloads or external filer mounts. Do not point it at app state that expects normal local filesystem semantics, frequent metadata updates, or permission changes.
  • Common Mount Controls: Each entry supports mountLocation, uid, gid, umask, and requiredByServices so consuming services can wait for the generated mount unit.
  • Health Recovery: Each entry also supports healthCheck.* options. By default the module uses the shared packaged swfs-mount-hook helper for prepare/stop/health actions, including lazy unmount of stale FUSE mounts, mount-service restart, and optional restart or reload of dependent services.

MinIO backend

  • Credential Management: By default the MinIO backend provisions and uses sops secrets with the pattern S3FS_AUTH/<NAME_IN_UPPERCASE>. These secrets must contain ACCESS_KEY_ID:SECRET_ACCESS_KEY.
  • Runtime Model: MinIO mounts now run as generated systemd services instead of fileSystems entries so they can share the same recovery model as SeaweedFS.

SeaweedFS backend

  • Mount Command: SeaweedFS mounts use weed mount directly against a filer endpoint and filer path.
  • Runtime Inputs: Configure the SeaweedFS backend through seaweedfs.filer, seaweedfs.filerPath, and optional runtime flags such as UID/GID mapping or write-buffer limits.

SeaweedFS Evaluation

SeaweedFS is documented separately because it is not part of the current software filesystem workflow. The repository uses it as an evaluation deployment gated by server.storagePrimaryHost — SeaweedFS services and their Caddy proxy rules only activate on the host designated as the storage primary. The swfsMount mount module itself remains role-agnostic; host placement is determined by which host runs the services.

MinIO services also live on the storage primary host. The MinIO endpoint presented to mounts is served through the IO Coordinator reverse proxy, which routes to the MinIO instance on whichever host currently holds the storage primary role.

See SeaweedFS Evaluation for details on scope, host gating, proxy behavior, and security material.

Secrets

  • S3FS_AUTH/<NAME_IN_UPPERCASE>: MinIO backend credentials, containing ACCESS_KEY_ID:SECRET_ACCESS_KEY.

Operational Notes / Assumptions

  • FUSE Access: The module enables programs.fuse.userAllowOther = true whenever mounts are defined so both s3fs and weed mount can expose shared FUSE mounts safely.
  • Network Dependency: Generated mount services depend on network-online.target before attempting either backend.
  • Recovery Behavior: The health-check timer uses mountpoint plus a bounded stat probe. On failure it lazily unmounts the path, restarts the generated mount service, and can restart configured dependent services.
  • SeaweedFS Scope: The SeaweedFS evaluation deployment remains separate from this abstraction. The new SeaweedFS backend only reuses weed mount for workload mounts and does not replace the evaluation stack.

References

SeaweedFS Evaluation — Evaluation-Only Storage Service

Purpose

SeaweedFS is deployed here as an evaluation-only storage service alongside MinIO. It exists to validate SeaweedFS as a possible replacement candidate without changing existing MinIO-backed workloads or the repository’s migration posture. The evaluation deployment provides an all-in-one SeaweedFS stack on the storage primary host so the repository can test endpoint shape, proxy integration, and service behavior in a realistic environment while keeping the current MinIO setup intact.

Entry Point

  • Main file: modules/nixos/server/storage/seaweedfs.nix
  • Supporting file: modules/nixos/server/storage/default.nix

Architecture / Services / Scope

  • Evaluation only: this deployment does not replace MinIO and does not perform any migration.
  • Storage primary only: the module is gated by config.server.storagePrimaryHost == config.networking.hostName.
  • All-in-one topology: the evaluation enables the SeaweedFS master, volume, filer, S3 endpoint, admin UI, and worker components on the coordinator host.
  • Proxy surface: endpoints are exposed through the existing server.proxy.virtualHosts integration instead of host-local Caddy configuration. Current surface includes the master, filer, S3-compatible, volume, and admin endpoints under the seaweedfs.<domain> subtree. Client-facing TLS terminates at Caddy; gRPC-backed component endpoints additionally get the backend transport settings required for SeaweedFS communication.
  • Option surface: the module uses the upstream services.seaweedfs option surface rather than introducing a repository-local server.storage.seaweedfs.* option tree.

Secrets

SeaweedFS SOPS entries are separate from MinIO secrets and are used for:

  • mTLS between Caddy and SeaweedFS components
  • JWT-based inter-component authentication inside SeaweedFS

These entries live under the SEAWEEDFS secret tree on the storage primary host and include both JWT material and TLS certificates/keys for the SeaweedFS component set.

Operational Notes / Assumptions

  • The evaluation deployment is separate from server.storage.swfsMount. The storage abstraction can use weed mount for SeaweedFS-backed workload mounts without changing the evaluation topology described here.
  • This deployment is intended to shake out integration details first; repository-local abstractions can be added later if SeaweedFS proves to be a good fit.

References

Server Proxy — Unified Caddy Reverse Proxy

Purpose

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, static API key authentication, and public exposure through Cloudflared tunnels. It abstracts the complexity of reverse proxying by allowing services to define their proxy requirements within their own module configuration, automatically coordinating between backend hosts and the primary IO host to ensure ports are open and traffic is correctly routed.

Entry Point

  • Main file: modules/nixos/server/proxy/default.nix
  • Supporting file: modules/nixos/server/proxy/options.nix
  • Supporting file: modules/nixos/server/proxy/config.nix
  • Supporting file: modules/nixos/server/proxy/kanidm.nix
  • Supporting file: modules/nixos/server/proxy/extensions.nix
  • Supporting file: modules/nixos/server/proxy/extensions/l4.nix

Options

server.proxy.domain

Typestring

The base domain for all virtual hosts.


server.proxy.extensions

Typeattribute set of (submodule)
Default{ }

Registry of proxy extensions. Each extension provides config functions that are injected into vhost Caddy blocks, sorted by priority.


server.proxy.extensions.<name>.config

Typefunction that evaluates to a(n) function that evaluates to a(n) function that evaluates to a(n) string

Function: vhostName -> vhostAttrSet -> hostConfig -> string. Returns Caddy directives to inject, or ‘’ for no-op. The vhostAttrSet includes the resolved extraConfig (already localhost-replaced) as _resolvedExtraConfig.


server.proxy.extensions.<name>.consumesExtraConfig

Typeboolean
Defaultfalse

Whether this extension embeds extraConfig inside its output. When true, config.nix skips the post-extension extraConfig append for this vhost.


server.proxy.extensions.<name>.enable

Typeboolean
Defaultfalse

Whether this extension is globally enabled. Each extension SHOULD auto-detect whether it has work to do and set this to true via mkDefault in its module config. User can explicitly override to force-disable (higher merge priority than mkDefault).


server.proxy.extensions.<name>.globalConfig

Typefunction that evaluates to a(n) string
Default<function>

Function: hostConfig -> string. Returns Caddy directives to inject into the top-level globalConfig block. Only called on the IO primary host. Sorted by priority across extensions.


server.proxy.extensions.<name>.priority

Typesigned integer
Default100

Lower values = earlier in Caddy config. Priority ranges: 0-49 reserved, 50-99 auth, 100-199 general, 200+ post-processing.


server.proxy.extensions.<name>.vhostModule

Typenull or module
Defaultnull

Optional module to inject into each vhost submodule. Use options.<extensionName> (relative to vhost scope) to declare per-vhost options.


server.proxy.kanidmContexts

Typeattribute set of (submodule)
Default{ }

Shared Kanidm OAuth2 context configurations.


server.proxy.kanidmContexts.<name>.allowGroups

Typelist of string
Default[ ]
Example[ "idm_all_persons@auth.racci.dev" "admins@auth.racci.dev" ]

Default list of Kanidm groups allowed to access virtualHosts using this context.


server.proxy.kanidmContexts.<name>.authDomain

Typenull or string
Defaultnull
Example"auth.example.com"

The domain where Kanidm is hosted. Defaults to auth.<server.proxy.domain> if not specified.


server.proxy.kanidmContexts.<name>.scopes

Typelist of string
Default[ "openid" "email" "profile" "groups" ]

OAuth scopes to request from Kanidm.


server.proxy.kanidmContexts.<name>.tokenLifetime

Typesigned integer
Default3600

Token lifetime in seconds for the authentication portal.


server.proxy.virtualHosts

Typeattribute set of (submodule)
Default{ }

Virtual hosts to be handled by the IO server and forwarded to the respective backend.


server.proxy.virtualHosts.<name>.aliases

Typelist of string
Default[ ]

A list of virtual host names that should be routed using this configuration. Options added here will inherit the base domain specified in <server.proxy.domain>.


server.proxy.virtualHosts.<name>.baseUrl

Typestring
Default${subdomain}.${getIOPrimaryHostAttr "server.proxy.domain"}

The base url including the configured base domain name.


server.proxy.virtualHosts.<name>.extensions

Typenull or (list of string)
Defaultnull

List of extension names to enable for this virtual host. When null (default), all globally enabled extensions apply. When set to a list, only those named extensions apply. Set to [] to disable all extensions for this vhost.


server.proxy.virtualHosts.<name>.extraConfig

Typestring
Default""

Configuration to be placed in the caddy virtualHost extraConfig.


server.proxy.virtualHosts.<name>.kanidm

Typenull or (submodule)
Defaultnull

Enable Kanidm OAuth2 authentication for this virtual host.


server.proxy.virtualHosts.<name>.kanidm.allowGroups

Typelist of string
Default[ ]
Example[ "idm_all_persons@auth.racci.dev" "admins@auth.racci.dev" ]

Default list of Kanidm groups allowed to access virtualHosts using this context.


server.proxy.virtualHosts.<name>.kanidm.authDomain

Typenull or string
Defaultnull
Example"auth.example.com"

The domain where Kanidm is hosted. Defaults to auth.<server.proxy.domain> if not specified.


server.proxy.virtualHosts.<name>.kanidm.bypassPaths

Typelist of string
Default[ ]
Example[ "/health" "/api/webhooks/*" ]

List of path patterns that should bypass authentication.


server.proxy.virtualHosts.<name>.kanidm.context

Typestring
Default"‹name›"

The OAuth context name for this virtual host.


server.proxy.virtualHosts.<name>.kanidm.scopes

Typelist of string
Default[ "openid" "email" "profile" "groups" ]

OAuth scopes to request from Kanidm.


server.proxy.virtualHosts.<name>.kanidm.tokenLifetime

Typesigned integer
Default3600

Token lifetime in seconds for the authentication portal.


server.proxy.virtualHosts.<name>.l4

Typenull or (submodule)
Defaultnull

This option has no description.


server.proxy.virtualHosts.<name>.l4.config

Typestring
Default""

Configuration for the L4 plugin.


server.proxy.virtualHosts.<name>.l4.listenPort

Type16 bit unsigned integer; between 0 and 65535 (both inclusive)

Port to listen on for L4 traffic.


server.proxy.virtualHosts.<name>.l4.protocol

Typeone of "tcp", "udp"
Default"tcp"

Protocol for L4 listener.


server.proxy.virtualHosts.<name>.listenPorts

Typenon-empty (list of 16 bit unsigned integer; between 0 and 65535 (both inclusive))
Default[ 443 ]

Port(s) to listen on for incoming traffic for this virtual host. If multiple ports are specified, the virtual host will be accessible on all of them.


server.proxy.virtualHosts.<name>.ports

Typelist of 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default[ ]

Ports to be opened from the host for IO Hosts to forward traffic to.


server.proxy.virtualHosts.<name>.public

Typeboolean
Defaultfalse

When enabled this service will be accessible to the public via Cloudflared Tunnels.


server.proxy.virtualHosts.<name>.requireApiKey

Typenull or (submodule)
Defaultnull

This option has no description.


server.proxy.virtualHosts.<name>.requireApiKey.bypassPaths

Typelist of string
Default[ ]
Example[ "/health" "/api/webhooks/*" ]

List of path patterns that bypass API key authentication.


server.proxy.virtualHosts.<name>.requireApiKey.enable

Typeboolean
Defaultfalse

Enable API key authentication for this virtual host.


server.proxy.virtualHosts.<name>.useAcmeCerts

Typeboolean
Defaulttrue

Whether to generate and use ACME certificates for this virtual host. If false, you must provide your own TLS configuration in extraConfig via the caddy tls directive.


Architecture / Services / Scope

File Layout

  • default.nix — Logic and helpers: resolving OAuth contexts and mapping local addresses to backend hostnames.
  • options.nix — Option definitions for virtual hosts and shared contexts.
  • config.nix — Caddy integration: generation of services.caddy.virtualHosts and ACME certificate requests. L4 (TCP/UDP) forwarding is handled by the l4 extension, not by config.nix.
  • kanidm.nix — Authentication security: generates the Caddy security block, including identity providers, portals, and authorization policies.
  • extensions.nix — System integration: connects the proxy to the dashboard, Cloudflared tunnels, and automates Kanidm client provisioning.

Extension System

The proxy module supports a registry-based extension system. Extensions are self-contained modules that inject Caddy directives into virtual host configurations — without modifying proxy internals.

Extension Registry

Extensions register themselves via server.proxy.extensions.<name>, an attribute set of submodules. Each extension has:

FieldTypeDefaultDescription
priorityint100Lower values = earlier Caddy config placement. Ranges: 0-49 reserved, 50-99 auth, 100-199 general, 200+ post-processing
enableboolfalseGlobally enabled. Set via mkDefault based on detected config
consumesExtraConfigboolfalseWhen true, the extension embeds vh._resolvedExtraConfig in its output. config.nix skips appending raw extraConfig
configvhostName -> vhostAttrSet -> hostConfig -> strrequiredPer-vhost Caddy directive generator
globalConfighostConfig -> str_ → ""Top-level Caddy globalConfig directives
vhostModulenullOr deferredModulenullPer-vhost option declarations

Per-Vhost Extension Selection

Each vhost has server.proxy.virtualHosts.<name>.extensions (default null = all enabled extensions). Set to a list of extension names for selective enablement, or [] to disable all extensions.

Config Function Signature

config :: vhostName -> vhostAttrSet -> hostConfig -> string

Arguments:

  • vhostName (str): The vhost’s attribute name (e.g., "grafana").
  • vhostAttrSet: The full vhost attribute set, including _resolvedExtraConfig (user’s extraConfig with replaceLocalHost applied) and _name.
  • hostConfig: Full host-level NixOS config.

GlobalConfig Function Signature

globalConfig :: hostConfig -> string

Called once per enabled extension on the IO primary host. Output concatenated into services.caddy.globalConfig, sorted by extension priority.

Auto-Enable Pattern

Extensions auto-detect whether they have work to do using mkDefault; users can force-disable with explicit enable = false.

Priority Ordering

Extensions sort by priority ascending. Equal priorities break alphabetically by extension name. Extensions with lower priority numbers generate config earlier.

Authoring a New Extension

  1. Create file: modules/nixos/server/proxy/extensions/<name>.nix
  2. Import in proxy/default.nix.
  3. Set server.proxy.extensions.<name> with priority, config function, etc.
  4. Declare per-vhost options via options.server.proxy.virtualHosts with attrsOf (submodule ...).
  5. Use proxyLib for helpers: replaceLocalHost, resolveKanidmContext, hasAnyKanidm.

API Key Auth Extension

The api-key-auth extension provides static API key authentication for virtual hosts. When enabled, requests must include a valid Req-API-Key header matching a securely generated secret. Bypass paths are supported per vhost. Mutual exclusivity with Kanidm on the same vhost is enforced by the existing consumesExtraConfig assertion.

Migrated Extensions

ExtensionPriorityPurpose
l410L4 TCP/UDP forwarding (layer4 Caddy block + firewall ports)
kanidm50Kanidm OAuth2 authentication per vhost
api-key-auth50Static API key authentication per vhost (with bypass paths)
dashboard200Auto-generate dashboard items
cloudflared200Cloudflared tunnel ingress

Secrets

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.

API Key Auth

Secrets are auto-generated via sops at PROXY_AUTH/<VHOST_NAME>_API_KEY, injected via systemd LoadCredential.

Operational Notes / Assumptions

  • 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.
  • Layer 4 Forwarding: L4 forwarding uses the caddy.layer4 plugin for non-HTTP traffic like database connections or SSH. Managed by the l4 extension (modules/nixos/server/proxy/extensions/l4.nix), which auto-enables when any vhost has l4 != null.

References

Server SSH — Root Login Development Shell

Purpose

The SSH submodule enhances administrative access by providing a session-only environment tailored for server management. It automatically transitions interactive root SSH sessions into a dedicated development shell, ensuring consistent tooling and a powerful shell experience across server environments, and removes the need for manual setup of common tools and aliases.

Entry Point

  • Main file: modules/nixos/server/ssh-shell/default.nix
  • Supporting file: modules/nixos/server/ssh-shell/shell.nix

Options

server.sshShell.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable Auto-enter a session-only devShell for root on interactive SSH logins..


server.sshShell.shellFile

Typeabsolute path
Default/nix/store/jq8636fkq2anq8f33kfqa816d0nrqw4m-source/modules/nixos/server/ssh-shell/shell.nix

Path to a single-file that defines a session-only environment. This file is evaluated by nix-shell and should import to use the system registry.


Architecture / Services / Scope

The module consists of two parts:

Auto-entry Logic (ssh-shell/default.nix)

  • Creates an indirect GC root for the SSH shell at login time by instantiating the shell expression to a derivation, then realizing it with nix-store --add-root --indirect --realise. This keeps the realized shell alive across upgrades without referencing config.system.build.toplevel during system evaluation.
  • 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.
  • 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/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.

Operational Notes / Assumptions

Opt-Out Behavior

Set the NIX_SKIP_SHELL environment variable on your local machine before connecting to log in as root without entering the specialized shell:

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. It runs nix-shell --add-root --indirect to build and enter the environment in a single call (pinning a GC root under /nix/var/nix/gcroots/per-user/root/ssh-shell-result), which triggers the shellHook and exec’s Fish. If that fails, the system falls back to the default shell, clears the guard, and prints a message to stderr.

References

Identity

Image

The image.nix module provides image and VM-related configuration for server hosts.

SSH Private Key Activation Script

On first boot of a freshly installed host, the SSH private key must be provisioned. The public key is baked into the image at /etc/ssh/ssh_host_ed25519_key.pub, but the private key is deliberately not packaged. Instead, the activation script prompts the operator interactively to input the private key, validates it, and stores it at core.openssh.hostPrivateKeyPath, where OpenSSH and sops age decryption both read the same canonical file. Before reading input, the script saves the current /dev/console tty state, switches the console into canonical line mode for reliable multiline paste handling, and restores the original tty state on exit.

The script is enabled unconditionally on all server hosts as a no-op unless both of the following hold:

  • /dev/console is available for I/O (it is under build-vm -nographic, where QEMU wires it to the host terminal)
  • A canonical host key does not already exist at core.openssh.hostPrivateKeyPath

Input handling and validation

The prompt ignores any lines before -----BEGIN OPENSSH PRIVATE KEY-----, then captures the key line-by-line until -----END OPENSSH PRIVATE KEY-----. If the final END line does not submit automatically, pressing Ctrl+D flushes that last line. Pressing Ctrl+D before the BEGIN line restarts the prompt with No key provided, and pressing Ctrl+D after BEGIN but before END discards the partial key and restarts the prompt.

The captured key is checked in two stages:

  1. The key file is parsed and validated as a valid Ed25519 private key.
  2. The derived public key must match the baked-in public key the image was built with.

On failure the key file is removed and the prompt repeats.

VM Variant Overrides

All servers import the proxmox-lxc module which inherently breaks nixos-rebuild build-vm because the Proxmox LXC image variant sets boot.isContainer = true and therefore disables the initrd. We override this behavior for build-vm so that a runnable QEMU VM can be built from such hosts with the following options:

  • boot.isContainer = false: restores the initrd and normal VM boot.
  • boot.loader.initScript.enable = false: avoids a unique-option conflict on system.build.installBootLoader between the grub and init-script loaders.
  • virtualisation.qemu.consoles = [ "tty0" "ttyS0,115200n8" ]: routes boot logs and /dev/console to the serial port so they are visible with -nographic (the last console= becomes /dev/console).
  • systemd.services."serial-getty@ttyS0".enable = true: enables root autologin on the serial console so the VM is usable with -nographic.

Building and running a VM

nixos-rebuild build-vm --flake .#<hostname>
./result/bin/run-<hostname>-vm -nographic

Building a Proxmox LXC image

nixos-rebuild build-image --image-variant proxmox-lxc --flake .#<hostname>

Flake Allocations — Cross-Host Cluster Configuration

Purpose

The flake allocations module declares cluster-wide configuration options at the flake level. Rather than configuring each NixOS system independently, allocations let you declare concerns like which machines have accelerators, which server acts as the IO Coordinator, and which servers act as distributed builders — in a single place — and then propagate those values into every host configuration.

Entry Point

Architecture / Services / Scope

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/) — propagate 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 a NixOS configuration is built, 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.

Allocation Options

allocations.accelerators

Typeattribute set of list of (one of "cuda", "rocm")
Default{ }
Example{ cudaAndRocmHost = [ "cuda" "rocm" ]; nothing = [ ]; onlyRocm = [ "rocm" ]; }

Define hardware accelerators allocated to a machine by hostname.

The attribute names are hostnames, and the values are lists of accelerator types assigned to that host.


allocations.hostTypes

Typeattribute set of list of string
Default{ desktop = [ "nixmi" ]; server = [ "nixai" "nixarr" "nixcloud" "nixdev" "nixio" "nixmon" "nixserv" ]; }
Example{ desktop = [ "workstation1" ]; server = [ "nixbuild1" "nixbuild2" ]; }

An Attribute set defining hostnames by their device type. The attribute names are device types, and the values are lists of hostnames assigned to that device type.


allocations.server.distributedBuilders

Typelist of (one of "nixai", "nixarr", "nixcloud", "nixdev", "nixio", "nixmon", "nixserv")
Default[ ]
Example[ "nixbuild1" "nixbuild2" ]

List of servers that will act as remote builders for server-side distributed builds.


allocations.server.ioPrimaryCoordinator

Typeone of "nixai", "nixarr", "nixcloud", "nixdev", "nixio", "nixmon", "nixserv"

Designate a server to act as the Primary I/O coordinator


allocations.server.monitoringPrimaryHost

Typeone of "nixai", "nixarr", "nixcloud", "nixdev", "nixio", "nixmon", "nixserv"

Designate a server to act as the primary monitoring collector.

This host will run Prometheus, Loki, Grafana, and Alertmanager for centralized observability of the entire server cluster.


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. Hosts not listed default to no accelerators. The builder 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 the host-discovery function, which scans the hosts/ directory structure.

Server primary-host allocations

These options each designate a specific server as the coordinator for a cluster role (IO Coordinator, Monitoring Coordinator, Database Coordinator, Storage Coordinator, Identity Coordinator). The type of each is constrained to an enum of server hostnames, automatically derived from hostTypes.server. Values flow through apply/system.nix into the corresponding server.* option on each server configuration:

  • allocations.server.ioPrimaryCoordinator → server.ioPrimaryHost (IO Coordinator)
  • allocations.server.monitoringPrimaryHost → server.monitoringPrimaryHost (Monitoring Coordinator)
  • allocations.server.databasePrimaryHost → server.databasePrimaryHost (Database Coordinator)
  • allocations.server.storagePrimaryHost → server.storagePrimaryHost (Storage Coordinator)
  • allocations.server.authPrimaryHost → server.authPrimaryHost (Identity Coordinator)

allocations.server.distributedBuilders

List of servers that act as remote builders for distributed builds. Flows into server.distributedBuilds.builders on each server configuration.

Apply Modules

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

apply/system.nix is imported during system construction. It receives allocations and deviceType via specialArgs and maps the server allocations onto the corresponding server.* options. It uses optionalAttrs to only apply server-specific options when deviceType == "server", preventing errors on non-server systems.

apply/home-manager.nix is imported by the Home-Manager builder. It is currently a no-op — a placeholder for future home-manager-level allocations.

References

AI Modules — Overview

Purpose

The modules/nixos/ai/ tree is the canonical home for AI infrastructure services in this NixOS configuration. It provides first-class NixOS modules for AI-related daemons and services that are independent of any specific agent container.

Entry Point

  • Main file: modules/nixos/ai/default.nix

Architecture / Services / Scope

What belongs in ai/ vs services/

LocationPurposeExamples
modules/nixos/ai/AI infrastructure daemonsMnemosyne sync server, future: LLM gateways, embedding servers
modules/nixos/services/Monolithic service containersAI Agent (Hermes), future: agent orchestration

The ai/ tree manages standalone services that an AI agent might consume, while services/ manages the agent container itself.

Operational Notes / Assumptions

The ai/ module tree is loaded on all device types by mkSystem.

References

  • Mnemosyne — Sync server, optional MCP server, and sync client orchestration for the Mnemosyne SQLite-based memory provider

Mnemosyne — SQLite-Backed Memory Provider

Purpose

SQLite-backed memory provider with sync and optional MCP server. Part of the ai/ module tree.

Entry Point

Architecture / Services / Scope

graph TB
    subgraph "NixAI Host"
        HC["Hermes Container<br/>(mnemosyne-hermes plugin)"]
        SS["Sync Server<br/>(mnemosyne sync-serve)"]
        MS["MCP Server<br/>(mnemosyne mcp)"]
        CD["Caddy Proxy"]
        CT["systemd Timer<br/>(sync client)"]
    end

    subgraph "External"
        EXT["External MCP Clients<br/>(Cursor, Claude Code)"]
        REMOTE["Remote Mnemosyne<br/>(laptop, other host)"]
    end

    HC -->|"plugin reads/writes"| DB[(mnemosyne.db<br/>in container)]
    CT -->|"mnemosyne sync --remote"| SS
    SS -->|"serve"| SDB[(mnemosyne.db<br/>/var/lib/mnemosyne)]
    MS -->|"mcp"| SDB
    CD -->|"reverse_proxy"| SS
    CD -->|"reverse_proxy"| MS
    EXT -->|"MCP/SSE"| CD
    REMOTE -->|"sync protocol"| CD

The module can run three kinds of services, each either natively on the host or inside a Docker container:

  • Sync server (mnemosyne sync-serve) — stdlib HTTP, no extra Python dependencies.
  • MCP server (mnemosyne mcp) — adds the mcp and anyio dependencies via the package’s optional mcp group.
  • Sync client — per-profile periodic sync to a remote server, driven by a systemd timer (default interval 10 minutes).

Options

services.mnemosyne.client.sync

Typeattribute set of (submodule)
Default{ }

Sync client profiles for periodic sync to remote servers.


services.mnemosyne.client.sync.<name>.apiKeyFile

Typenull or absolute path
Defaultnull

Runtime path to a file containing the API key for authentication.


services.mnemosyne.client.sync.<name>.container

Typenull or string
Defaultnull

Docker container to run the sync client inside. If null, the server runs natively on the host.

Additionally, this will only work if the /nix/store is mounted inside the container.


services.mnemosyne.client.sync.<name>.interval

Typestring
Default"*:0/10"

Systemd OnCalendar interval for sync. Default runs every 10 minutes.


services.mnemosyne.client.sync.<name>.remote

Typestring

Sync server URL (e.g. http://sync.example.com).


services.mnemosyne.client.sync.<name>.user

Typenull or string
Defaultnull

User to run the sync client as inside the container.


services.mnemosyne.dataDir

Typestring
Default"/var/lib/mnemosyne"

Data directory for Mnemosyne state.


services.mnemosyne.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Mnemosyne memory service.


services.mnemosyne.server.mcp.container

Typenull or string
Defaultnull

Docker container to run the mcp server inside. If null, the server runs natively on the host.

Additionally, this will only work if the /nix/store is mounted inside the container.


services.mnemosyne.server.mcp.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Mnemosyne mcp server.


services.mnemosyne.server.mcp.host

Typestring
Default"127.0.0.1"

Host address for the mcp server to listen on.


services.mnemosyne.server.mcp.port

Type16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default8766

Port for the mcp server to listen on.


services.mnemosyne.server.mcp.user

Typenull or string
Defaultnull

User to run the mcp server as inside the container.


services.mnemosyne.server.sync.apiKeyFile

Typenull or absolute path
Defaultnull

Runtime path to a file containing the API key for authentication.


services.mnemosyne.server.sync.container

Typenull or string
Defaultnull

Docker container to run the sync server inside. If null, the server runs natively on the host.

Additionally, this will only work if the /nix/store is mounted inside the container.


services.mnemosyne.server.sync.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Mnemosyne sync server.


services.mnemosyne.server.sync.host

Typestring
Default"127.0.0.1"

Host address for the sync server to listen on.


services.mnemosyne.server.sync.port

Type16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default8765

Port for the sync server to listen on.


services.mnemosyne.server.sync.user

Typenull or string
Defaultnull

User to run the sync server as inside the container.


Secrets

  • MNEMOSYNE_SYNC_KEY — API key for sync server authentication (host-level sops secret), provided via apiKeyFile and loaded into the services through systemd credentials.

Operational Notes / Assumptions

  • Sync protocol is plain HTTP with delta-based bidirectional sync.
  • Sync interval default is 10 minutes.

Usage Examples

Server-only (central sync)

{
  services.mnemosyne = {
    enable = true;
    server.sync.enable = true;
  };
}

Client-only (sync to remote)

{
  services.mnemosyne = {
    enable = true;
    client.sync.hermes = {
      enable = true;
      remote = "http://sync.example.com:8765";
      interval = "*:0/15";
    };
  };
}

References

DIY & Making — 3D Printing and Maker Tooling

Purpose

The purpose.diy Home-Manager modules provide tooling and configuration for hardware tinkering, 3D printing, and related maker activities.

Entry Point

Architecture / Services / Scope

Printing

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

Options

purpose.diy.printing.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable 3D printing support.


purpose.diy.printing.gitSync.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Auto-commit OrcaSlicer settings changes to a local git repository.


purpose.diy.printing.gitSync.remoteUrl

Typenull or string
Defaultnull

Optional remote URL to push commits to. If set, the git sync service will attempt to push commits to this remote after creating them.

The remote must be configured with appropriate credentials (e.g. via SSH keys) for non-interactive authentication.


purpose.diy.printing.gitSync.repoPath

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

Absolute path to the directory that will be tracked 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.

Defaults to the standard OrcaSlicer per-user profile directory so that filament, process, and machine profiles are all captured without any additional configuration.


Git Sync

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

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 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 a short debounce period to absorb rapid bursts of writes (e.g. when OrcaSlicer rewrites multiple files at once).
  4. All pending changes are staged and committed once per batch. The commit message is derived from the first changed path in that batch, using the same profile-aware naming convention documented above.
  5. If the watched directory does not yet exist (e.g. OrcaSlicer has never been run), the service polls 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 / Assumptions

  • The git repository is initialised with git init and an initial commit the first time the service starts if no .git directory exists.
  • The service is set to restart on failure 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.

AI Editors & Assistants — AI-Assisted Development Tooling

Purpose

The purpose.development.editors.ai Home-Manager module configures editor and agent tooling for AI-assisted development, centered around OpenCode and shared skill directories.

Entry Point

Architecture / Services / Scope

When enabled, the module:

  • Ensures a shared AI filesystem directory exists at activation time.
  • Adds useful global git ignores (e.g. editor workspace and tool-state directories).
  • Configures Zed to expose an OpenCode agent server.
  • Enables and configures programs.opencode with:
    • plugins
    • Nix formatter integration
    • LSP integrations across Nix, config formats, and general-purpose languages
    • command permissions policy
    • a local MCP server (e.g. mcp-nixos via uvx)
  • Writes OpenCode config files (e.g. ~/.config/opencode/oh-my-opencode.json, opencode-notifier.json).
  • Registers AI skills under ~/.agents/skills/<name> via home.file.
  • Persists OpenCode state directories so they survive reboots on impermanence-based systems.

Options

purpose.development.editors.ai.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable AI Tools & Assistants.


purpose.development.editors.ai.includeDefaults

Typeboolean
Defaulttrue

Whether to include the default set of agents and skills provided by this module. This includes the agents and skills defined in the ./agents and ./skills directories of this module.

Disabling this will result in a minimal setup with only the base configuration for OpenCode and no pre-registered agents or skills.


purpose.development.editors.ai.skills

Typelist of string
Default[ ]
Example'' [ ''${inputs.my-skill-repo}/skills/my-skill ''${self}/skills/another-skill ] ''

List of additional AI skills to add to the global registry. These should be paths to a skill directory, this could be through a flake input or a path in the flake.

These skills will be installed to ~/.agents/skills and will be available to all agents that support the skill system, such as Claude and OpenCode.


purpose.development.editors.ai.tabby.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Tabby agent for tab completion.


Usage Example

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

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

Operational Notes / Assumptions

  • 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.

Hyprland Helpers — Typed Nix API for the Home-Manager Hyprland Module

Purpose

The core.hyprland modules extend the upstream Home-Manager wayland.windowManager.hyprland module with a typed Nix API for window rules, permissions, slide-in popups, input defaults, and Lua config generation. They target the HM-native Lua configuration format (configType = "lua"), which is the repository default.

Entry Point

The module structure is:

default.nix           # Top-level importer
├── permission.nix    # custom-settings.permission
├── slideIn.nix       # custom-settings.slideIn
├── input.nix         # settings.config defaults (cursor, binds, input, misc)
├── workspaces.nix    # custom-settings.workspaces
├── lua.nix           # custom-settings.lua (Lua config generation)
│   └── lua/          # Lua source files, with @placeholder@ substitution
│       └── opt/      # Conditionally loaded Lua modules
└── types.nix         # Shared type definitions

Options

wayland.windowManager.hyprland.custom-settings.lua.applicationBinds

Typeattribute set of string
Default{ }

Application binds to generate in Lua config.


wayland.windowManager.hyprland.custom-settings.lua.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Pure Lua configuration files for Hyprland, with a hint of nix substitution magic..


wayland.windowManager.hyprland.custom-settings.lua.luaExtras

Typelist of absolute path
Default[ ]

Extra Lua files to copy to the config directory. These files will be copied to the config directory but not required in init.lua, so you can use them as libraries or for other purposes.

Files defined here will respect parent directories and will be copied to the same relative path in the config directory. The absolute root of the directory tree will be calculated by finding the closest lua ancestor directory, and copying the entire tree from that root to the config directory.

If a directory is specified, it will be recursively copied to the config directory, preserving the directory structure.


wayland.windowManager.hyprland.custom-settings.lua.luaModules

Typelist of absolute path
Default[ ]

Lua modules to load in the main init.lua file. Each module is a path to a Lua file, which will be copied to the config directory and required in init.lua. Each module will have variables substituted according to the “variables” option, so you can use that to inject paths to nix packages or other dynamic values.


wayland.windowManager.hyprland.custom-settings.lua.variables

Typeattribute set of (null or string)
Default{ }

Variables to substitute in Lua files. Each key “foo” replaces @foo@ in source files with the value.


wayland.windowManager.hyprland.custom-settings.permission.plugin

Typelist of (package or string)
Default[ ]

List of plugins that are allowed to run.


wayland.windowManager.hyprland.custom-settings.permission.screenCopy

Typelist of (package or string)
Default[ ]

List of applications allowed to copy the screen.


wayland.windowManager.hyprland.custom-settings.slideIn

Typelist of (submodule)
Default[ ]

List of slide-in popups that slide in from the edge of the screen.


wayland.windowManager.hyprland.custom-settings.slideIn.*.bind

Typestring

Key binding to trigger the slide-in popup.

This is passed through to the lua config without checking for validity, the lua config will throw an error if the binding is invalid.


wayland.windowManager.hyprland.custom-settings.slideIn.*.class

Typestring

Window class for the slide-in popup.


wayland.windowManager.hyprland.custom-settings.slideIn.*.exec

Typestring

Command to execute for the slide-in popup.


wayland.windowManager.hyprland.custom-settings.slideIn.*.extProp

Typeattribute set of anything
Default{ }

Extra properties to pass to the scratchpad configuration in pyprland.


wayland.windowManager.hyprland.custom-settings.slideIn.*.position

Typeone of "left", "right", "top", "bottom"
Default"top"

Direction from which the popup slides in.


wayland.windowManager.hyprland.custom-settings.slideIn.*.size

Typesubmodule
Default{ }

Size of the slide-in popup. Can specify ‘width’ and/or ‘height’.


wayland.windowManager.hyprland.custom-settings.slideIn.*.size.height

Typenull or string
Defaultnull

Height of the slide-in popup. Can be specified as a percentage (e.g., ‘33%’) or in pixels (e.g., ‘300px’).


wayland.windowManager.hyprland.custom-settings.slideIn.*.size.width

Typenull or string
Defaultnull

Width of the slide-in popup. Can be specified as a percentage (e.g., ‘20%’) or in pixels (e.g., ‘400px’).


wayland.windowManager.hyprland.custom-settings.workspaces.definitions

Typeattribute set of (submodule)
Default{ }

Workspace definitions by ID


wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.extraRules

Typeattribute set of anything
Default{ }

Additional workspace rule properties to pass to workspace_rule().


wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.monitor

Typenull or string
Defaultnull

Monitor to assign workspace on startup.


wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.name

Typenull or string
Defaultnull

Workspace default name.


wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.startup

Typelist of (string or attribute set of string)
Default[ ]

Commands to run when workspace is first created empty.


wayland.windowManager.hyprland.custom-settings.workspaces.enable

Typeboolean
Defaultfalse

Enable workspace configuration module.


Architecture / Services / Scope

input.nix

Sets sensible default values under settings.config for cursor behavior, input device settings, keyboard binds, and misc Hyprland options.

Default config covers:

  • cursor: warp behavior, hardware cursors, inactivity timeout, hide-on-key-press
  • binds: workspace back-and-forth, allow workspace cycles, focus method
  • input: keyboard layout, follow-mouse, touchpad, sensitivity, accel profile
  • misc: DPMS on key/mouse events

permission.nix

Defines custom-settings.permission for screen copy and plugin permission grants:

custom-settings.permission = {
  screenCopy = [ pkgs.firefox pkgs.obs ];
  plugin = [ pkgs.hyprlandPlugins.hy3 ];
};

slideIn.nix

Add the option custom-settings.slideIn to define a list of edge-sliding popup windows. Each entry configures a keybind, executable, window class, position, and optional window rules. Uses Pyprland scratchpads for dropdown-style window management.

Architecture:

  • Nix module: generates ~/.config/pypr/config.toml with scratchpad definitions, registers systemd.user.services.pyprland.
  • Lua module: registers hl.bind(...) calls that toggle the scratchpads.

lua.nix

Defines custom-settings.lua — the Lua config generation subsystem:

  • enable (boolean) — Enable pure Lua configuration files with Nix substitution support.
  • variables (attrs of nullOr str) — Key-value pairs for @placeholder@ substitution in Lua source files. Each key foo replaces @foo@ only in Lua modules that reference that placeholder. Modules that don’t reference a given placeholder are unaffected, so bundled modules can use disjoint placeholder sets. A placeholder referenced by a module but absent from variables is an error. Some variables are pre-populated automatically (see applicationBinds below). Common injected values include paths to playerctl, wpctl, zenity, hyprshutdown, and uwsm-app.
  • luaModules (list of paths) — Lua source files to copy into the Hyprland config directory and require from init.lua. Each file undergoes @placeholder@ substitution using the variables attrset. Defaults to the bundled lua/binds.lua.
  • applicationBinds (attrs of str) — Application keybinds passed into Lua generation. Each attr key is a bind string and each attr value is a command string. Rendered into @applicationBinds@ as Lua table entries consumed by binds.lua. Generated Lua iterates over those table entries and creates hl.bind(..., hl.dsp.exec_cmd(...)) calls for each bind/command pair.

Lua bind pattern

In lua/binds.lua, binds use the inline Lua expression pattern via settings.bind with attrsToLuaInlineArgs. The generated Lua calls hl.bind(...) with first-class dispatcher functions:

hl.bind("SUPER + Q", hl.dsp.window.kill())
hl.bind("SUPER + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" }))
hl.bind("ALT + R", hl.dsp.submap("resize"))
hl.define_submap("resize", function()
  hl.bind("ESCAPE", hl.dsp.submap("reset"))
  -- ...
end)

This pattern keeps bind and submap definitions inline in Lua. Submaps are defined via hl.define_submap(name, fn) alongside related hl.bind(...) calls.

workspaces.nix

Workspace configuration module at modules/home-manager/core/hyprland/workspaces.nix. Handles workspace naming, monitor assignments, and startup applications with startup-only monitor assignment behavior:

  • Startup-only assignment: Monitor assignments run once via hl.on("hyprland.start") Lua event hook. Users can move workspaces freely after startup without interference.
  • Persistent naming: Workspace names and startup commands persist via hl.workspace_rule(), ensuring defaults are restored if a workspace is recreated.
  • Conditional Lua module: The lua/opt/workspaces.lua module is automatically added to the Lua config only when custom-settings.workspaces.enable = true. If disabled, no workspace Lua code is loaded.
  • Configuration source: Workspace data (name, monitor, startup commands) is defined in user configs via the typed custom-settings.workspaces.definitions option and passed to Lua as @workspaceConfig@ variable.

Enable and configure workspaces in user home config:

wayland.windowManager.hyprland.custom-settings.workspaces = {
  enable = true;
  definitions = {
    "1" = {
      name = "Terminal";
      monitor = "DP-6";  # startup-only; workspace can be moved after launch
      startup = [ (lib.getExe pkgs.alacritty) ];  # runs when workspace first created
    };
    "2" = {
      name = "Browser";
      monitor = "DP-1";
      startup = [ (lib.getExe config.programs.firefox.package) ];
    };
    # ...
  };
};

The module generates:

  • Lua substitution variable @workspaceConfig@ with workspace definitions as JSON
  • Conditionally loads lua/opt/workspaces.lua to register startup hooks and workspace rules

lua/opt/workspaces.lua

Lua module at modules/home-manager/core/hyprland/lua/opt/workspaces.lua that registers workspace configuration. Only loaded when custom-settings.workspaces.enable = true. Receives workspace data via @workspaceConfig@ placeholder substitution and:

  • Registers hl.on("hyprland.start") hook to assign workspaces to monitors on startup only
  • Calls hl.workspace_rule() for each workspace to set persistent names and startup commands
PlaceholderSourceDescription
@workspaceConfig@custom-settings.workspaces.definitionsWorkspace config table as JSON

lua/binds.lua

The default Lua bind template at modules/home-manager/core/hyprland/lua/binds.lua. Uses @placeholder@ substitution for dynamic injection. Substitution is per-file — only placeholders actually present in this template are replaced; other Lua modules are unaffected by binds.lua’s placeholder set.

PlaceholderSourceDescription
@applicationBinds@custom-settings.lua.applicationBindsAuto-generated Lua table of app keybinds
@playerctl@Auto-injectedPath to playerctl binary
@wpctl@Auto-injectedPath to wpctl binary
@zenity@Auto-injectedPath to zenity binary
@hyprshutdown@Auto-injectedPath to hyprshutdown binary
@uwsmApp@Auto-injectedPath to uwsm-app helper
@DEFAULT_AUDIO_SINK@custom-settings.lua.variablesAudio sink name
@DEFAULT_AUDIO_SOURCE@custom-settings.lua.variablesAudio source name

Add custom placeholders by extending custom-settings.lua.variables.

noctalia.nix

Integrates the Noctalia desktop shell as a Hyprland companion. Requires the noctalia flake input.

The module:

  • Enables programs.noctalia and systemd, pins package from the noctalia flake input’s packages, and applies Hyprland support for Noctalia windows.
  • Mirrors a full exported Noctalia config as a typed Nix attrset (noctaliaSettings), covering bar layouts with monitor overrides, shell panel/screen corners/screenshot/session actions, theme, wallpaper, calendar, control-center shortcuts, desktop/lockscreen widgets, notification layer, plugin settings, widget config, brightness, and more.
  • Does not declare top-level colors or plugins HM options, and does not manage raw JSON files directly.
  • Persists ~/.local/share/noctalia via user.persistence.directories.
  • Reads core.profile.avatar.path → shell.avatar_path and core.profile.wallpaper.directory → wallpaper.directory. Wallpaper fill mode is hardcoded (not a profile option).
  • Location is driven by core.profile.location.secret (a SOPS secret name). Two modes:
    • Normal (secret == null): sets programs.noctalia.settings. No location block.
    • Secret (secret != null): base TOML generated at build time; activation copies it to ~/.config/noctalia/config.toml and appends [location] address from the decrypted SOPS secret.

The user-side Hyprland config pairs with this module via Noctalia IPC keybinds (fullscreen, special workspace toggles, settings, audio/brightness dispatchers). Workspace rules in the user config set persistent = true for defined workspaces so they are always available regardless of Noctalia lifecycle.

types.nix

Shared type definitions used across the modules:

  • monitorSelector — typed Nix attrs for monitor matching (by name or index)
  • workspaceSelector — typed Nix attrs for workspace matching (by id, relativeId, name, or special)
  • rule — all typed window rule properties (float, fullscreen, opacity, size, move, center, monitor, workspace, and dozens more)
  • windowMatch — match condition types (class, title, initialClass, initialTitle, tag, xwayland, float, fullscreen, pin, focus, group, modal, fullscreenstate, workspace, content, xdg_tag)

Usage Example

{
  wayland.windowManager.hyprland = {
    enable = true;
    configType = "lua";

    custom-settings = {
      permission = {
        screenCopy = [ pkgs.firefox ];
      };

      lua = {
        enable = true;
        luaModules = [ ./lua/window_rules.lua ];
        applicationBinds = {
          "SUPER + Return" = "${pkgs.kitty}/bin/kitty";
          "SUPER + E" = "${pkgs.nautilus}/bin/nautilus";
        };
      };
    };
  };
}

Operational Notes / Assumptions

  • All options live under custom-settings to avoid collision with upstream HM Hyprland options.
  • lua.nix auto-injects applicationBinds, playerctl, wpctl, zenity, hyprshutdown, and uwsmApp as substitution variables — no need to set those manually.
  • Variable substitution is per-file: each Lua module only receives replacements for @placeholder@ tokens it actually contains. A variable defined but unused by a given module is silently ignored for that module. A placeholder referenced by a module but missing from variables is an error.
  • Unknown dispatchers in Lua raise a runtime error from Hyprland’s Lua parser, not a build-time error.
  • CamelCase naming in Nix (e.g. fullscreenState, idleInhibit, keepAspectRatio, noCloseFor, forceRgbx, syncFullscreen) is translated to snake_case in the Lua output.

Core Profile — Shared User Profile Options

Purpose

The core.profile module declares a set of shared user-profile options that act as a single source of truth for values consumed by other modules — desktop environments, shell companions, and display managers — rather than hardcoded literals.

Entry Point

Architecture / Services / Scope

The module only declares options; it does nothing on its own. Consumers use config.core.profile.* to read the values.

Options

core.profile.avatar.path

Typestring
Default${config.home.homeDirectory}/.face

Path to user avatar image.


core.profile.location.secret

Typenull or string
Defaultnull

SOPS secret name used for Noctalia location address.


core.profile.wallpaper.directory

Typestring
Default${config.home.homeDirectory}/Pictures/Wallpapers

Path to wallpaper image directory.


core.profile.avatar.path

Path to the user avatar image. Used by the Noctalia desktop shell (via programs.noctalia.settings.shell.avatar_path) to display the user picture in the bar and session UI.

core.profile.wallpaper.directory

Directory containing wallpaper images.

Consumers:

ConsumerHow it uses the value
Noctalia desktop shellSets programs.noctalia.settings.wallpaper.directory
GNOME azwallpaper extensionSets org/gnome/shell/extensions/azwallpaper slideshow-directory

core.profile.location.secret

Name of a SOPS secret whose decrypted value becomes the Noctalia location.address. When unset, the location block is omitted from the Noctalia config entirely. At activation time, the decrypted secret is appended to a base Noctalia config TOML (generated by the module) and written to ~/.config/noctalia/config.toml — the clear-text address never lands in the repo or Nix store.

Secrets

  • core.profile.location.secret — name of a SOPS secret (not a literal value) whose decrypted content supplies the Noctalia location address at activation time.

Usage Example

{ ... }: {
  core.profile = {
    avatar.path = "/home/user/.face";
    wallpaper.directory = "/home/user/Pictures/Backgrounds";
    location.secret = "noctalia-location";
  };
}

Operational Notes / Assumptions

  • The GNOME azwallpaper extension consumes core.profile.wallpaper.directory via dconf-extensions.nix (home/shared/desktop/gnome/).

list-ephemeral — Ephemeral Path Discovery and Persistence Snippets

Purpose

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.

Entry Point

Architecture / Services / Scope

Options

programs.list-ephemeral.enable

Typeboolean
DefaulthostPersistEnabled || config.user.persistence.enable
Exampletrue

Whether to enable list-ephemeral helper.


programs.list-ephemeral.extraExcludes

Typelist of string
Default[ ]
Example[ "home/*/.local/share/Trash" ]

Additional exclude patterns for list-ephemeral.


programs.list-ephemeral.extraIncludes

Typelist of string
Default[ ]
Example[ ".config/my-app" "/var/lib/my-app" ]

Additional paths to always include as candidates.


The module writes a generated config file consumed by the TUI:

  • Excludes default ephemeral paths (caches, logs, Electron app state, etc.) so they never appear as persistence candidates.
  • Supplies the currently persisted files and directories from user.persistence / host.persistence, which are marked as already persisted in the UI.
  • Supplies installed program names so the TUI can filter candidates by program context.

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.

Architecture / Services / Scope

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.
    • colour-picker: Hyprland color picker wrapper that temporarily lowers pointer sensitivity, launches hyprpicker, then restores the prior sensitivity.
    • folder-diff: Nushell helper that compares two directories by tracking added, modified, and deleted files in a temporary Git repo, then prints a binary-safe diff plus per-file change summaries.
    • 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.
    • compressor: Python tool that hashes image/video files, detects media from magic headers, caches WebP image conversions and AV1 MP4 video conversions. For videos, it samples 3 random segments to estimate compression ratio and auto-skip expansion cases. Those sample encodes now use live HandBrake JSON progress too, with per-sample ETA plus total sample ETA in Rich progress bars. Full sequential video encodes also parse live JSON progress, update Rich progress bars with per-video ETA plus rolling queue ETA, fall back to elapsed/progress-derived ETA when HandBrake reports zero, and --debug prints periodic runtime progress snapshots. Records applied outputs in its TSV cache and prints aligned Rich tables.
    • ocr-region: Wayland OCR helper that captures selected region, preprocesses image, runs multilingual Tesseract OCR, copies detected text, and notifies result.
    • monocoque: Sim-racing dashboard and telemetry tool.
    • orca-slicer-git-sync: Watches an OrcaSlicer profile repository, commits batched changes with profile-aware commit messages, and optionally pushes to a configured remote.
    • python: Packages for home assistant python components.
    • screenshot: Wayland screenshot helper that captures an area or output, stores timestamped files, and optionally opens or annotates the result.
    • ssh-relay: WSL SSH agent relay helper that bridges a Windows named pipe into a local Unix socket.
    • ssh-to-age-keys: Converts one or more SSH private keys into a deduplicated keys.txt age key file.
    • sunshine-tools: Shared Sunshine helpers for socket-proxy startup and Hyprland monitor disable/restore hooks.
    • swfs-mount-hooks: Shared mount helper for server.storage.swfsMount prepare, stop, and health-recovery actions.
    • take-control-viewer: Remote support viewer for N-able Take Control via Wine.
    • virtualisation-tools: Shared libvirt and VFIO hook helpers for CPU isolation, GPU detach/attach, and guest hook dispatch.
    • wait-for-io-tools: Shared reachability and database-readiness helpers for IO Guardian-managed services.
    • wlprop: Wayland helper that selects a visible Hyprland window region with slurp and prints matching client JSON.

Operational Notes / Assumptions

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.
  • Package CI discovery: The package build workflow enumerates package names lazily from packages.<system> and skips entries whose meta.broken evaluation fails or resolves to true, so one package that cannot be evaluated does not abort the entire build matrix.

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.

Architecture / Services / Scope

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).

Operational Notes / Assumptions

Key Options/Knobs

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

Notable Overrides

  • kernelPackages.universal-pidff: Pinned to upstream commit 595c65bb from main. Provides a newer force-feedback kernel module driver than the version bundled in the current nixpkgs release.
  • discord: Overridden to enable OpenASAR and Vencord.
  • hermes-agent: Local overlay that builds Hermes Agent from upstream source plus patches:
    • PR #87820 — desktop renderer build typecheck isolation.
    • PR #93896 — home-aware managed detection.
  • hermes-desktop (pkgs/default.nix): Routes to pkgs.hermes-agent.hermesDesktop, exposing the patched Hermes Desktop package as a top-level pkgs entry for use in home-manager configs.
  • fastembed-hermes: Overrides python312Packages.fastembed to strip Python deps already bundled in the Hermes sealed uv2nix environment (e.g. snowballstemmer). Avoids plugin/core package collision checks during Hermes plugin builds like nixai.

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.

Architecture / Services / Scope

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.

Configuration Structure

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

Operational Notes / Assumptions

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/.

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) 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";

References

Server Hosts

Purpose

Documentation of the individual server host machines managed by this repository.

Architecture / Services / Scope

Entry Points

  • NixAI: AI agent and inference host
  • NixArr: Media management and playback host
  • NixCloud: Application host, user-facing cloud services
  • NixDev: Development, CI, and registry host
  • NixIO: Ingress host, reverse proxy, and network gateway
  • NixMon: Monitoring and observability host
  • NixStor: Storage host, SeaweedFS evaluation

Configuration Structure

Host-specific configurations live in hosts/server/{hostname}/default.nix. Services are gated per host via flake allocations (see Flake Allocations) so that each service runs on exactly one host.

Operational Notes / Assumptions

  • Servers depend on the Database Coordinator for startup ordering (see IO Guardian).
  • Shared secrets for all server hosts are stored in the shared file.

References

nixai — AI Agent Host

Purpose

nixai is the AI infrastructure host. It runs the AI agent, local inference via Ollama, the Open WebUI chat interface, Mnemosyne memory sync, and voice services.

Entry Point

Architecture / Services / Scope

AI Agent

  • Containerized AI agent with a web dashboard, an OpenAI-compatible API server, and a webhook platform.
  • Memory is enabled, backed by Mnemosyne sync.
  • Voice is enabled, using the local Wyoming STT server.
  • A hardened SSH daemon is provisioned inside the container for agent administration.

Local Inference

  • Runs local models on the host’s AMD iGPU (ROCm/Vulkan), loading a small set of chat and embedding models.
  • Used by Open WebUI and the agent for local model access.

Open WebUI (web.nix)

  • User-facing chat interface with RAG, web search (via SearXNG), and local Whisper STT.
  • Backed by a PostgreSQL database on the Database Coordinator and tied into the database availability target so it only starts when the DB is reachable.

Mnemosyne (mnemosyne.nix)

  • Mnemosyne memory provider sync server, used by the agent for persistent memory. Served on its own vhost.

Voice (voice.nix)

  • Wyoming Piper (TTS) and faster-whisper (STT) servers exposed over TCP for local voice assistants.

Secrets

Declared secrets

Secret keyPurpose
AI_AGENT/AZURE_FOUNDRY_API_KEYModel API key (Azure Foundry)
AI_AGENT/AZURE_FOUNDRY_BASE_URLModel API base URL
AI_AGENT/OPENROUTER_API_KEYOpenRouter model API key
AI_AGENT/DISCORD_BOT_TOKENDiscord bot token
AI_AGENT/API_SERVER_TOKENAgent API server auth
MCP/N8N_API_KEYMCP access to n8n
MCP/API_TOKENMCP bridge API token
MCP/HASSIO_TOKENHome Assistant MCP token
MCP/GITHUB_TOKENGitHub MCP token
MCP/ANILIST_TOKENAniList MCP token
MNEMOSYNE_SYNC_KEYMnemosyne sync API key

Operational Notes / Assumptions

  • Ollama relies on the AMD iGPU; ROCm on the iGPU is noted as unreliable upstream, so the config pins an override and waits on a Vulkan fix.
  • Open WebUI, the agent dashboard, Mnemosyne, and the voice/whisper endpoints are exposed through the IO Coordinator reverse proxy.
  • The agent’s SSH port is opened to the subnet for administration.

References

nixarr — Media Management

Purpose

nixarr is the media management host for the fleet. All downloading happens through a VPN tunnel so P2P traffic is isolated.

Entry Point

Architecture / Services / Scope

Playback

  • Jellyfin: Media server with hardware-accelerated transcoding (VA-API via /dev/dri/renderD128), hardware encoding for HEVC and hardware decoding for AV1/H.264/HEVC/VP9.

Media Management (“arr” stack)

AppRole
RadarrMovie management
SonarrTV series management
ProwlarrIndexer management for the whole stack
LidarrMusic management
ReadarrBook management
BazarrSubtitle management
TransmissionBitTorrent downloader (Flood UI, cross-seed)
SabnzbdUsenet downloader
SeerrUser-facing media request portal

Networking / VPN

  • All downloading apps run inside a WireGuard VPN namespace (vpnNamespaces.wg) so P2P/usenet traffic egresses through the VPN rather than the host’s normal connection.
  • The VPN config is provided as a sops binary secret (wg.conf) that restarts wg.service when rotated.
  • Access to VPN-isolated apps is allowed from the LAN and the configured tailnet; see the Tailscale module documentation for the cluster’s tailnet integration.

Authentication

  • An arr-services Kanidm OAuth2 context restricts the media apps to the sysadmin group on the Identity Coordinator.

Secrets

Declared secrets

Secret keyPurpose
wireguardWireGuard VPN config (binary, wg.conf)

Operational Notes / Assumptions

  • VPN download services restart on failure and wait for wg.service to come up before starting, so they don’t fail during early boot when networking isn’t ready.
  • Hardware transcoding relies on the host exposing a working /dev/dri device.
  • Media apps are exposed through the IO Coordinator reverse proxy.

References

NixAuth

nixcloud - Application Server

Purpose

NixCloud is an application server hosting user-facing cloud services. General purpose application server for replacing cloud services with self-hosted alternatives.

Entry Point

Architecture / Services / Scope

Application Workloads

WorkloadServiceFileDomain
Home Assistanthome-assistanthosts/server/nixcloud/home-assistant/hassio.racci.dev
Homeboxhomeboxhosts/server/nixcloud/homebox.nixhomebox.racci.dev
Immichimmichhosts/server/nixcloud/immich.nixphotos.racci.dev
Musicnavidromehosts/server/nixcloud/music.nixmusic.racci.dev
Nextcloudnextcloudhosts/server/nixcloud/nextcloud.nixnc.racci.dev
Searchsearxhosts/server/nixcloud/search.nixsearch.racci.dev

Operational Notes / Assumptions

  • All apps are exposed via the cluster’s reverse proxy on the IO Coordinator.
  • Applications using OAuth2/OIDC/SSO use the Identity Coordinator for authentication and authorization.
  • Database services for these apps are provided by the cluster Database Coordinator via server.database.postgres.*.
  • Media storage for Nextcloud and Immich uses seaweedfs mounts on the Storage Coordinator.

References

NixDB

nixdev — Development & CI

Purpose

nixdev is the development and CI host. It runs the self-hosted development services: continuous integration, Coder workspace platform, workflow automation, a Docker registry, and a forgesync mirror job.

Entry Point

Architecture / Services / Scope

CI / Automation

  • Woodpecker CI: Self-hosted CI server + local Docker-backed agent, supporting GitHub and Codeberg forges. Served on its own vhost with a separate gRPC agent endpoint.
  • GitHub Actions runners: A pool of 10 self-hosted nixos-runner-* runners for the nix-config repo.

Workspaces

  • Coder: Self-hosted development workspaces, backed by the Docker daemon. Coder users are granted Docker access.

Workflow Automation

  • n8n: Workflow automation with task runners, backed by PostgreSQL and Redis on the Database Coordinator.

Registry & Mirroring

  • Docker Registry: Self-hosted OCI registry storing images on the Storage Coordinator, with htpasswd auth.
  • Forgesync: Mirrors repositories between Codeberg and GitHub on a daily schedule.

Secrets

Declared secrets

Secret keyPurpose
GITHUB_TOKENToken for the self-hosted runners
POSTGRES/N8N_PASSWORDn8n database password
POSTGRES/CODER_PASSWORDCoder database password
POSTGRES/WOODPECKER_PASSWORDWoodpecker database password
POSTGRES/WINDMILL_PASSWORDWindmill database password
REDIS_PASSWORDn8n Redis password
N8N/ENCRYPTION_KEYn8n encryption key
N8N/RUNNER_AUTH_TOKENn8n task runner auth
WOODPECKER/GRPC_SECRETWoodpecker gRPC secret
WOODPECKER/AGENT_SECRETWoodpecker agent secret
WOODPECKER/GITHUB_CLIENT / GITHUB_SECRETGitHub forge OAuth
WOODPECKER/CODEBERG_CLIENT / CODEBERG_SECRETCodeberg forge OAuth
REGISTRY/SECRETRegistry shared secret
REGISTRY/HTPASSWDRegistry auth htpasswd
REGISTRY/S3_ACCESS_KEY / S3_SECRET_KEYS3 storage credentials
FORGESYNC/SOURCE_TOKEN / TARGET_TOKEN / MIRROR_TOKENForgesync tokens

Operational Notes / Assumptions

  • Runs Docker with auto-pruning for workspaces and CI workloads.
  • Woodpecker, n8n, Coder, the registry, and CI are exposed through the IO Coordinator reverse proxy.
  • Databases are provided by the Database Coordinator; n8n and Woodpecker depend on the database availability target.

References

nixio - Ingress Host

Purpose

NixIO serves as the cluster’s primary ingress and network gateway. It handles all external traffic routing, VPN tunnelling, DNS filtering, and dashboard aggregation for the server fleet.

Entry Point

Architecture / Services / Scope

Services

ServiceModule / PathRole
Caddyhosts/server/nixio/proxy.nixReverse-proxy and TLS termination for all cluster services
Tailscale Tunnelhosts/server/nixio/tunnel/Mesh VPN connectivity, subnet routing, and ingress via Tailscale tags
Dashy Dashboardhosts/server/nixio/dashboard.nixAggregated service dashboard displayed on the IO Coordinator
AdGuard Homehosts/server/nixio/adguard.nixLocal DNS filtering and ad-blocking for the home network
Network Configdefault.nixSubnet declarations, IP forwarding (IPv4 + IPv6)

Secrets

Declared secrets

Secret keyPurpose
CLOUDFLARE/EMAILACME DNS challenge account email
CLOUDFLARE/ZONE_API_TOKENACME DNS challenge zone token
CLOUDFLARE/DNS_API_TOKENACME DNS challenge API token

Operational Notes / Assumptions

  • This host is expected to have stable upstream network access plus reachability to the cluster LAN and tailnet, because ingress, DNS, and tunnel traffic all terminate here.
  • Caddy terminates public TLS for cluster services, while some backends also use separate internal TLS or mTLS; certificate trust and backend server names must stay aligned with those upstream services.
  • Reverse-proxied services remain individually responsible for their own authn/authz. Publishing a route here does not replace service-level access controls.

References

nixmon — Monitoring

Purpose

nixmon is the monitoring primary host for the cluster. It runs the observability stack of metrics, logs, alerting, and dashboards.

Entry Point

Architecture / Services / Scope

Uptime Monitoring

  • Uptime Kuma: Service status monitoring.

Observability Stack

As the monitoringPrimaryHost, nixmon runs the collector services defined by the Server Monitoring.

Secrets

Declared secrets

Secret keyPurpose
MONITORING/OLTP/BEARER_TOKENBearer token for OTLP ingestion
MONITORING/GRAFANA/SECRET_KEYGrafana session secret
MONITORING/GRAFANA/OAUTH_SECRETKanidm OAuth2 secret for Grafana
MONITORING/HOME_ASSISTANT/WEBHOOK_URLHome Assistant alert webhook
MONITORING/NEXTCLOUD_TALK/WEBHOOK_URLNextcloud Talk alert webhook
MONITORING/MINIO_PROMETHEUS_TOKENPrometheus token for MinIO metrics
PROXMOX/USERProxmox API user
PROXMOX/TOKEN_IDProxmox API token ID
PROXMOX/TOKEN_SECRETProxmox API token secret
S3FS_AUTH/LOKIS3 credentials for Loki storage

Operational Notes / Assumptions

  • Grafana requires a matching KANIDM/OAUTH2/GRAFANA_SECRET in the Identity Coordinator’s provisioning so the OAuth2 client works.
  • Monitoring web UIs are exposed through the IO Coordinator reverse proxy.

References

nixserv — Nix Build Server

Purpose

nixserv is the dedicated binary cache and distributed build server for the cluster. It runs Attic backed by PostgreSQL and S3 storage, and acts as a remote build daemon for distributed Nix builds.

Entry Point

Architecture / Services / Scope

Attic Binary Cache

  • Attic: Self-hosted Nix binary cache server, served at cache.racci.dev.
  • Storage is backed by the Storage Coordinator with zstd chunked/compressed NAR storage.
  • The Attic database uses PostgreSQL on the Database Coordinator.
  • Requires proof-of-possession for uploads, with a 14-day default garbage-collection retention period run on a schedule.

Distributed Builds

As a distributedBuilders host, nixserv exposes the builder user over SSH. Other hosts configure nix.distributedBuilds and connect to it as a remote build machine (ssh-ng) to offload Nix builds.

Secrets

Declared secrets

Secret keyPurpose
ATTIC_ENVIRONMENTAttic environment file
POSTGRES/ATTIC_PASSWORDAttic database password

Operational Notes / Assumptions

  • The cache endpoint and the build daemon rely on the Storage Coordinator for object storage and the Database Coordinator for the metadata database.
  • The Attic HTTP endpoint is served through the IO Coordinator reverse proxy.

References

NixStor

Lib Overview

Purpose

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

Architecture / Services / Scope

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/: Specialized builders for system and home configurations. Builders forward shared module arguments (e.g. importExternals and repo-level args) into both NixOS and nested Home Manager extraSpecialArgs, enabling modules that conditionally import external inputs.

Operational Notes / Assumptions

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