Compare commits

..

No commits in common. "9671dfb7933340d9ff17f37258deb4e6f6b0a1c1" and "1145f1ca5a0c507c10aa21b33be38765ba382c9b" have entirely different histories.

19 changed files with 2186 additions and 2123 deletions

View file

@ -2,28 +2,18 @@
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 **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-Mediaserver** — home media server
- **FredOS-Macbook** — MacBook laptop - **FredOS-Macbook** — MacBook laptop
## Structure ## Structure
- `flake.nix` — flake inputs/outputs; all hosts track the `nixos-26.05` stable channel - `flake.nix` — flake inputs/outputs; all hosts use `nixpkgs` unstable
- `common.nix` — shared configuration across all hosts - `common.nix` — shared configuration across all hosts
- `hosts/` — per-host NixOS configuration modules (imported per-host by `mkHost` in flake.nix) - `hosts/` — per-host NixOS configuration modules
- `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, gated by hostname with `lib.mkIf` - `services/` — modular service definitions imported by hosts
- `settings/` — shared settings (desktop, hyprland, quickshell, stylix, …) - `settings/` — shared settings/variables
- `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
@ -33,9 +23,6 @@ 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,12 +3,14 @@
{ {
imports = [ imports = [
# Host modules are imported per-host by mkHost in flake.nix. # Hosts #
./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
@ -87,10 +89,6 @@
# 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;
}; };
# WAN exposure is controlled by nftables in services/router.nix + # Open firewall for SSH
# ports.toml (networking.firewall is disabled on this host). networking.firewall.allowedTCPPorts = [ 22 11434 ];
services.openssh = { services.openssh = {
enable = true; enable = true;
settings = { settings = {

View file

@ -45,5 +45,22 @@ name = "7DTD-coop voice/dynamic"
ports = "26911-26912" ports = "26911-26912"
protocol = "udp" protocol = "udp"
# DR (Dungeon Runners) forwards removed — services/dr-server.nix is disabled. [[forward]]
# Re-add 2110 tcp, 2603 both, 2604-2605 udp, 2606 tcp if it comes back. name = "DR auth"
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,89 +1,86 @@
# FredOS NixOS Configuration # 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. Flake-based NixOS configuration for three machines, built and deployed directly from GitHub. No local config management required after initial setup.
## Machines ## Machines
| Hostname | Description | | Hostname | Description |
|---|---| |---|---|
| FredOS-Gaming | AMD desktop, UEFI/systemd-boot, CachyOS kernel | | FredOS-Gaming | AMD desktop, UEFI/systemd-boot |
| FredOS-Macbook | Intel laptop, UEFI/systemd-boot | | FredOS-Macbook | Intel laptop, UEFI/systemd-boot |
| FredOS-Mediaserver | Intel server, UEFI/systemd-boot — media services **and** the home router | | FredOS-Mediaserver | Intel server, BIOS/GRUB |
## Structure ## Structure
``` ```
├── .forgejo ├── .github
│ └── workflows │ └── workflows
│ └── update.yml # Auto-updates flake.lock daily (self-hosted runner) │ └── update.yml # Auto-updates flake.lock daily
├── apps ├── apps
│ └── zen.nix # Zen browser (flake input) │ └── zen.nix # Zen browser config
├── 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, DWT daemon │ ├── FredOS-Macbook.nix # Macbook: packages, power management, boot options
│ ├── FredOS-Mediaserver.nix # Mediaserver: packages, SSH, auto-upgrade │ ├── FredOS-Mediaserver.nix # Mediaserver: packages, networking, SSH
│ └── hardware │ └── hardware
│ ├── FredOS-Gaming.nix # AMD GPU, CachyOS kernel overlay, filesystems, hostname │ ├── FredOS-Gaming.nix # AMD GPU, kernel modules, filesystems, bootloader, hostname
│ ├── FredOS-Macbook.nix # Broadcom WiFi, Intel GPU, filesystems, hostname │ ├── FredOS-Macbook.nix # Broadcom WiFi, Intel GPU, Bluetooth, filesystems, bootloader, hostname
│ └── FredOS-Mediaserver.nix # NVIDIA NVENC, data disks, mergerfs pool, hostname │ └── FredOS-Mediaserver.nix # Intel CPU, data disks, mergerfs pool, GRUB, 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 for the nginx vhosts │ ├── authelia.nix # SSO/2FA gateway (protects homepage & camera)
│ ├── 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 + nftables bouncer + ntfy alerts │ ├── crowdsec.nix # Intrusion prevention / bouncer
│ ├── dr-server.nix # Dungeon Runners game server (Wine) — currently disabled │ ├── dr-server.nix # Disaster recovery / backup service
│ ├── forgejo-runner.nix # CI runner for forg.gregersen.it │ ├── forgejo-runner.nix # CI/CD runner for Forgejo
│ ├── frigate.nix # NVR with object detection │ ├── game-servers.nix # Dockerised game servers (7 Days to Die)
│ ├── 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 (container) │ ├── profilarr.nix # Quality profile manager for *arr apps
│ ├── 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 # Shared media dir permissions │ ├── server-permissions.nix # File/dir permission setup
│ └── sonarr.nix # TV management │ └── sonarr.nix # TV management
├── settings ├── settings
│ ├── audio.nix # PipeWire / audio config │ ├── audio.nix # PipeWire / audio config
│ ├── desktop.nix # Display manager, theming, flatpak │ ├── gnome.nix # GNOME desktop settings
│ ├── hyprland.nix # Hyprland compositor config (Lua), anyrun │ ├── hyprland.nix # Hyprland Wayland compositor config
│ ├── 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, SSH keys │ └── users.nix # User accounts
├── 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 Forgejo Actions ├── flake.lock # Auto-generated, updated daily by GitHub 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 in the Forgejo repo (or locally and push), then on the machine run: Edit files directly on GitHub, then on the machine run:
```bash ```bash
update 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`). 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.
Other useful aliases: Other useful aliases:
@ -101,38 +98,65 @@ Boot the NixOS installer and complete the standard installation.
### 2. Enable flakes temporarily ### 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: Add this to `/etc/nixos/configuration.nix` and rebuild:
```nix ```nix
nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ];
``` ```
### 3. Create the hardware config in the repo ```bash
sudo nixos-rebuild switch
```
Copy `/etc/nixos/hardware-configuration.nix` to `hosts/hardware/FredOS-NEWHOST.nix` and append the hostname and bootloader config: ### 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:
```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` for machine-specific packages or services. `mkHost` imports it automatically — no changes to `common.nix` needed. 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
```
### 6. Switch to the flake ### 6. Switch to the flake
Run once with the explicit hostname: Run this once on the new machine with the explicit hostname:
```bash ```bash
sudo nixos-rebuild switch --refresh --flake git+https://forg.gregersen.it/rope/nixos#FredOS-NEWHOST sudo nixos-rebuild switch --flake github:ediblerope/nixos-config#FredOS-NEWHOST --refresh --no-write-lock-file
``` ```
After this succeeds, the plain `update` alias works from then on. After this succeeds, the plain `update` alias works from then on.
@ -143,19 +167,22 @@ After this succeeds, the plain `update` alias works from then on.
| Input | Source | | Input | Source |
|---|---| |---|---|
| nixpkgs | `github:NixOS/nixpkgs/nixos-26.05` | | nixpkgs | `github:NixOS/nixpkgs/nixos-unstable` |
| home-manager | `github:nix-community/home-manager/release-26.05` | | nixpkgs-stable | `github:NixOS/nixpkgs/nixos-25.11` |
| stylix | `github:nix-community/stylix/release-26.05` | | home-manager-stable | `github:nix-community/home-manager/release-25.11` |
| zen-browser | `github:0xc000022070/zen-browser-flake` | | 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) | | nix-cachyos-kernel | `github:xddxdd/nix-cachyos-kernel/release` |
| 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 stored on the machine (not in the repo). After a fresh deploy, create these before running `update`: 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`:
```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
@ -163,23 +190,18 @@ 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
# CrowdSec ntfy.sh alert topic (injected into the notification config at service start) # Authelia secrets — auto-migrated from Docker on first deploy
echo 'https://ntfy.sh/your-private-topic' | sudo tee /var/secrets/ntfy-url # If migrating from Docker, ensure these exist at /home/fred/docker/authelia/:
sudo chmod 600 /var/secrets/ntfy-url # - configuration.yml (jwt_secret, session secret, storage key are extracted)
# - users_database.yml (copied to /var/lib/authelia-main/)
# Forgejo Actions runner registration token (one-time use, KEY=value format) # For a fresh install, create manually:
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 chown root:authelia-main /var/secrets/authelia/* sudo chmod 600 /var/secrets/authelia/*
sudo chmod 640 /var/secrets/authelia/*
# Authelia user database # Authelia user database (for a fresh install)
# Create users_database.yml with this structure: # Create users_database.yml with this structure:
# --- # ---
# users: # users:
@ -197,7 +219,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 (`backup-server.sh` automates most of it): When moving FredOS-Mediaserver to new hardware, back up these state directories from the old server:
```bash ```bash
# Service databases and config (stop services first) # Service databases and config (stop services first)
@ -210,7 +232,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/ # See "Mediaserver secrets" above /var/secrets/ # Cloudflare token, go2rtc RTSP URL, Authelia secrets
# Media files # Media files
/mnt/storage/ # The mergerfs pool (torrents, media libraries, audiobooks) /mnt/storage/ # The mergerfs pool (torrents, media libraries, audiobooks)
@ -219,18 +241,17 @@ 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. Update `hosts/hardware/FredOS-Mediaserver.nix` from the new `/etc/nixos/hardware-configuration.nix` (new disk UUIDs, bootloader config) 2. Create `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 --refresh --flake git+https://forg.gregersen.it/rope/nixos#FredOS-Mediaserver` 5. Run `sudo nixos-rebuild switch --flake github:ediblerope/nixos-config#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 self-initialize with empty databases — redo initial setup in each web UI; `arr-interconnect` auto-wires the connections between them. 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.
## Notes ## 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 to the repo — they contain UUIDs and disk layout but no sensitive credentials
- `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-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` - 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
- `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,7 +45,8 @@
}; };
}; };
# LAN clients reach :53 via the nftables "LAN trusted" rule in router.nix; # LAN DNS — router blocks WAN:53 so this is effectively LAN-only
# WAN:53 is dropped there. networking.firewall.allowedTCPPorts = [ 53 ];
networking.firewall.allowedUDPPorts = [ 53 ];
}; };
} }

View file

@ -1,6 +1,49 @@
# services/authelia.nix — Native Authelia SSO # services/authelia.nix — Native Authelia SSO with auto-migration from Docker
# 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") {
@ -57,5 +100,17 @@
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,6 +6,7 @@
# 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,34 +11,14 @@
# #
# 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 600 /var/secrets/ntfy-url # sudo chmod 640 /var/secrets/ntfy-url
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
let let
# The real URL is injected at service start (see ExecStartPre below) — ntfyUrlFile = "/var/secrets/ntfy-url";
# eval-time builtins.readFile can't see /var/secrets under pure flake ntfyUrl =
# evaluation, which is how the `update` alias builds. if builtins.pathExists ntfyUrlFile
ntfyUrlPlaceholder = "@NTFY_URL@"; then lib.removeSuffix "\n" (builtins.readFile ntfyUrlFile)
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
@ -148,7 +128,7 @@ in
name = "ntfy_http"; name = "ntfy_http";
type = "http"; type = "http";
log_level = "info"; log_level = "info";
url = ntfyUrlPlaceholder; url = ntfyUrl;
method = "POST"; method = "POST";
headers = { headers = {
Title = "CrowdSec alert"; Title = "CrowdSec alert";
@ -183,15 +163,28 @@ 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
}; };
}; };
# WAN forwards for 2110/2603-2606 were removed from ports.toml when this networking.firewall.allowedTCPPorts = [ 2110 2603 2604 2605 2606 ];
# service was disabled — re-add them there if this comes back. networking.firewall.allowedUDPPorts = [ 2110 2603 2604 2605 2606 ];
}; };
} }

View file

@ -187,5 +187,8 @@
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,6 +87,7 @@ 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,6 +5,7 @@
# 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,5 +135,7 @@ in
}; };
}; };
}; };
networking.firewall.allowedTCPPorts = [ 80 443 ];
}; };
} }

View file

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

View file

@ -6,6 +6,7 @@
# 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,6 +6,7 @@
# 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

File diff suppressed because it is too large Load diff