refactor: move host configs under hosts/{jupiter,vps}

- git-mv jupiter/ vps/ into hosts/; fix ../ -> ../../ for common/services/secrets
- flake.nix + deploy point at hosts/<config>/
- README structure updated
- verified: jupiter/vps/vbox all eval
This commit is contained in:
erik
2026-07-13 19:34:27 +02:00
parent fb782cb9fe
commit b6c393ff98
12 changed files with 47 additions and 38 deletions
+58
View File
@@ -0,0 +1,58 @@
{ 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/samba.nix
../../services/avahi.nix
../../services/audiobookshelf.nix
../../services/containers.nix
../../services/caddy.nix
../../services/tailscale.nix
];
# ---- 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;
# 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
};
# ---- Caddy vhosts (LAN) ----
# whoami on :80, audiobookshelf via a pihole local-DNS name -> jupiter IP.
services.caddy.virtualHosts = {
"http://localhost".extraConfig = "reverse_proxy localhost:8080";
"http://audiobookshelf.jupiter.sol".extraConfig = "reverse_proxy localhost:8000";
};
system.stateVersion = "26.05";
}
+43
View File
@@ -0,0 +1,43 @@
{ ... }:
# Declarative OS-disk layout (disko). UEFI: GPT with an ESP + ext4 root.
# disko both PARTITIONS/FORMATS this disk and generates the NixOS
# `fileSystems.*` entries, so hardware-configuration.nix must NOT define
# fileSystems for "/" or "/boot".
#
# ⚠️ This disk is WIPED on install. Set `device` to the OS disk ONLY.
# Your NAS data disk is NOT listed here — keep it out of disko so it is
# never formatted; mount it as a plain read-write fileSystem instead
# (see configuration.nix, /mnt/data).
#
# Find the stable id: ls -l /dev/disk/by-id (use by-id, never /dev/sdX)
{
disko.devices.disk.os = {
type = "disk";
# ZimaBlade internal eMMC (29.1G). NOT the data RAID (sda/sdb/md0).
device = "/dev/disk/by-id/mmc-C9A551_0xaa057ad0";
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
}
+18
View File
@@ -0,0 +1,18 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "sd_mod" "sdhci_pci" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
+28
View File
@@ -0,0 +1,28 @@
{ config, ... }:
# sops-nix secret wiring (real host only; not imported by vm.nix).
# Encrypted values live in ../../secrets/jupiter.yaml, decrypted at activation to
# /run/secrets/<name>.
#
# The host decrypts with its OWN SSH host key (age identity derived via
# ssh-to-age, recipient listed in ../../.sops.yaml). The key is pre-generated on
# the laptop and shipped once at install as /etc/ssh/ssh_host_ed25519_key
# (nixos-anywhere --extra-files) — so decryption works on boot #1 and there is
# no separate sops-only key to manage.
{
sops.defaultSopsFile = ../../secrets/jupiter.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
# Decrypts to /run/secrets/samba_password (root-only by default).
sops.secrets.samba_password = { };
# darman's login password (a sha-512 hash, not plaintext — generate with
# `mkpasswd -m sha-512`, edit via ./edit_secrets). neededForUsers makes it
# available before user setup, at /run/secrets-for-users/darman_password.
sops.secrets.darman_password.neededForUsers = true;
users.users.darman.hashedPasswordFile =
config.sops.secrets.darman_password.path;
# Headscale pre-auth key for tailscale auto-registration (see configuration.nix).
sops.secrets.tailscale_authkey = { };
}
+49
View File
@@ -0,0 +1,49 @@
{ config, pkgs, lib, modulesPath, ... }:
# VirtualBox test image — mirrors jupiter's services (minus tailscale/sops/disk)
# and adds console/SSH login credentials. Disk + bootloader come from the
# virtualbox-image module, so hardware-configuration.nix is NOT imported.
{
imports = [
(modulesPath + "/virtualisation/virtualbox-image.nix")
../../common.nix # shared base: user / ssh / nix / firewall
../../services/samba.nix
../../services/avahi.nix
../../services/audiobookshelf.nix
../../services/containers.nix
../../services/caddy.nix
];
networking.hostName = "jupiter";
networking.networkmanager.enable = true;
users.users.darman.extraGroups = [ "docker" ];
# Allow password login for testing (real host is key-only).
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
# Login: darman / test (change or remove for anything but local testing).
users.users.darman.initialPassword = "test";
users.users.root.initialPassword = "test";
# Throwaway SMB password for testing (samba-smbd login = darman / test).
# Single line — the provisioning oneshot feeds it twice for smbpasswd.
# Real host must NOT do this — plaintext lands in the world-readable Nix
# store. It uses sops-nix (secrets.nix) instead.
environment.etc."samba/smb-password" = {
text = "test\n";
mode = "0600";
};
# Caddy vhosts (same as the LAN host, so the proxy path is testable).
services.caddy.virtualHosts = {
"http://localhost".extraConfig = "reverse_proxy localhost:8080";
"http://audiobookshelf.jupiter.sol".extraConfig = "reverse_proxy localhost:8000";
};
# Guest additions for clipboard/resize (optional).
virtualisation.virtualbox.guest.enable = true;
# Smaller virtual disk = faster image assembly + VMDK compression.
# Size in MiB (default is ~50G).
virtualisation.diskSize = 6144; # 6 GiB total disk
}
+52
View File
@@ -0,0 +1,52 @@
{ config, pkgs, lib, ... }:
# netcup VPS (UEFI, /dev/vda). Public reverse proxy + tailnet node.
{
imports = [
./hardware-configuration.nix
./disk-config.nix # disko: vda partitions + filesystems
./secrets.nix # sops-nix: tailscale authkey
../../common.nix # shared base: user / ssh / nix / firewall
../../services/caddy.nix
../../services/tailscale.nix
];
# ---- Boot (UEFI) ----
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Root is on the virtio disk — pin these so stage-1 mounts it regardless of
# what nixos-generate-config detects in the installer.
boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_blk" "virtio_scsi" ];
networking.hostName = "vps";
# ---- Static networking (netcup) ----
# No LAN fallback: get this right or the box is unreachable (use netcup's
# VNC console / rescue system to fix). Values captured from the running VPS.
networking.useDHCP = false;
networking.usePredictableInterfaceNames = false; # keep the NIC named eth0
networking.interfaces.eth0 = {
ipv4.addresses = [ { address = "159.195.64.117"; prefixLength = 22; } ];
ipv6.addresses = [ { address = "2a0a:4cc0:c2:19e1:44b4:8dff:fe4d:c7d7"; prefixLength = 64; } ];
};
networking.defaultGateway = { address = "159.195.64.1"; interface = "eth0"; };
# netcup IPv6 gateway is conventionally fe80::1 — VERIFY with `ip -6 route`
# on the running VPS; wrong v6 gw won't break v4 reachability.
networking.defaultGateway6 = { address = "fe80::1"; interface = "eth0"; };
# Public resolvers for early boot; tailscale MagicDNS overrides once up.
networking.nameservers = [ "9.9.9.9" "1.1.1.1" "2620:fe::fe" ];
# firewall (enable + 22), caddy (80/443), tailscale (trust tailscale0 + join
# headscale) come from ../../common.nix and ../../services/{caddy,tailscale}.nix.
# ---- Public reverse proxy vhosts ----
# Caddy gets automatic public HTTPS (Let's Encrypt) for real domains.
# Proxies to jupiter's audiobookshelf over the tailnet (MagicDNS name).
# Needs a public A record -> this VPS IP (ports 80/443 opened by the module).
services.caddy.virtualHosts."audiobookshelf.mgaction.town".extraConfig = ''
reverse_proxy http://jupiter.hosts.mgaction.town:8000
'';
# TODO: port your other VPS services' vhosts here before deploying.
system.stateVersion = "26.05"; # set at install time; do NOT bump on upgrades
}
+37
View File
@@ -0,0 +1,37 @@
{ ... }:
# Declarative disk layout (disko) for the netcup VPS.
# UEFI: GPT with an ESP + ext4 root on the single virtio disk.
#
# ⚠️ /dev/vda is WIPED on install — this destroys everything currently on the
# VPS (docker containers, data, existing caddy config). Back up / port your
# services into NixOS BEFORE running nixos-anywhere.
{
disko.devices.disk.vps = {
type = "disk";
device = "/dev/vda"; # netcup virtio disk (256G)
content = {
type = "gpt";
partitions = {
ESP = {
size = "512M";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
}
+23
View File
@@ -0,0 +1,23 @@
# PLACEHOLDER — replace on install.
#
# disko (disk-config.nix) owns the filesystems, so this file only carries
# kernel modules + platform. nixos-anywhere regenerates it via:
# nixos-generate-config --no-filesystems
# Keep the imports/kernel-module lines; the virtio modules are pinned in
# configuration.nix so root mounts even if the generator misses them.
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "virtio_blk" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
# NO fileSystems here — disko defines "/" and "/boot".
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
+12
View File
@@ -0,0 +1,12 @@
{ config, ... }:
# sops-nix wiring for the VPS. Encrypted values live in ../../secrets/vps.yaml,
# decrypted with the VPS's own SSH host key (recipient in ../../.sops.yaml).
# The host key is pre-generated on the laptop and shipped at install
# (nixos-anywhere --extra-files -> /etc/ssh/ssh_host_ed25519_key).
{
sops.defaultSopsFile = ../../secrets/vps.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
sops.secrets.tailscale_authkey = { };
}