docs: update readme and CLAUDE.md for forgejo and 26.05

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-06-11 10:00:02 +01:00
parent a4351473d0
commit 9671dfb793
2 changed files with 86 additions and 94 deletions

View file

@ -2,18 +2,28 @@
This is a NixOS flake-based configuration for multiple hosts: This is a NixOS flake-based configuration for multiple hosts:
- **FredOS-Gaming** — gaming desktop - **FredOS-Gaming** — gaming desktop
- **FredOS-Mediaserver** — home media server - **FredOS-Mediaserver** — home media server **and the home router** (nftables NAT/firewall in `services/router.nix`; `networking.firewall` is disabled on this host, WAN exposure comes from `ports.toml`)
- **FredOS-Macbook** — MacBook laptop - **FredOS-Macbook** — MacBook laptop
## Structure ## Structure
- `flake.nix` — flake inputs/outputs; all hosts use `nixpkgs` unstable - `flake.nix` — flake inputs/outputs; all hosts track the `nixos-26.05` stable channel
- `common.nix` — shared configuration across all hosts - `common.nix` — shared configuration across all hosts
- `hosts/` — per-host NixOS configuration modules - `hosts/` — per-host NixOS configuration modules (imported per-host by `mkHost` in flake.nix)
- `hosts/hardware/` — hardware-specific configuration - `hosts/hardware/` — hardware-specific configuration
- `home-manager/` — Home Manager configuration (via NixOS module) - `home-manager/` — Home Manager configuration (via NixOS module)
- `services/` — modular service definitions imported by hosts - `services/` — modular service definitions, gated by hostname with `lib.mkIf`
- `settings/` — shared settings/variables - `settings/` — shared settings (desktop, hyprland, quickshell, stylix, …)
- `modules/crowdsec/` — vendored crowdsec modules from nixpkgs PR #446307; delete once that PR lands in the pinned channel
- `ports.toml` — WAN → LAN port forwards consumed by `services/router.nix`
## Deployment
Hosts never pull this repo locally — they rebuild from the Forgejo remote via the
`update` alias (`nixos-rebuild switch --refresh --flake git+https://forg.gregersen.it/rope/nixos`).
That means evaluation is **pure**: config can never read files outside the repo
(e.g. `/var/secrets`) at eval time. Secrets must be injected at service runtime
(see `services/crowdsec.nix` and `services/go2rtc.nix` for the pattern).
## Code Evaluation ## Code Evaluation
@ -23,6 +33,9 @@ Always validate Nix expressions with `nix eval` before committing. For example:
# Evaluate a specific attribute to check for syntax/type errors # Evaluate a specific attribute to check for syntax/type errors
nix eval .#nixosConfigurations.FredOS-Gaming.config.system.stateVersion nix eval .#nixosConfigurations.FredOS-Gaming.config.system.stateVersion
# Full eval of a host without building
nix eval --raw .#nixosConfigurations.FredOS-Mediaserver.config.system.build.toplevel.drvPath
# Evaluate the full flake outputs to catch top-level errors # Evaluate the full flake outputs to catch top-level errors
nix eval .#nixosConfigurations --apply builtins.attrNames nix eval .#nixosConfigurations --apply builtins.attrNames
``` ```

157
readme.md
View file

@ -1,86 +1,89 @@
# FredOS NixOS Configuration # FredOS NixOS Configuration
Flake-based NixOS configuration for three machines, built and deployed directly from GitHub. No local config management required after initial setup. 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 ## Machines
| Hostname | Description | | Hostname | Description |
|---|---| |---|---|
| FredOS-Gaming | AMD desktop, UEFI/systemd-boot | | FredOS-Gaming | AMD desktop, UEFI/systemd-boot, CachyOS kernel |
| FredOS-Macbook | Intel laptop, UEFI/systemd-boot | | FredOS-Macbook | Intel laptop, UEFI/systemd-boot |
| FredOS-Mediaserver | Intel server, BIOS/GRUB | | FredOS-Mediaserver | Intel server, UEFI/systemd-boot — media services **and** the home router |
## Structure ## Structure
``` ```
├── .github ├── .forgejo
│ └── workflows │ └── workflows
│ └── update.yml # Auto-updates flake.lock daily │ └── update.yml # Auto-updates flake.lock daily (self-hosted runner)
├── apps ├── apps
│ └── zen.nix # Zen browser config │ └── zen.nix # Zen browser (flake input)
├── home-manager ├── home-manager
│ ├── fred.nix # User-level Home Manager config │ └── fred.nix # User-level Home Manager config
│ └── gnome-hm.nix # GNOME Home Manager settings
├── hosts ├── hosts
│ ├── FredOS-Gaming.nix # Gaming: packages, Steam, boot options │ ├── FredOS-Gaming.nix # Gaming: packages, Steam, boot options
│ ├── FredOS-Macbook.nix # Macbook: packages, power management, boot options │ ├── FredOS-Macbook.nix # Macbook: packages, power management, DWT daemon
│ ├── FredOS-Mediaserver.nix # Mediaserver: packages, networking, SSH │ ├── FredOS-Mediaserver.nix # Mediaserver: packages, SSH, auto-upgrade
│ └── hardware │ └── hardware
│ ├── FredOS-Gaming.nix # AMD GPU, kernel modules, filesystems, bootloader, hostname │ ├── FredOS-Gaming.nix # AMD GPU, CachyOS kernel overlay, filesystems, hostname
│ ├── FredOS-Macbook.nix # Broadcom WiFi, Intel GPU, Bluetooth, filesystems, bootloader, hostname │ ├── FredOS-Macbook.nix # Broadcom WiFi, Intel GPU, filesystems, hostname
│ └── FredOS-Mediaserver.nix # Intel CPU, data disks, mergerfs pool, GRUB, 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 ├── services
│ ├── adguard.nix # Network-wide DNS ad blocking │ ├── adguard.nix # Network-wide DNS ad blocking
│ ├── arr-interconnect.nix # Cross-service API key wiring for *arr apps │ ├── arr-interconnect.nix # Cross-service API key wiring for *arr apps
│ ├── authelia.nix # SSO/2FA gateway (protects homepage & camera) │ ├── authelia.nix # SSO/2FA gateway for the nginx vhosts
│ ├── bazarr.nix # Subtitle management │ ├── bazarr.nix # Subtitle management
│ ├── bazarr-sync.nix # Subtitle sync timers (podman container)
│ ├── cloudflare-ddns.nix # Cloudflare dynamic DNS │ ├── cloudflare-ddns.nix # Cloudflare dynamic DNS
│ ├── code-server.nix # Browser-based VS Code IDE │ ├── code-server.nix # Browser-based VS Code IDE
│ ├── crowdsec.nix # Intrusion prevention / bouncer │ ├── crowdsec.nix # Intrusion prevention + nftables bouncer + ntfy alerts
│ ├── dr-server.nix # Disaster recovery / backup service │ ├── dr-server.nix # Dungeon Runners game server (Wine) — currently disabled
│ ├── forgejo-runner.nix # CI/CD runner for Forgejo │ ├── forgejo-runner.nix # CI runner for forg.gregersen.it
│ ├── game-servers.nix # Dockerised game servers (7 Days to Die) │ ├── frigate.nix # NVR with object detection
│ ├── game-servers.nix # Dockerised 7 Days to Die servers
│ ├── go2rtc.nix # Camera/RTSP streaming │ ├── go2rtc.nix # Camera/RTSP streaming
│ ├── homepage.nix # Homepage dashboard with auto-extracted API keys │ ├── homepage.nix # Homepage dashboard with auto-extracted API keys
│ ├── jellyfin.nix # Media server │ ├── jellyfin.nix # Media server
│ ├── memos.nix # Flatnotes notes app (container)
│ ├── nginx.nix # Reverse proxy + ACME wildcard cert via Cloudflare DNS-01 │ ├── nginx.nix # Reverse proxy + ACME wildcard cert via Cloudflare DNS-01
│ ├── profilarr.nix # Quality profile manager for *arr apps │ ├── profilarr.nix # Quality profile manager for *arr apps (container)
│ ├── prowlarr.nix # Indexer manager │ ├── prowlarr.nix # Indexer manager
│ ├── qbittorrent-nox.nix # Torrent client │ ├── qbittorrent-nox.nix # Torrent client
│ ├── radarr.nix # Movie management │ ├── radarr.nix # Movie management
│ ├── router.nix # Mediaserver as home router (NAT, DHCP, nftables) │ ├── router.nix # Mediaserver as home router (NAT, DHCP, nftables)
│ ├── sabnzbd.nix # Usenet downloader │ ├── sabnzbd.nix # Usenet downloader
│ ├── server-permissions.nix # File/dir permission setup │ ├── server-permissions.nix # Shared media dir permissions
│ └── sonarr.nix # TV management │ └── sonarr.nix # TV management
├── settings ├── settings
│ ├── audio.nix # PipeWire / audio config │ ├── audio.nix # PipeWire / audio config
│ ├── gnome.nix # GNOME desktop settings │ ├── desktop.nix # Display manager, theming, flatpak
│ ├── hyprland.nix # Hyprland Wayland compositor config │ ├── hyprland.nix # Hyprland compositor config (Lua), anyrun
│ ├── quickshell.nix # Quickshell bar/notifications (QML)
│ ├── locale.nix # Locale, timezone, keyboard │ ├── locale.nix # Locale, timezone, keyboard
│ ├── shell.nix # Fish shell, powerline prompt, fastfetch, nerd fonts │ ├── shell.nix # Fish shell, powerline prompt, fastfetch, nerd fonts
│ ├── stylix.nix # Unified colour theming (wallpaper-derived palette) │ ├── stylix.nix # Unified colour theming (wallpaper-derived palette)
│ └── users.nix # User accounts │ └── users.nix # User accounts, SSH keys
├── templates # CSS templates recoloured by stylix.nix
├── walls # Wallpapers ├── 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 ├── common.nix # Shared config imported by all hosts
├── flake.lock # Auto-generated, updated daily by GitHub Actions ├── flake.lock # Auto-generated, updated daily by Forgejo Actions
└── flake.nix # Flake inputs and host definitions └── flake.nix # Flake inputs and host definitions
``` ```
## Day-to-day usage ## Day-to-day usage
Edit files directly on GitHub, then on the machine run: Edit files in the Forgejo repo (or locally and push), then on the machine run:
```bash ```bash
update update
``` ```
That's it. The alias is defined in `common.nix` and expands to: 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`).
```bash
sudo nixos-rebuild switch --flake github:ediblerope/nixos-config --refresh --no-write-lock-file
```
Nix automatically matches the running machine's hostname to the correct `nixosConfigurations` entry.
Other useful aliases: Other useful aliases:
@ -98,65 +101,38 @@ Boot the NixOS installer and complete the standard installation.
### 2. Enable flakes temporarily ### 2. Enable flakes temporarily
Add this to `/etc/nixos/configuration.nix` and rebuild: (The flake config enables them declaratively, but the stock installer config doesn't.) Add to `/etc/nixos/configuration.nix` and rebuild:
```nix ```nix
nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ];
``` ```
```bash ### 3. Create the hardware config in the repo
sudo nixos-rebuild switch
```
### 3. Create the hardware config on GitHub Copy `/etc/nixos/hardware-configuration.nix` to `hosts/hardware/FredOS-NEWHOST.nix` and append the hostname and bootloader config:
Copy the contents of `/etc/nixos/hardware-configuration.nix` and create `hosts/hardware/FredOS-NEWHOST.nix` on GitHub. Append the hostname and bootloader config to it:
```nix ```nix
networking.hostName = "FredOS-NEWHOST"; networking.hostName = "FredOS-NEWHOST";
# For UEFI/systemd-boot machines:
boot.loader.systemd-boot.enable = true; boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
# For BIOS/GRUB machines instead:
# boot.loader.grub.enable = true;
# boot.loader.grub.devices = [ "/dev/sda" ]; # verify with: sudo grub-probe --target=disk /
``` ```
### 4. Register the host in flake.nix ### 4. Register the host in flake.nix
In `flake.nix` on GitHub, add to `nixosConfigurations`:
```nix ```nix
FredOS-NEWHOST = mkHost "FredOS-NEWHOST"; FredOS-NEWHOST = mkHost "FredOS-NEWHOST" [];
``` ```
### 5. Add host-specific config ### 5. Add host-specific config
Create `hosts/FredOS-NEWHOST.nix` on GitHub for any machine-specific packages or services: Create `hosts/FredOS-NEWHOST.nix` for machine-specific packages or services. `mkHost` imports it automatically — no changes to `common.nix` needed.
```nix
{ config, pkgs, lib, ... }:
{
config = lib.mkIf (config.networking.hostName == "FredOS-NEWHOST") {
# host-specific packages and services here
};
}
```
Then add it to the imports list in `common.nix`:
```nix
./hosts/FredOS-NEWHOST.nix
```
### 6. Switch to the flake ### 6. Switch to the flake
Run this once on the new machine with the explicit hostname: Run once with the explicit hostname:
```bash ```bash
sudo nixos-rebuild switch --flake github:ediblerope/nixos-config#FredOS-NEWHOST --refresh --no-write-lock-file 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. After this succeeds, the plain `update` alias works from then on.
@ -167,22 +143,19 @@ After this succeeds, the plain `update` alias works from then on.
| Input | Source | | Input | Source |
|---|---| |---|---|
| nixpkgs | `github:NixOS/nixpkgs/nixos-unstable` | | nixpkgs | `github:NixOS/nixpkgs/nixos-26.05` |
| nixpkgs-stable | `github:NixOS/nixpkgs/nixos-25.11` | | home-manager | `github:nix-community/home-manager/release-26.05` |
| home-manager-stable | `github:nix-community/home-manager/release-25.11` | | stylix | `github:nix-community/stylix/release-26.05` |
| zen-browser | `github:0xc000022070/zen-browser-flake` | | zen-browser | `github:0xc000022070/zen-browser-flake` |
| nix-cachyos-kernel | `github:xddxdd/nix-cachyos-kernel/release` | | 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` | | proton-cachyos-nix | `github:powerofthe69/proton-cachyos-nix` |
| hyprland | `github:hyprwm/Hyprland` |
| stylix | `github:nix-community/stylix/release-25.11` |
## Mediaserver secrets ## Mediaserver secrets
Several services on FredOS-Mediaserver require secrets that are stored on the machine (not in the repo). After a fresh deploy, create these before running `update`: Several services on FredOS-Mediaserver require secrets stored on the machine (not in the repo). After a fresh deploy, create these before running `update`:
```bash ```bash
# Cloudflare API token (used by DDNS and ACME wildcard cert) # Cloudflare API token (used by DDNS and ACME wildcard cert)
# See services/cloudflare-ddns.md for token permissions
echo -n 'your-cloudflare-api-token' | sudo tee /var/secrets/cloudflare-token echo -n 'your-cloudflare-api-token' | sudo tee /var/secrets/cloudflare-token
sudo chmod 600 /var/secrets/cloudflare-token sudo chmod 600 /var/secrets/cloudflare-token
@ -190,18 +163,23 @@ sudo chmod 600 /var/secrets/cloudflare-token
echo -n 'rtsp://username:password@camera-ip:554/stream1' | sudo tee /var/secrets/go2rtc-rtsp-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 sudo chmod 600 /var/secrets/go2rtc-rtsp-url
# Authelia secrets — auto-migrated from Docker on first deploy # CrowdSec ntfy.sh alert topic (injected into the notification config at service start)
# If migrating from Docker, ensure these exist at /home/fred/docker/authelia/: echo 'https://ntfy.sh/your-private-topic' | sudo tee /var/secrets/ntfy-url
# - configuration.yml (jwt_secret, session secret, storage key are extracted) sudo chmod 600 /var/secrets/ntfy-url
# - users_database.yml (copied to /var/lib/authelia-main/)
# For a fresh install, create manually: # 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 sudo mkdir -p /var/secrets/authelia
echo -n 'random-jwt-secret' | sudo tee /var/secrets/authelia/jwt_secret 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-session-secret' | sudo tee /var/secrets/authelia/session_secret
echo -n 'random-storage-encryption-key' | sudo tee /var/secrets/authelia/storage_encryption_key echo -n 'random-storage-encryption-key' | sudo tee /var/secrets/authelia/storage_encryption_key
sudo chmod 600 /var/secrets/authelia/* sudo chown root:authelia-main /var/secrets/authelia/*
sudo chmod 640 /var/secrets/authelia/*
# Authelia user database (for a fresh install) # Authelia user database
# Create users_database.yml with this structure: # Create users_database.yml with this structure:
# --- # ---
# users: # users:
@ -219,7 +197,7 @@ sudo chown authelia-main:authelia-main /var/lib/authelia-main/users_database.yml
## Migrating to a new server ## Migrating to a new server
When moving FredOS-Mediaserver to new hardware, back up these state directories from the old server: When moving FredOS-Mediaserver to new hardware, back up these state directories from the old server (`backup-server.sh` automates most of it):
```bash ```bash
# Service databases and config (stop services first) # Service databases and config (stop services first)
@ -232,7 +210,7 @@ When moving FredOS-Mediaserver to new hardware, back up these state directories
/var/lib/authelia-main/ # User database and session storage /var/lib/authelia-main/ # User database and session storage
# Secrets # Secrets
/var/secrets/ # Cloudflare token, go2rtc RTSP URL, Authelia secrets /var/secrets/ # See "Mediaserver secrets" above
# Media files # Media files
/mnt/storage/ # The mergerfs pool (torrents, media libraries, audiobooks) /mnt/storage/ # The mergerfs pool (torrents, media libraries, audiobooks)
@ -241,17 +219,18 @@ When moving FredOS-Mediaserver to new hardware, back up these state directories
Steps: Steps:
1. Install NixOS on the new server 1. Install NixOS on the new server
2. Create `hosts/hardware/FredOS-Mediaserver.nix` from the new `/etc/nixos/hardware-configuration.nix` (new disk UUIDs, bootloader config) 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` 3. Set up the mergerfs pool and mount at `/mnt/storage`
4. Restore `/var/secrets/` (see Mediaserver secrets section above) 4. Restore `/var/secrets/` (see Mediaserver secrets section above)
5. Run `sudo nixos-rebuild switch --flake github:ediblerope/nixos-config#FredOS-Mediaserver` 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 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 7. Update Cloudflare DNS if the server's public IP changed
If starting fresh instead of migrating, the services will self-initialize with empty databases. You'll need to redo initial setup in each web UI (add media libraries in Jellyfin, set root folders in Sonarr/Radarr, configure qBittorrent download paths, etc.). The `arr-interconnect` service will auto-wire the connections between them. 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 ## Notes
- `hosts/hardware/` files are committed to the repo — they contain UUIDs and disk layout but no sensitive credentials - 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.
- Host-specific behaviour is gated with `lib.mkIf (config.networking.hostName == "...")` or `lib.elem config.networking.hostName [...]` - `hosts/hardware/` files are committed — they contain UUIDs and disk layout but no sensitive credentials
- GitHub API rate limit (60 req/hour unauthenticated) can occasionally be hit if running `update` many times in quick succession during active config changes — wait ~15 minutes and retry - 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