{ 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"; }