# Shared HA stack config for both test nodes. # These are throwaway test VMs — not production hosts. # No sops-nix, no clan, no home-manager. { lib, pkgs, vars, ... }: let node1Ip = "192.168.2.200"; node2Ip = "192.168.2.201"; drbdPort = 7789; # Test-only corosync authkey (128 bytes = 1024 bits minimum for corosync). # Not secret — this is a disposable test cluster, not production. testAuthKey = "ha-test-cluster-auth-key-NOT-FOR-PRODUCTION-use-corosync-keygen-for-real-clusters-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; in { system.stateVersion = "26.05"; # ── Hardware (Proxmox VM) ────────────────────────────────────────────── imports = [ ../../modules/hardware-configuration/vm/proxmox.nix ../../modules/boot/efi.nix ../../modules/ha/pacemaker-stack.nix ../../modules/ha/iscsi-target.nix ]; # ── Nix settings ────────────────────────────────────────────────────── nix.settings.experimental-features = [ "nix-command" "flakes" ]; users.users.root.openssh.authorizedKeys.keys = [ vars.adminSshKey # Claude Code session key (this machine) — test-lab only "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGygkCljN6uKpdJbHTOQtn8ZnH+wKXDLAwrDFbLrE/65 nixos@nixos" ]; # ── Services ────────────────────────────────────────────────────────── services = { openssh = { enable = true; settings.PermitRootLogin = "yes"; }; # Allow QEMU guest exec for key injection fallback qemuGuest.enable = true; drbd = { enable = true; config = '' global { usage-count yes; } common { net { protocol C; ping-int 1; verify-alg sha256; after-sb-0pri discard-zero-changes; after-sb-1pri discard-secondary; } disk { # dont-care: DRBD itself won't fence before promoting. Production # clusters should use resource-only here and configure a STONITH # fence agent (e.g. fence_pve_ssh) in Pacemaker so DRBD can safely # protect against split-brain without risking dual-Primary. # For this test cluster (no fence device) dont-care lets promotion # proceed; the DRBD kernel module still refuses dual-Primary without # allow-two-primaries in net {}. fencing dont-care; # LVM before/after-resync-target handlers omitted: the LVM snapshot # scripts (/usr/lib/drbd/snapshot-resync-target-lvm.sh) don't exist # on NixOS paths. If present, DRBD calls them on resync and exits 127, # dropping the peer connection and leaving the secondary Outdated. } } resource ha-data { volume 0 { device /dev/drbd0; disk /dev/sdb; # scsi1 in Proxmox VM → sdb meta-disk internal; } on ha-test-node1 { address ${node1Ip}:${toString drbdPort}; } on ha-test-node2 { address ${node2Ip}:${toString drbdPort}; } } ''; }; # services.corosync.enable = true is set by modules/ha/pacemaker-stack.nix corosync = { clusterName = "ha-test"; nodelist = [ { nodeid = 1; name = "ha-test-node1"; ring_addrs = [ node1Ip ]; } { nodeid = 2; name = "ha-test-node2"; ring_addrs = [ node2Ip ]; } ]; }; }; # Corosync authkey (test-only, not secret — generated with # `corosync-keygen` for production). environment.etc."corosync/authkey" = { source = builtins.toFile "authkey" testAuthKey; mode = "0400"; }; # ── Packages ────────────────────────────────────────────────────────── # corosync, pacemaker, ocf-resource-agents, targetcli-fb already added # by the ha/ modules; add the remaining stack-specific tools here. environment.systemPackages = with pkgs; [ # Storage drbd # drbdadm, drbdsetup, drbdmon xfsprogs # mkfs.xfs, xfs_admin, xfs_info # Networking / debug iproute2 # ip, ss iputils # ping tcpdump lsof # Scripting / config python3 curl jq vim htop ]; # ── Networking ──────────────────────────────────────────────────────── networking = { useDHCP = false; defaultGateway = "192.168.2.1"; nameservers = [ "192.168.2.1" "8.8.8.8" ]; firewall = { enable = true; allowedTCPPorts = [ 22 # SSH 3260 # iSCSI 3121 # pacemaker-remoted 2224 # pcsd drbdPort ]; allowedUDPPorts = [ 5404 # corosync 5405 # corosync 5407 # corosync crypto ]; extraCommands = '' iptables -A INPUT -s ${node1Ip}/32 -j ACCEPT iptables -A INPUT -s ${node2Ip}/32 -j ACCEPT ''; }; }; # ── Locale / time ───────────────────────────────────────────────────── time.timeZone = vars.timeZone; i18n.defaultLocale = "en_AU.UTF-8"; }