Archived
Root SSH was failing because only the RSA admin key was authorized but the local dev box only has an ed25519 key. Fix: - cluster-config.nix: add ed25519 keys to root (same set as nixos user) so future deployments work without the temp-key workaround - deploy.sh/acceptance-tests.sh: SSH as nixos user with sudo instead of root@ - cluster-init.sh: HA_USER/HA_KEY env vars + n2_ssh()/n2_scp() helpers so inter-node SSH works regardless of whether root-to-root is available - deploy.sh Phase 6: generate temp keypair, authorize on node2, place on node1 for root to use during cluster-init, clean up afterward Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HaH1cSGvhogRP5ExoF6nD8
119 lines
4.0 KiB
Nix
119 lines
4.0 KiB
Nix
# 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, ... }:
|
|
{
|
|
# 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;
|
|
|
|
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
|
|
'';
|
|
};
|
|
}
|