nixos/common.nix

260 lines
11 KiB
Nix
Raw Normal View History

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
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";
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=
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
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
# `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.
#
# 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
}
'';
post = "nvd diff $OLD_SYSTEM /run/current-system && (command -v record-update &>/dev/null && record-update $OLD_SYSTEM /run/current-system || true)";
# 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
# `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}'";
in
2025-12-02 21:44:07 +00:00
{
2025-12-25 12:08:14 +00:00
imports = [
# Host modules are imported per-host by mkHost in flake.nix.
2026-01-21 09:27:33 +00:00
# Generic settings #
./settings/desktop.nix
./settings/hyprland.nix
./settings/quickshell.nix
./settings/theming.nix
2025-12-25 12:08:14 +00:00
./settings/locale.nix
./settings/audio.nix
./settings/users.nix
./settings/shell.nix
2026-03-28 14:54:56 +00:00
./apps/zen.nix
./apps/lookout.nix
2026-01-21 09:27:33 +00:00
# Services #
2026-02-20 21:42:53 +00:00
./services/server-permissions.nix
./services/game-servers.nix
./services/pelican.nix
# ./services/dr-server.nix
2026-01-21 22:56:04 +00:00
./services/qbittorrent-nox.nix
./services/nginx.nix
./services/go2rtc.nix
# ./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
./services/seerr.nix
2026-02-01 13:09:34 +00:00
./services/bazarr.nix
./services/bazarr-sync.nix
./services/cloudflare-ddns.nix
./services/authelia.nix
./services/homepage.nix
./services/arr-interconnect.nix
./services/profilarr.nix
./services/shelfarr.nix
./services/adguard.nix
./services/router.nix
./services/router-ui.nix
./services/crowdsec.nix
./services/service-health.nix
2026-05-04 01:38:14 -07:00
./services/sabnzbd.nix
./services/forgejo-runner.nix
./services/code-server.nix
./services/memos.nix
./services/hardware-health.nix
./services/wan-watchdog.nix
./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;
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;
# 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
# 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;
# 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" ];
# 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;
# 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 = {
update =
if config.networking.hostName == "FredOS-Mediaserver"
then localUpdate
else remoteUpdate;
# 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";
# 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";
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
nvd
nix-output-monitor
jq
dnsutils
busybox
2025-12-25 12:08:14 +00:00
];
2026-05-14 14:46:31 +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.
# 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-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";
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" ];
}];
# 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
}