Introduction
Installation Guide
This guide covers various installation scenarios for the nix-config repository.
Windows Subsystem for Linux (WSL)
Prerequisites
- Windows 10 version 2004 and higher (Build 19041 and higher) or Windows 11
- WSL 2 enabled
- Administrator access to Windows
Installation Steps
1. Install WSL 2
# Run in PowerShell as Administrator
wsl --install
# If WSL is already installed, ensure you're using WSL 2
wsl --set-default-version 2
2. Setup NixOS for WSL
Download and install NixOS-WSL via NixOS-WSL:
# Download the latest NixOS-WSL tarball
# Import the NixOS-WSL distribution
wsl --import NixOS .\NixOS\ nixos-wsl.tar.gz --version 2
# Start the NixOS instance
wsl -d NixOS
3. Configure NixOS-WSL
After starting your NixOS-WSL instance:
# Clone this repository
sudo git clone https://github.com/DaRacci/nix-config.git /etc/nixos
# Apply the WSL configuration
sudo nixos-rebuild switch --flake /etc/nixos#winix
4. WSL-Specific Features
The WSL configuration includes:
- SSH agent relay between Windows and WSL
- Hardware acceleration support for development
- Remote desktop capabilities
- Optimized for headless operation
Native NixOS Installation
Prerequisites
- NixOS installation media
- Target hardware
- Network connectivity
- Backup of important data
Installation Process
1. Boot from NixOS Installation Media
- Download NixOS ISO from nixos.org
- Create bootable USB/DVD
- Boot from installation media
2. Network Configuration
# For WiFi connections
sudo systemctl start wpa_supplicant
wpa_cli
> add_network
> set_network 0 ssid "YourSSID"
> set_network 0 psk "YourPassword"
> enable_network 0
> quit
# Verify connectivity
ping nixos.org
3. Disk Setup
Follow standard NixOS installation procedures for disk partitioning and filesystem setup as described in the NixOS manual.
4. Generate Hardware Configuration
# Generate hardware configuration
nixos-generate-config --root /mnt
# Copy to your host configuration
mkdir -p /mnt/etc/nixos/hosts/{device-type}/{hostname}
cp /mnt/etc/nixos/hardware-configuration.nix /mnt/etc/nixos/hosts/{device-type}/{hostname}/hardware.nix
# Clone this repository
cd /mnt/etc/nixos
git clone https://github.com/DaRacci/nix-config.git .
5. Customize Host Configuration
Edit hosts/{device-type}/{hostname}/default.nix and hardware.nix according to your needs.
6. Install NixOS
# Install with your specific host configuration
nixos-install --flake .#{hostname}
# Set root password when prompted
7. Post-Installation
# Reboot into new system
reboot
# After reboot, ensure configuration is applied
sudo nixos-rebuild switch --flake /etc/nixos#{hostname}
Existing NixOS System Migration
From Traditional NixOS Configuration
1. Backup Current Configuration
# Backup current configuration (adjust path if using flakes)
sudo cp -r /etc/nixos /etc/nixos.backup
2. Clone This Repository
# Clone to a working directory
git clone https://github.com/DaRacci/nix-config.git /tmp/nix-config
sudo cp -r /tmp/nix-config/* /etc/nixos/
3. Create Host Configuration
# Create your host directory
sudo mkdir -p /etc/nixos/hosts/{device-type}/{hostname}
# Migrate your hardware configuration
sudo cp /etc/nixos.backup/hardware-configuration.nix /etc/nixos/hosts/{device-type}/{hostname}/hardware.nix
# Create default.nix based on your old configuration
# Edit to follow the new structure
4. Test and Apply
# Test the new configuration
sudo nixos-rebuild build --flake .#{hostname}
# Apply if build succeeds
sudo nixos-rebuild switch --flake .#{hostname}
IO Guardian - Database Availability System
The IO Guardian system ensures that services across the infrastructure are aware
of the availability of centralized databases (PostgreSQL and Redis) hosted on config.server.ioPrimaryHost.
It provides graceful startup and shutdown coordination between the database host and dependent services on other servers.
Overview
The system consists of two components:
-
Guardian Server (runs on client servers)
- WebSocket server that listens for commands from the coordinator
- Executes drain/undrain commands by controlling
io-databases.target
-
Guardian Client (runs on the IO Host)
- WebSocket client that connects to all guardian servers
- Sends
undraincommand after databases are online (start dependent services) - Sends
draincommand before database shutdown (stop dependent services)
How It Works
System Startup
- Client servers boot and run
wait-for-io-databases.service - This service waits (with retries) until PostgreSQL and Redis on the IO Host are reachable
- Once databases are confirmed available, the service completes
- The
io-databases.targetis now ready to be activated - When the IO Hosts
io-database-coordinator.servicestarts, it sendsundrainto all clients - Clients start
io-databases.target, which starts all dependent services
Database Shutdown (Graceful Drain)
- When
io-database-coordinator.servicestops (before databases stop) - It connects to all guardian servers via WebSocket
- Sends
draincommand to each server - Guardian servers stop
io-databases.target - Dependent services stop gracefully before databases go down
Database Startup (Undrain)
- When databases come online on the IO Host
io-database-coordinator.servicestarts- It sends
undraincommand to all guardian servers - Guardian servers start
io-databases.target - 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.targetis active - Stop when
io-databases.targetstops - Restart when the target restarts
Systemd Units
On Client Servers
| Unit | Type | Description |
|---|---|---|
io-guardian.service | simple | WebSocket server for receiving commands |
io-databases.target | target | Represents “databases are online” |
wait-for-io-databases.service | oneshot | Waits for databases at boot (runs once) |
On nixio
| Unit | Type | Description |
|---|---|---|
io-database-coordinator.service | oneshot | Sends 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_PSKsecret is properly configured - Verify the sops decryption is working:
cat /run/secrets/IO_GUARDIAN_PSK
Services not starting after boot:
- Check wait service
logs:
journalctl -u wait-for-io-databases.service - Verify network connectivity to an IO Host on ports 5432 (Postgres) and 6379 (Redis)
- Ensure an IO Hosts coordinator has sent the undrain command
Authentication failures in logs:
- Ensure the same PSK is deployed to all servers
- Re-encrypt secrets if the key was changed
Protocol Reference
The guardian uses a simple JSON-based WebSocket protocol:
Authentication
// Client sends:
{"type": "auth", "key": "<psk>"}
// Server responds:
{"type": "auth", "status": "ok", "message": "Authentication successful"}
// or
{"type": "auth", "status": "error", "message": "Invalid key"}
Commands
// Coordinator sends:
{"type": "command", "action": "drain"}
// or
{"type": "command", "action": "undrain"}
// or
{"type": "command", "action": "ping"}
// Server responds:
{"type": "response", "action": "<action>", "status": "ok", "message": "..."}
// or
{"type": "response", "action": "<action>", "status": "error", "message": "..."}
Server Cluster Monitoring
The monitoring module provides a comprehensive observability stack for the server cluster using Prometheus (metrics), Loki (logs), and Grafana (visualization). All components are configured as reusable NixOS modules with automatic cross-host discovery.
Overview
The system consists of three layers:
-
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, andstatusare available in Loki - Ingest-time log parsing for journal
stdoutentries and Caddy access logs to inferdetected_leveland normalize common timestamp formats - Application-specific exporters (Caddy, PostgreSQL, Redis) enabled automatically
-
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
-
Visualization (runs on the monitoring primary host)
- Grafana with provisioned datasources and dashboards
- Native Kanidm OAuth2 authentication
Architecture
┌─────────────────────────────────────────────────────┐
│ nixmon (Monitoring Primary) │
│ ┌──────────┐ ┌──────┐ ┌─────────┐ ┌──────────┐ │
│ │Prometheus │ │ Loki │ │ Grafana │ │Alertmgr │ │
│ │ :9090 │ │:3100 │ │ :3000 │ │ :9093 │ │
│ └────┬──┬──┘ └──┬───┘ └─────────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┘ │ ┌────┘ ┌────────────────┘ │
│ │ scrape│ │ push │ webhooks │
├──┼───────┼───┼─────────────┼────────────────────────┤
│ ▼ ▼ ▼ ▼ │
│ All servers: Home Assistant / Nextcloud │
│ - node_exporter :9100 │
│ - alloy → Loki │
│ - caddy metrics :2019 (if proxy configured) │
│ - postgres_exporter :9187 (if postgres configured) │
│ - redis_exporter :9121 (if redis configured) │
│ - pve_exporter :9221 (nixmon only, Proxmox API) │
└─────────────────────────────────────────────────────┘
Configuration
Enabling Monitoring
Monitoring is enabled by default on all servers (server.monitoring.enable = true).
The monitoring primary host is configured via the allocations.server.monitoringPrimaryHost
option, currently set to nixmon.
Options Reference
All options live under server.monitoring:
| Option | Type | Default | Description |
|---|---|---|---|
enable | bool | true | Enable monitoring for this server |
retention.metrics | string | "90d" | Prometheus TSDB retention period |
retention.logs | string | "90d" | Loki log retention period |
exporters.node.enable | bool | true | Enable node_exporter |
exporters.caddy.enable | bool | auto | Enable Caddy metrics (auto if proxy configured) |
exporters.postgres.enable | bool | auto | Enable PostgreSQL exporter (auto on IO host) |
exporters.redis.enable | bool | auto | Enable Redis exporter (auto on IO host) |
logs.enable | bool | true | Enable Alloy log shipping |
collector.enable | bool | auto | Enable collectors (auto on monitoring host) |
collector.grafana.kanidm.enable | bool | true | Enable Kanidm OAuth2 for Grafana |
collector.alerting.enable | bool | true | Enable Alertmanager |
collector.alerting.homeAssistant.enable | bool | false | Enable Home Assistant webhook alerting |
collector.alerting.nextcloudTalk.enable | bool | false | Enable Nextcloud Talk webhook alerting |
collector.proxmox.enable | bool | true | Enable Proxmox VE metrics collection |
Auto-Detection
The module automatically detects and enables exporters based on host role:
- Caddy exporter: Enabled when
server.proxy.virtualHostsis 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
processescollector to expose per-process stats - Collector services: Enabled only on the monitoring primary host
Secrets
The monitoring module requires the following secrets in hosts/server/nixmon/secrets.yaml:
MONITORING:
GRAFANA:
SECRET_KEY: <random-secret-key>
OAUTH_SECRET: <kanidm-oauth2-secret>
HOME_ASSISTANT:
WEBHOOK_URL: <ha-webhook-url>
NEXTCLOUD_TALK:
WEBHOOK_URL: <nc-talk-webhook-url>
PROXMOX:
USER: <proxmox-user-at-realm>
TOKEN_ID: <proxmox-token-name>
TOKEN_SECRET: <proxmox-token-secret>
Generating Secrets
Generate the Grafana secret key:
cat /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 48
The MONITORING/GRAFANA/OAUTH_SECRET must match the value in hosts/server/nixcloud/secrets.yaml
under KANIDM/OAUTH2/GRAFANA_SECRET (the Kanidm provisioning side).
Caddy Virtual Hosts
The module configures three virtual hosts on nixmon:
| Service | Subdomain | Access |
|---|---|---|
| Grafana | grafana.<domain> | Public |
| Prometheus | prometheus.<domain> | LAN |
| Loki | loki.<domain> | LAN |
These are defined in hosts/server/nixmon/default.nix and collected by the IO
primary host’s Caddy configuration.
Alert Rules
The following alerts are configured by default:
| Alert | Condition | Severity |
|---|---|---|
HostDown | up{job="node"} == 0 for 2 minutes | Critical |
DiskSpaceCritical | Root filesystem < 10% free for 5 minutes | Critical |
HighCPUUsage | CPU usage > 90% for 5 minutes | Warning |
HighMemoryUsage | Memory usage > 90% for 5 minutes | Warning |
ServiceDown | up{job!="node"} == 0 for 2 minutes | Critical |
Alerts are routed to:
- Home Assistant: All critical and warning alerts via webhook (requires
collector.alerting.homeAssistant.enable = true) - Nextcloud Talk: Critical alerts only via webhook (requires
collector.alerting.nextcloudTalk.enable = true)
Module Structure
modules/nixos/server/monitoring/
├── default.nix # Entry point, imports sub-modules
├── options.nix # All server.monitoring.* options
├── collector/
│ ├── default.nix # Imports collector sub-modules
│ ├── prometheus.nix # Prometheus server + scrape targets
│ ├── loki.nix # Loki server + storage config
│ ├── grafana.nix # Grafana + Kanidm OAuth2
│ ├── alerting.nix # Alertmanager + alert rules
│ └── dashboards.nix # Dashboard provisioning
├── exporters/
│ ├── default.nix # Imports exporter sub-modules
│ ├── node.nix # node_exporter
│ ├── caddy.nix # Caddy metrics
│ ├── postgres.nix # PostgreSQL exporter
│ └── redis.nix # Redis exporter
├── logs/
│ └── alloy.nix # Alloy log shipping
└── integrations/
└── proxmox.nix # PVE exporter for Proxmox API
Troubleshooting
Checking Service Status
On the monitoring host (nixmon):
systemctl status prometheus.service
systemctl status loki.service
systemctl status grafana.service
systemctl status prometheus-alertmanager.service
systemctl status prometheus-pve-exporter.service
On any server:
systemctl status prometheus-node-exporter.service
systemctl status alloy.service
Verifying Metrics Collection
Check Prometheus targets are up:
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {instance: .labels.instance, health: .health}'
Verifying Log Collection
Alloy applies ingest-time parsing for journal stdout logs and Caddy access logs before forwarding to Loki:
-
Caddy access logs are read as JSON, not plain text
-
Legacy timestamps in form
YYYY/MM/DD HH:MM:SSare parsed and used as event timestamps -
ISO-8601 timestamps with a log level prefix are parsed and normalized
-
detected_leveldefaults toinfowhen the source log line does not provide one -
Caddy JSON fields
level,ts,logger, andstatusare extracted into Loki labels and timestamps -
Caddy access logs are read from
/var/log/caddy-access-*.logand 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_SECRETin nixmon matchesKANIDM/OAUTH2/GRAFANA_SECRETin 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_idandproxmox/token_secretare 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}
3. Link User to Hosts
The auto-discovery system will automatically link users to hosts if:
- A file
home/{username}/{hostname}.nixexists - 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
modules/nixos/: Contains NixOS-specific modules.modules/flake/: Flake-level modules for cross-host configuration.modules/home-manager/: Contains Home-Manager-specific modules.
NixOS Modules
This section covers all NixOS modules provided by this flake.
NixOS Services
This section documents the custom NixOS service modules available in this configuration. These modules provide specialised integrations and monitoring capabilities.
AI Agent
AI Agent
Autonomous AI Agent service powered by Zeroclaw, providing intelligent task automation with security controls for code review and development tasks.
- Entry point:
modules/nixos/services/ai-agent.nix - Upstream: Zeroclaw Services
Special Options
services.ai-agent.enable: Enable the autonomous AI Agent service.
Allowed Domains
The AI Agent is configured to access the following domains for development, coding, and general information tasks:
- Version Control & Collaboration:
github.com,gitlab.com,codeberg.org,git.sr.ht,raw.githubusercontent.com - Programming Languages & Frameworks:
rust-lang.org,golang.org,registry.npmjs.org,pypi.org,developer.mozilla.org,cppreference.com - Documentation & Reference:
docs.rs,crates.io,devdocs.io,learn.microsoft.com,w3.org,gnu.org,curl.se,man.archlinux.org - General Information:
wikipedia.org - Package Management & Containers:
docker.io,hub.docker.com - Development Tools:
stackoverflow.com - NixOS Ecosystem:
nixos.org
Allowed Commands
The AI Agent has automatic approval for the following common and safe bash utilities and developer tools:
- File Operations:
cat,diff,fd,file,find,head,od,strings,tail,tree,wc - Text Processing & Search:
awk,cut,echo,grep,jq,printf,rg,sed,sort,tr,uniq - Archiving & Compression:
gzip,gunzip,tar - Hashing & Encoding:
base64,md5sum,sha256sum - System Information:
date,pwd,uname,whoami - Network & Connectivity:
curl,dig,ping,wget - Text Editors & Pagers:
less,more,nano,vim - Rust Development:
cargo,cargo-build,cargo-check,cargo-test,rustc,rustfmt,rustup - Nix Tools:
nix,nix-build,nix-env,nix-flake,nix-fmt,nix-shell - C/C++ Development:
clang,cmake,g++,gcc - Version Control:
git,hg,jj - Language Runtimes:
go,lua,node,npm,pip,poetry,python,python3,ruby - Build Tools:
make - Debugging & Analysis:
gdb,lldb,ltrace,strace,valgrind
Specialized Agents
The AI Agent service includes 15 specialized agents designed for different development tasks. Each agent is optimized for specific roles:
Deep Work & Architecture
- sisyphus: Deep architectural analysis and foundational problem solving (Claude Opus 4.6)
- metis: Strategic planning, best practices, and architectural wisdom (Claude Opus 4.6)
- architect: System design, architecture decisions, and scalability planning (Claude Opus 4.6)
Code Understanding & Exploration
- atlas: Code exploration, pattern discovery, and codebase navigation (GPT-5.2 Codex)
- prometheus: Code analysis, refactoring planning, and optimization strategies (GPT-5.2 Codex)
- explorer: Quick code exploration and debugging assistance (GPT-5 Mini)
Implementation & Building
- hephaestus: Implementation, building, and crafting solutions (GPT-5.2 Codex)
- validator: Test design, QA strategy, and reliability verification (GPT-5.2)
Quality & Security
- critic: Code review, quality analysis, and best practice enforcement (Claude Opus 4.6)
- guardian: Security analysis, vulnerability assessment, and hardening (Claude Opus 4.6)
Operations & Infrastructure
- devops: Infrastructure, deployment, and operational excellence (Claude Opus 4.6)
Knowledge & Communication
- oracle: General technical inquiry and consultation (GPT-5.2)
- librarian: Documentation lookup, API reference, and quick answers (GPT-5 Mini)
- scribe: Documentation writing, README creation, and technical communication (GPT-5 Mini)
General Purpose
- default: General purpose agent for miscellaneous tasks (Claude Opus 4.6)
Usage Example
{ ... }: {
services.ai-agent = {
enable = true;
};
}
Operational Notes
The AI Agent service runs with automatic approval for safe operations (file_read, memory_recall, web_fetch, web_search) as well as a curated set of non-destructive bash commands. It enforces security controls including OTP gating for sensitive domains (banking, finance, medical, government, identity providers) and an emergency stop capability. The service is configured to operate only within designated workspace boundaries and maintains a local SQLite memory backend with automatic saving. It runs a heartbeat check every 15 minutes to ensure operational health.
Huntress
Huntress
Managed EDR (Endpoint Detection and Response) platform that protects systems by detecting malicious footholds used by attackers.
- Entry point:
modules/nixos/services/huntress.nix - Upstream: Huntress Managed EDR
Special Options
services.huntress.accountKeyFile: Path to a file containing the Huntress account key.services.huntress.organisationKeyFile: Path to a file containing the Huntress organisation key.
Usage Example
{ config, ... }: {
services.huntress = {
enable = true;
accountKeyFile = config.sops.secrets.huntress_account_key.path;
organisationKeyFile = config.sops.secrets.huntress_org_key.path;
};
}
Operational Notes
The agent configuration is generated at /etc/huntress/agent_config.yaml during the service’s preStart phase. It merges the provided account and organisation keys using yaml-merge. The keys are securely loaded into the service using systemd LoadCredential.
MCPO
MCPO (Model Context Protocol Orchestrator)
Orchestrates Model Context Protocol (MCP) servers, providing a centralized way to manage and expose multiple MCP servers.
- Entry point:
modules/nixos/services/mcpo.nix - Upstream: MCPO GitHub Repository
Special Options
services.mcpo.configuration: An attribute set defining the MCP servers to orchestrate.services.mcpo.apiTokenFile: Optional path to a file containing an API token for the service.services.mcpo.extraPackages: Additional packages to include in the service’sPATH.services.mcpo.helpers: Read-only attribute set of helper functions for common server types (e.g.,npxServer,uvxServer).
Usage Example
{ config, ... }: {
services.mcpo = {
enable = true;
configuration = {
everything = config.services.mcpo.helpers.npxServer "@modelcontextprotocol/server-everything";
};
};
}
Operational Notes
MCPO runs as a DynamicUser with a state directory at /var/lib/mcpo. The configuration is rendered via sops.templates and loaded into the service via systemd credentials. The service’s PATH includes bash, nodejs, and uv by default to support various MCP server types.
Metrics
Metrics & Hacompanion
Comprehensive metrics collection and integration with Home Assistant via hacompanion.
- Entry point:
modules/nixos/services/metrics.nix - Upstream: Hacompanion GitHub Repository
Special Options
services.metrics.hacompanion.enable: Enable the Home Assistant Companion service.services.metrics.hacompanion.sensor.<name>.enable: Enable specific built-in sensors (e.g.,cpu_temp,memory,uptime).services.metrics.hacompanion.script: Define custom scripts to expose as sensors or switches in Home Assistant.services.metrics.hacompanion.storage: Configure monitoring for storage devices and ZFS pools.services.metrics.upgradeStatus.enable: Enable a specialized sensor for tracking NixOS upgrade status.
Usage Example
{ ... }: {
services.metrics.hacompanion = {
enable = true;
sensor.cpu_temp.enable = true;
sensor.memory.enable = true;
storage.main = {
name = "Main OS Drive";
sensors.used = true;
};
};
}
Operational Notes
Hacompanion uses a generated TOML configuration file and securely loads the Home Assistant API token from sops.secrets.HACOMPANION_ENV. The upgradeStatus feature can also integrate with Uptime Kuma to provide heartbeat notifications for successful system upgrades.
Tailscale
Tailscale
Extensions to the standard NixOS Tailscale module, providing easier tag management.
- Entry point:
modules/nixos/services/tailscale.nix - Upstream: Tailscale Tags Documentation
Special Options
services.tailscale.tags: A list of tags to advertise for this device. These tags are automatically prefixed withtag:when passed totailscale up.
Usage Example
{ ... }: {
services.tailscale = {
enable = true;
tags = [ "server" "internal" ];
};
}
Operational Notes
This module simplifies the application of Tailscale tags by automatically constructing the --advertise-tags flag. Ensure that the device has the necessary permissions in your Tailscale ACLs to apply the requested tags.
Core Module
Documents shared NixOS core modules used across hosts.
Purpose
modules/nixos/core/ contains reusable host-level defaults and feature modules.
It also defines top-level baseline options under core.* that control this shared behavior for most hosts.
Top-Level Options
core.enable
| Type | bool |
| Default | true |
Master switch for shared core baseline.
core.audio.enable
| Type | bool |
| Default | !config.host.device.isHeadless |
Enable shared audio stack on non-headless hosts by default.
core.bluetooth.enable
| Type | bool |
| Default | !config.host.device.isHeadless |
Enable Bluetooth support on non-headless hosts by default.
core.network.enable
| Type | bool |
| Default | !config.host.device.isVirtual |
Enable shared network baseline on non-virtual hosts by default.
Baseline Behaviour
When core.enable is true, module applies shared defaults from modules/nixos/core/default.nix:
- sets
services.dbus.implementation = "broker", - enables PipeWire audio stack and disables PulseAudio when
core.audio.enableis on, - enables Bluetooth stack, Blueman, and persisted Bluetooth state when
core.bluetooth.enableis on, - enables NetworkManager and adds
networkto shared default groups whencore.network.enableis on, and - on non-headless hosts, adds
videoandi2cgroups and enablesdleyna,gnome-keyring,udisks2,colord,xserver.updateDbusEnvironment, andpolkit.
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
- Activation
- Auto Upgrade
- Containers
- Display Manager
- Gaming
- Generators
- Groups
- Locale
- Nix
- OpenSSH
- Printing
- Remote Access
- Security
- SOPS
- Stylix
- Virtualisation
- WSL
Usage Example
{ ... }: {
core = {
enable = true;
audio.enable = true;
bluetooth.enable = true;
network.enable = true;
};
}
Notes
These modules are imported through modules/nixos/core/default.nix. Most feature pages document their own core.<name> option namespaces, while some baseline modules such as Nix apply unconditionally once imported.
Activation
Reports system generation changes during NixOS activation.
- Entry point:
modules/nixos/core/activation.nix
Overview
This module adds activation-time diff reporting with nvd. During activation it compares previous and new system generations and prints package and closure changes.
Options
core.activation.enable
| Type | bool |
| Default | config.core.enable |
Enable activation diff reporting.
Behaviour
When enabled, module installs system.activationScripts.report-changes that:
- finds previous and newest system profile links under
/nix/var/nix/profiles, - resolves both links to store paths, and
- runs
nvd diffbetween 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 ifnvd diffreturns non-zero. - Default follows top-level
core.enable, so most hosts get generation diff reporting automatically.
Auto Upgrade
Schedules automatic NixOS upgrades from flake host outputs.
- Entry point:
modules/nixos/core/auto-upgrade.nix
Overview
This module configures system.autoUpgrade to rebuild host from github:DaRacci/nix-config#<host>. It also applies resource limits to nixos-upgrade.service so scheduled upgrades run with lower CPU and IO priority.
Options
core.auto-upgrade.enable
| Type | bool |
| Default | true |
Enable automatic upgrade configuration.
core.auto-upgrade.hostName
| Type | string |
| Default | config.networking.hostName |
Host output name used in flake reference github:DaRacci/nix-config#<hostName>.
Behaviour
When enabled, module configures:
system.autoUpgrade.dates = "04:00",randomizedDelaySec = "45min"to spread out upgrade times across hosts,- flags
--refresh,--accept-flake-config, and--no-update-lock-file, - service resource controls for
nixos-upgrade.service.
Auto-upgrade itself only turns on when flake has self.rev, meaning repository is in clean revisioned state.
Usage Example
{ ... }: {
core.auto-upgrade = {
enable = true;
hostName = "my-host";
};
}
Operational Notes
- Dirty working trees or non-revisioned evaluations leave
system.autoUpgrade.enable = false. - Module always points upgrades at GitHub flake source, not local checkout.
- Resource limits set
CPUWeight = 20,CPUQuota = 65%, andIOWeight = 20on upgrade service.
Containers
Enables Docker-based container runtime defaults.
- Entry point:
modules/nixos/core/containers.nix
Overview
This module turns on Docker as primary container backend and configures OCI containers to use Docker. It also enables weekly image pruning and persists Docker state directories.
Options
core.containers.enable
| Type | bool |
| Default | disabled |
Enable shared container runtime configuration.
Behaviour
When enabled, module:
- enables
virtualisation.docker, - sets
virtualisation.docker.package = pkgs.docker, - enables CDI support with
daemon.settings.features.cdi = true, - enables weekly
docker autoPrune, - sets
virtualisation.oci-containers.backend = "docker", - adds
dockertocore.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.defaultGroupshandling.
Display Manager
Configures display manager for graphical sessions on desktop and laptop hosts.
- Entry point:
modules/nixos/core/display-manager.nix
Overview
This module sets up greetd with tuigreet as default display manager. It is automatically enabled on hosts where host.device.isHeadless = false.
When session packages are present in services.displayManager.sessionPackages, module also passes both Wayland and X session directories to tuigreet.
Options
core.display-manager.enable
| Type | bool |
| Default | !config.host.device.isHeadless |
Enable display manager configuration. Automatically disabled on headless hosts.
Behaviour
When enabled, greetd starts with tuigreet providing terminal-based greeter that:
- shows current time with
--time, - remembers last logged-in user with
--remember, - remembers last selected session with
--remember-session, and - adds
--sessionsand--xsessionsonly whenservices.displayManager.sessionPackagesis 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
greetdruns asgreeteruser.- Both Wayland (
wayland-sessions) and X11 (xsessions) session paths are built dynamically from installed session packages, so adding new session package is enough to make it appear in greeter.
Gaming
Enables gaming, VR, and Steam-focused desktop features.
- Entry point:
modules/nixos/core/gaming.nix
Overview
This module configures desktop gaming stack around Steam, 32-bit graphics support, Android ADB tools, and WiVRn streaming. It also adds firewall rules, udev rules for common gaming devices, and optional Decky Loader lifecycle integration.
Options
core.gaming.enable
| Type | bool |
| Default | disabled |
Enable gaming feature set.
Behaviour
When enabled, module:
- adds
adbuserstocore.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-runandpkgs.xwininfoas Steam extra packages, - adds
pkgs.proton-ge-binas compatibility package, - opens Steam Remote Play and local transfer firewall rules,
- enables
services.wivrnwithautoStart,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.servicefrom auto-starting at boot, - adds Polkit rule so active local user can start and stop
decky-loader.service, and - when Home Manager is present, installs user service that polls Steam PID file, starts Decky Loader once Steam is running, and stops it after Steam exits.
Usage Example
{ ... }: {
core.gaming.enable = true;
}
Operational Notes
- Module assumes desktop-class host with graphics stack and Steam support.
- WiVRn config uses NVENC H.265 encoder entries and enables
pkgs.wayvras application. - Some extra behavior only appears when related modules already exist, such as Jovian Decky Loader and Home Manager.
Generators
Configures image and container generator support for shared NixOS hosts.
- Entry point:
modules/nixos/core/generators.nix
Overview
This module imports nixos-generators formats and exposes core.generators options for generator-specific setup.
Current focus is Proxmox LXC image generation. When enabled for virtual hosts, module adds activation logic that prompts for SSH host private key on first boot, validates it with ssh-keygen, and stores it under /persist/etc/ssh/ssh_host_ed25519_key so later secret management can install it into /etc/ssh.
Options
core.generators.enable
| Type | bool |
| Default | config.core.enable |
Enable shared generator configuration and import generator formats from nixos-generators.
core.generators.proxmoxLXC.enable
| Type | bool |
| Default | cfg.enable && config.host.device.isVirtual |
Enable Proxmox LXC generator integration. Adds first-boot SSH private key prompt and Proxmox LXC format configuration.
core.generators.proxmoxLXC.sedPath
| Type | path |
| Default | getExe' pkgs.busybox "sed" |
Path to sed binary used to extract pasted OpenSSH private key block from terminal input.
core.generators.proxmoxLXC.sshKeygenPath
| Type | path |
| Default | getExe' pkgs.openssh "ssh-keygen" |
Path to ssh-keygen binary used to validate pasted private key and derive matching public key.
core.generators.proxmoxLXC.clearPath
| Type | path |
| Default | getExe' pkgs.busybox "clear" |
Path to clear binary used between failed interactive prompt attempts.
Assertions and Behaviour
When core.generators.proxmoxLXC.enable = true, module asserts that image contains /etc/ssh/ssh_host_ed25519_key.pub.
If activation runs without controlling terminal, prompt is skipped and activation exits cleanly. If terminal exists, module loops until pasted private key:
- contains valid OpenSSH private key block,
- passes
ssh-keygen -y, and - matches public key already present at
/etc/ssh/ssh_host_ed25519_key.pub.
Usage Example
{ ... }: {
core.generators = {
enable = true;
proxmoxLXC = {
enable = true;
};
};
}
Operational Notes
nixos-generatorsformats are imported unconditionally by module, but runtime configuration only applies whencore.generators.enableis on.- Proxmox LXC flow is designed for images where public host key is baked into image but private key must be supplied interactively after boot.
- Stored private key lives in
/persist, so persistence and later secret deployment must be configured for target host.
Groups
Applies shared extra group membership to all declared users.
- Entry point:
modules/nixos/core/groups.nix
Overview
This module defines core.defaultGroups, shared list of Unix groups appended to every configured standard user. Other core modules use this option to grant access to subsystems like audio, networking, Docker, printing, and virtualisation.
Options
core.defaultGroups
| Type | list of string |
| Default | [] |
Additional groups added to all users by default.
Behaviour
When core.defaultGroups is non-empty, module rewrites users.users entries so each declared user receives extraGroups = mkAfter cfg.defaultGroups.
This means module appends shared groups after any user-specific group configuration instead of replacing it.
Usage Example
{ ... }: {
core.defaultGroups = [
"audio"
"network"
"lp"
];
}
Operational Notes
- This module has no enable flag. It activates whenever
core.defaultGroupscontains values. - Group assignment applies to every user passed through module argument
users.
Locale
Sets shared timezone and locale defaults.
- Entry point:
modules/nixos/core/locale.nix
Overview
This module provides opinionated regional defaults for timezone and locale. It sets Australia/Sydney timezone and enables Australian and US English UTF-8 locales.
Options
core.locale.enable
| Type | bool |
| Default | true |
Enable locale baseline configuration.
Behaviour
When enabled, module sets:
time.timeZone = "Australia/Sydney",i18n.defaultLocale = "en_AU.UTF-8", andi18n.supportedLocales = [ "en_AU.UTF-8/UTF-8" "en_US.UTF-8/UTF-8" ].
All values use mkDefault, so host-specific configuration can still override them.
Usage Example
{ ... }: {
core.locale.enable = true;
}
Operational Notes
- Module is enabled by default.
- Because settings use
mkDefault, this module acts as baseline rather than hard override.
Nix
Defines shared Nix daemon, cache, and registry defaults.
- Entry point:
modules/nixos/core/nix.nix
Overview
This module establishes global Nix configuration for hosts in this flake. It sets overlays, system.stateVersion, trusted users, experimental features, binary caches, garbage collection, and registry-derived nixPath.
It also provisions cache push secret and attic-watch-store service for automatic uploads to remote Attic cache.
Options
This module does not define core.* options. It applies shared baseline configuration directly.
Behaviour
Module configures:
- overlays from
inputs.angrrandinputs.nix4vscode, system.stateVersionfromstate.versionfile in flake root,- trusted Nix users
rootand@wheel, nix.settings.auto-optimise-store = mkForce true,- experimental features
nix-command,flakes, andpipe-operator, - substituters, trusted substituters, and trusted public keys for
cache.nixos.org,nix-community, andcache.racci.dev, - daily automatic Nix GC, and
nix.nixPathderived fromconfig.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-storedepends onsops.secrets.CACHE_PUSH_KEYfromhosts/secrets.yaml.services.angrrkeeps system profiles for 14 days, latest 3 generations, current system, and booted system.
OpenSSH
Configures opinionated SSH server and client defaults.
- Entry point:
modules/nixos/core/openssh.nix
Overview
This module enables OpenSSH with ed25519-only host keys, disables password authentication, and generates known-host entries for all configured NixOS hosts in flake outputs.
It also wires SOPS-managed host private key into sshd and publishes matching public key under /etc/ssh/ssh_host_ed25519_key.pub.
Options
core.openssh.enable
| Type | bool |
| Default | true |
Enable opinionated OpenSSH server and client configuration.
Behaviour
When enabled, module:
- enables
services.openssh, - disables password authentication,
- sets
PermitRootLogin = "prohibit-password", - sets
GatewayPorts = "clientspecified", - configures
services.openssh.hostKeysfromconfig.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.knownHostsentries for every host inoutputs.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
localhostas 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, socore.sopsintegration usually pairs with this module.
Printing
Enables shared printer support for workstation-class NixOS hosts.
- Entry point:
modules/nixos/core/printing.nix
Overview
This module enables CUPS printing support on non-server, non-virtual hosts and installs common printer drivers used in this configuration.
When active, it turns on services.printing, adds HP and Gutenprint drivers, and includes Brother MFC-L3770CDW driver packages.
Options
core.printing.enable
| Type | bool |
| Default | config.host.device.role != "server" && !config.host.device.isVirtual |
Enable printing support. Default keeps printing off for servers and virtual machines.
Behaviour
When both config.core.enable and core.printing.enable are true, module:
- enables
services.printing, - installs printer drivers from
pkgs.hplip,pkgs.gutenprint,pkgs.gutenprint-bin,pkgs.cups-filters,pkgs.mfcl3770cdwlpr, andpkgs.mfcl3770cdwcupswrapper, and - adds
lptocore.defaultGroups.
Usage Example
{ ... }: {
core.printing.enable = true;
}
Operational Notes
- Module does not activate unless top-level
core.enableis also enabled. - Default is tuned for physical desktop or laptop systems where local or network printer access is expected.
core.defaultGroups = [ "lp" ]ensures standard users can access printer devices through shared default group handling.
Remote Access
Provides optional remote desktop and game-streaming capabilities for desktop hosts.
- Entry point:
modules/nixos/core/remote.nix
Overview
This module exposes core.remote with two independent sub-features:
| Sub-feature | Implementation | Purpose |
|---|---|---|
| Remote Desktop | xrdp | Full desktop access over RDP |
| Streaming | Sunshine | Low-latency game or desktop streaming |
Options
core.remote.enable
| Type | bool |
| Default | disabled |
Master switch. Nothing in this module activates unless this is true.
core.remote.remoteDesktop.enable
| Type | bool |
| Default | disabled |
Enable xrdp-based remote desktop access.
core.remote.remoteDesktop.startCommand
| Type | string |
| Default | "gnome-session" |
Command xrdp uses as defaultWindowManager.
core.remote.streaming.enable
| Type | bool |
| Default | disabled |
Enable Sunshine streaming server.
Behaviour
When core.remote.enable = true:
remoteDesktop.enableturns onservices.xrdp, setsdefaultWindowManager, and opens firewall for RDP.streaming.enableturns onservices.sunshine, enables auto-start, opens firewall, and setscapSysAdmin = true.- if Home Manager is present, streaming also persists
.config/sunshinethrough 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 DesktopandExclusive Desktop, - creates headless output at login with
hyprctl output create headless, and - keeps
HEADLESS-2disabled until Sunshine prep commands enable it.
| Application | Behaviour |
|---|---|
| Shared Desktop | Enables HEADLESS-2 at client resolution and leaves physical monitors active. |
| Exclusive Desktop | Enables HEADLESS-2, saves active physical monitor state to $XDG_STATE_HOME/hyprland-disabled-monitors-pre-sunshine.json, disables physical monitors, then restores them on disconnect. |
Usage Examples
Streaming only
{ ... }: {
core.remote = {
enable = true;
streaming.enable = true;
};
}
RDP only
{ ... }: {
core.remote = {
enable = true;
remoteDesktop = {
enable = true;
startCommand = "hyprland";
};
};
}
Operational Notes
- Two sub-features are independent. You can enable streaming without RDP, or RDP without streaming.
- Sunshine persistence and Hyprland settings are only added when Home Manager is present in system configuration.
- Hyprland-specific Sunshine application entries are only added when both streaming and Hyprland are enabled.
Security
Applies shared host security defaults.
- Entry point:
modules/nixos/core/security.nix
Overview
This module enables baseline security features such as sudo-rs, TPM2 support, Polkit, kernel protection flags, and open-file limits for users.
Options
core.security.enable
| Type | bool |
| Default | true |
Enable shared security baseline.
core.security.userLimit
| Type | unsigned integer |
| Default | 32768 |
Maximum number of open files per user. Applied to PAM login limits and user systemd default limit.
Behaviour
When enabled, module:
- sets
security.protectKernelImage = true, - enables
security.polkit, - disables classic
sudoand enablessecurity.sudo-rs.execWheelOnly, - enables
security.tpm2, - sets PAM
nofilelimit for all users tocore.security.userLimit, - sets
systemd.user.extraConfig = "DefaultLimitNOFILE=<limit>", and - sets kernel sysctl
fs.file-max = 65536.
Usage Example
{ ... }: {
core.security = {
enable = true;
userLimit = 65536;
};
}
Operational Notes
- Module leaves
security.lockKernelModules = falseeven while enabling other hardening defaults. userLimitaffects both PAM sessions and user systemd services, keeping file descriptor limits aligned.
SOPS
Configures shared SOPS and age decryption defaults.
- Entry point:
modules/nixos/core/sops.nix
Overview
This module imports sops-nix, sets host default secrets file, derives age SSH key paths from persisted host SSH keys, and declares managed SSH private key secret for OpenSSH.
Options
core.sops.enable
| Type | bool |
| Default | config.core.enable |
Enable automatic SOPS configuration.
core.sops.hostSecretsFile
| Type | path |
| Default | "${hostDirectory}/secrets.yaml" |
Path to host-specific SOPS secrets file inside flake.
Behaviour
When enabled, module:
- imports
inputs.sops-nix.nixosModules.sopsunless function argumentimportExternals = false, - sets
sops.defaultSopsFiletocore.sops.hostSecretsFile, - builds
sops.age.sshKeyPathsfrom persisted host SSH key path first, then appends any configured ed25519 OpenSSH host keys, and - declares
sops.secrets.SSH_PRIVATE_KEYat/etc/ssh/ssh_host_ed25519_keywithsshd.servicerestart 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.hostKeysto ed25519 keys before adding them tosops.age.sshKeyPaths. core.opensshtypically consumessops.secrets.SSH_PRIVATE_KEYdeclared here.
Stylix
Applies shared system theme defaults with Stylix.
- Entry point:
modules/nixos/core/stylix.nix
Overview
This module imports Stylix and enables dark Tokyo Night theming on non-headless hosts by default.
Options
core.stylix.enable
| Type | bool |
| Default | !config.host.device.isHeadless |
Enable Stylix configuration. Default disables theming on headless hosts.
Behaviour
When enabled, module:
- imports
inputs.stylix.nixosModules.stylixunless function argumentimportExternals = false, - sets
stylix.enable = true, - sets
stylix.polarity = "dark", and - uses Tokyo Night dark Base16 scheme from
tinted-schemesinput.
Usage Example
{ ... }: {
core.stylix.enable = true;
}
Operational Notes
- Module is intended for graphical hosts.
- Theme source is
${inputs.stylix.inputs.tinted-schemes}/base16/tokyo-night-dark.yaml.
Virtualisation
Configures libvirt, VFIO passthrough, bridge networking, and guest isolation helpers.
- Entry point:
modules/nixos/core/virtualisation.nix
Overview
This module enables libvirt with QEMU, VFIO GPU passthrough, Looking Glass shared memory, bridge networking, custom OVMF firmware metadata, and libvirt hook helpers for selected guests.
It also generates helper scripts that change AllowedCPUs on host slices while selected guests run, detach and reattach passthrough GPUs for -single guests, and block host sleep while libvirt domains are active.
Options
core.virtualisation.enable
| Type | bool |
| Default | disabled |
Master switch for virtualisation support.
core.virtualisation.vmUsers
| Type | list of string |
| Default | [] |
Users that receive kvm and libvirtd group membership for VM management.
core.virtualisation.isolatedGuests
| Type | list of string |
| Default | [ "win11" "win11-gaming" ] |
Guest names that receive generated libvirt hook directories for CPU isolation helpers. Each guest also gets -single hook variants that add GPU detach and attach helpers.
core.virtualisation.bridgeInterface
| Type | string |
| Default | "br0" |
Bridge interface exposed to libvirt guests.
core.virtualisation.externalInterface
| Type | string |
| Default | "eth0" |
Physical interface attached to core.virtualisation.bridgeInterface.
core.virtualisation.cpuCores
| Type | int >= 4 |
| Default | 24 |
Total CPU core or thread count used by isolation helpers.
core.virtualisation.gpu.video
| Type | string |
| Default | "10de:1b06" |
PCI ID for passthrough GPU video device.
core.virtualisation.gpu.audio
| Type | string |
| Default | "10de:1bef" |
PCI ID for passthrough GPU audio device.
Behaviour
When enabled, module:
- imports external virtualisation helpers from
crtified.modules.virtualisation.nixand../desktop/vfio.nix, - enables
virtualisation.libvirtd, Spice USB redirection, andservices.spice-autorandr, - enables VFIO with
IOMMUType = "amd",disableEFIfb = true, and configured GPU devices, - configures Looking Glass shared memory file
looking-glassowned byracci:qemu-libvirtd, - adds
virt-manager,virtiofsd,virtio-win, andwin-spiceto system packages, - sets
LIBVIRT_DEFAULT_URI = qemu:///system, - creates bridge networking with DHCP on
bridgeInterfaceandexternalInterfaceenslaved into bridge, - adds
kvmfrkernel module package and modprobe configstatic_size_mb=128, and - installs udev rule for
/dev/kvmfraccess.
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, andinit.scopeCPU 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.cpuCoresis validated by both option type and assertion, so values below4fail evaluation.vmUsersis opt-in. Only listed users receivekvmandlibvirtdaccess.- Hook generation assumes guest naming convention where
<name>-singlemeans single-GPU passthrough workflow.
WSL
Adds Windows Subsystem for Linux specific integration and fixes.
- Entry point:
modules/nixos/core/wsl.nix
Overview
This module configures WSL-focused defaults such as default user, Windows interop, graphics library paths, nix-ld, and Start Menu launcher syncing.
Options
core.wsl.enable
| Type | bool |
| Default | disabled |
Enable WSL-specific configuration.
core.wsl.user
| Type | string |
| Default | none |
Default user used for WSL integration.
Behaviour
When enabled, module:
- sets
users.allowNoPasswordLogin = true, - installs
pkgs.wslu, - enables
programs.nix-ldwith C toolchain library for Remote WSL compatibility, - sets session variables for WSL graphics and library paths,
- enables
hardware.graphicsand addsconfig.hardware.graphics.package,config.hardware.graphics.package32, andpkgs.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, andunamethroughwsl.extraBinfor VS Code Remote WSL compatibility, and - copies per-user Home Manager
applicationsandiconsinto/usr/shareduring activation so launchers appear in Windows Start Menu.
Usage Example
{ ... }: {
core.wsl = {
enable = true;
user = "racci";
};
}
Operational Notes
core.wsl.useris required when WSL integration is enabled.- Extra binaries
dirname,readlink, andunameare exposed for VS Code Remote WSL compatibility. - Some behavior is conditional on separate WSL module being available in
options.
Server Module
The Server module provides a cluster-aware configuration for server hosts in the flake. It must be explicitly enabled using the server.enable option.
Purpose
The primary purpose of this module is to establish a shared environment for servers in the cluster, defining a coordinator node (ioPrimaryHost) and providing helper functions for inter-server communication and attribute collection.
Entry Point
modules/nixos/server/default.nix
Special Options and Behaviors
The main configuration entry point is server.enable. Once enabled, it sets up the server-specific baseline:
- Journald Persistence: Configured with a 14-day retention period and storage limits.
- Pre-Switch Checks: Runs
dixon 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
ioPrimaryHostis a critical component of the cluster, as many services (like Dashy or MinIO) rely on it as the central point of coordination.
Server Dashboard Module
The Server Dashboard module provides an integrated dashboard for monitoring and accessing services within the server cluster.
Purpose
The dashboard module integrates with Dashy and collects dashboard sections from all servers in the cluster to display on the ioPrimaryHost.
Entry Point
modules/nixos/server/dashboard.nix
Special Options and Behaviors
The module provides options under server.dashboard to define the section for each server:
server.dashboard.name: The name of the section in the dashboard, defaulting to the capitalized hostname.server.dashboard.icon: An optional icon for the section.server.dashboard.items: A set of dashboard items (withtitle,icon, andurl) to be displayed.server.dashboard.displayData: Arbitrary JSON data to be passed to the dashboard configuration.
Example Usage
Configure the dashboard section for a server:
# hosts/server/nixserv/default.nix
{
server.dashboard = {
name = "Services";
icon = "fas fa-server";
items = {
"Grafana" = {
title = "Grafana Dashboard";
icon = "fas fa-chart-line";
url = "https://grafana.example.com";
};
};
};
}
Operational Notes
- This module uses
getAllAttrsFuncto gatherserver.dashboardconfigurations from all servers in the cluster. - The aggregated configuration is only applied to the
ioPrimaryHost, which runs the primary Dashy instance. - This allows each server to define its own dashboard items, which are then automatically collected and displayed on a single unified dashboard.
Server Network Module
The Server Network module provides a declarative way to manage network configurations and firewall rules across the server cluster.
Purpose
The network module coordinates network subnet definitions and firewall rules, allowing for centralized configuration of subnets and automatic propagation of these settings to other servers in the cluster.
Entry Point
modules/nixos/server/network.nix
Special Options and Behaviors
The main configuration options are under server.network:
server.network.subnets: A list of subnet definitions, each with:dns: The DNS server for the subnet.domain: The domain name for the subnet.ipv4,ipv6: Configuration options (like CIDR and ARPA) for the subnet’s IP range.
server.network.openPortsForSubnet: Defines TCP and UDP ports to be opened on the firewall for each defined subnet.
Example Usage
Configure a subnet and open ports on a server:
# hosts/server/nixio/default.nix
{
server.network = {
subnets = [
{
dns = "192.168.1.1";
domain = "lan.example.com";
ipv4.cidr = "192.168.1.0/24";
}
];
openPortsForSubnet = {
tcp = [ 80 443 ];
};
};
}
Operational Notes
- This module uses
getIOPrimaryHostAttrto fetch theserver.network.subnetsconfiguration from theioPrimaryHost. - This ensures that all servers in the cluster are aware of the network structure defined on the coordinator host.
- The module automatically generates
iptablesandip6tablesrules for the specified ports, allowing traffic only from the defined subnets. - These rules are added to the
nixos-fwchain and are managed through thenetworking.firewall.extraCommandsandnetworking.firewall.extraStopCommandsoptions.
Server Distributed Builds Module
The Server Distributed Builds module provides a declarative way to manage distributed builds across the server cluster.
Purpose
The distributed builds module allows for distributed building of Nix derivations using remote build machines, providing a coordinator host and several build machines to distribute the build load.
Entry Point
modules/nixos/server/distributed-builds.nix
Special Options and Behaviors
The module provides options under server.distributedBuilder:
server.distributedBuilder.builderUser: The user to use when connecting to remote build daemons (default:builder).server.distributedBuilder.builders: A list of hostnames of remote build daemons to connect to for distributed builds.
Example Usage
Configure a build server and a host to use it for distributed builds:
# hosts/server/nixserv/default.nix (build server)
{
server.distributedBuilder = {
builders = [ "nixserv" ];
};
}
# hosts/server/nixdev/default.nix (host using build server)
{
server.distributedBuilder = {
builders = [ "nixserv" ];
};
}
Operational Notes
- This module coordinates the creation of a system user (
builder) on the build server and adds the necessary SSH keys to allow other hosts to connect. - On the hosts using the build server, the module automatically configures
nix.distributedBuildsand sets up the build machines usingnix.buildMachines. - The
builderuser is automatically added tonix.settings.trusted-userson the build server. - The module uses
self.nixosConfigurationsto dynamically discover the system architecture of the build machines. - For more information on distributed builds in Nix, see the NixOS Manual.
Database Submodule
The database submodule provides a managed interface for PostgreSQL and Redis across the server infrastructure. It centralizes database configuration on the primary database host (config.server.ioPrimaryHost) while allowing client services to declaratively request databases.
The module is implemented across several files in modules/nixos/server/database/:
default.nix: Core options and connection management.postgres.nix: PostgreSQL-specific provisioning and secrets.redis.nix: Redis-specific ID mappings and security.guardian.nix: Lifecycle synchronization and the IO Guardian.
Purpose
This submodule automates:
- Provisioning of PostgreSQL databases and roles.
- Management of Redis database IDs via static mappings.
- Synchronization of service lifecycle with database availability using the IO Guardian.
- Automated password handling via SOPS secrets.
Entry Points
server.database.postgres: Manage PostgreSQL databases and users (inpostgres.nix).server.database.redis: Manage Redis database instances (inredis.nix).server.database.host: Centralized host address for database connections (indefault.nix).server.database.dependentServices: Lifecycle coordination for dependent services (inguardian.nix).
Key Options and Behaviors
Connection Management
The server.database.host option determines how services connect to databases. On the primary database host (ioPrimaryHost), it defaults to localhost. On all other hosts, it defaults to the value of config.server.ioPrimaryHost.
PostgreSQL Management
When a service defines a database in server.database.postgres:
- Automatic Provisioning: The IO Host automatically creates the database and a role with the same name.
- Password Management: A SOPS secret is expected at
POSTGRES/<DB_NAME_UPPER>_PASSWORD. Database names containing hyphens (-) replace them with underscores (_) when constructing the secret path. The system automatically sets this password for the role during thepostgresql-setupservice. - 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/PASSWORDin SOPS. - Tooling: Use the
update-redis-mappingscommand 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-coordinatorservice manages thedrainandundrainsignals sent to clients during system startup and shutdown.
IO Primary Host Behavior
The host designated as the IO Primary Host (config.server.ioPrimaryHost) is responsible for running the actual database engines. It aggregates all database requirements from every host in the flake and applies them locally.
Storage
The storage module manages persistent storage abstractions, specifically for mounting S3-compatible buckets from MinIO as local filesystems.
Purpose
This submodule provides a declarative way to mount remote storage buckets. It handles the underlying FUSE configuration and credential mapping automatically.
Key Options and Behaviors
Bucket Mounts
The bucketMounts option uses s3fs-fuse to mount buckets from https://minio.racci.dev.
- Credential Management: It automatically looks for sops secrets with the pattern
S3FS_AUTH/<NAME_IN_UPPERCASE>. These secrets should contain the credentials in theACCESS_KEY_ID:SECRET_ACCESS_KEYformat. - Mount Points: Buckets are mounted at
/mnt/buckets/<bucket-name>unless a differentmountLocationis specified. - Ownership and Permissions: You can control the mount ownership using
uidandgid. Theumaskoption (defaulting to022) controls the default file and directory permissions.
Example
The following example mounts a “media” bucket and sets specific ownership.
{
server.storage.bucketMounts.media = {
uid = 1000;
gid = 1000;
umask = 007;
};
}
Operational Notes
- s3fs-fuse: This module uses the
s3fspackage. It relies on FUSE, so it requiresprograms.fuse.userAllowOther = truewhich the module enables automatically when mounts are defined. - Network Dependency: Mounts use the
_netdevoption to ensure they are only attempted after the network is up. - Credential Format: Ensure that your sops secrets provide the exact string format required by s3fs.
- MinIO Endpoint: The module is currently configured to use
https://minio.racci.dev.
References
Proxy Submodule
The Proxy submodule provides a unified interface for exposing internal services through Caddy. It handles virtual host configuration, automatic SSL via ACME, OAuth2 authentication with Kanidm, and public exposure through Cloudflared tunnels.
Purpose
This module abstracts the complexity of reverse proxying by allowing services to define their proxy requirements within their own module configuration. It automatically coordinates between backend hosts and the primary IO host to ensure ports are open and traffic is correctly routed.
Entry Points
The primary configuration is managed through:
server.proxy.domain: The root domain for all services (e.g.,example.com).server.proxy.virtualHosts: An attribute set of service configurations.
Key Options and Behaviors
Global Options
server.proxy.domain: Defines the base domain. All virtual hosts default to<name>.<domain>unless overridden.server.proxy.kanidmContexts: Defines shared OAuth2 configurations that can be reused across multiple virtual hosts.scopes: Default scopes are["openid" "email" "profile" "groups"].tokenLifetime: Default lifetime is3600seconds.
Virtual Host Options
aliases: A list of additional hostnames (relative toserver.proxy.domain) that route to this service.public: If true, the service is added to the Cloudflared tunnel ingress for public access.ports: List of ports to open on the backend host to allow traffic from the IO primary host.kanidm: Configures OAuth2 protection. If enabled, the module generates Caddy security blocks and handles Kanidm client provisioning.bypassPaths: List of path patterns (e.g.,/api/*) that should bypass authentication.allowGroups: List of groups allowed access. Note: This cannot be empty if Kanidm is enabled.
l4: Configures Layer 4 forwarding using the Caddy L4 plugin. This opens both TCP and UDP ports on the IO primary host.extraConfig: Injected directly into the Caddyhandleblock.localhostreferences are automatically replaced with the backend host’s address.
Per-Module Examples
default.nix - Logic and Helpers
This file contains the internal logic for resolving OAuth contexts and mapping local addresses to backend hostnames.
# Example: How contextToEnvPrefix transforms names for environment variables
contextToEnvPrefix "my-service" # Returns "MY_SERVICE"
options.nix - Option Definitions
Defines the structure of virtual hosts and shared contexts.
server.proxy.kanidmContexts.admin-apps = {
authDomain = "auth.internal.example.com";
allowGroups = [ "admins@auth.example.com" ];
};
server.proxy.virtualHosts.grafana = {
public = true;
kanidm = {
context = "admin-apps";
allowGroups = [ "grafana-users@auth.example.com" ];
bypassPaths = [ "/health" ];
};
extraConfig = "reverse_proxy localhost:3000";
};
config.nix - Caddy Integration
Handles the generation of services.caddy.virtualHosts and ACME certificate requests.
# Generated Caddy block for a vhost with Kanidm
grafana.example.com {
import default
import public
@bypass_auth_grafana path /health
handle @bypass_auth_grafana {
reverse_proxy 10.0.0.5:3000
}
route /auth/* {
authenticate with grafana_portal
}
handle {
authorize with grafana_policy
reverse_proxy 10.0.0.5:3000
}
}
kanidm.nix - Authentication Security
Generates the Caddy security block, including identity providers, portals, and authorization policies.
security {
oauth identity provider admin-apps {
realm admin-apps
client_id "admin-apps"
client_secret {env.OAUTH_ADMIN_APPS_CLIENT_SECRET}
metadata_url https://auth.internal.example.com/oauth2/openid/admin-apps/.well-known/openid-configuration
}
# ... portals and policies
}
extensions.nix - System Integration
Connects the proxy to the dashboard, Cloudflared tunnels, and automates Kanidm client provisioning.
# Automatic Kanidm provisioning based on proxy config
services.kanidm.provision.systems.oauth2.admin-apps = {
displayName = "Admin Apps";
originUrl = [ "https://grafana.example.com/auth/oauth2/admin-apps/authorization-code-callback" ];
# ...
};
Operational Notes
Caddy Integration
The module assumes the existence of a default Caddy snippet for common headers and security settings. When public is enabled, it also expects a public snippet.
Dashboard Integration
Services defined in server.proxy.virtualHosts are automatically added to the server dashboard with default titles and icons derived from the host name.
Kanidm OAuth2 Context
Authentication requires specific secrets per context, managed via sops-nix:
KANIDM/OAUTH2/<UPPER_CONTEXT>_SECRET: Provisioning secret for Kanidm systems.OAUTH_<PREFIX>_CLIENT_SECRET: The OAuth2 client secret for Caddy.<PREFIX>_SHARED_KEY: A shared key used by Caddy to sign and verify authentication tokens.
These are automatically managed if Kanidm provisioning is enabled on the same host.
Layer 4 Forwarding
L4 forwarding uses the caddy.layer4 plugin. It is primarily used for non-HTTP traffic like database connections or SSH.
Public services are routed through the Cloudflared tunnel with ID 8d42e9b2-3814-45ea-bbb5-9056c8f017e2. Ensure this tunnel is correctly configured on the IO host.
References
Server SSH Module
The Server SSH module provides a rich interactive environment for root users upon login. It automatically transitions interactive root sessions into a dedicated development shell, ensuring consistent tooling and a powerful shell experience across server environments.
Purpose
The SSH submodule enhances administrative access by providing a session-only environment tailored for server management. It removes the need for manual setup of common tools and aliases by automatically entering a pre-configured nix-shell when a root user logs in interactively over SSH.
Key Options and Behaviors
Auto-entry Logic (ssh/default.nix)
The module modifies /etc/bashrc to detect interactive root logins via SSH. It evaluates several conditions before launching the session shell:
- User must be root (
EUID=0). - Session must be via SSH (
SSH_CONNECTIONpresent). - Session must be interactive (
stdinis a TTY). - No active session shell detected (
SSH_NIX_SHELLunset). - User has not opted out via
NIX_SKIP_SHELL.
The module also configures OpenSSH to accept the NIX_SKIP_SHELL environment variable from clients, allowing remote users to bypass the auto-shell entry when necessary.
Session Environment (ssh/shell.nix)
The default session shell is a nix-shell environment containing:
- Modern Shells: Fish shell with Starship prompt, Zoxide navigation, and Carapace completions.
- Enhanced Tooling: Replacements for standard utilities such as
bat(cat),fd(find),ripgrep(grep), andprocs(ps). - System Diagnostics: Tools like
btop,doggo,gping,inxi, andhyfetch.
The shellHook in shell.nix starts an interactive Fish session and immediately exits the nix-shell wrapper once the Fish session concludes.
Per-Module Examples
Enabling the SSH Shell
Enable the auto-shell behavior in your host configuration:
{
server.sshShell.enable = true;
}
Customizing the Shell File
Override the shell definition file if you require a different set of tools:
{
server.sshShell.shellFile = ./my-custom-shell.nix;
}
Operational Notes
Opt-Out Behavior
If you need to log in as root without entering the specialized shell, set the NIX_SKIP_SHELL environment variable on your local machine before connecting:
NIX_SKIP_SHELL=1 ssh root@your-server
This is particularly useful for automated scripts or troubleshooting scenarios where the standard Bash environment is preferred.
Guard Mechanism
The auto-entry script uses the SSH_NIX_SHELL environment variable to prevent recursive shell entries. If nix-shell fails to start, the system falls back to the default shell and provides a warning message.
References
Flake Allocations
The flake allocations module defines cross-host configuration options at the flake level. Rather than configuring each NixOS system independently, allocations let you declare cluster-wide concerns — like which machines have GPUs, which server coordinates I/O, and which servers act as distributed builders — in a single place.
How It Works
The allocation system has three layers:
- Option Definitions (
modules/flake/allocations.nix) — Declares the available allocation options. - Configuration (
flake/nixos/flake-module.nix) — Sets the actual values for those options. - Apply Modules (
modules/flake/apply/) — Propagates allocation values into each NixOS or Home-Manager configuration viaspecialArgs.
Data Flow
allocations.nix flake-module.nix apply/system.nix
┌──────────────┐ ┌──────────────────────┐ ┌───────────────────────┐
│ Define opts │──▶│ Set values │──▶│ Map to NixOS options │
│ (types, │ │ (which host has what)│ │ per system via │
│ defaults) │ │ │ │ specialArgs │
└──────────────┘ └──────────────────────┘ └───────────────────────┘
When mkSystem builds a NixOS configuration, it receives the allocations attribute set and passes it as a specialArgs argument. The apply module then conditionally maps those allocations to NixOS module options based on the host’s device type.
Options
allocations.accelerators
Maps hostnames to their available hardware accelerators (cuda, rocm). Used by the builder system to configure nixpkgs with the correct cudaSupport / rocmSupport flags per host.
allocations.accelerators = {
nixmi = [ "cuda" ];
nixai = [ ];
};
Hosts not listed default to no accelerators. The builder (lib/builders/default.nix) reads allocations.accelerators.${hostname} and sets the corresponding nixpkgs config flags.
allocations.hostTypes
Read-only attribute set mapping device types to their hostnames. Auto-populated from getHostsByType, which scans hosts/ directory structure.
# Automatically resolves to something like:
allocations.hostTypes = {
server = [ "nixio" "nixserv" "nixmon" ];
desktop = [ "nixmi" ];
};
allocations.server.ioPrimaryCoordinator
Designates a server as the primary I/O coordinator for the cluster. This is the host that runs primary database instances, the reverse proxy, and storage master nodes.
The type is constrained to an enum of server hostnames (automatically derived from hostTypes.server).
allocations.server.ioPrimaryCoordinator = "nixio";
This value flows through apply/system.nix into server.ioPrimaryHost on each server configuration.
allocations.server.distributedBuilders
List of servers that act as remote builders for distributed builds.
allocations.server.distributedBuilders = [ "nixserv" ];
Flows into server.distributedBuilder.builders on each server configuration.
Apply Modules
The apply modules (modules/flake/apply/) bridge flake-level allocations to per-system NixOS options.
apply/system.nix
Imported by mkSystem during system construction. Receives allocations and deviceType via specialArgs. For server-type hosts, it maps:
allocations.server.ioPrimaryCoordinator→server.ioPrimaryHostallocations.server.distributedBuilders→server.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
| File | Role |
|---|---|
modules/flake/allocations.nix | Option definitions |
modules/flake/apply/system.nix | NixOS system apply |
modules/flake/apply/home-manager.nix | Home-Manager apply (placeholder) |
flake/nixos/flake-module.nix | Actual configuration values |
lib/builders/default.nix | Builder that consumes allocations |
DIY & Making
This section documents the Home-Manager modules under purpose.diy, which provide tooling and configuration for hardware tinkering, 3D printing, and related maker activities.
Printing
The printing module installs 3D-printing software and wires up persistent storage so that settings survive reboots on impermanence-based systems.
- Entry point:
modules/home-manager/purpose/diy/printing.nix
Options
purpose.diy.printing.enable
| Type | bool |
| Default | false |
Enables 3D-printing support. Installs OrcaSlicer and LycheeSlicer and registers their configuration directories for persistence.
Git Sync
The gitSync sub-module adds a long-running systemd user service that watches the OrcaSlicer profile directory and automatically creates a git commit every time a profile file is added, changed, or removed. This gives you a full revision history of your slicer settings with zero manual effort.
purpose.diy.printing.gitSync.enable
| Type | bool |
| Default | false |
Enable the OrcaSlicer git auto-commit watcher. Requires purpose.diy.printing.enable = true.
purpose.diy.printing.gitSync.repoPath
| Type | string |
| Default | "${config.home.homeDirectory}/.config/OrcaSlicer/user/default" |
Absolute path to the directory that will be managed as a git repository. The directory is initialised automatically the first time the watcher service starts, so it does not need to exist at activation time.
The default points at the standard OrcaSlicer per-user profile directory, which contains the filament/, process/, and machine/ sub-directories, so all profile types are tracked without any additional configuration.
Commit Message Convention
Commit messages are generated automatically based on the type of filesystem event and the location of the file within the repository:
| Event | Commit message format |
|---|---|
| File added / created | feat(<type>): added <name> |
| File modified | refactor(<type>): updated <name> |
| File deleted | chore(<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 typeconfig.<name>is the filename stripped of its extension (e.g. a file namedPrusament_PLA.jsonyields the namePrusament_PLA).
Examples:
feat(filament): added Prusament_PLA
refactor(process): updated Standard_0.2mm_Quality
chore(machine): removed Prusa_MK4S
How It Works
- A systemd user service (
orca-slicer-git-sync.service) is started at login and kept alive by systemd. - The service uses
inotifywait(frominotify-tools) in one-shot mode inside a loop to detect any filesystem event under the repo path (excluding the.gitdirectory). - 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).
- All pending changes are then committed one file at a time, each with an individually crafted commit message.
- 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 initand an initial commit (chore: initial commit) the first time the service starts if no.gitdirectory 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/AIFSexists at activation time. - Adds useful global git ignores:
.workspace.sisyphus
- Configures Zed to expose an
OpenCodeagent server (opencode acp). - Enables and configures
programs.opencodewith:- 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
- Nix:
- command permissions policy
- local MCP server (
mcp-nixosviauvx)
- Writes:
~/.config/opencode/oh-my-opencode.json~/.config/opencode/opencode-notifier.json
- Registers AI skills under
~/.agents/skills/<name>viahome.file. - Persists OpenCode state directories:
.local/share/opencode.local/state/opencode
Options
purpose.development.editors.ai.enable
| Type | bool |
| Default | false |
Enable AI tools and assistant/editor integrations for the user profile.
purpose.development.editors.ai.includeDefaults
| Type | bool |
| Default | true |
Whether to include the module’s built-in skills and agents from:
modules/home-manager/purpose/development/editors/ai/skillsmodules/home-manager/purpose/development/editors/ai/agents
Set to false for a minimal setup with only base OpenCode configuration.
purpose.development.editors.ai.skills
| Type | list of string |
| Default | [] |
Additional skill source paths to register globally under ~/.agents/skills.
Each entry should point to a skill directory (for example from a flake input or from this repository). The basename of each source path is used as the destination directory name.
Example:
"${inputs.my-skill-repo}/skills/my-skill""${self}/skills/another-skill"
Usage example
{ self, inputs, ... }: {
purpose.development.editors.ai = {
enable = true;
includeDefaults = true;
skills = [
"${inputs.my-skill-repo}/skills/my-skill"
"${self}/skills/another-skill"
];
};
}
Notes
- Skill links are generated under
~/.agents/skills/<basename>. - Default skills are discovered automatically from the module’s local
skills/directory whenincludeDefaults = true. - The module currently defines default agent discovery as well, but only skill link materialization is active in
home.fileoutput.
Home-Manager: list-ephemeral
list-ephemeral is a shell utility that helps discover ephemeral paths and generate Nix snippets for persistence. It integrates with Home-Manager to supply defaults, persisted paths, and program context.
Options
programs.list-ephemeral.enable
Enable the list-ephemeral integration and generate the runtime config at:
$XDG_CONFIG_HOME/list-ephemeral/config.json
programs.list-ephemeral.extraExcludes
Additional exclude patterns for the fd scan.
programs.list-ephemeral.extraIncludes
Additional paths to always include as candidates. Relative paths are treated as relative to $HOME.
Usage
Default TUI (fzf-based with keybindings):
list-ephemeral
TUI Keybindings
| Key | Action |
|---|---|
/ | Enable search mode (type to fuzzy filter) |
Escape | Disable search and clear query |
Ctrl-P | Open program filter (gum picker) |
Ctrl-X | Clear program filter |
Space | Toggle selection and move down |
Ctrl-A | Select all |
Ctrl-D | Deselect all |
Ctrl-C | Quit (standard fzf behavior) |
Enter | Confirm 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
$HOMEare emitted asuser.persistence.filesoruser.persistence.directorieswith paths relative to$HOME. - Paths outside
$HOMEare emitted ashost.persistence.filesorhost.persistence.directorieswith absolute paths.
If the selection includes both kinds, the snippet contains both blocks.
Packages Overview
Purpose
This section documents the custom packages defined in this repository. These are packages that are either not available in nixpkgs or require custom builds.
Entry Points
pkgs/: Contains the package definitions, typically organized by package name.alvr-bin: Binaries for ALVR that allows nvidia accelerated by using the AppImage.drive-stats: Tool for monitoring and reporting drive statistics.helpers: Collection of helper scripts for configuration management.huntress: Integration for Huntress security agent.hypr-gamemode: Script to optimize Hyprland performance for gaming.io-guardian: Database lifecycle management across hosts.lidarr-plugins: Lidarr plugins branch.list-ephemeral: Utility to identify ephemeral paths, trace file access, and generate persistence snippets.lix-woodpecker: Woodpecker CI runner.mcp-sequential-thinking: MCP server for step-by-step reasoning.mcp-server-amazon: MCP server for Amazon services interaction.proton-mcp: MCP server for ProtonMail.monocoque: Sim-racing dashboard and telemetry tool.orca-slicer-zink: Orca Slicer configured to use the Zink Vulkan driver to resolve nvidia rendering issues.python: Packages for home assistant python components.take-control-viewer: Remote support viewer for N-able Take Control via Wine.
Key Options/Knobs
Custom packages may expose different build options depending on their derivation definition.
Common Workflows
- Adding a Package: Create a new directory in
pkgs/with adefault.nixfile. - Using a Package: Reference the package via
pkgs.<name>if thepkgsoverlay 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 (frompkgs/and external inputs) and modifications (overrides for upstream packages).
Key Options/Knobs
Overlays themselves don’t typically have “knobs,” but they affect the available packages and their versions in the pkgs set.
Common Workflows
- Adding an Overlay: Create a new
.nixfile in theoverlays/directory. - Applying an Overlay: Overlays are typically applied in the
flake.nixconfiguration 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 undermodules/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-provideddecky-loader.serviceto remove it frommulti-user.target, suppresses noisy CSS_Loader health-check log spam viaLogFilterPatterns, 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 behindlib.mkIf (config.jovian.decky-loader.enable or false)guard, so it is no-op on machines without Jovian. -
Home-Manager shared module injected by
modules/nixos/core/gaming.nix— definesdecky-loader-steam-watchsystemd user service, active for duration of graphical session. It polls~/.steam/steam.pidevery 3 seconds to detect Steam starting, then startsdecky-loader.service, and usestail --pidto block until Steam exits before stopping it again. Service is only enabled whenosConfig.jovian.decky-loader.enableis 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 themineandbuildersnamespaces.files.nix: Utilities for filesystem operations and path handling.hardware.nix: Detection and configuration helpers for hardware acceleration and drivers.hypr.nix: Specialized helpers for Hyprland window manager configurations.keys.nix: Management of SSH, GPG, and other cryptographic keys.package.nix: Custom package definitions and derivation helpers.persistence.nix: Helpers for managing path persistence in ephemeral (TempFS) environments.strings.nix: String manipulation and formatting utilities.
lib/builders/: Contains specialized builders for system and home configurations.
Key Options/Knobs
The functions in lib take various arguments depending on their purpose. Builders typically take parameters for hostnames, user names, and modules.
Common Workflows
- Using a Lib Function: Access functions via
outputs.lib.<functionName>or by importing the relevant file. - Creating a Builder: Add new builder logic to
lib/builders/.