No description
Find a file
2026-06-30 20:00:32 +01:00
.claude/skills/ponytail Update skills.md 2026-06-30 19:26:52 +01:00
.forgejo/workflows update workflow: always commit so scheduled runs have a consistent label 2026-05-19 19:54:21 +01:00
apps Remove Helium browser 2026-04-12 21:18:25 +01:00
home-manager fix: remove gtk4.theme null override from fred.nix 2026-06-04 12:25:18 +01:00
hosts Move insecure-pnpm/broadcom-sta allowance to common.nix (vesktop on all hosts) 2026-06-30 20:00:32 +01:00
modules/crowdsec crowdsec: vendor PR #446307 rewrite to fix bootstrap 2026-04-25 15:00:32 +01:00
scripts Revert transcode-hevc thread cap; let ffmpeg use all cores 2026-04-20 12:14:46 +01:00
services hardware-health: drop fwupd; no P700 BIOS published on LVFS 2026-06-30 10:42:08 +01:00
settings hypridle: don't lock/dpms/suspend while MPRIS media is playing 2026-06-26 15:09:13 +01:00
templates remove matugen remnants — theming is now handled by stylix 2026-05-20 17:45:11 +01:00
walls Upload files to "walls" 2026-05-17 05:36:26 -07:00
.mcp.json Add mcp-nixos MCP server (nix run) 2026-06-25 10:40:40 +01:00
backup-server.sh Strip mediaserver hardware config for new server migration 2026-04-14 15:33:07 +01:00
CLAUDE.md Document: verify Nix options via nixos MCP before writing 2026-06-25 10:42:22 +01:00
common.nix Move insecure-pnpm/broadcom-sta allowance to common.nix (vesktop on all hosts) 2026-06-30 20:00:32 +01:00
flake.lock Update flake inputs 2026-06-30 04:00:57 +00:00
flake.nix pin stylix to release-26.05 branch 2026-06-03 13:20:35 +01:00
ports.toml neko: Guild Wars in a browser (Xfce+Wine+NVIDIA), Authelia-gated 2026-06-25 10:07:36 +01:00
readme.md docs: update readme and CLAUDE.md for forgejo and 26.05 2026-06-11 10:00:49 +01:00

FredOS NixOS Configuration

Flake-based NixOS configuration for three machines, built and deployed directly from the Forgejo repo at https://forg.gregersen.it/rope/nixos. No local checkout required after initial setup.

Machines

Hostname Description
FredOS-Gaming AMD desktop, UEFI/systemd-boot, CachyOS kernel
FredOS-Macbook Intel laptop, UEFI/systemd-boot
FredOS-Mediaserver Intel server, UEFI/systemd-boot — media services and the home router

Structure

├── .forgejo
│   └── workflows
│       └── update.yml               # Auto-updates flake.lock daily (self-hosted runner)
├── apps
│   └── zen.nix                      # Zen browser (flake input)
├── home-manager
│   └── fred.nix                     # User-level Home Manager config
├── hosts
│   ├── FredOS-Gaming.nix            # Gaming: packages, Steam, boot options
│   ├── FredOS-Macbook.nix           # Macbook: packages, power management, DWT daemon
│   ├── FredOS-Mediaserver.nix       # Mediaserver: packages, SSH, auto-upgrade
│   └── hardware
│       ├── FredOS-Gaming.nix        # AMD GPU, CachyOS kernel overlay, filesystems, hostname
│       ├── FredOS-Macbook.nix       # Broadcom WiFi, Intel GPU, filesystems, hostname
│       └── FredOS-Mediaserver.nix   # NVIDIA NVENC, data disks, mergerfs pool, hostname
├── modules
│   └── crowdsec                     # Vendored crowdsec modules (nixpkgs PR #446307, still open)
├── scripts                          # Helper scripts wrapped as packages on the mediaserver
├── services
│   ├── adguard.nix                  # Network-wide DNS ad blocking
│   ├── arr-interconnect.nix         # Cross-service API key wiring for *arr apps
│   ├── authelia.nix                 # SSO/2FA gateway for the nginx vhosts
│   ├── bazarr.nix                   # Subtitle management
│   ├── bazarr-sync.nix              # Subtitle sync timers (podman container)
│   ├── cloudflare-ddns.nix          # Cloudflare dynamic DNS
│   ├── code-server.nix              # Browser-based VS Code IDE
│   ├── crowdsec.nix                 # Intrusion prevention + nftables bouncer + ntfy alerts
│   ├── dr-server.nix                # Dungeon Runners game server (Wine) — currently disabled
│   ├── forgejo-runner.nix           # CI runner for forg.gregersen.it
│   ├── frigate.nix                  # NVR with object detection
│   ├── game-servers.nix             # Dockerised 7 Days to Die servers
│   ├── go2rtc.nix                   # Camera/RTSP streaming
│   ├── homepage.nix                 # Homepage dashboard with auto-extracted API keys
│   ├── jellyfin.nix                 # Media server
│   ├── memos.nix                    # Flatnotes notes app (container)
│   ├── nginx.nix                    # Reverse proxy + ACME wildcard cert via Cloudflare DNS-01
│   ├── profilarr.nix                # Quality profile manager for *arr apps (container)
│   ├── prowlarr.nix                 # Indexer manager
│   ├── qbittorrent-nox.nix          # Torrent client
│   ├── radarr.nix                   # Movie management
│   ├── router.nix                   # Mediaserver as home router (NAT, DHCP, nftables)
│   ├── sabnzbd.nix                  # Usenet downloader
│   ├── server-permissions.nix       # Shared media dir permissions
│   └── sonarr.nix                   # TV management
├── settings
│   ├── audio.nix                    # PipeWire / audio config
│   ├── desktop.nix                  # Display manager, theming, flatpak
│   ├── hyprland.nix                 # Hyprland compositor config (Lua), anyrun
│   ├── quickshell.nix               # Quickshell bar/notifications (QML)
│   ├── locale.nix                   # Locale, timezone, keyboard
│   ├── shell.nix                    # Fish shell, powerline prompt, fastfetch, nerd fonts
│   ├── stylix.nix                   # Unified colour theming (wallpaper-derived palette)
│   └── users.nix                    # User accounts, SSH keys
├── templates                        # CSS templates recoloured by stylix.nix
├── walls                            # Wallpapers
├── backup-server.sh                 # One-shot mediaserver state backup (run manually)
├── ports.toml                       # WAN → LAN port forwards consumed by router.nix
├── common.nix                       # Shared config imported by all hosts
├── flake.lock                       # Auto-generated, updated daily by Forgejo Actions
└── flake.nix                        # Flake inputs and host definitions

Day-to-day usage

Edit files in the Forgejo repo (or locally and push), then on the machine run:

update

The alias (defined in common.nix) runs sudo nixos-rebuild switch --refresh --flake git+https://forg.gregersen.it/rope/nixos with nix-output-monitor, then shows an nvd diff of what changed. The mediaserver also auto-upgrades daily at 05:15 (system.autoUpgrade).

Other useful aliases:

clean    # sudo nix-collect-garbage -d

Adding a new machine

1. Fresh NixOS install

Boot the NixOS installer and complete the standard installation.

2. Enable flakes temporarily

(The flake config enables them declaratively, but the stock installer config doesn't.) Add to /etc/nixos/configuration.nix and rebuild:

nix.settings.experimental-features = [ "nix-command" "flakes" ];

3. Create the hardware config in the repo

Copy /etc/nixos/hardware-configuration.nix to hosts/hardware/FredOS-NEWHOST.nix and append the hostname and bootloader config:

networking.hostName = "FredOS-NEWHOST";
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;

4. Register the host in flake.nix

FredOS-NEWHOST = mkHost "FredOS-NEWHOST" [];

5. Add host-specific config

Create hosts/FredOS-NEWHOST.nix for machine-specific packages or services. mkHost imports it automatically — no changes to common.nix needed.

6. Switch to the flake

Run once with the explicit hostname:

sudo nixos-rebuild switch --refresh --flake git+https://forg.gregersen.it/rope/nixos#FredOS-NEWHOST

After this succeeds, the plain update alias works from then on.


Flake inputs

Input Source
nixpkgs github:NixOS/nixpkgs/nixos-26.05
home-manager github:nix-community/home-manager/release-26.05
stylix github:nix-community/stylix/release-26.05
zen-browser github:0xc000022070/zen-browser-flake
nix-cachyos-kernel github:xddxdd/nix-cachyos-kernel/release (own nixpkgs pin — keeps their kernel binary cache usable)
proton-cachyos-nix github:powerofthe69/proton-cachyos-nix

Mediaserver secrets

Several services on FredOS-Mediaserver require secrets stored on the machine (not in the repo). After a fresh deploy, create these before running update:

# Cloudflare API token (used by DDNS and ACME wildcard cert)
echo -n 'your-cloudflare-api-token' | sudo tee /var/secrets/cloudflare-token
sudo chmod 600 /var/secrets/cloudflare-token

# go2rtc RTSP camera URL
echo -n 'rtsp://username:password@camera-ip:554/stream1' | sudo tee /var/secrets/go2rtc-rtsp-url
sudo chmod 600 /var/secrets/go2rtc-rtsp-url

# CrowdSec ntfy.sh alert topic (injected into the notification config at service start)
echo 'https://ntfy.sh/your-private-topic' | sudo tee /var/secrets/ntfy-url
sudo chmod 600 /var/secrets/ntfy-url

# Forgejo Actions runner registration token (one-time use, KEY=value format)
echo 'TOKEN=YOUR_REGISTRATION_TOKEN' | sudo tee /var/secrets/forgejo-runner-token
sudo chmod 600 /var/secrets/forgejo-runner-token

# Authelia secrets — readable by the authelia-main group
sudo mkdir -p /var/secrets/authelia
echo -n 'random-jwt-secret'              | sudo tee /var/secrets/authelia/jwt_secret
echo -n 'random-session-secret'          | sudo tee /var/secrets/authelia/session_secret
echo -n 'random-storage-encryption-key'  | sudo tee /var/secrets/authelia/storage_encryption_key
sudo chown root:authelia-main /var/secrets/authelia/*
sudo chmod 640 /var/secrets/authelia/*

# Authelia user database
# Create users_database.yml with this structure:
#   ---
#   users:
#     username:
#       password: "$argon2id$..."    # hashed — see below
#       displayname: Display Name
#       email: user@example.com
#
# Generate a password hash with:
#   nix-shell -p authelia --run "authelia crypto hash generate argon2"
sudo mkdir -p /var/lib/authelia-main
sudo nano /var/lib/authelia-main/users_database.yml
sudo chown authelia-main:authelia-main /var/lib/authelia-main/users_database.yml

Migrating to a new server

When moving FredOS-Mediaserver to new hardware, back up these state directories from the old server (backup-server.sh automates most of it):

# Service databases and config (stop services first)
/var/lib/jellyfin/          # Library database, users, metadata, API keys
/var/lib/sonarr/            # TV library database, config.xml (API key)
/var/lib/radarr/            # Movie library database, config.xml (API key)
/var/lib/prowlarr/          # Indexer database, config.xml (API key)
/var/lib/bazarr/            # Subtitle database and config
/var/lib/qbittorrent/       # Torrent client config and state
/var/lib/authelia-main/     # User database and session storage

# Secrets
/var/secrets/               # See "Mediaserver secrets" above

# Media files
/mnt/storage/               # The mergerfs pool (torrents, media libraries, audiobooks)

Steps:

  1. Install NixOS on the new server
  2. Update hosts/hardware/FredOS-Mediaserver.nix from the new /etc/nixos/hardware-configuration.nix (new disk UUIDs, bootloader config)
  3. Set up the mergerfs pool and mount at /mnt/storage
  4. Restore /var/secrets/ (see Mediaserver secrets section above)
  5. Run sudo nixos-rebuild switch --refresh --flake git+https://forg.gregersen.it/rope/nixos#FredOS-Mediaserver
  6. Stop all services, restore the /var/lib/ directories listed above, then start services
  7. Update Cloudflare DNS if the server's public IP changed

If starting fresh instead of migrating, the services self-initialize with empty databases — redo initial setup in each web UI; arr-interconnect auto-wires the connections between them.

Notes

  • The mediaserver is also the home router: services/router.nix owns nftables NAT/firewall and networking.firewall is disabled on that host. WAN exposure is controlled solely by ports.toml; LAN traffic is trusted wholesale.
  • hosts/hardware/ files are committed — they contain UUIDs and disk layout but no sensitive credentials
  • Host-specific behaviour is gated with lib.mkIf (config.networking.hostName == "...") or lib.elem config.networking.hostName [...]; host modules themselves are selected by mkHost in flake.nix
  • modules/crowdsec/ vendors the crowdsec module rewrite from nixpkgs PR #446307 — delete it (and the disabledModules/imports lines in services/crowdsec.nix) once that PR lands in the pinned channel