After the rack move one of the RAID0 disks failed to enumerate, and jupiter boot-looped into an emergency shell nobody could use — root is locked, so sulogin offers a prompt with no answer, and there is no ssh from there: Timed out waiting for device /dev/disk/by-uuid/dadbff6f-... Dependency failed for /mnt/data. Dependency failed for /var/lib/private/prowlarr. Dependency failed for Local File Systems. local-fs.target: Job local-fs.target/start failed with result 'dependency' Reached target Emergency Mode. `nofail` on /mnt/data did not help, because the prowlarr and seerr bind mounts layered on top of it had none: without it a mount is RequiredBy local-fs.target, so those two failed the target on the array's behalf. Give them `nofail` too and let them fail alone. `systemd.enableEmergencyMode = false` then keeps a bad array from costing a reachable box at all — far more useful on a headless host than a console prompt. Booting further is only safe if nothing quietly relocates onto the 29G eMMC, so pin the array-backed services to the mount. systemd derives RequiresMountsFor from a unit's own paths, which for these is somewhere under /var/lib (eMMC) — nothing pointed immich at mediaLocation or sabnzbd at its configFile, so with the array gone they would have started and written to the OS disk, into directories that go invisible the moment /mnt/data mounts over them. jellyfin, sonarr, radarr and gitea already had a real dependency and are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
4.8 KiB
Nix
102 lines
4.8 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
# ZimaBlade NAS host: hardware + disk + the services it runs.
|
|
{
|
|
imports = [
|
|
./hardware-configuration.nix
|
|
./disk-config.nix # disko: OS-disk partitions + filesystems
|
|
./secrets.nix # sops-nix: samba password, tailscale key, ...
|
|
../../common.nix # shared base: user / ssh / nix / firewall
|
|
../../services/network/samba.nix
|
|
../../services/network/avahi.nix
|
|
../../services/media/audiobookshelf.nix
|
|
../../services/containers.nix
|
|
../../services/network/caddy.nix
|
|
../../services/vpn/tailscale.nix
|
|
../../services/media/jellyfin.nix
|
|
../../services/media/sabnzbd.nix
|
|
../../services/media/prowlarr.nix
|
|
../../services/media/sonarr.nix
|
|
../../services/media/radarr.nix
|
|
../../services/media/clonarr.nix
|
|
../../services/media/seerr.nix
|
|
../../services/media/immich.nix
|
|
../../services/dev/gitea.nix
|
|
];
|
|
|
|
# sabnzbd's unrar dependency is unfree; scope the allowance to just that
|
|
# package rather than blanket-allowing unfree across the host.
|
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "unrar" ];
|
|
|
|
# ---- Host identity ----
|
|
networking.hostName = "jupiter";
|
|
networking.networkmanager.enable = true;
|
|
users.users.darman.extraGroups = [ "docker" ]; # merges with common.nix
|
|
|
|
# ---- Boot ----
|
|
# systemd-boot for UEFI. If ZimaBlade boots legacy/BIOS, switch to grub.
|
|
boot.loader.systemd-boot.enable = true;
|
|
boot.loader.efi.canTouchEfiVariables = true;
|
|
# common.nix's cap of 5 comes from this box's own 34-generation incident,
|
|
# but at ~5G free on a 29G eMMC even 5 is too many — override down to 2.
|
|
boot.loader.systemd-boot.configurationLimit = lib.mkForce 2;
|
|
|
|
# Root lives on the ZimaBlade eMMC (mmcblk0). nixos-generate-config runs in
|
|
# the RAM installer and does NOT detect these, so pin them here (merged with
|
|
# hardware-configuration.nix) or stage-1 can't mount root and the box panics.
|
|
boot.initrd.availableKernelModules = [ "mmc_block" "sdhci_pci" "sdhci_acpi" ];
|
|
|
|
# Warm reboot hangs at firmware reset on this board (cold power-cycle works).
|
|
# Force the PCI-chipset reset method. If a warm `reboot` still hangs, try the
|
|
# next value: acpi -> bios -> cold -> efi.
|
|
boot.kernelParams = [ "reboot=pci" ];
|
|
|
|
# ---- NAS data array ----
|
|
# Existing ext4 on the mdadm RAID0 over sda+sdb (md0, 29.1T).
|
|
# Mounted, NOT formatted; kept out of disko so it is never wiped.
|
|
# ⚠️ RAID0 = no redundancy: either 16TB disk failing loses ALL data.
|
|
boot.swraid.enable = true; # assemble the mdadm array at boot
|
|
fileSystems."/mnt/data" = {
|
|
# fs UUID (stable) — the array may enumerate as /dev/md127, so avoid /dev/md0.
|
|
device = "/dev/disk/by-uuid/dadbff6f-652e-49b2-bfed-eb1308ab8b78";
|
|
fsType = "ext4";
|
|
options = [ "nofail" ]; # don't block boot if the array is degraded/absent
|
|
};
|
|
|
|
# `nofail` above is necessary but NOT sufficient — any mount layered on the
|
|
# array (prowlarr/seerr binds) is RequiredBy local-fs.target and will fail it
|
|
# regardless, and emergency mode on this box is a dead end: root is locked, so
|
|
# sulogin drops you at a prompt you cannot answer, with no ssh. 2026-08-06: a
|
|
# drive that failed to enumerate after the rack move did exactly this —
|
|
# "Timed out waiting for device /dev/disk/by-uuid/dadbff6f-…" -> Dependency
|
|
# failed for Local File Systems -> Reached target Emergency Mode, twice.
|
|
# Boot as far as possible instead and leave the failed units to be read over
|
|
# ssh. The array-backed services carry RequiresMountsFor=/mnt/data so they
|
|
# still refuse to start rather than writing to the eMMC.
|
|
systemd.enableEmergencyMode = false;
|
|
|
|
# ---- Caddy vhosts (LAN) ----
|
|
# Reached via pihole local-DNS names -> jupiter IP.
|
|
services.caddy.virtualHosts = {
|
|
"http://audiobookshelf.jupiter.sol".extraConfig = "reverse_proxy localhost:8000";
|
|
"http://jellyfin.jupiter.sol".extraConfig = "reverse_proxy localhost:8096";
|
|
"http://sabnzbd.jupiter.sol".extraConfig = "reverse_proxy localhost:8085";
|
|
"http://prowlarr.jupiter.sol".extraConfig = "reverse_proxy localhost:9696";
|
|
"http://sonarr.jupiter.sol".extraConfig = "reverse_proxy localhost:8989";
|
|
"http://radarr.jupiter.sol".extraConfig = "reverse_proxy localhost:7878";
|
|
"http://clonarr.jupiter.sol".extraConfig = "reverse_proxy localhost:6060";
|
|
"http://seerr.jupiter.sol".extraConfig = "reverse_proxy localhost:5055";
|
|
"http://gitea.jupiter.sol".extraConfig = "reverse_proxy localhost:3000";
|
|
# Immich uploads are large: raise the body limit off caddy's default and
|
|
# give slow phone uploads room before the proxy gives up.
|
|
"http://immich.jupiter.sol".extraConfig = ''
|
|
request_body {
|
|
max_size 50GB
|
|
}
|
|
reverse_proxy localhost:2283
|
|
'';
|
|
};
|
|
|
|
system.stateVersion = "26.05";
|
|
}
|