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
sudo nixos-rebuild switch --flake /etc/nixos#winix

4. WSL-Specific Features

The WSL configuration includes:

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

Native NixOS Installation

Prerequisites

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

Installation Process

1. Boot from NixOS Installation Media

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

2. Network Configuration

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

# Verify connectivity
ping nixos.org

3. Disk Setup

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

4. Generate Hardware Configuration

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

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

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

5. Customize Host Configuration

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

6. Install NixOS

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

# Set root password when prompted

7. Post-Installation

# Reboot into new system
reboot

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

Existing NixOS System Migration

From Traditional NixOS Configuration

1. Backup Current Configuration

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

2. Clone This Repository

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

3. Create Host Configuration

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

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

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

4. Test and Apply

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

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

IO Guardian - Database Availability System

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

Overview

The system consists of two components:

  1. Guardian Server (runs on client servers)

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

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

How It Works

System Startup

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

Database Shutdown (Graceful Drain)

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

Database Startup (Undrain)

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

Security

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

Generating the PSK

Generate a new PSK using OpenSSL:

openssl rand -base64 32

Adding the Secret

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

IO_GUARDIAN_PSK: <your-generated-key>

Then encrypt the file:

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

Configuration

Port

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

Dependent Services

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

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

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

Services listed here will:

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

Systemd Units

On Client Servers

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

On nixio

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

Troubleshooting

Checking Guardian Status

On client servers:

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

On IO Hosts:

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

Manual Commands

To manually start dependent services on a client:

systemctl start io-databases.target

To manually stop dependent services:

systemctl stop io-databases.target

Common Issues

Guardian server won’t start:

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

Services not starting after boot:

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

Authentication failures in logs:

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

Protocol Reference

The guardian uses a simple JSON-based WebSocket protocol:

Authentication

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

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

Commands

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

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

Server Cluster Monitoring

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

Overview

The system consists of three layers:

  1. Exporters (run on all servers)

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

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

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

Architecture

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

Configuration

Enabling Monitoring

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

Options

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.


Auto-Detection

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

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

Secrets

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

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

Generating Secrets

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/nixcloud/secrets.yaml under KANIDM/OAUTH2/GRAFANA_SECRET (the Kanidm provisioning side).

Caddy Virtual Hosts

The module configures four virtual hosts on nixmon:

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 primary host’s Caddy configuration.

Alert Rules

The following alerts are configured by default:

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

Alerts are routed to:

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

Module Structure

modules/nixos/server/monitoring/
├── default.nix              # Entry point, imports sub-modules
├── options.nix              # All server.monitoring.* options
├── collector/
│   ├── default.nix          # Imports collector sub-modules
│   ├── prometheus.nix       # Prometheus server + scrape targets
│   ├── loki.nix             # Loki server + storage config
│   ├── grafana.nix          # Grafana + Kanidm OAuth2
│   ├── 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

Troubleshooting

Checking Service Status

On the monitoring host (nixmon):

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

On any server:

systemctl status prometheus-node-exporter.service
systemctl status 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 GRAFANA_OAUTH_SECRET in nixmon matches KANIDM/OAUTH2/GRAFANA_SECRET in nixcloud
  • Check Kanidm provisioning has the grafana OAuth2 client configured
  • Verify DNS resolves auth.<domain> correctly

Prometheus targets showing as down:

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

Proxmox metrics missing:

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

Creating New Users

To add a new user configuration:

1. Create User Directory

mkdir -p home/newuser

2. Create User Configuration Files

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

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

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

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

Create feature modules in home/newuser/features/:

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

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

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

4. Test User Configuration

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

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

Creating New Hosts

To add a new host to your configuration:

1. Create Host Directory Structure

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

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

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

2. Create Required Configuration Files

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

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

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

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

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

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

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

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

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

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

  # Add hardware-specific configuration
}

3. Add Hardware Acceleration (Optional)

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

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

4. Build and Test

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

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

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

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

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

Step 1: Define the Forked Repository

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

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

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

Step 2: Use Packages or Modules from the Forked Repository

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

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

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

Declarative Gnome Dconf

Description

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


DConf Locations

The locations for where to save DConf settings to is:

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

Getting the Output

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

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

dconf dump / | dconf2nix > dconf.nix

Using a Package/Module from a Fork

Modules Overview

Purpose

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

Entry Points

NixOS Modules

This section covers all NixOS modules provided by this flake.

NixOS Services

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

Nested service modules emit generated fragments such as services-ai-agent-options.md.

AI Agent

AI Agent

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

  • Entry point: modules/nixos/services/ai-agent.nix
  • Upstream: Hermes Agent
  • Package: The module routes services.hermes-agent.package through the local pkgs.hermes-agent overlay, which carries the lazy-deps managed-install fix from PR #48637. This ensures Hermes fails fast with FeatureUnavailable on read-only NixOS installs rather than retrying ensurepip.

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

Typeboolean
Defaultfalse
Exampletrue

Whether to enable enable extra plugins for the agent.


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"

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"stepfun/step-3.5-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.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.


Secrets Management

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.environmentFile = config.sops.templates."HERMES_ENV".path;

Usage Example

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

Voice & STT

Enable voice input and output with services.ai-agent.voice.enable = true;.

Wyoming STT (Reuse Existing Server)

Instead of running a separate Whisper instance inside the Hermes container, you can point Hermes at an existing Wyoming faster-whisper server (e.g. the one already running on nixai at port 10300):

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

This sets HERMES_LOCAL_STT_COMMAND to invoke wyoming-transcribe, which sends audio over the Wyoming protocol to the faster-whisper server and returns the transcript. No second Whisper process needed.

Dashboard Service

Enable the web dashboard with services.ai-agent.dashboard.enable = true;.

This adds a separate hermes-dashboard systemd service that 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.

OIDC Authentication

Enable OpenID Connect authentication for the dashboard with services.ai-agent.dashboard.oidc.enable = true;.

The dashboard uses a public PKCE client (no client_secret). The client ID is a public identifier — it does not need to be stored as a secret.

{ ... }: {
  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" ];
      };
    };
  };
}

The module generates a HERMES_DASHBOARD_OIDC_ENV environment file with the OIDC settings, which is loaded by the hermes-dashboard service.

Memory (Mnemosyne)

Enable long-term memory with services.ai-agent.memory.enable = true;. This switches the memory provider from the built-in user profile (USER.md injection) to Mnemosyne, a local SQLite-backed memory system with semantic recall.

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

Database location: /home/hermes/mnemosyne.db (SQLite with FTS5 hybrid ranking + vector search).

Huntress

Huntress

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

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.


Usage Example

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

Operational Notes

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

MCPO

MCPO (Model Context Protocol Orchestrator)

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

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.


Usage Example

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

Operational Notes

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

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.

Metrics

Metrics & Hacompanion

Comprehensive metrics collection and integration with Home Assistant via hacompanion.

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.


Usage Example

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

Operational Notes

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

Tailscale

Tailscale

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

  • Entry point: modules/nixos/services/tailscale.nix

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.


Usage Example

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

Operational Notes

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

Core Module

Documents shared NixOS core modules used across hosts.

Purpose

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

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

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.generators.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable generators configuration.


core.generators.proxmoxLXC.clearPath

Typeabsolute path
DefaultgetExe' pkgs.busybox "clear"

Clear binary to use for clearing the screen when asking the user for the SSH private key


core.generators.proxmoxLXC.enable

Typeboolean
Defaultcfg.enable && config.host.device.isVirtual
Exampletrue

Whether to enable Proxmox LXC generator configuration.


core.generators.proxmoxLXC.sedPath

Typeabsolute path
DefaultgetExe' pkgs.busybox "sed"

Sed package to use for validating the SSH private key provided by the user


core.generators.proxmoxLXC.sshKeygenPath

Typeabsolute path
DefaultgetExe' pkgs.openssh "ssh-keygen"

SSH package to use for validating the SSH private key provided by the user


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


Baseline Behaviour

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

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

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

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

Key Pages

Usage Example

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

Notes

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

Activation

Reports system generation changes during NixOS activation.


Overview

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


Options

core.activation.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable report diff on activation.



Behaviour

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

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

If no previous generation exists yet, script does nothing.


Usage Example

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

Operational Notes

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

Auto Upgrade

Schedules automatic NixOS upgrades from flake host outputs.


Overview

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


Options

core.auto-upgrade.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable auto-upgrade.


core.auto-upgrade.hostName

Typestring
Defaultconfig.networking.hostName

The hostName to use for auto-upgrade



Behaviour

When enabled, module configures:

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

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


Usage Example

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

Operational Notes

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

Containers

Enables Docker-based container runtime defaults.


Overview

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


Options

core.containers.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable container support.



Behaviour

When enabled, module:

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

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


Usage Example

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

Operational Notes

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

Display Manager

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


Overview

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

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


Options

core.display-manager.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable display manager configuration.



Behaviour

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

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

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


Usage Example

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

  core.display-manager.enable = true;
}

Operational Notes

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

Gaming

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


Overview

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


Options

purpose.gaming.controllerSupport

Typeboolean
Defaultfalse
Exampletrue

Whether to enable controller support.


purpose.gaming.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Gaming support base..


purpose.gaming.minecraft.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Minecraft support.


purpose.gaming.modding.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable modding support.


purpose.gaming.modding.enableBeatSaber

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable beatsaber modding support.


purpose.gaming.modding.enableSatisfactory

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable satisfactory modding support.


purpose.gaming.modding.enableThunderstore

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable thunderstore support.


purpose.gaming.osu.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable OSU!.


purpose.gaming.osu.lazerPackages

Typepackage
Default<derivation osu-lazer-bin-2026.726.0>

The package to install for OSU! Lazer


purpose.gaming.roblox.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Roblox launcher.


purpose.gaming.roblox.vinegarPackage

Typepackage
Default<derivation vinegar-1.9.3>

The package to use for Vinegar


purpose.gaming.simulator.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable simulator support.


purpose.gaming.simulator.enableRacing

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Moza Racing.


purpose.gaming.steam.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Steam.


purpose.gaming.vr.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable VR support.


purpose.gaming.minecraft.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Minecraft support.


purpose.gaming.modding.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable modding support.


purpose.gaming.modding.enableBeatSaber

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable beatsaber modding support.


purpose.gaming.modding.enableSatisfactory

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable satisfactory modding support.


purpose.gaming.modding.enableThunderstore

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable thunderstore support.


purpose.gaming.osu.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable OSU!.


purpose.gaming.osu.lazerPackages

Typepackage
Default<derivation osu-lazer-bin-2026.726.0>

The package to install for OSU! Lazer


purpose.gaming.roblox.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Roblox launcher.


purpose.gaming.roblox.vinegarPackage

Typepackage
Default<derivation vinegar-1.9.3>

The package to use for Vinegar


purpose.gaming.simulator.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable simulator support.


purpose.gaming.simulator.enableRacing

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable Moza Racing.


purpose.gaming.steam.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Steam.


purpose.gaming.vr.enable

Typeboolean
Defaultfalse
Exampletrue

Whether to enable Enable VR support.



Behaviour

When enabled, module:

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

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


Decky Loader Integration

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

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

Usage Example

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

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

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

Generators

Configures image and container generator support for shared NixOS hosts.


Overview

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

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


Options

core.generators.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable generators configuration.


core.generators.proxmoxLXC.clearPath

Typeabsolute path
DefaultgetExe' pkgs.busybox "clear"

Clear binary to use for clearing the screen when asking the user for the SSH private key


core.generators.proxmoxLXC.enable

Typeboolean
Defaultcfg.enable && config.host.device.isVirtual
Exampletrue

Whether to enable Proxmox LXC generator configuration.


core.generators.proxmoxLXC.sedPath

Typeabsolute path
DefaultgetExe' pkgs.busybox "sed"

Sed package to use for validating the SSH private key provided by the user


core.generators.proxmoxLXC.sshKeygenPath

Typeabsolute path
DefaultgetExe' pkgs.openssh "ssh-keygen"

SSH package to use for validating the SSH private key provided by the user



Assertions and Behaviour

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

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

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

Usage Example

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

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

Operational Notes

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

Groups

Locale

Sets shared timezone and locale defaults.


Overview

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


Options

core.locale.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable locale configuration.



Usage Example

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

Operational Notes

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

Nix

Defines shared Nix daemon, cache, and registry defaults.


Overview

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

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


Options

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


Behaviour

Module configures:

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

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


Operational Notes

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

OpenSSH

Configures opinionated SSH server and client defaults.


Overview

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

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


Options

core.openssh.enable

Typeboolean
Defaulttrue
Exampletrue

Whether to enable OpenSSH server and client opinionated configuration.



Behaviour

When enabled, module:

  • enables services.openssh,
  • disables socket activation (startWhenNeeded = false) to prevent mid-connection disruptions during system configuration switches,
  • disables password authentication,
  • sets PermitRootLogin = "prohibit-password",
  • sets GatewayPorts = "clientspecified",
  • configures services.openssh.hostKeys from config.sops.secrets.SSH_PRIVATE_KEY.path,
  • publishes current host public key at /etc/ssh/ssh_host_ed25519_key.pub,
  • enables security.pam.sshAgentAuth,
  • adds current host public host key to users.users.root.openssh.authorizedKeys.keyFiles, and
  • generates programs.ssh.knownHosts entries for every host in outputs.nixosConfigurations.

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


Usage Example

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

Operational Notes

  • Module expects matching host public key file to exist in flake for each host.
  • Current host gets localhost as extra known-host alias in generated SSH client config.
  • Root authorization here uses host key material from flake, not per-user login keys.
  • Private host key comes from SOPS secret SSH_PRIVATE_KEY, so core.sops integration usually pairs with this module.
  • Socket activation disabled: By default, NixOS uses socket activation for SSH which spawns per-connection service instances (sshd@...service). When these instances are restarted during a configuration switch, it disconnects active SSH sessions. Disabling socket activation (startWhenNeeded = false) runs SSH as a traditional always-on service, preventing remote disconnection during nixos-rebuild switch over SSH.

Printing

Enables shared printer support for workstation-class NixOS hosts.


Overview

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

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


Options

core.printing.enable

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

Whether to enable printing support.



Behaviour

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

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

Usage Example

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

Operational Notes

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

Remote Access

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


Overview

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

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

Options

core.remote.enable

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.



Behaviour

When core.remote.enable = true:

  • remoteDesktop.enable turns on services.xrdp, sets defaultWindowManager, and opens firewall for RDP.
  • streaming.enable turns on services.sunshine, 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 = "3",
  • adds two Sunshine application entries named Shared Desktop and Exclusive Desktop,
  • creates headless output at login via Home Manager, and
  • keeps HEADLESS-2 disabled by default until Sunshine prep commands enable it.

Lua Mode Handling

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

  • Startup hook: settings.on with hyprland.start event triggers hyprctl output create headless.
  • Monitor rule: { output = "HEADLESS-2"; disabled = true; } (attrset form instead of old string).
  • Screencopy permission: routed through wayland.windowManager.hyprland.custom-settings.permission.screenCopy, which handles both Lua and hyprlang modes automatically.

For hyprlang mode, the old string forms (exec-once, monitor = "HEADLESS-2,disable") are used unchanged.

ApplicationBehaviour
Shared DesktopEnables HEADLESS-2 at client resolution and leaves physical monitors active.
Exclusive DesktopEnables HEADLESS-2, saves active physical monitor state to $XDG_STATE_HOME/hyprland-disabled-monitors-pre-sunshine.json, disables physical monitors, then restores them on disconnect.

Usage Examples

Streaming only

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

RDP only

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

Operational Notes

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

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 sunshine-proxy-wrapper 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.

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

Applies shared host security defaults.


Overview

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


Options

core.security.enable

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.



Usage Example

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

Operational Notes

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

SOPS

Configures shared SOPS and age decryption defaults.


Overview

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


Options

core.sops.enable

Typeboolean
Defaultconfig.core.enable
Exampletrue

Whether to enable SOPS auto configuration.


core.sops.hostSecretsFile

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

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



Behaviour

When enabled, module:

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

Usage Example

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

Operational Notes

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

Stylix

Applies shared system theme defaults with Stylix.


Overview

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


Options

core.stylix.enable

Typeboolean
Default!config.host.device.isHeadless
Exampletrue

Whether to enable Stylix configuration.



Behaviour

When enabled, module:

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

Usage Example

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

Operational Notes

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

Virtualisation

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


Overview

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

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


Options

core.virtualisation.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.



Behaviour

When enabled, module:

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

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


Isolation and Hook Helpers

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

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

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


Firmware and Persistence

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

Persisted paths include:

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

Usage Example

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

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

Operational Notes

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

WSL

Adds Windows Subsystem for Linux specific integration and fixes.


Overview

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


Options

core.wsl.enable

Typeboolean
Defaultfalse
Exampletrue

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


core.wsl.user

Typestring

The default user to use for WSL.



Behaviour

When enabled, module:

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

If wsl module exists in option tree, module also:

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

Usage Example

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

Operational Notes

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

Server Module

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

Purpose

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

Entry Point

  • modules/nixos/server/default.nix

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


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 coordinator host for the cluster. This host runs primary database instances, the reverse proxy, and storage master nodes. This option is typically set on the coordinator host and used by other servers in the cluster for synchronization.

Example Usage

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

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

Operational Notes

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

Server Dashboard Module

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

Purpose

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

Entry Point

  • modules/nixos/server/dashboard.nix

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.


Example Usage

Configure the dashboard section for a server:

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

Operational Notes

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

Server Network Module

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

Purpose

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

Entry Point

  • modules/nixos/server/network.nix

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.


Example Usage

Configure a subnet and open ports on a server:

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

Operational Notes

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

Server Distributed Builds Module

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

Purpose

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

Entry Point

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

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.


Example Usage

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

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

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

Operational Notes

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

Database Submodule

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

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

Purpose

This submodule automates:

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

Entry Points

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

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.


Key Options and Behaviors

Connection Management

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

PostgreSQL Management

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

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

Redis Management

Redis management uses a similar aggregation pattern:

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

Per-Module Examples

Connection Configuration (default.nix)

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

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

PostgreSQL Example (postgres.nix)

Requesting a PostgreSQL database for a service:

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

Redis Example (redis.nix)

Requesting a Redis database:

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

Guardian Dependency Example (guardian.nix)

Manually adding services to the database lifecycle coordination:

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

Operational Notes

IO Guardian Coordination

Lifecycle management is handled by the IO Guardian.

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

IO Primary Host Behavior

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

Storage

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

Purpose

This area provides:

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

Key Options and Behaviors

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 generates a timer-driven probe that can lazily unmount stale FUSE mounts, restart the mount service, and optionally restart 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 that runs alongside MinIO on the IO primary host and exposes its endpoint set through the existing Caddy proxy integration.

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

Example

The following example mounts a MinIO-backed media bucket and sets specific ownership.

{
  server.storage.swfsMount.media = {
    backend = "minio";
    uid = 1000;
    gid = 1000;
    umask = 007;
  };
}

Operational Notes

  • 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.
  • MinIO Endpoint: The MinIO backend defaults to https://minio.racci.dev unless a mount overrides the endpoint explicitly.
  • 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

SeaweedFS is currently 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.

Purpose

The evaluation deployment provides an all-in-one SeaweedFS stack on the IO 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 Points

  • modules/nixos/server/storage/seaweedfs.nix
  • modules/nixos/server/storage/default.nix

Deployment Scope

  • Evaluation only: this deployment does not replace MinIO and does not perform any migration.
  • IO primary only: the module is gated by config.server.ioPrimaryHost == 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

The SeaweedFS evaluation endpoints are exposed through the existing server.proxy.virtualHosts integration instead of host-local Caddy configuration.

Current proxy surface includes:

  • seaweedfs.<domain> for the master endpoint
  • filer.seaweedfs.<domain> for the filer endpoint
  • s3.seaweedfs.<domain> for the S3-compatible endpoint
  • volume.seaweedfs.<domain> for the volume endpoint
  • admin.seaweedfs.<domain> for the admin endpoint

Client-facing TLS terminates at Caddy. For gRPC-backed component endpoints, the proxy is additionally configured with the backend transport settings required for SeaweedFS communication.

Security Material

The 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 IO primary host and include both JWT material and TLS certificates/keys for the SeaweedFS component set.

Operational Notes

  • The SeaweedFS module uses the upstream services.seaweedfs option surface rather than introducing a repository-local server.storage.seaweedfs.* option tree.
  • The evaluation deployment is still separate from server.storage.swfsMount. The new 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

Proxy Submodule

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

Purpose

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

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.


Per-Module Examples

default.nix - Logic and Helpers

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

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

options.nix - Option Definitions

Defines the structure of virtual hosts and shared contexts.

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

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

config.nix - Caddy Integration

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

Note: L4 (TCP/UDP) forwarding is handled by the l4 extension, not by config.nix. See Layer 4 Forwarding.

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

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

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

kanidm.nix - Authentication Security

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

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

extensions.nix - System Integration

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

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

Operational Notes

Caddy Integration

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

Dashboard Integration

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

Kanidm OAuth2 Context

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

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

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

Layer 4 Forwarding

L4 forwarding uses the caddy.layer4 plugin 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

Extension Architecture

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

The vhost attrset contains _resolvedExtraConfig — the user’s extraConfig field with localhost/127.0.0.1 already replaced for non-IO hosts.

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:

server.proxy.extensions.myext.enable = mkDefault (
  # check if any vhost uses my extension's features
);

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: (importModule ./extensions/<name>.nix { inherit proxyLib; })
  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

Example skeleton:

{ proxyLib, ... }:
{ config, lib, ... }:
let
  inherit (lib) mkOption types mkDefault;
in
{
  options.server.proxy.virtualHosts = mkOption {
    type = attrsOf (submodule ({ name, ... }: {
      options.mycustom = mkOption {
        type = bool;
        default = false;
      };
    }));
  };

  config = {
    server.proxy.extensions.mycustom = {
      priority = 75;
      config = name: vh: hostCfg:
        if !vh.mycustom then "" else "header X-Custom on";
      globalConfig = hostCfg: "";
      vhostModule = null;
    };
  };
}

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.

server.proxy.virtualHosts.myservice = {
  requireApiKey = {
    enable = true;
    bypassPaths = [ "/health" "/metrics" ];  # paths that skip auth
  };
  extraConfig = "reverse_proxy localhost:8080";
};

Generated Caddy config per vhost:

@myservice_apikey_key {
    header Req-API-Key {env.API_KEY_MYSERVICE}
}
route /auth/apikey/* {
    authorize with myservice_apikey_authorizer
}
handle {
    authorize with myservice_apikey_authorizer
    reverse_proxy localhost:8080
}

Global caddy-security config:

order authorize before reverse_proxy
authorize with myservice_apikey_authorizer {
    with @myservice_apikey_key
}

Secrets auto-generated via sops at PROXY_AUTH/<VHOST_NAME>_API_KEY, injected via systemd LoadCredential. 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

Server SSH Module

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

Purpose

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

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


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

The module creates an indirect GC root for the SSH shell at login time by instantiating shell expression to derivation, then realizing it with nix-store --add-root --indirect --realise. This keeps realized shell alive across upgrades without referencing config.system.build.toplevel during system evaluation.

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

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

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

Session Environment (ssh-shell/shell.nix)

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

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

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

Per-Module Examples

Enabling the SSH Shell

Enable the auto-shell behavior in your host configuration:

{
  server.sshShell.enable = true;
}

Customizing the Shell File

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

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

Operational Notes

Opt-Out Behavior

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

NIX_SKIP_SHELL=1 ssh root@your-server

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

Guard Mechanism

The auto-entry script uses the SSH_NIX_SHELL environment variable to prevent recursive shell entries. 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

Flake Allocations

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

How It Works

The allocation system has three layers:

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

Data Flow

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

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

Options

allocations.accelerators

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.

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

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

allocations.hostTypes

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

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

allocations.server.ioPrimaryCoordinator

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

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

allocations.server.ioPrimaryCoordinator = "nixio";

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

allocations.server.distributedBuilders

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

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

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

Apply Modules

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

apply/system.nix

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

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

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

apply/home-manager.nix

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

Source Files

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

AI Modules

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.

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.

Current Modules

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

Usage

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

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

Mnemosyne

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

Architecture

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 --sse)"]
        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

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.


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

Notes

  • Sync server uses mnemosyne sync serve with stdlib HTTP — no extra Python dependencies.
  • MCP server adds mcp and anyio dependencies (via pkgs.mnemosyne-mcp).
  • Sync protocol is plain HTTP with delta-based bidirectional sync.
  • Sync interval default is 10 minutes.

DIY & Making

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


Printing

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

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 that watches the OrcaSlicer profile directory and automatically creates a git commit every time a profile file is added, changed, or removed. This gives you a full revision history of your slicer settings with zero manual effort.


Commit Message Convention

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

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

Where:

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

Examples:

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

How It Works

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

Usage Example

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

  purpose.diy.printing = {
    enable = true;

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

Operational Notes

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

Home-Manager: AI Editors & Assistants

This page documents the Home-Manager module at:

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

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


What this module sets up

When enabled, the module:

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

Options

purpose.development.editors.ai.enable

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

Notes

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

Home-Manager: Hyprland

This page documents the custom Hyprland Home-Manager helper modules at:

  • modules/home-manager/core/hyprland/

These modules extend wayland.windowManager.hyprland with a typed Nix API for window rules, permissions, slide-in popups, input defaults, and Lua config generation targeting the HM-native Lua configuration format.

The repo default (set in home/shared/desktop/hyprland/default.nix) uses configType = "lua".

In Lua mode, direct settings.* attr names must be Lua-safe identifiers. Use underscore-style names like exec_once, window_rule, and workspace_rule instead of dashed hyprlang names like exec-once.

The module structure is:

default.nix       # Top-level importer (imports all submodules)
├── permission.nix   # custom-settings.permission
├── slideIn.nix      # custom-settings.slideIn
├── windowRule.nix   # custom-settings.windowrule
├── input.nix        # settings.config defaults (cursor, binds, input, misc)
├── lua.nix          # custom-settings.lua (Lua config generation)
│   └── lua/binds.lua  # Default Lua bind template with @placeholder@ substitution
└── types.nix        # Shared type definitions

Module Files

input.nix

Sets sensible default values under settings.config for cursor behavior, input device settings, keyboard binds, and misc Hyprland options. This module activates automatically when the Hyprland HM module is enabled — no custom-settings option is involved. Override any value via settings.config.input.* etc.

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

windowRule.nix

Defines custom-settings.windowrule: an attribute set of named window rules. Each rule has:

  • name — rule name (defaults to the attribute key)
  • matcher — list of match conditions (window class, title, workspace, etc.)
  • rule — rule properties (float, fullscreen, size, move, opacity, center, monitor, workspace, pin, group, border, animation, idle inhibit, and many more)
custom-settings.windowrule = {
  "kitty-floating" = {
    matcher = [ { class = "kitty"; } ];
    rule = {
      float = true;
      center = true;
      size = {
        width = "40%";
        height = "60%";
      };
    };
  };

  "firefox-picture-in-picture" = {
    matcher = [
      {
        title = "Picture-in-Picture";
        class = "firefox";
      }
    ];
    rule = {
      pin = true;
      opacity.activeopacity = 0.9;
    };
  };
};

Complex matchers and compound rule types (workspace selectors, monitor selectors, fullscreen state, opacity, center with reserved area, max/min size, move) are fully typed in types.nix.

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

Defines custom-settings.slideIn — a list of edge-sliding popup windows. Each entry configures a keybind, executable, window class, position (left/right/top/bottom/edge/side), and optional window rules. Uses hdrop for dropdown-style window management.

lua.nix

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

  • enable (boolean, default false) — Enable pure Lua configuration files with Nix substitution support.

  • variables (attrs of nullOr str, default {}) — Key-value pairs for @placeholder@ substitution in Lua source files. Each key foo replaces @foo@ in all sourced Lua modules with the given value. 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, default [ ./lua/binds.lua ]) — Lua source files to copy into the Hyprland config directory and require from init.lua. Each file undergoes @placeholder@ substitution using the variables attrset. The default list includes lua/binds.lua.

  • applicationBinds (attrs of str, default {}) — Application keybinds passed into Lua generation. Each attr key is bind string (for example "SUPER+Return") and each attr value is command string. Rendered into @applicationBinds@ as Lua table entries consumed by binds.lua:

    custom-settings.lua.applicationBinds = {
      "SUPER + Return" = "${pkgs.kitty}/bin/kitty";
      "SUPER + E" = "${pkgs.nautilus}/bin/nautilus";
    };
    

    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.

lua/binds.lua

The default Lua bind template at modules/home-manager/core/hyprland/lua/binds.lua. Uses @placeholder@ substitution for dynamic injection:

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 null)
@DEFAULT_AUDIO_SOURCE@custom-settings.lua.variablesAudio source name (default null)

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 (added in flake/home-manager/flake.nix).

The module:

  • Enables programs.noctalia and systemd, pins package from inputs.noctalia.packages, and applies a Hyprland layer blur rule for Noctalia windows.
  • Mirrors a full exported Noctalia v5 config as a typed Nix attrset (noctaliaSettings), covering bar layouts with monitor overrides, shell panel/screen corners/screenshot/session actions, theme (builtin “Noctalia” with community palette “Tokyo Night Moon”), wallpaper (directory, default/last/monitor paths, automation), 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.pathshell.avatar_path and core.profile.wallpaper.directorywallpaper.directory. Wallpaper fill mode is hardcoded to crop (not a profile option).
  • Location driven by core.profile.location.secret (SOPS secret name). Two modes:
    • Normal (secret == null): sets programs.noctalia.settings with build-time validation. 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 decrypted sops.secrets.<name>.path. Clear text never in repo or Nix store.

The user-side Hyprland config (home/racci/features/desktop/hyprland/) pairs with this module via Noctalia IPC keybinds:

BindingAction
SUPER+SPACESUPER+SHIFT+Ffullscreen (displaced by Noctalia launcher bind)
SUPER+SSUPER+gravespecial workspace toggle (displaced by Noctalia control center bind)
SUPER+SHIFT+SSUPER+SHIFT+gravemove window to special workspace (displaced by Noctalia settings bind)
SUPER+commaNoctalia settings
Audio/brightness keysnoctalia msg ... dispatchers

Workspace rules in the user config now set persistent = true for defined workspaces, ensuring they are always available regardless of Noctalia lifecycle.

Look settings are tuned toward Noctalia documentation recommendations: gaps_in = 5, gaps_out = 10, rounding_power = 2, shadow range/render/color tuned, and blur size/passes/vibrancy adjusted.

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 = {
      windowrule."kitty-floating" = {
        matcher = [ { class = "kitty"; } ];
        rule = {
          float = true;
          size = { width = "40%"; height = "60%"; };
          center = true;
        };
      };

      permission = {
        screenCopy = [ pkgs.firefox ];
      };

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

Notes

  • 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.
  • Unknown dispatchers in Lua raises 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.

Home-Manager: Profile

This page documents the core profile module at:

  • modules/home-manager/core/profile.nix

It is imported automatically via modules/home-manager/core/default.nix.

The module declares a set of shared user-profile options under core.profile. These values are consumed by other modules — desktop environments, shell companions, and display managers — so they reference a single source of truth rather than hardcoded literals.


Options

core.profile.avatar.path

  • Type: string
  • Default: ~/.face

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

  • Type: string
  • Default: ~/Pictures/Wallpapers

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

  • Type: nullOr string
  • Default: null

Name of a SOPS secret whose decrypted value becomes the Noctalia location.address. When set to null 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.


Usage Example

{ ... }: {
  core.profile = {
    avatar.path = "/home/user/.face";

    wallpaper.directory = "/home/user/Pictures/Backgrounds";

    location.secret = "noctalia-location";
  };
}

Notes

  • The module only declares options; it does nothing on its own. Consumers use config.core.profile.* to read the values.
  • The GNOME azwallpaper extension consumes core.profile.wallpaper.directory via dconf-extensions.nix (home/shared/desktop/gnome/).

Home-Manager: list-ephemeral

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

Options

programs.list-ephemeral.enable

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.


Usage

Default TUI (fzf-based with keybindings):

list-ephemeral

TUI Keybindings

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

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

List mode:

list-ephemeral list

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

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

Snippet Generation

The TUI generates Nix snippets based on path location:

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

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

Packages Overview

Purpose

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

Entry Points

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

Key Options/Knobs

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

Common Workflows

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

Overlays Overview

Purpose

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

Entry Points

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

Key Options/Knobs

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

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.
  • hermes-agent: Local overlay that builds Hermes Agent from upstream source plus two patches:
  • 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.
  • home/racci/hm-config.nix: Uses pkgs.hermes-desktop (patched via overlay) instead of the unpatched upstream inputs.hermes-agent.packages.<system>.desktop.

Common Workflows

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

Hosts Overview

Purpose

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

Entry Points

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

Key Options/Knobs

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

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

Examples:

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

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

Common Workflows

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

References


Decky Loader Lifecycle

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

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

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

Log filtering

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

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

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

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

Lib Overview

Purpose

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

Entry Points

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

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