# 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: # Production setting is resource-only: DRBD waits for the STONITH fence # agent to confirm the peer is dead before promoting to Primary. This # requires a working fence_pve_ssh STONITH resource in Pacemaker # (see scripts/ha/cluster-enable-stonith.sh). On a fresh cluster with # no fence device yet, temporarily change to dont-care and run # cluster-enable-stonith.sh once the fence key is deployed. { lib, vars, ... }: { 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; } } resource ha-data { volume 0 { device /dev/drbd0; disk /dev/sdb; 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 ''; }; }