Compare commits

..

6 commits

Author SHA1 Message Date
9671dfb793 docs: update readme and CLAUDE.md for forgejo and 26.05
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:49 +01:00
a4351473d0 common: enable flakes, drop duplicate host imports, import quickshell
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:49 +01:00
7bf997176e quickshell: split QML out of hyprland.nix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:49 +01:00
8dd70a2d9d mediaserver: drop no-op firewall rules, close unused DR forwards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:49 +01:00
f65675bd80 authelia: drop docker migration, tighten secret perms
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:49 +01:00
93e79509c4 crowdsec: inject ntfy url at runtime, drop obsolete hub prune
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:00:49 +01:00
19 changed files with 2123 additions and 2186 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
``` ```

View file

@ -3,14 +3,12 @@
{ {
imports = [ imports = [
# Hosts # # Host modules are imported per-host by mkHost in flake.nix.
./hosts/FredOS-Gaming.nix
./hosts/FredOS-Macbook.nix
./hosts/FredOS-Mediaserver.nix
# Generic settings # # Generic settings #
./settings/desktop.nix ./settings/desktop.nix
./settings/hyprland.nix ./settings/hyprland.nix
./settings/quickshell.nix
./settings/locale.nix ./settings/locale.nix
./settings/audio.nix ./settings/audio.nix
./settings/users.nix ./settings/users.nix
@ -89,6 +87,10 @@
# Allow unfree packages # Allow unfree packages
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
# Flakes — nixos-rebuild self-enables these, but plain `nix eval` /
# `nix flake check` on the hosts need them too.
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Enable network-manager # Enable network-manager
networking.networkmanager.enable = true; networking.networkmanager.enable = true;

View file

@ -43,8 +43,8 @@
allowReboot = true; allowReboot = true;
}; };
# Open firewall for SSH # WAN exposure is controlled by nftables in services/router.nix +
networking.firewall.allowedTCPPorts = [ 22 11434 ]; # ports.toml (networking.firewall is disabled on this host).
services.openssh = { services.openssh = {
enable = true; enable = true;
settings = { settings = {

View file

@ -45,22 +45,5 @@ name = "7DTD-coop voice/dynamic"
ports = "26911-26912" ports = "26911-26912"
protocol = "udp" protocol = "udp"
[[forward]] # DR (Dungeon Runners) forwards removed — services/dr-server.nix is disabled.
name = "DR auth" # Re-add 2110 tcp, 2603 both, 2604-2605 udp, 2606 tcp if it comes back.
port = 2110
protocol = "tcp"
[[forward]]
name = "DR game"
port = 2603
protocol = "both"
[[forward]]
name = "DR aux UDP"
ports = "2604-2605"
protocol = "udp"
[[forward]]
name = "DR queue"
port = 2606
protocol = "tcp"

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

View file

@ -45,8 +45,7 @@
}; };
}; };
# LAN DNS — router blocks WAN:53 so this is effectively LAN-only # LAN clients reach :53 via the nftables "LAN trusted" rule in router.nix;
networking.firewall.allowedTCPPorts = [ 53 ]; # WAN:53 is dropped there.
networking.firewall.allowedUDPPorts = [ 53 ];
}; };
} }

View file

@ -1,49 +1,6 @@
# services/authelia.nix — Native Authelia SSO with auto-migration from Docker # services/authelia.nix — Native Authelia SSO
# Secrets live in /var/secrets/authelia (root:authelia-main, 640) — see readme.
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let
# Migrates secrets + user DB from the old Docker Authelia setup
setupScript = pkgs.writeShellScript "authelia-setup" ''
set -euo pipefail
YQ="${pkgs.yq-go}/bin/yq"
DOCKER_CONFIG="/home/fred/docker/authelia/configuration.yml"
SECRETS_DIR="/var/secrets/authelia"
STATE_DIR="/var/lib/authelia-main"
mkdir -p "$SECRETS_DIR"
mkdir -p "$STATE_DIR"
# Migrate secrets from Docker config if they haven't been extracted yet
if [ -f "$DOCKER_CONFIG" ]; then
if [ ! -f "$SECRETS_DIR/jwt_secret" ]; then
$YQ '.identity_validation.reset_password.jwt_secret' "$DOCKER_CONFIG" \
| tr -d '"' > "$SECRETS_DIR/jwt_secret"
echo "Migrated jwt_secret"
fi
if [ ! -f "$SECRETS_DIR/session_secret" ]; then
$YQ '.session.secret' "$DOCKER_CONFIG" \
| tr -d '"' > "$SECRETS_DIR/session_secret"
echo "Migrated session_secret"
fi
if [ ! -f "$SECRETS_DIR/storage_encryption_key" ]; then
$YQ '.storage.encryption_key' "$DOCKER_CONFIG" \
| tr -d '"' > "$SECRETS_DIR/storage_encryption_key"
echo "Migrated storage_encryption_key"
fi
fi
chmod 644 "$SECRETS_DIR"/*
# Migrate users database
if [ ! -f "$STATE_DIR/users_database.yml" ] && \
[ -f "/home/fred/docker/authelia/users_database.yml" ]; then
cp /home/fred/docker/authelia/users_database.yml "$STATE_DIR/"
chown authelia-main:authelia-main "$STATE_DIR/users_database.yml"
echo "Migrated users_database.yml"
fi
echo "Authelia setup complete."
'';
in
{ {
config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") { config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") {
@ -100,17 +57,5 @@ in
notifier.filesystem.filename = "/var/lib/authelia-main/notification.txt"; notifier.filesystem.filename = "/var/lib/authelia-main/notification.txt";
}; };
}; };
# Auto-migrate Docker Authelia data on first deploy
systemd.services.authelia-setup = {
description = "Migrate Authelia secrets and user database from Docker";
before = [ "authelia-main.service" ];
requiredBy = [ "authelia-main.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = setupScript;
};
};
}; };
} }

View file

@ -6,7 +6,6 @@
# Bazarr # Bazarr
services.bazarr = { services.bazarr = {
enable = true; enable = true;
openFirewall = true; # Opens port 7878
dataDir = "/var/lib/bazarr"; dataDir = "/var/lib/bazarr";
user = "bazarr"; user = "bazarr";
group = "media"; group = "media";

View file

@ -11,14 +11,34 @@
# #
# Before first deploy, create /var/secrets/ntfy-url with your topic URL: # Before first deploy, create /var/secrets/ntfy-url with your topic URL:
# echo 'https://ntfy.sh/nordhammer-<random>' | sudo tee /var/secrets/ntfy-url # echo 'https://ntfy.sh/nordhammer-<random>' | sudo tee /var/secrets/ntfy-url
# sudo chmod 640 /var/secrets/ntfy-url # sudo chmod 600 /var/secrets/ntfy-url
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
ntfyUrlFile = "/var/secrets/ntfy-url"; # The real URL is injected at service start (see ExecStartPre below) —
ntfyUrl = # eval-time builtins.readFile can't see /var/secrets under pure flake
if builtins.pathExists ntfyUrlFile # evaluation, which is how the `update` alias builds.
then lib.removeSuffix "\n" (builtins.readFile ntfyUrlFile) ntfyUrlPlaceholder = "@NTFY_URL@";
else "https://ntfy.sh/CHANGE-ME-CREATE-VAR-SECRETS-NTFY-URL";
# The module renders settings.notifications into /etc/crowdsec/notifications/
# as a symlink into /etc/static (the store). Re-render it from the static
# source with the secret substituted on every service start; nixos-rebuild
# restores the symlink on activation, so this never goes stale.
injectNtfyUrl = pkgs.writeShellScript "crowdsec-inject-ntfy-url" ''
set -euo pipefail
src=/etc/static/crowdsec/notifications/0-nixos-generated.yaml
dst=/etc/crowdsec/notifications/0-nixos-generated.yaml
secret=/var/secrets/ntfy-url
if [ ! -f "$secret" ]; then
echo "WARNING: $secret not found; ntfy notifications will not work" >&2
exit 0
fi
url=$(${pkgs.coreutils}/bin/tr -d '\n' < "$secret")
tmp=$(${pkgs.coreutils}/bin/mktemp "$dst.XXXXXX")
${pkgs.gnused}/bin/sed "s|${ntfyUrlPlaceholder}|$url|g" "$src" > "$tmp"
${pkgs.coreutils}/bin/chmod 600 "$tmp"
${pkgs.coreutils}/bin/chown crowdsec:crowdsec "$tmp"
${pkgs.coreutils}/bin/mv "$tmp" "$dst"
'';
# nixpkgs only builds the agent + cscli; the new module also expects # nixpkgs only builds the agent + cscli; the new module also expects
# notification plugins at $out/libexec/crowdsec/plugins/. Compile them # notification plugins at $out/libexec/crowdsec/plugins/. Compile them
@ -128,7 +148,7 @@ in
name = "ntfy_http"; name = "ntfy_http";
type = "http"; type = "http";
log_level = "info"; log_level = "info";
url = ntfyUrl; url = ntfyUrlPlaceholder;
method = "POST"; method = "POST";
headers = { headers = {
Title = "CrowdSec alert"; Title = "CrowdSec alert";
@ -163,28 +183,15 @@ in
}; };
}; };
# Inject the ntfy topic URL into the rendered notification config before
# every start. "+" runs the script with full privileges (it reads the
# root-owned secret and replaces a root-owned /etc symlink).
systemd.services.crowdsec.serviceConfig.ExecStartPre = [ "+${injectNtfyUrl}" ];
# Firewall bouncer enforces decisions via nftables; auto-registers with LAPI # Firewall bouncer enforces decisions via nftables; auto-registers with LAPI
services.crowdsec-firewall-bouncer = { services.crowdsec-firewall-bouncer = {
enable = true; enable = true;
registerBouncer.enable = true; registerBouncer.enable = true;
}; };
# The hub keeps tracking upstream master, but nixpkgs stable's crowdsec
# binary is a few versions behind and doesn't know newer expr functions
# (e.g. LookupFile, used by crowdsecurity/http-technology-probing). The
# agent then refuses to load the entire bucket and crashes on startup.
# Strip incompatible scenarios after crowdsec-setup repopulates the hub
# but before crowdsec.service tries to load them.
systemd.services.crowdsec-prune-incompatible-hub-items = {
description = "Remove hub scenarios incompatible with the bundled crowdsec";
after = [ "crowdsec-setup.service" ];
before = [ "crowdsec.service" ];
requiredBy = [ "crowdsec.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${pkgs.coreutils}/bin/rm -f /etc/crowdsec/scenarios/http-technology-probing.yaml";
};
};
}; };
} }

View file

@ -66,7 +66,7 @@ in
}; };
}; };
networking.firewall.allowedTCPPorts = [ 2110 2603 2604 2605 2606 ]; # WAN forwards for 2110/2603-2606 were removed from ports.toml when this
networking.firewall.allowedUDPPorts = [ 2110 2603 2604 2605 2606 ]; # service was disabled — re-add them there if this comes back.
}; };
} }

View file

@ -187,8 +187,5 @@
StartLimitIntervalSec = 300; StartLimitIntervalSec = 300;
StartLimitBurst = 5; StartLimitBurst = 5;
}; };
networking.firewall.allowedTCPPorts = [ 26900 26910 ];
networking.firewall.allowedUDPPorts = [ 26900 26901 26902 26910 26911 26912 ];
}; };
} }

View file

@ -87,7 +87,6 @@ in
services.homepage-dashboard = { services.homepage-dashboard = {
enable = true; enable = true;
openFirewall = true;
listenPort = 8084; listenPort = 8084;
# Allow access from anywhere on the LAN # Allow access from anywhere on the LAN

View file

@ -5,7 +5,6 @@
# Jellyfin # Jellyfin
services.jellyfin = { services.jellyfin = {
enable = true; enable = true;
openFirewall = true;
}; };
# Ensure Jellyfin can write thumbnails/artwork to media directories # Ensure Jellyfin can write thumbnails/artwork to media directories

View file

@ -135,7 +135,5 @@ in
}; };
}; };
}; };
networking.firewall.allowedTCPPorts = [ 80 443 ];
}; };
} }

View file

@ -19,7 +19,6 @@
# Prowlarr # Prowlarr
services.prowlarr = { services.prowlarr = {
enable = true; enable = true;
openFirewall = true;
dataDir = "/var/lib/prowlarr"; dataDir = "/var/lib/prowlarr";
}; };
}; };

View file

@ -6,7 +6,6 @@
# Radarr # Radarr
services.radarr = { services.radarr = {
enable = true; enable = true;
openFirewall = true; # Opens port 7878
dataDir = "/var/lib/radarr"; dataDir = "/var/lib/radarr";
user = "radarr"; user = "radarr";
group = "media"; group = "media";

View file

@ -6,7 +6,6 @@
# Sonarr # Sonarr
services.sonarr = { services.sonarr = {
enable = true; enable = true;
openFirewall = true;
dataDir = "/var/lib/sonarr"; dataDir = "/var/lib/sonarr";
user = "sonarr"; user = "sonarr";
group = "media"; group = "media";

File diff suppressed because it is too large Load diff

1987
settings/quickshell.nix Normal file

File diff suppressed because it is too large Load diff