2025-12-03 09:55:43 +00:00
|
|
|
# Common.nix
|
2026-03-28 12:41:29 +00:00
|
|
|
{ config, pkgs, lib, inputs, ... }:
|
2025-12-02 21:44:07 +00:00
|
|
|
|
2026-08-09 09:07:59 +01:00
|
|
|
let
|
|
|
|
|
isMacbook = config.networking.hostName == "FredOS-Macbook";
|
|
|
|
|
|
|
|
|
|
# nom's Haskell renderer measured 91% CPU during a rebuild — a full core of
|
|
|
|
|
# the Macbook's four, spent drawing a progress tree, while nix itself wanted
|
|
|
|
|
# 146%. Plain `-L` output there; the other hosts can afford the pretty one.
|
|
|
|
|
buildLog = lib.optionalString (!isMacbook)
|
|
|
|
|
" --log-format internal-json 2>&1 | nom --json";
|
2026-08-13 11:49:19 +01:00
|
|
|
|
|
|
|
|
flake = "git+https://forg.gregersen.it/rope/nixos";
|
|
|
|
|
|
2026-08-13 14:50:14 +01:00
|
|
|
# Now that the clients only copy, the copy is the whole wait. nix's own
|
|
|
|
|
# --log-format bar prints counters but no gauge, so render one from the json
|
|
|
|
|
# log: activity type 103 is the top-level copy-paths, and its type-105
|
|
|
|
|
# progress results carry [done, expected, running, failed] path counts.
|
|
|
|
|
# Messages at warning level and above still go through to stderr — otherwise
|
|
|
|
|
# folding nix's stderr into the filter would swallow the errors too.
|
|
|
|
|
copyBar = pkgs.writeShellScriptBin "nix-copy-bar" ''
|
|
|
|
|
set -o pipefail
|
|
|
|
|
${pkgs.jq}/bin/jq -Rr --unbuffered '
|
|
|
|
|
ltrimstr("@nix ") | fromjson? // empty
|
|
|
|
|
| if .action == "start" and .type == 103 then "A \(.id)"
|
|
|
|
|
elif .action == "result" and .type == 105 then "P \(.id) \(.fields[0]) \(.fields[1])"
|
|
|
|
|
elif .action == "msg" and .level <= 2 then "M \(.msg)"
|
|
|
|
|
else empty end' |
|
|
|
|
|
{
|
|
|
|
|
blocks='########################################'
|
|
|
|
|
drawn=
|
2026-08-17 09:51:40 +01:00
|
|
|
total=0
|
2026-08-13 14:50:14 +01:00
|
|
|
while read -r tag rest; do
|
|
|
|
|
case "$tag" in
|
|
|
|
|
A) copy_id=$rest ;;
|
|
|
|
|
M) [ -n "$drawn" ] && printf '\n' && drawn=
|
|
|
|
|
printf '%s\n' "$rest" >&2 ;;
|
|
|
|
|
P) set -- $rest
|
|
|
|
|
[ "$1" = "''${copy_id:-}" ] && [ "''${3:-0}" -gt 0 ] || continue
|
2026-08-17 09:51:40 +01:00
|
|
|
total=$3
|
2026-08-13 14:50:14 +01:00
|
|
|
pct=$(( $2 * 100 / $3 ))
|
|
|
|
|
printf '\r[%-40s] %3d%% %d/%d paths' \
|
|
|
|
|
"''${blocks:0:$(( pct * 40 / 100 ))}" "$pct" "$2" "$3"
|
|
|
|
|
drawn=1 ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
2026-08-13 14:57:32 +01:00
|
|
|
# `if`, not `[ … ] && …`: with nothing to copy the bar never draws, and a
|
|
|
|
|
# bare test as the last statement would exit 1 and break the caller's
|
|
|
|
|
# && chain. A real copy failure still surfaces via the caller's pipefail.
|
2026-08-17 09:51:40 +01:00
|
|
|
#
|
|
|
|
|
# nix stops emitting type-105 results once the last path lands, so the
|
|
|
|
|
# bar's last live frame is always one path short. Redraw it full on EOF.
|
|
|
|
|
# An error would have cleared $drawn via the M branch first, so this only
|
|
|
|
|
# fires on a copy that actually finished.
|
|
|
|
|
if [ -n "$drawn" ]; then
|
|
|
|
|
printf '\r[%-40s] 100%% %d/%d paths\n' "$blocks" "$total" "$total"
|
|
|
|
|
fi
|
2026-08-13 14:50:14 +01:00
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
|
2026-08-21 17:13:12 +01:00
|
|
|
post = "nvd diff $OLD_SYSTEM /run/current-system && (command -v record-update &>/dev/null && record-update $OLD_SYSTEM /run/current-system || true)";
|
2026-08-13 11:49:19 +01:00
|
|
|
|
|
|
|
|
# The server evaluates and builds for itself.
|
|
|
|
|
localUpdate = "bash -c 'set -o pipefail && OLD_SYSTEM=$(readlink /run/current-system) && sudo nixos-rebuild switch $@ --refresh --flake ${flake} -L${buildLog} && ${post}' --";
|
|
|
|
|
|
|
|
|
|
# The clients offload the *evaluation* too, not just the build. nixos-rebuild
|
|
|
|
|
# always evaluates on the machine it runs on (--build-host ships the
|
|
|
|
|
# derivation, not the expression), so the only way to move eval off a client
|
|
|
|
|
# is to run nix on the server over SSH and hand the resulting closure to
|
|
|
|
|
# --store-path, which skips eval and build entirely. Same lantian substituter
|
|
|
|
|
# as services/prebuild.nix, and --out-link keeps a gc root on the server so
|
2026-08-13 11:53:52 +01:00
|
|
|
# `clean` there can't reap the closure mid-copy. SSH and the copy run as
|
|
|
|
|
# fred, not root: root has no key on the server, and require-sigs = false
|
|
|
|
|
# below is a daemon setting, so the daemon takes the unsigned closure from an
|
|
|
|
|
# untrusted user just fine. Only the activation needs sudo.
|
2026-08-13 14:50:14 +01:00
|
|
|
remoteUpdate = "bash -c 'set -o pipefail && OLD_SYSTEM=$(readlink /run/current-system) && NEW=$(ssh fred@nordhammer.it \"nix build --refresh --print-out-paths --out-link prebuild-${config.networking.hostName} --option extra-substituters https://attic.xuyh0120.win/lantian --option extra-trusted-public-keys lantian:EeAUQ+W+6r7EtwnmYjeVwx5kOGEBpjlBfPlzGlTNvHc= ${flake}#nixosConfigurations.${config.networking.hostName}.config.system.build.toplevel\") && nix copy --from ssh://fred@nordhammer.it $NEW --log-format internal-json 2>&1 | ${copyBar}/bin/nix-copy-bar && sudo nixos-rebuild switch --no-reexec --store-path $NEW && ${post}'";
|
2026-08-09 09:07:59 +01:00
|
|
|
in
|
2025-12-02 21:44:07 +00:00
|
|
|
{
|
2025-12-25 12:08:14 +00:00
|
|
|
imports = [
|
2026-06-11 10:00:02 +01:00
|
|
|
# Host modules are imported per-host by mkHost in flake.nix.
|
|
|
|
|
|
2026-01-21 09:27:33 +00:00
|
|
|
# Generic settings #
|
2026-05-16 17:50:01 +01:00
|
|
|
./settings/desktop.nix
|
2026-05-10 20:03:43 +01:00
|
|
|
./settings/hyprland.nix
|
2026-06-11 10:00:02 +01:00
|
|
|
./settings/quickshell.nix
|
2026-08-19 14:31:24 +01:00
|
|
|
./settings/theming.nix
|
2025-12-25 12:08:14 +00:00
|
|
|
./settings/locale.nix
|
|
|
|
|
./settings/audio.nix
|
|
|
|
|
./settings/users.nix
|
2026-04-08 14:03:34 +01:00
|
|
|
./settings/shell.nix
|
2026-03-28 14:54:56 +00:00
|
|
|
./apps/zen.nix
|
2026-08-23 10:18:13 +01:00
|
|
|
./apps/lookout.nix
|
2026-01-21 09:27:33 +00:00
|
|
|
|
|
|
|
|
# Services #
|
2026-02-20 21:42:53 +00:00
|
|
|
./services/server-permissions.nix
|
2026-04-17 22:28:49 +01:00
|
|
|
./services/game-servers.nix
|
2026-08-09 11:57:15 +01:00
|
|
|
./services/pelican.nix
|
2026-05-30 16:47:12 +01:00
|
|
|
# ./services/dr-server.nix
|
2026-01-21 22:56:04 +00:00
|
|
|
./services/qbittorrent-nox.nix
|
2026-04-14 21:59:18 +01:00
|
|
|
./services/nginx.nix
|
|
|
|
|
./services/go2rtc.nix
|
2026-06-18 21:11:06 +01:00
|
|
|
# ./services/frigate.nix
|
2026-01-22 09:36:15 +00:00
|
|
|
./services/sonarr.nix
|
2026-01-25 11:14:49 +00:00
|
|
|
./services/radarr.nix
|
2026-01-22 10:43:58 +00:00
|
|
|
./services/prowlarr.nix
|
2026-01-22 10:46:41 +00:00
|
|
|
./services/jellyfin.nix
|
2026-07-28 07:43:59 +01:00
|
|
|
./services/seerr.nix
|
2026-02-01 13:09:34 +00:00
|
|
|
./services/bazarr.nix
|
2026-05-19 10:46:30 +01:00
|
|
|
./services/bazarr-sync.nix
|
2026-04-14 21:59:18 +01:00
|
|
|
./services/cloudflare-ddns.nix
|
|
|
|
|
./services/authelia.nix
|
Add Homepage dashboard for FredOS-Mediaserver
Covers all running services: Jellyfin, Sonarr, Radarr, Bazarr, Prowlarr,
qBittorrent, Nginx Proxy Manager, Authelia, go2rtc. Live widgets for
*arr apps, Jellyfin now-playing, and qBittorrent speed use API keys
loaded from /etc/homepage-secrets (outside the Nix store).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 12:55:37 +01:00
|
|
|
./services/homepage.nix
|
2026-04-07 13:45:53 +01:00
|
|
|
./services/arr-interconnect.nix
|
2026-04-30 20:00:33 +01:00
|
|
|
./services/profilarr.nix
|
2026-07-21 10:01:57 +01:00
|
|
|
./services/shelfarr.nix
|
2026-04-22 13:40:30 +01:00
|
|
|
./services/adguard.nix
|
Turn mediaserver into a home router
Adds services/router.nix with systemd-networkd (eno1=WAN via DHCP,
eth0=LAN 10.0.0.1/24), nftables (NAT + firewall, default drop on WAN
in), dnsmasq (DHCP only — AdGuard Home keeps :53 for DNS), and sysctl
IP forwarding. NetworkManager is forced off on this host.
Port forwards live in ports.toml at the repo root and are imported via
builtins.fromTOML. Supports single ports, ranges ("26901-26902"), and
"both" protocol. Initial forwards: 22, 80, 443, 26900, 26901-26902.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 09:48:38 +01:00
|
|
|
./services/router.nix
|
2026-08-15 12:15:54 +01:00
|
|
|
./services/router-ui.nix
|
2026-04-24 22:30:16 +01:00
|
|
|
./services/crowdsec.nix
|
2026-06-13 17:54:37 +01:00
|
|
|
./services/service-health.nix
|
2026-05-04 01:38:14 -07:00
|
|
|
./services/sabnzbd.nix
|
2026-05-01 15:58:24 +01:00
|
|
|
./services/forgejo-runner.nix
|
2026-05-16 10:59:35 +01:00
|
|
|
./services/code-server.nix
|
2026-05-17 14:19:52 +01:00
|
|
|
./services/memos.nix
|
2026-06-25 19:37:35 +01:00
|
|
|
./services/hardware-health.nix
|
2026-07-31 13:58:07 +01:00
|
|
|
./services/wan-watchdog.nix
|
2026-08-09 11:26:09 +01:00
|
|
|
./services/prebuild.nix
|
2025-12-03 15:48:10 +00:00
|
|
|
];
|
2026-01-20 08:17:51 +00:00
|
|
|
|
2026-01-31 20:49:51 +00:00
|
|
|
### Make build time quicker
|
|
|
|
|
documentation.nixos.enable = false;
|
|
|
|
|
|
2026-01-20 08:17:51 +00:00
|
|
|
# Home Manager #
|
|
|
|
|
home-manager.useGlobalPkgs = true;
|
|
|
|
|
home-manager.useUserPackages = true;
|
2026-05-18 14:08:29 +01:00
|
|
|
home-manager.backupFileExtension = "hm-bak";
|
2026-03-28 12:40:30 +00:00
|
|
|
home-manager.extraSpecialArgs = { inherit inputs; };
|
2026-01-20 08:41:29 +00:00
|
|
|
home-manager.users.fred = import ./home-manager/fred.nix;
|
2026-01-20 08:17:51 +00:00
|
|
|
|
2026-01-11 10:35:34 +00:00
|
|
|
#############################################################################
|
2026-05-15 22:11:57 +01:00
|
|
|
# Kill all user processes on logout so systemd user services don't linger
|
|
|
|
|
# in broken states across sessions (e.g. waybar failing to start on re-login).
|
|
|
|
|
services.logind.settings.Login.KillUserProcesses = true;
|
|
|
|
|
|
2026-05-16 19:41:21 +01:00
|
|
|
# Shorten shutdown stop timeout to avoid long "stop job" waits
|
|
|
|
|
systemd.settings.Manager.DefaultTimeoutStopSec = "10s";
|
|
|
|
|
|
2026-01-11 10:26:51 +00:00
|
|
|
# Make boot time quicker
|
2026-03-28 15:34:14 +00:00
|
|
|
boot.loader.timeout = lib.mkDefault 5;
|
2026-01-11 10:26:51 +00:00
|
|
|
systemd.services.NetworkManager-wait-online.enable = false;
|
|
|
|
|
systemd.services.systemd-udev-settle.enable = false;
|
2026-01-11 10:35:34 +00:00
|
|
|
systemd.services.firewall = {
|
|
|
|
|
wantedBy = lib.mkForce [ ];
|
|
|
|
|
after = [ "multi-user.target" ];
|
|
|
|
|
};
|
2026-02-24 19:22:44 +00:00
|
|
|
|
2026-01-11 10:52:17 +00:00
|
|
|
boot.initrd.verbose = false;
|
2026-01-11 10:35:34 +00:00
|
|
|
#############################################################################
|
2026-01-11 10:26:51 +00:00
|
|
|
|
2026-05-01 13:38:27 +01:00
|
|
|
# Compressed in-memory swap as a safety net during local build storms.
|
|
|
|
|
# Cheap when idle; without it a transient OOM during an uncached build
|
|
|
|
|
# can stall AdGuard/Jellyfin to the point of freezing the box.
|
|
|
|
|
zramSwap = {
|
|
|
|
|
enable = true;
|
|
|
|
|
memoryPercent = 50;
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-25 12:08:14 +00:00
|
|
|
# Use latest kernel
|
|
|
|
|
boot.kernelPackages = pkgs.linuxPackages_latest;
|
|
|
|
|
|
|
|
|
|
# Allow unfree packages
|
|
|
|
|
nixpkgs.config.allowUnfree = true;
|
|
|
|
|
|
2026-06-30 20:00:32 +01:00
|
|
|
# vesktop (multiple hosts) builds with pnpm via fetchPnpmDeps, which nixpkgs
|
|
|
|
|
# marks insecure (build-time only, hash-pinned FOD — not in PATH). broadcom-sta
|
|
|
|
|
# is Macbook-only Wi-Fi but allowing it everywhere is harmless (absent on others).
|
|
|
|
|
nixpkgs.config.allowInsecurePredicate = pkg:
|
|
|
|
|
lib.any (p: lib.hasPrefix p (lib.getName pkg)) [ "broadcom-sta" "pnpm" ];
|
|
|
|
|
|
2026-06-11 10:00:02 +01:00
|
|
|
# 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" ];
|
|
|
|
|
|
2025-12-25 12:08:14 +00:00
|
|
|
# Enable network-manager
|
|
|
|
|
networking.networkmanager.enable = true;
|
|
|
|
|
|
Switch to fish shell, ghostty terminal, and simplified prompt
- Replace bash with fish as default shell (all hosts)
- Replace kgx with ghostty (desktop hosts), update Super+T keybinding
- Custom two-line fish prompt: NixOS icon, username, path, hostname, ❯
- Nix-shell awareness, red ❯ on error
- Simplify fastfetch: user@host, OS, kernel, shell, terminal, uptime, memory
- Ghostty config: FiraCode Nerd Font, catppuccin-mocha, no titlebar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 13:22:21 +01:00
|
|
|
# Fish shell
|
|
|
|
|
programs.fish.enable = true;
|
|
|
|
|
users.defaultUserShell = pkgs.fish;
|
|
|
|
|
|
|
|
|
|
# Shell aliases (work in both bash and fish)
|
2025-12-25 12:08:14 +00:00
|
|
|
environment.shellAliases = {
|
2026-08-13 11:49:19 +01:00
|
|
|
update =
|
|
|
|
|
if config.networking.hostName == "FredOS-Mediaserver"
|
|
|
|
|
then localUpdate
|
|
|
|
|
else remoteUpdate;
|
2026-08-13 13:53:01 +01:00
|
|
|
# Fallback for when the server is down or the alias itself changed and the
|
|
|
|
|
# live generation still has the old one: eval and build right here.
|
|
|
|
|
update2 = localUpdate;
|
2025-12-25 12:08:14 +00:00
|
|
|
clean = "sudo nix-collect-garbage -d";
|
2026-08-10 09:51:24 +01:00
|
|
|
# Throw away the VM's disk first so every run is a real first boot —
|
|
|
|
|
# greeter, Plasma first-run, the lot. Builds into ./result in $PWD.
|
|
|
|
|
kaylavm = "rm -f FredOS-Kayla.qcow2 && nixos-rebuild build-vm --refresh --flake git+https://forg.gregersen.it/rope/nixos#kayla-vm && ./result/bin/run-FredOS-Kayla-vm";
|
2025-12-25 12:08:14 +00:00
|
|
|
ll = "ls -alh";
|
2026-04-08 13:34:36 +01:00
|
|
|
clear = "command clear";
|
2026-05-15 20:20:35 +01:00
|
|
|
reboot = "bash -c 'if [ \"$(hostname)\" = \"FredOS-Mediaserver\" ]; then read -r -p \"Reboot $(hostname)? [y/N] \" confirm; case \"$confirm\" in [Yy]) ;; *) exit 0 ;; esac; fi; sudo systemctl reboot'";
|
2025-12-25 12:08:14 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Add packages
|
|
|
|
|
environment.systemPackages = with pkgs; [
|
|
|
|
|
git
|
2026-03-18 10:49:24 +00:00
|
|
|
localsend
|
2026-04-13 11:41:23 +01:00
|
|
|
nvd
|
2026-05-04 19:58:49 +01:00
|
|
|
nix-output-monitor
|
2026-05-04 20:08:15 +01:00
|
|
|
jq
|
2026-05-06 13:16:16 +01:00
|
|
|
dnsutils
|
2026-04-22 19:57:55 +01:00
|
|
|
busybox
|
2025-12-25 12:08:14 +00:00
|
|
|
];
|
2026-05-14 14:46:31 +01:00
|
|
|
|
2026-06-03 17:17:00 +01:00
|
|
|
# Hard-link identical files in the store as new paths are added, so the
|
|
|
|
|
# store de-duplicates itself on every build instead of drifting.
|
2026-08-09 09:07:59 +01:00
|
|
|
# Off on the Macbook: it hashes and relinks every file the daemon writes,
|
|
|
|
|
# which lands on top of the substitution writes on a slow SATA blade —
|
|
|
|
|
# measured io pressure full avg60=57% during a rebuild. Run `nix store
|
|
|
|
|
# optimise` by hand there if the store ever gets fat.
|
|
|
|
|
nix.settings.auto-optimise-store = !isMacbook;
|
2026-06-03 17:17:00 +01:00
|
|
|
|
2026-05-14 14:46:31 +01:00
|
|
|
# Offload builds to the media server. Excluded on the server itself to
|
|
|
|
|
# avoid a pointless SSH round-trip to localhost.
|
|
|
|
|
nix.distributedBuilds =
|
|
|
|
|
lib.mkIf (config.networking.hostName != "FredOS-Mediaserver") true;
|
|
|
|
|
|
|
|
|
|
nix.buildMachines =
|
|
|
|
|
lib.mkIf (config.networking.hostName != "FredOS-Mediaserver") [{
|
|
|
|
|
hostName = "nordhammer.it";
|
2026-05-17 21:12:53 +01:00
|
|
|
systems = [ "x86_64-linux" "i686-linux" ];
|
2026-05-14 14:46:31 +01:00
|
|
|
sshUser = "fred";
|
|
|
|
|
sshKey = "/root/.ssh/id_ed25519";
|
|
|
|
|
maxJobs = 4;
|
|
|
|
|
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
|
|
|
|
|
}];
|
|
|
|
|
|
2026-08-13 13:56:44 +01:00
|
|
|
# nix.buildMachines runs from the daemon, i.e. as root, so it needs root's own
|
|
|
|
|
# key — fred's doesn't count, and ssh refuses a key file owned by another user
|
|
|
|
|
# outright. Copying fred's (root can read it anyway, so this leaks nothing new)
|
|
|
|
|
# beats committing a private key to a repo the hosts fetch over plain HTTPS.
|
|
|
|
|
# C+ re-copies on every activation, so rotating fred's key rotates root's too.
|
|
|
|
|
systemd.tmpfiles.rules = lib.mkIf
|
|
|
|
|
(lib.elem config.networking.hostName [ "FredOS-Gaming" "FredOS-Macbook" ]) [
|
|
|
|
|
"d /root/.ssh 0700 root root -"
|
|
|
|
|
"C+ /root/.ssh/id_ed25519 0600 root root - /home/fred/.ssh/id_ed25519"
|
|
|
|
|
];
|
|
|
|
|
|
2026-05-14 14:46:31 +01:00
|
|
|
# Accept unsigned paths copied back from the remote builder.
|
|
|
|
|
nix.extraOptions =
|
|
|
|
|
lib.mkIf (config.networking.hostName != "FredOS-Mediaserver") ''
|
|
|
|
|
require-sigs = false
|
|
|
|
|
'';
|
2025-12-02 21:44:07 +00:00
|
|
|
}
|