Compare commits
No commits in common. "9671dfb7933340d9ff17f37258deb4e6f6b0a1c1" and "1145f1ca5a0c507c10aa21b33be38765ba382c9b" have entirely different histories.
9671dfb793
...
1145f1ca5a
19 changed files with 2186 additions and 2123 deletions
23
CLAUDE.md
23
CLAUDE.md
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
||||||
12
common.nix
12
common.nix
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
21
ports.toml
21
ports.toml
|
|
@ -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
157
readme.md
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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 ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -135,5 +135,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
# Prowlarr
|
# Prowlarr
|
||||||
services.prowlarr = {
|
services.prowlarr = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
dataDir = "/var/lib/prowlarr";
|
dataDir = "/var/lib/prowlarr";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue