# Cluster-wide HA config shared by both ha-server nodes. # # Covers everything that is identical on both nodes and references cluster # topology (node IPs, hostnames, DRBD resource). Per-node identity # (hostname, static IP, stateVersion) lives in hosts/ha-server-{1,2}/host.nix. # # Corosync authkey: # /etc/corosync/authkey (mode 0400) is managed by sops-nix below. # Bootstrap: run scripts/ha/cluster-init.sh on node1 to generate the key, # then encrypt it with: sops -e --input-type binary /etc/corosync/authkey > secrets/ha-corosync-authkey # Both host keys must be registered via sync-host-keys.sh first so both nodes can decrypt it. # # DRBD fencing: # resource-only with crm-fence-peer.sh: DRBD calls the Pacemaker-aware # crm-fence-peer.sh handler before promoting. The handler checks the CIB # to confirm the peer's DRBD resource is stopped and returns 7 (successfully # fenced), allowing safe promotion without requiring power-fencing (STONITH). # The unfence handler crm-unfence-peer.sh clears the outdate flag when the # peer reconnects. This is the correct setting for Pacemaker+DRBD clusters # with STONITH disabled; crm-fence-peer.sh replaces the need for a separate # STONITH device during the testing phase. Switch to resource-and-stonith # once the fence_pve_ssh STONITH resource is active (see # scripts/ha/cluster-enable-stonith.sh). { lib, vars, ... }: { # Root SSH access — same key set as nixos user so all admin keys can reach root. users.users.root.openssh.authorizedKeys.keys = [ vars.adminSshKey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICMJhrfFayLBG+gWtO6oAvgambw5nWWgztiTFEaaaVRH debian@surface" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGygkCljN6uKpdJbHTOQtn8ZnH+wKXDLAwrDFbLrE/65 nixos@nixos" ]; # Passwordless sudo for wheel — operator SSHes as nixos and uses sudo for # cluster management commands (drbdadm, crm*, pcs, etc.) security.sudo.wheelNeedsPassword = lib.mkForce false; # DRBD lock-file directory (drbd-utils checks for it; missing → harmless but noisy warnings). systemd.tmpfiles.rules = [ "d /var/lib/drbd 0750 root root -" ]; # Prevent drbd.service from auto-starting at boot / nixos-rebuild switch. # Pacemaker's OCF drbd agent calls drbdadm up/down directly when managing # the resource. If drbd.service also runs drbdadm up all while DRBD is # already Primary under Pacemaker, apply-al fails with "device busy" (exit 20). systemd.services.drbd.wantedBy = lib.mkForce []; services.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 { fencing resource-only; } handlers { fence-peer "/run/current-system/sw/lib/drbd/crm-fence-peer.sh"; unfence-peer "/run/current-system/sw/lib/drbd/crm-unfence-peer.sh"; } } resource ha-data { volume 0 { device /dev/drbd0; disk ${vars.haServerDrbdDisk}; meta-disk internal; } on ${vars.haServer1Host} { address ${vars.haServer1StorageIp}:${toString vars.ports.haServerDrbd}; } on ${vars.haServer2Host} { address ${vars.haServer2StorageIp}:${toString vars.ports.haServerDrbd}; } } ''; }; # /etc/corosync/authkey — sops binary secret, identical on both nodes. # Decryptable by both ha-server host keys (added by sync-host-keys.sh). sops.secrets.corosync_authkey = { sopsFile = ../../secrets/ha-corosync-authkey; format = "binary"; path = "/etc/corosync/authkey"; mode = "0400"; restartUnits = [ "corosync.service" ]; }; # NixOS common config enables NetworkManager by default; HA cluster nodes # need stable static IPs with predictable interface names — NM is not suitable. networking.networkmanager.enable = lib.mkForce false; # services.corosync.enable is set by modules/ha/pacemaker-stack.nix. services.corosync = { clusterName = "ha-cluster"; nodelist = [ { nodeid = 1; name = vars.haServer1Host; ring_addrs = [ vars.haServer1StorageIp ]; } { nodeid = 2; name = vars.haServer2Host; ring_addrs = [ vars.haServer2StorageIp ]; } ]; }; networking.firewall = { allowedTCPPorts = [ vars.ports.haServerIscsi vars.ports.haServerPacemakerRemoted vars.ports.haServerPcsd vars.ports.haServerDrbd vars.ports.nfsRpcbind vars.ports.nfsd vars.ports.nfsMountd ]; allowedUDPPorts = [ vars.ports.haServerCorosync1 vars.ports.haServerCorosync2 vars.ports.haServerCorosyncCrypto vars.ports.nfsRpcbind vars.ports.nfsd vars.ports.nfsMountd ]; extraCommands = '' iptables -A INPUT -s ${vars.haServer1Ip}/32 -j ACCEPT iptables -A INPUT -s ${vars.haServer2Ip}/32 -j ACCEPT iptables -A INPUT -s ${vars.haStorageCidr} -j ACCEPT ''; }; }