diff --git a/CLAUDE.md b/CLAUDE.md index 706dc1c..8287bfe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,18 +2,28 @@ This is a NixOS flake-based configuration for multiple hosts: - **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 ## 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 -- `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 - `home-manager/` — Home Manager configuration (via NixOS module) -- `services/` — modular service definitions imported by hosts -- `settings/` — shared settings/variables +- `services/` — modular service definitions, gated by hostname with `lib.mkIf` +- `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 @@ -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 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 nix eval .#nixosConfigurations --apply builtins.attrNames ``` diff --git a/readme.md b/readme.md index 8d004a3..d692548 100644 --- a/readme.md +++ b/readme.md @@ -1,86 +1,89 @@ # 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 | 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-Mediaserver | Intel server, BIOS/GRUB | +| FredOS-Mediaserver | Intel server, UEFI/systemd-boot — media services **and** the home router | ## Structure ``` -├── .github +├── .forgejo │ └── workflows -│ └── update.yml # Auto-updates flake.lock daily +│ └── update.yml # Auto-updates flake.lock daily (self-hosted runner) ├── apps -│ └── zen.nix # Zen browser config +│ └── zen.nix # Zen browser (flake input) ├── home-manager -│ ├── fred.nix # User-level Home Manager config -│ └── gnome-hm.nix # GNOME Home Manager settings +│ └── fred.nix # User-level Home Manager config ├── hosts │ ├── FredOS-Gaming.nix # Gaming: packages, Steam, boot options -│ ├── FredOS-Macbook.nix # Macbook: packages, power management, boot options -│ ├── FredOS-Mediaserver.nix # Mediaserver: packages, networking, SSH +│ ├── FredOS-Macbook.nix # Macbook: packages, power management, DWT daemon +│ ├── FredOS-Mediaserver.nix # Mediaserver: packages, SSH, auto-upgrade │ └── hardware -│ ├── FredOS-Gaming.nix # AMD GPU, kernel modules, filesystems, bootloader, hostname -│ ├── FredOS-Macbook.nix # Broadcom WiFi, Intel GPU, Bluetooth, filesystems, bootloader, hostname -│ └── FredOS-Mediaserver.nix # Intel CPU, data disks, mergerfs pool, GRUB, hostname +│ ├── 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 (protects homepage & camera) +│ ├── 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 / bouncer -│ ├── dr-server.nix # Disaster recovery / backup service -│ ├── forgejo-runner.nix # CI/CD runner for Forgejo -│ ├── game-servers.nix # Dockerised game servers (7 Days to Die) +│ ├── 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 +│ ├── 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 # File/dir permission setup +│ ├── server-permissions.nix # Shared media dir permissions │ └── sonarr.nix # TV management ├── settings │ ├── audio.nix # PipeWire / audio config -│ ├── gnome.nix # GNOME desktop settings -│ ├── hyprland.nix # Hyprland Wayland compositor 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 +│ └── 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 GitHub Actions +├── flake.lock # Auto-generated, updated daily by Forgejo Actions └── flake.nix # Flake inputs and host definitions ``` ## 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 update ``` -That's it. The alias is defined in `common.nix` and expands to: - -```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. +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: @@ -98,65 +101,38 @@ Boot the NixOS installer and complete the standard installation. ### 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.settings.experimental-features = [ "nix-command" "flakes" ]; ``` -```bash -sudo nixos-rebuild switch -``` +### 3. Create the hardware config in the repo -### 3. Create the hardware config on GitHub - -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: +Copy `/etc/nixos/hardware-configuration.nix` to `hosts/hardware/FredOS-NEWHOST.nix` and append the hostname and bootloader config: ```nix networking.hostName = "FredOS-NEWHOST"; - -# For UEFI/systemd-boot machines: boot.loader.systemd-boot.enable = 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 -In `flake.nix` on GitHub, add to `nixosConfigurations`: - ```nix -FredOS-NEWHOST = mkHost "FredOS-NEWHOST"; +FredOS-NEWHOST = mkHost "FredOS-NEWHOST" []; ``` ### 5. Add host-specific config -Create `hosts/FredOS-NEWHOST.nix` on GitHub for any machine-specific packages or services: - -```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 -``` +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 this once on the new machine with the explicit hostname: +Run once with the explicit hostname: ```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. @@ -167,22 +143,19 @@ After this succeeds, the plain `update` alias works from then on. | Input | Source | |---|---| -| nixpkgs | `github:NixOS/nixpkgs/nixos-unstable` | -| nixpkgs-stable | `github:NixOS/nixpkgs/nixos-25.11` | -| home-manager-stable | `github:nix-community/home-manager/release-25.11` | +| 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` | +| 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` | -| hyprland | `github:hyprwm/Hyprland` | -| stylix | `github:nix-community/stylix/release-25.11` | ## 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 # 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 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 sudo chmod 600 /var/secrets/go2rtc-rtsp-url -# Authelia secrets — auto-migrated from Docker on first deploy -# If migrating from Docker, ensure these exist at /home/fred/docker/authelia/: -# - configuration.yml (jwt_secret, session secret, storage key are extracted) -# - users_database.yml (copied to /var/lib/authelia-main/) -# For a fresh install, create manually: +# 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 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: # --- # users: @@ -219,7 +197,7 @@ 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: +When moving FredOS-Mediaserver to new hardware, back up these state directories from the old server (`backup-server.sh` automates most of it): ```bash # 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 # Secrets -/var/secrets/ # Cloudflare token, go2rtc RTSP URL, Authelia secrets +/var/secrets/ # See "Mediaserver secrets" above # Media files /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: 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` 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 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 -- `hosts/hardware/` files are committed to the repo — 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 [...]` -- 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 +- 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