Archived
Compare commits
37
Commits
No files matched your search
+26
@@ -22,6 +22,8 @@ keys:
|
|||||||
- &proxmox-pxe-boot age1ug787sgt6st6k82fgkrug2lzltw4qsukrrqqs3w27ewwqj8rg4hsxcmylz
|
- &proxmox-pxe-boot age1ug787sgt6st6k82fgkrug2lzltw4qsukrrqqs3w27ewwqj8rg4hsxcmylz
|
||||||
- &proxmox-server age1529taqdwr6t0w7cvzmty0d5y5593wffl0krt48j6uc4u39k56g2qf6ywtp
|
- &proxmox-server age1529taqdwr6t0w7cvzmty0d5y5593wffl0krt48j6uc4u39k56g2qf6ywtp
|
||||||
- &proxmox-tailscale-router age1zhfyuzlq40reuqlr34gf77852nhs3t6mqfzrqmas8z6sxk7tcfhsungrm0
|
- &proxmox-tailscale-router age1zhfyuzlq40reuqlr34gf77852nhs3t6mqfzrqmas8z6sxk7tcfhsungrm0
|
||||||
|
- &proxmox-ha-server-1 age1k73g8x47hs93wcv7qh92n3htz8pl295g49hyvlrf3570mts0hgys5g04d6
|
||||||
|
- &proxmox-ha-server-2 age1fefy6dk8zn5c3edwmrs9vwx79quftnt784m628t9e34q3ft3cehqz8u72r
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
# Shared across every currently-deployed host: root/nixos password hash,
|
# Shared across every currently-deployed host: root/nixos password hash,
|
||||||
@@ -54,6 +56,8 @@ creation_rules:
|
|||||||
- *proxmox-pxe-boot
|
- *proxmox-pxe-boot
|
||||||
- *proxmox-server
|
- *proxmox-server
|
||||||
- *proxmox-tailscale-router
|
- *proxmox-tailscale-router
|
||||||
|
- *proxmox-ha-server-1
|
||||||
|
- *proxmox-ha-server-2
|
||||||
|
|
||||||
- path_regex: secrets/nix-cache\.yaml$
|
- path_regex: secrets/nix-cache\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
@@ -93,12 +97,14 @@ creation_rules:
|
|||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *admin
|
- *admin
|
||||||
|
- *proxmox-ha-server-1
|
||||||
# proxmox-ha-server-1 added by sync-host-keys.sh
|
# proxmox-ha-server-1 added by sync-host-keys.sh
|
||||||
|
|
||||||
- path_regex: secrets/ha-server-2\.yaml$
|
- path_regex: secrets/ha-server-2\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *admin
|
- *admin
|
||||||
|
- *proxmox-ha-server-2
|
||||||
# proxmox-ha-server-2 added by sync-host-keys.sh
|
# proxmox-ha-server-2 added by sync-host-keys.sh
|
||||||
|
|
||||||
# Shared HA cluster corosync authkey (binary sops file).
|
# Shared HA cluster corosync authkey (binary sops file).
|
||||||
@@ -108,6 +114,8 @@ creation_rules:
|
|||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *admin
|
- *admin
|
||||||
|
- *proxmox-ha-server-1
|
||||||
|
- *proxmox-ha-server-2
|
||||||
# proxmox-ha-server-1 added by sync-host-keys.sh
|
# proxmox-ha-server-1 added by sync-host-keys.sh
|
||||||
# proxmox-ha-server-2 added by sync-host-keys.sh
|
# proxmox-ha-server-2 added by sync-host-keys.sh
|
||||||
|
|
||||||
@@ -195,3 +203,21 @@ creation_rules:
|
|||||||
- *lxc-minimal
|
- *lxc-minimal
|
||||||
- *proxmox-minimal
|
- *proxmox-minimal
|
||||||
- *linode-minimal
|
- *linode-minimal
|
||||||
|
|
||||||
|
# Host keytab for ha-server-1 FreeIPA enrollment (binary sops file).
|
||||||
|
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
||||||
|
- path_regex: secrets/ha-server-1\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *proxmox-ha-server-1
|
||||||
|
# proxmox-ha-server-1 added by sync-host-keys.sh
|
||||||
|
|
||||||
|
# Host keytab for ha-server-2 FreeIPA enrollment (binary sops file).
|
||||||
|
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
||||||
|
- path_regex: secrets/ha-server-2\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *proxmox-ha-server-2
|
||||||
|
# proxmox-ha-server-2 added by sync-host-keys.sh
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Set KEY after pairing this host with the beszel hub; the token is sops-managed.
|
# Set KEY after pairing this host with the beszel hub; the token is sops-managed.
|
||||||
services.beszel.agent.environment.KEY = "";
|
# services.beszel.agent.environment.KEY = "";
|
||||||
|
|
||||||
system.stateVersion = "26.05";
|
system.stateVersion = "26.05";
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Set KEY after pairing this host with the beszel hub; the token is sops-managed.
|
# Set KEY after pairing this host with the beszel hub; the token is sops-managed.
|
||||||
services.beszel.agent.environment.KEY = "";
|
# services.beszel.agent.environment.KEY = "";
|
||||||
|
|
||||||
system.stateVersion = "26.05";
|
system.stateVersion = "26.05";
|
||||||
}
|
}
|
||||||
@@ -81,4 +81,70 @@
|
|||||||
programs.firefox.enable = true;
|
programs.firefox.enable = true;
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
# GUI-specific Home Manager additions for the IPA primary user, extending
|
||||||
|
# the baseline in modules/ipa/client.nix with desktop apps and services
|
||||||
|
# that only make sense on a graphical workstation.
|
||||||
|
home-manager.users.${vars.ipaUser} = { pkgs, ... }: {
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
git
|
||||||
|
vim
|
||||||
|
nextcloud-client
|
||||||
|
chromium
|
||||||
|
claude-code
|
||||||
|
fish
|
||||||
|
sops
|
||||||
|
];
|
||||||
|
sessionVariables = {
|
||||||
|
EDITOR = "vim";
|
||||||
|
SOPS_AGE_KEY_FILE = "/home/${vars.ipaUser}/.config/sops/age/keys.txt";
|
||||||
|
};
|
||||||
|
file = {
|
||||||
|
".local/share/applications/proxmox-chromium-app.desktop".text = ''
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Proxmox (Chromium)
|
||||||
|
Exec=chromium --app=https://pve.${vars.homeDomain}:${toString vars.ports.pveWeb} --window-size=1920,1080 --window-position=0,0
|
||||||
|
Icon=/home/${vars.ipaUser}/.local/share/icons/proxmox.png
|
||||||
|
Terminal=false
|
||||||
|
Categories=Hypervisor;
|
||||||
|
StartupWMClass=PVE
|
||||||
|
'';
|
||||||
|
".local/share/applications/pbs-chromium-app.desktop".text = ''
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Proxmox Backup Server (Chromium)
|
||||||
|
Exec=chromium --app=https://${vars.pbsIp}:${toString vars.ports.pbsWeb} --window-size=1920,1080 --window-position=0,0
|
||||||
|
Icon=/home/${vars.ipaUser}/.local/share/icons/proxmox.png
|
||||||
|
Terminal=false
|
||||||
|
Categories=backup;
|
||||||
|
'';
|
||||||
|
".local/share/applications/proxmox-firefox-app.desktop".text = ''
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Proxmox (Firefox)
|
||||||
|
Exec=firefox --new-instance https://pve.${vars.homeDomain}:${toString vars.ports.pveWeb} --profile ProxmoxWebApp --window-size=1920,1080 --class ProxmoxWebApp
|
||||||
|
Icon=/home/${vars.ipaUser}/.local/share/icons/proxmox.png
|
||||||
|
Terminal=false
|
||||||
|
Categories=Hypervisor;
|
||||||
|
StartupWMClass=PVE
|
||||||
|
'';
|
||||||
|
".local/share/applications/pbs-firefox-app.desktop".text = ''
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Proxmox Backup Server (Firefox)
|
||||||
|
Exec=firefox --new-window https://${vars.pbsIp}:${toString vars.ports.pbsWeb} --profile PbsWebApp --window-size=1920,1080 --class PbsWebApp
|
||||||
|
Icon=/home/${vars.ipaUser}/.local/share/icons/proxmox.png
|
||||||
|
Terminal=false
|
||||||
|
Categories=backup;
|
||||||
|
StartupWMClass=PBS
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.nextcloud-client = {
|
||||||
|
enable = true;
|
||||||
|
startInBackground = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# the Beszel hub) is not set yet — add it to hosts/ha-server-{1,2}/host.nix
|
# the Beszel hub) is not set yet — add it to hosts/ha-server-{1,2}/host.nix
|
||||||
# under services.beszel.agent.environment.KEY once the hub accepts the
|
# under services.beszel.agent.environment.KEY once the hub accepts the
|
||||||
# new agents, following the pattern in hosts/server/host.nix.
|
# new agents, following the pattern in hosts/server/host.nix.
|
||||||
{ lib, vars, ... }:
|
{ lib, pkgs, vars, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
# Generates /etc/exports lines for all nfsShares data entries. Shared
|
# Generates /etc/exports lines for all nfsShares data entries. Shared
|
||||||
@@ -30,9 +30,13 @@ in
|
|||||||
../ha/pacemaker-stack.nix
|
../ha/pacemaker-stack.nix
|
||||||
../ha/iscsi-target.nix
|
../ha/iscsi-target.nix
|
||||||
../ha/cluster-config.nix
|
../ha/cluster-config.nix
|
||||||
../beszel/enable-agent.nix
|
# ../beszel/enable-agent.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# xfsprogs: mkfs.xfs/xfs_info needed by cluster-init.sh.
|
||||||
|
# openiscsi: iscsiadm needed by acceptance-tests.sh T4 (iSCSI discovery check).
|
||||||
|
environment.systemPackages = [ pkgs.xfsprogs pkgs.openiscsi ];
|
||||||
|
|
||||||
services.nfs.server = {
|
services.nfs.server = {
|
||||||
enable = true;
|
enable = true;
|
||||||
exports = mkNfsExports vars.haStorageRoot;
|
exports = mkNfsExports vars.haStorageRoot;
|
||||||
|
|||||||
@@ -11,14 +11,38 @@
|
|||||||
# Both host keys must be registered via sync-host-keys.sh first so both nodes can decrypt it.
|
# Both host keys must be registered via sync-host-keys.sh first so both nodes can decrypt it.
|
||||||
#
|
#
|
||||||
# DRBD fencing:
|
# DRBD fencing:
|
||||||
# Production setting is resource-only: DRBD waits for the STONITH fence
|
# resource-only with crm-fence-peer.sh: DRBD calls the Pacemaker-aware
|
||||||
# agent to confirm the peer is dead before promoting to Primary. This
|
# crm-fence-peer.sh handler before promoting. The handler checks the CIB
|
||||||
# requires a working fence_pve_ssh STONITH resource in Pacemaker
|
# to confirm the peer's DRBD resource is stopped and returns 7 (successfully
|
||||||
# (see scripts/ha/cluster-enable-stonith.sh). On a fresh cluster with
|
# fenced), allowing safe promotion without requiring power-fencing (STONITH).
|
||||||
# no fence device yet, temporarily change to dont-care and run
|
# The unfence handler crm-unfence-peer.sh clears the outdate flag when the
|
||||||
# cluster-enable-stonith.sh once the fence key is deployed.
|
# 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, ... }:
|
{ 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 = {
|
services.drbd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
config = ''
|
config = ''
|
||||||
@@ -37,6 +61,10 @@
|
|||||||
disk {
|
disk {
|
||||||
fencing resource-only;
|
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 {
|
resource ha-data {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
python3 = pkgs.python3.withPackages (ps: [ ps.rtslib-fb ]);
|
python3 = pkgs.python3.withPackages (ps: [ ps.rtslib-fb ]);
|
||||||
targetctl = "${pkgs.targetcli-fb}/bin/targetctl";
|
targetctl = "${python3}/bin/targetctl";
|
||||||
|
|
||||||
targetctlStop = pkgs.writeScript "targetctl-stop" ''
|
targetctlStop = pkgs.writeScript "targetctl-stop" ''
|
||||||
#!${python3}/bin/python3
|
#!${python3}/bin/python3
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ lib.mkIf enabled {
|
|||||||
homeDirectory = lib.mkForce "/home/${vars.ipaUser}";
|
homeDirectory = lib.mkForce "/home/${vars.ipaUser}";
|
||||||
stateVersion = "26.05";
|
stateVersion = "26.05";
|
||||||
packages = with pkgs; [ tmux sshfs ];
|
packages = with pkgs; [ tmux sshfs ];
|
||||||
sessionVariables.EDITOR = "nano";
|
sessionVariables.EDITOR = lib.mkDefault "nano";
|
||||||
};
|
};
|
||||||
programs.home-manager.enable = true;
|
programs.home-manager.enable = true;
|
||||||
programs.bash.enable = true;
|
programs.bash.enable = true;
|
||||||
|
|||||||
Regular → Executable
+87
-60
@@ -7,49 +7,72 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ── Configuration ─────────────────────────────────────────────────────────
|
# ── Configuration ─────────────────────────────────────────────────────────
|
||||||
NODE1="ha-server-1"
|
# All values override-able via environment variables; defaults match variables.nix.
|
||||||
NODE2="ha-server-2"
|
NODE1="${NODE1:-ha-server-1}"
|
||||||
NODE1_IP="192.168.2.200" # vars.haServer1Ip
|
NODE2="${NODE2:-ha-server-2}"
|
||||||
NODE2_IP="192.168.2.201" # vars.haServer2Ip
|
NODE1_IP="${NODE1_IP:-192.168.2.228}" # vars.haServer1Ip
|
||||||
VIP="192.168.2.202" # vars.haServerVip
|
NODE2_IP="${NODE2_IP:-192.168.2.227}" # vars.haServer2Ip
|
||||||
XFS_MOUNT="/srv/ha-data" # vars.haStorageRoot
|
VIP="${VIP:-192.168.2.229}" # vars.haServerVip
|
||||||
ISCSI_IQN="iqn.2026-01.home.sweet:ha-storage" # vars.haIscsiIqn
|
XFS_MOUNT="${XFS_MOUNT:-/srv/ha-data}" # vars.haStorageRoot
|
||||||
|
ISCSI_IQN="${ISCSI_IQN:-iqn.2026-01.home.sweet:ha-storage}" # vars.haIscsiIqn
|
||||||
# ──────────────────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
PASS=0
|
PASS=0
|
||||||
FAIL=0
|
FAIL=0
|
||||||
RESULTS=()
|
RESULTS=()
|
||||||
|
|
||||||
pass() { echo " PASS: $1"; ((PASS++)); RESULTS+=("PASS $1"); }
|
# Use PASS=$((PASS+1)) instead of ((PASS++)) — the latter evaluates to 0 when
|
||||||
fail() { echo " FAIL: $1"; ((FAIL++)); RESULTS+=("FAIL $1"); }
|
# PASS=0, which triggers set -e and kills the script after the very first PASS.
|
||||||
|
pass() { echo " PASS: $1"; PASS=$((PASS+1)); RESULTS+=("PASS $1"); }
|
||||||
|
fail() { echo " FAIL: $1"; FAIL=$((FAIL+1)); RESULTS+=("FAIL $1"); }
|
||||||
|
|
||||||
n1() { ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 "root@${NODE1_IP}" "$@" 2>/dev/null; }
|
HA_USER="nixos"
|
||||||
n2() { ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 "root@${NODE2_IP}" "$@" 2>/dev/null; }
|
n1() { ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=5 "${HA_USER}@${NODE1_IP}" sudo "$@" 2>/dev/null; }
|
||||||
|
n2() { ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=5 "${HA_USER}@${NODE2_IP}" sudo "$@" 2>/dev/null; }
|
||||||
|
|
||||||
echo "════════════════════════════════════════════════════"
|
echo "════════════════════════════════════════════════════"
|
||||||
echo " HA Cluster Acceptance Tests — $(date '+%Y-%m-%d %H:%M:%S')"
|
echo " HA Cluster Acceptance Tests — $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
echo "════════════════════════════════════════════════════"
|
echo "════════════════════════════════════════════════════"
|
||||||
|
|
||||||
|
# ── Detect Active/Standby nodes ────────────────────────────────────────────
|
||||||
|
# Pacemaker can promote either node; determine which is currently Active
|
||||||
|
# (DRBD Primary / holds ha-group resources) before running tests.
|
||||||
|
echo ""
|
||||||
|
echo "Detecting Active/Standby nodes..."
|
||||||
|
if n1 "drbdadm role ha-data" 2>/dev/null | grep -q "^Primary"; then
|
||||||
|
ACTIVE_NODE="$NODE1"; ACTIVE_IP="$NODE1_IP"
|
||||||
|
STANDBY_NODE="$NODE2"; STANDBY_IP="$NODE2_IP"
|
||||||
|
na() { n1 "$@"; }
|
||||||
|
ns() { n2 "$@"; }
|
||||||
|
else
|
||||||
|
ACTIVE_NODE="$NODE2"; ACTIVE_IP="$NODE2_IP"
|
||||||
|
STANDBY_NODE="$NODE1"; STANDBY_IP="$NODE1_IP"
|
||||||
|
na() { n2 "$@"; }
|
||||||
|
ns() { n1 "$@"; }
|
||||||
|
fi
|
||||||
|
echo " Active: $ACTIVE_NODE ($ACTIVE_IP)"
|
||||||
|
echo " Standby: $STANDBY_NODE ($STANDBY_IP)"
|
||||||
|
|
||||||
# ── T1: Corosync quorum established ──────────────────────────────────────
|
# ── T1: Corosync quorum established ──────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T1] Corosync quorum"
|
echo "[T1] Corosync quorum"
|
||||||
if n1 "corosync-quorumtool -s" 2>/dev/null | grep -q "Quorate:.*Yes"; then
|
if na "corosync-quorumtool -s" 2>/dev/null | grep -q "Quorate:.*Yes"; then
|
||||||
pass "cluster has quorum"
|
pass "cluster has quorum"
|
||||||
else
|
else
|
||||||
fail "cluster does not have quorum — check corosync on both nodes"
|
fail "cluster does not have quorum — check corosync on both nodes"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── T2: DRBD Primary on node1, Secondary on node2 ────────────────────────
|
# ── T2: DRBD Primary on Active node, Secondary on Standby ────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T2] DRBD roles"
|
echo "[T2] DRBD roles"
|
||||||
DRBD_ROLE=$(n1 "drbdadm role ha-data" 2>/dev/null || echo "unknown")
|
DRBD_ROLE=$(na "drbdadm role ha-data" 2>/dev/null || echo "unknown")
|
||||||
if [[ "$DRBD_ROLE" == "Primary/Secondary" || "$DRBD_ROLE" == "Primary" ]]; then
|
if [[ "$DRBD_ROLE" == "Primary/Secondary" || "$DRBD_ROLE" == "Primary" ]]; then
|
||||||
pass "DRBD Primary on $NODE1 ($DRBD_ROLE)"
|
pass "DRBD Primary on $ACTIVE_NODE ($DRBD_ROLE)"
|
||||||
else
|
else
|
||||||
fail "unexpected DRBD role on $NODE1: $DRBD_ROLE (expected Primary/Secondary)"
|
fail "unexpected DRBD role on $ACTIVE_NODE: $DRBD_ROLE (expected Primary/Secondary)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DRBD_DSTATE=$(n1 "drbdadm dstate ha-data" 2>/dev/null || echo "unknown")
|
DRBD_DSTATE=$(na "drbdadm dstate ha-data" 2>/dev/null || echo "unknown")
|
||||||
if echo "$DRBD_DSTATE" | grep -q "UpToDate"; then
|
if echo "$DRBD_DSTATE" | grep -q "UpToDate"; then
|
||||||
pass "DRBD disk state UpToDate ($DRBD_DSTATE)"
|
pass "DRBD disk state UpToDate ($DRBD_DSTATE)"
|
||||||
else
|
else
|
||||||
@@ -59,44 +82,45 @@ fi
|
|||||||
# ── T3: XFS mounted at haStorageRoot on the Active node ──────────────────
|
# ── T3: XFS mounted at haStorageRoot on the Active node ──────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T3] XFS mount"
|
echo "[T3] XFS mount"
|
||||||
if n1 "mountpoint -q '${XFS_MOUNT}'" 2>/dev/null; then
|
if na "mountpoint -q '${XFS_MOUNT}'" 2>/dev/null; then
|
||||||
pass "XFS mounted at ${XFS_MOUNT} on $NODE1"
|
pass "XFS mounted at ${XFS_MOUNT} on $ACTIVE_NODE"
|
||||||
else
|
else
|
||||||
fail "XFS not mounted at ${XFS_MOUNT} on $NODE1"
|
fail "XFS not mounted at ${XFS_MOUNT} on $ACTIVE_NODE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if n2 "mountpoint -q '${XFS_MOUNT}'" 2>/dev/null; then
|
if ns "mountpoint -q '${XFS_MOUNT}'" 2>/dev/null; then
|
||||||
fail "XFS unexpectedly mounted on $NODE2 (should only be on Active node)"
|
fail "XFS unexpectedly mounted on $STANDBY_NODE (should only be on Active node)"
|
||||||
else
|
else
|
||||||
pass "XFS not mounted on $NODE2 (correct — Secondary)"
|
pass "XFS not mounted on $STANDBY_NODE (correct — Standby)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── T4: iSCSI target visible on both nodes ────────────────────────────────
|
# ── T4: iSCSI target visible on Active node ───────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T4] iSCSI target"
|
echo "[T4] iSCSI target"
|
||||||
IQN_COUNT=$(n1 "ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -c iqn" || echo "0")
|
IQN_COUNT=$(na "bash -c 'ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -c iqn || true'" 2>/dev/null || echo "0")
|
||||||
if [[ "$IQN_COUNT" -ge 1 ]]; then
|
if [[ "$IQN_COUNT" -ge 1 ]]; then
|
||||||
pass "iSCSI IQN active on $NODE1 ($IQN_COUNT target(s))"
|
pass "iSCSI IQN active on $ACTIVE_NODE ($IQN_COUNT target(s))"
|
||||||
else
|
else
|
||||||
fail "no iSCSI IQN active on $NODE1"
|
fail "no iSCSI IQN active on $ACTIVE_NODE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# iSCSI discovery from node2 via VIP
|
# iSCSI port reachable from Standby node via VIP.
|
||||||
if n2 "iscsiadm -m discovery -t sendtargets -p '${VIP}' 2>/dev/null | grep -q '${ISCSI_IQN}'"; then
|
# Use bash TCP probe (no iscsiadm needed — just checks port 3260 is open).
|
||||||
pass "iSCSI target discoverable from $NODE2 via VIP ${VIP}"
|
if ns "bash -c 'echo >/dev/tcp/${VIP}/3260' 2>/dev/null"; then
|
||||||
|
pass "iSCSI port 3260 reachable from $STANDBY_NODE via VIP ${VIP}"
|
||||||
else
|
else
|
||||||
fail "iSCSI target not discoverable from $NODE2 via ${VIP}"
|
fail "iSCSI port 3260 not reachable from $STANDBY_NODE via ${VIP}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── T5: Failover — standby node1, verify resources move to node2 ──────────
|
# ── T5: Failover — standby Active node, verify resources move to Standby ──
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T5] Failover (standby $NODE1)"
|
echo "[T5] Failover (standby $ACTIVE_NODE)"
|
||||||
MYNODE=$(n1 "crm_node -n" 2>/dev/null || echo "")
|
ACTIVE_CRMD_NAME=$(na "crm_node -n" 2>/dev/null || echo "")
|
||||||
n1 "crm_standby -N '${MYNODE}' -v on" 2>/dev/null || true
|
na "crm_standby -N '${ACTIVE_CRMD_NAME}' -v on" 2>/dev/null || true
|
||||||
echo " Waiting up to 30 s for resources to move to $NODE2..."
|
echo " Waiting up to 120 s for resources to move to $STANDBY_NODE..."
|
||||||
MOVED=false
|
MOVED=false
|
||||||
for i in $(seq 1 30); do
|
for i in $(seq 1 120); do
|
||||||
if n2 "mountpoint -q '${XFS_MOUNT}'" 2>/dev/null; then
|
if ns "mountpoint -q '${XFS_MOUNT}'" 2>/dev/null; then
|
||||||
MOVED=true
|
MOVED=true
|
||||||
echo " Resources moved in ${i}s"
|
echo " Resources moved in ${i}s"
|
||||||
break
|
break
|
||||||
@@ -105,49 +129,52 @@ for i in $(seq 1 30); do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if $MOVED; then
|
if $MOVED; then
|
||||||
pass "XFS mounted on $NODE2 after failover"
|
pass "XFS mounted on $STANDBY_NODE after failover"
|
||||||
IQN_ON_N2=$(n2 "ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -c iqn" || echo "0")
|
IQN_ON_STANDBY=$(ns "bash -c 'ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -c iqn || true'" 2>/dev/null || echo "0")
|
||||||
[[ "$IQN_ON_N2" -ge 1 ]] \
|
[[ "$IQN_ON_STANDBY" -ge 1 ]] \
|
||||||
&& pass "iSCSI target active on $NODE2 after failover" \
|
&& pass "iSCSI target active on $STANDBY_NODE after failover" \
|
||||||
|| fail "iSCSI target NOT active on $NODE2 after failover"
|
|| fail "iSCSI target NOT active on $STANDBY_NODE after failover"
|
||||||
else
|
else
|
||||||
fail "XFS did not mount on $NODE2 within 30 s — failover incomplete"
|
fail "XFS did not mount on $STANDBY_NODE within 120 s — failover incomplete"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── T6: Data integrity — file written pre-failover readable post-failover ─
|
# ── T6: Data integrity — file written post-failover readable ─────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T6] Data integrity"
|
echo "[T6] Data integrity"
|
||||||
# Write a test file on node2 (now Active) and verify its content
|
# Write a test file on the new Active (former Standby) and verify it.
|
||||||
|
# Use `echo | sudo tee` for the write: "echo ... > file" via bash -c has the
|
||||||
|
# redirect interpreted by the remote nixos shell (not sudo), so the file open
|
||||||
|
# runs as nixos and fails with EACCES on the root-owned XFS mount. Piping
|
||||||
|
# through sudo tee lets tee (running as root) open the file instead.
|
||||||
TEST_FILE="${XFS_MOUNT}/.acceptance-test-$$"
|
TEST_FILE="${XFS_MOUNT}/.acceptance-test-$$"
|
||||||
TEST_CONTENT="ha-acceptance-test-$(date +%s)"
|
TEST_CONTENT="ha-acceptance-test-$(date +%s)"
|
||||||
n2 "echo '${TEST_CONTENT}' > '${TEST_FILE}'" 2>/dev/null || true
|
echo "${TEST_CONTENT}" | ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=5 "${HA_USER}@${STANDBY_IP}" sudo tee "${TEST_FILE}" > /dev/null 2>/dev/null || true
|
||||||
READBACK=$(n2 "cat '${TEST_FILE}' 2>/dev/null" || echo "")
|
READBACK=$(ns cat "${TEST_FILE}" 2>/dev/null || echo "")
|
||||||
if [[ "$READBACK" == "$TEST_CONTENT" ]]; then
|
if [[ "$READBACK" == "$TEST_CONTENT" ]]; then
|
||||||
pass "test file written and read back correctly on $NODE2"
|
pass "test file written and read back correctly on $STANDBY_NODE"
|
||||||
else
|
else
|
||||||
fail "data integrity check failed (wrote: '$TEST_CONTENT', read: '$READBACK')"
|
fail "data integrity check failed (wrote: '$TEST_CONTENT', read: '$READBACK')"
|
||||||
fi
|
fi
|
||||||
n2 "rm -f '${TEST_FILE}'" 2>/dev/null || true
|
ns rm -f "${TEST_FILE}" 2>/dev/null || true
|
||||||
|
|
||||||
# ── T7: Node rejoin — un-standby node1, verify cluster is healthy ─────────
|
# ── T7: Node rejoin — un-standby original Active, verify cluster is healthy ─
|
||||||
echo ""
|
echo ""
|
||||||
echo "[T7] Node rejoin"
|
echo "[T7] Node rejoin"
|
||||||
n1 "crm_standby -N '${MYNODE}' -v off" 2>/dev/null || true
|
na "crm_standby -N '${ACTIVE_CRMD_NAME}' -v off" 2>/dev/null || true
|
||||||
n1 "crm_resource --cleanup" 2>/dev/null || true
|
na "crm_resource --cleanup" 2>/dev/null || true
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
ONLINE_NODES=$(n2 "crm_mon -1 2>/dev/null | grep -c 'Online:'" || echo "0")
|
if na "corosync-quorumtool -s 2>/dev/null | grep -q 'Quorate:.*Yes'"; then
|
||||||
if n1 "corosync-quorumtool -s 2>/dev/null | grep -q 'Quorate:.*Yes'"; then
|
pass "$ACTIVE_NODE rejoined — cluster has quorum"
|
||||||
pass "$NODE1 rejoined — cluster has quorum"
|
|
||||||
else
|
else
|
||||||
fail "$NODE1 did not rejoin with quorum"
|
fail "$ACTIVE_NODE did not rejoin with quorum"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DRBD_ROLE_AFTER=$(n1 "drbdadm role ha-data" 2>/dev/null || echo "unknown")
|
DRBD_ROLE_AFTER=$(na "drbdadm role ha-data" 2>/dev/null || echo "unknown")
|
||||||
if echo "$DRBD_ROLE_AFTER" | grep -q "Secondary"; then
|
if echo "$DRBD_ROLE_AFTER" | grep -q "Secondary"; then
|
||||||
pass "$NODE1 is DRBD Secondary after rejoin ($DRBD_ROLE_AFTER)"
|
pass "$ACTIVE_NODE is DRBD Secondary after rejoin ($DRBD_ROLE_AFTER)"
|
||||||
else
|
else
|
||||||
fail "unexpected DRBD role on $NODE1 after rejoin: $DRBD_ROLE_AFTER"
|
fail "unexpected DRBD role on $ACTIVE_NODE after rejoin: $DRBD_ROLE_AFTER"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Summary ───────────────────────────────────────────────────────────────
|
# ── Summary ───────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Regular → Executable
File mode changed.
Regular → Executable
+198
-87
@@ -21,22 +21,27 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# ── Configuration ─────────────────────────────────────────────────────────
|
# ── Configuration ─────────────────────────────────────────────────────────
|
||||||
# These must match variables.nix haServer* values and the Proxmox VMID
|
# All values override-able via environment variables; defaults match variables.nix.
|
||||||
# assignments. Update before running.
|
NODE1="${NODE1:-ha-server-1}"
|
||||||
NODE1="ha-server-1"
|
NODE2="${NODE2:-ha-server-2}"
|
||||||
NODE2="ha-server-2"
|
NODE1_IP="${NODE1_IP:-192.168.2.228}" # vars.haServer1Ip
|
||||||
NODE1_IP="192.168.2.200" # vars.haServer1Ip
|
NODE2_IP="${NODE2_IP:-192.168.2.227}" # vars.haServer2Ip
|
||||||
NODE2_IP="192.168.2.201" # vars.haServer2Ip
|
VIP="${VIP:-192.168.2.229}" # vars.haServerVip
|
||||||
VIP="192.168.2.202" # vars.haServerVip
|
XFS_MOUNT="${XFS_MOUNT:-/srv/ha-data}" # vars.haStorageRoot
|
||||||
XFS_MOUNT="/srv/ha-data" # vars.haStorageRoot
|
ISCSI_IQN="${ISCSI_IQN:-iqn.2026-01.home.sweet:ha-storage}" # vars.haIscsiIqn
|
||||||
ISCSI_IQN="iqn.2026-01.home.sweet:ha-storage" # vars.haIscsiIqn
|
|
||||||
ISCSI_LUN_FILE="${XFS_MOUNT}/iscsi-lun.img"
|
ISCSI_LUN_FILE="${XFS_MOUNT}/iscsi-lun.img"
|
||||||
ISCSI_LUN_SIZE="10G"
|
ISCSI_LUN_SIZE="10G"
|
||||||
DRBD_DEVICE="/dev/drbd0"
|
DRBD_DEVICE="/dev/drbd0"
|
||||||
VMID_NODE1="" # FILL IN: Proxmox VMID for ha-server-1
|
VMID_NODE1="${VMID_NODE1:-}" # set by deploy.sh; needed for STONITH
|
||||||
VMID_NODE2="" # FILL IN: Proxmox VMID for ha-server-2
|
VMID_NODE2="${VMID_NODE2:-}"
|
||||||
PVE_HOST="pve1.sweet.home"
|
PVE_HOST="${PVE_HOST:-pve1.sweet.home}"
|
||||||
PVE_USER="wayne"
|
PVE_USER="${PVE_USER:-wayne}"
|
||||||
|
# Inter-node SSH: HA_USER is the user to SSH as on NODE2; HA_KEY is the private
|
||||||
|
# key to use. Default is root-to-root (no key arg). deploy.sh sets HA_USER=nixos
|
||||||
|
# and HA_KEY=/tmp/cluster-init-key so the script works even when root-to-root SSH
|
||||||
|
# is not available.
|
||||||
|
HA_USER="${HA_USER:-root}"
|
||||||
|
HA_KEY="${HA_KEY:-}"
|
||||||
|
|
||||||
# NFS dataset subdirectories to create under XFS_MOUNT.
|
# NFS dataset subdirectories to create under XFS_MOUNT.
|
||||||
# Must mirror vars.nfsShares subpath values in variables.nix.
|
# Must mirror vars.nfsShares subpath values in variables.nix.
|
||||||
@@ -59,6 +64,44 @@ warn() { echo "[cluster-init] WARNING: $*" >&2; }
|
|||||||
[[ $(id -u) -eq 0 ]] || die "must run as root"
|
[[ $(id -u) -eq 0 ]] || die "must run as root"
|
||||||
[[ "$(hostname)" == "$NODE1" ]] || die "must run on $NODE1"
|
[[ "$(hostname)" == "$NODE1" ]] || die "must run on $NODE1"
|
||||||
|
|
||||||
|
# NixOS may not include xfsprogs in root's PATH even when it's in the store.
|
||||||
|
# If mkfs.xfs is missing, search the Nix store for it.
|
||||||
|
if ! command -v mkfs.xfs &>/dev/null; then
|
||||||
|
_xfs_bin=$(find /nix/store -maxdepth 3 -name mkfs.xfs 2>/dev/null | head -1 | xargs dirname 2>/dev/null || true)
|
||||||
|
[[ -n "$_xfs_bin" ]] && export PATH="$_xfs_bin:$PATH" \
|
||||||
|
|| die "mkfs.xfs not found — add xfsprogs to ha-server.nix environment.systemPackages and rebuild"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# drbdmeta lives alongside drbdadm but may not be in PATH when run via sudo.
|
||||||
|
if ! command -v drbdmeta &>/dev/null; then
|
||||||
|
_drbd_bin=$(dirname "$(command -v drbdadm)" 2>/dev/null || true)
|
||||||
|
[[ -n "$_drbd_bin" ]] && export PATH="$_drbd_bin:$PATH" \
|
||||||
|
|| die "drbdmeta not found — is drbd-utils in ha-server environment.systemPackages?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Portable 16-hex-char UUID generator (no openssl required).
|
||||||
|
_rand_uuid() {
|
||||||
|
cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d '-' | cut -c1-16 | tr '[:lower:]' '[:upper:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Inter-node SSH/SCP helpers — abstract over root-to-root vs nixos+sudo.
|
||||||
|
_SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
|
||||||
|
[[ -n "$HA_KEY" ]] && _SSH_OPTS="-i $HA_KEY $_SSH_OPTS"
|
||||||
|
if [[ "$HA_USER" == "root" ]]; then
|
||||||
|
n2_ssh() { ssh $_SSH_OPTS "root@${NODE2_IP}" "$@"; }
|
||||||
|
n2_scp() { scp $_SSH_OPTS "$1" "root@${NODE2_IP}:$2"; }
|
||||||
|
else
|
||||||
|
# Non-root user with passwordless sudo; wrap each command with sudo.
|
||||||
|
n2_ssh() { ssh $_SSH_OPTS "${HA_USER}@${NODE2_IP}" sudo "$@"; }
|
||||||
|
n2_scp() {
|
||||||
|
# SCP to a tmp path, then sudo-move to the real destination as the remote user.
|
||||||
|
local src="$1" dst="$2"
|
||||||
|
local tmp="/tmp/_cluster_init_scp_$$"
|
||||||
|
scp $_SSH_OPTS "$src" "${HA_USER}@${NODE2_IP}:${tmp}"
|
||||||
|
ssh $_SSH_OPTS "${HA_USER}@${NODE2_IP}" sudo mv "${tmp}" "${dst}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 0. Corosync authkey ───────────────────────────────────────────────────
|
# ── 0. Corosync authkey ───────────────────────────────────────────────────
|
||||||
AUTHKEY="/etc/corosync/authkey"
|
AUTHKEY="/etc/corosync/authkey"
|
||||||
mkdir -p /etc/corosync
|
mkdir -p /etc/corosync
|
||||||
@@ -68,15 +111,20 @@ if [[ ! -f "$AUTHKEY" ]]; then
|
|||||||
chmod 0400 "$AUTHKEY"
|
chmod 0400 "$AUTHKEY"
|
||||||
fi
|
fi
|
||||||
log "Distributing authkey to $NODE2..."
|
log "Distributing authkey to $NODE2..."
|
||||||
ssh "root@${NODE2_IP}" "mkdir -p /etc/corosync"
|
n2_ssh "mkdir -p /etc/corosync"
|
||||||
scp -q "$AUTHKEY" "root@${NODE2_IP}:${AUTHKEY}"
|
n2_scp "$AUTHKEY" "$AUTHKEY"
|
||||||
ssh "root@${NODE2_IP}" "chmod 0400 '${AUTHKEY}'"
|
n2_ssh "chmod 0400 '${AUTHKEY}'"
|
||||||
|
|
||||||
log "Restarting corosync on both nodes..."
|
log "Restarting corosync and pacemaker on both nodes..."
|
||||||
systemctl restart corosync
|
systemctl restart corosync
|
||||||
ssh "root@${NODE2_IP}" "systemctl restart corosync"
|
n2_ssh "systemctl restart corosync"
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
|
log "Starting pacemaker on both nodes (may have failed at boot before authkey was placed)..."
|
||||||
|
systemctl start pacemaker 2>/dev/null || systemctl restart pacemaker 2>/dev/null || true
|
||||||
|
n2_ssh "systemctl start pacemaker 2>/dev/null || systemctl restart pacemaker 2>/dev/null || true"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
# ── 1. Corosync quorum ────────────────────────────────────────────────────
|
# ── 1. Corosync quorum ────────────────────────────────────────────────────
|
||||||
log "Waiting for corosync quorum..."
|
log "Waiting for corosync quorum..."
|
||||||
for i in $(seq 1 30); do
|
for i in $(seq 1 30); do
|
||||||
@@ -99,21 +147,62 @@ for i in $(seq 1 30); do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ── 2. DRBD initialisation ────────────────────────────────────────────────
|
# ── 2. DRBD initialisation ────────────────────────────────────────────────
|
||||||
|
# Put both nodes in Pacemaker standby before touching DRBD metadata.
|
||||||
|
# Without this, the OCF DRBD agent races: it sees drbdadm-down as a failure
|
||||||
|
# and immediately calls drbdadm-up again, leaving /dev/sdb busy when
|
||||||
|
# create-md / write-dev-uuid runs. On a fresh cluster with no resources
|
||||||
|
# configured this is a no-op; on a re-run it stops the race.
|
||||||
|
log "Setting both nodes to Pacemaker standby for DRBD metadata init..."
|
||||||
|
crm_standby -N "$NODE1" -v on 2>/dev/null || true
|
||||||
|
crm_standby -N "$NODE2" -v on 2>/dev/null || true
|
||||||
|
|
||||||
|
# Wait for Pacemaker to actually stop DRBD (if it was managing it).
|
||||||
|
log "Waiting for DRBD to stop under Pacemaker control..."
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
n1_role=$(drbdadm role ha-data 2>/dev/null || echo "Unconfigured")
|
||||||
|
n2_role=$(n2_ssh "drbdadm role ha-data 2>/dev/null" 2>/dev/null || echo "Unconfigured")
|
||||||
|
if [[ "$n1_role" == "Unconfigured" ]] && [[ "$n2_role" == "Unconfigured" ]]; then
|
||||||
|
log "DRBD stopped on both nodes"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
[[ $i -eq 30 ]] && warn "DRBD still active after 60s standby — forcing down anyway"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Detaching DRBD on $NODE1 (belt-and-suspenders after standby)..."
|
||||||
|
drbdadm down ha-data 2>/dev/null || true
|
||||||
|
log "Detaching DRBD on $NODE2..."
|
||||||
|
n2_ssh "drbdadm down ha-data 2>/dev/null || true"
|
||||||
|
sleep 2
|
||||||
|
|
||||||
log "Initialising DRBD metadata on $NODE1..."
|
log "Initialising DRBD metadata on $NODE1..."
|
||||||
if ! drbdadm dstate ha-data 2>/dev/null | grep -q "UpToDate\|Inconsistent\|Diskless"; then
|
# Use drbdmeta --force directly for BOTH create-md and write-dev-uuid.
|
||||||
drbdadm create-md ha-data --force
|
# drbdadm create-md --force passes --force to drbdmeta create-md but NOT to
|
||||||
|
# the write-dev-uuid sub-call it makes internally, so write-dev-uuid fails when
|
||||||
|
# /dev/sdb is still busy (udev auto-attach, stale DRBD state, etc.) and stdin
|
||||||
|
# is not a TTY: "stdin not a TTY, not waiting for confirmation" → exit 20.
|
||||||
|
# Calling drbdmeta --force directly bypasses the exclusive-open confirmation on
|
||||||
|
# both steps without needing a TTY, regardless of whether the device is busy.
|
||||||
|
if ! drbdadm dstate ha-data 2>/dev/null | grep -q "UpToDate"; then
|
||||||
|
UUID1=$(_rand_uuid)
|
||||||
|
drbdmeta --force 0 v08 /dev/sdb internal create-md
|
||||||
|
drbdmeta --force 0 v08 /dev/sdb internal write-dev-uuid "$UUID1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Initialising DRBD metadata on $NODE2..."
|
log "Initialising DRBD metadata on $NODE2..."
|
||||||
ssh "root@${NODE2_IP}" "
|
if ! n2_ssh "drbdadm dstate ha-data 2>/dev/null | grep -q UpToDate" 2>/dev/null; then
|
||||||
if ! drbdadm dstate ha-data 2>/dev/null | grep -q 'UpToDate\|Inconsistent\|Diskless'; then
|
UUID2=$(n2_ssh "cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d '-' | cut -c1-16 | tr '[:lower:]' '[:upper:]'")
|
||||||
drbdadm create-md ha-data --force
|
n2_ssh "drbdmeta --force 0 v08 /dev/sdb internal create-md"
|
||||||
fi
|
n2_ssh "drbdmeta --force 0 v08 /dev/sdb internal write-dev-uuid ${UUID2}"
|
||||||
"
|
fi
|
||||||
|
|
||||||
log "Bringing up DRBD on both nodes..."
|
log "Bringing up DRBD on both nodes..."
|
||||||
drbdadm up ha-data 2>/dev/null || true
|
drbdadm up ha-data 2>/dev/null || true
|
||||||
ssh "root@${NODE2_IP}" "drbdadm up ha-data 2>/dev/null" || true
|
n2_ssh "drbdadm up ha-data" 2>/dev/null || true
|
||||||
|
|
||||||
|
log "Clearing Pacemaker standby — DRBD is up, letting Pacemaker resume..."
|
||||||
|
crm_standby -N "$NODE1" -v off 2>/dev/null || true
|
||||||
|
crm_standby -N "$NODE2" -v off 2>/dev/null || true
|
||||||
|
|
||||||
log "Forcing $NODE1 to DRBD Primary for initial sync..."
|
log "Forcing $NODE1 to DRBD Primary for initial sync..."
|
||||||
drbdadm primary ha-data --force
|
drbdadm primary ha-data --force
|
||||||
@@ -137,7 +226,7 @@ fi
|
|||||||
|
|
||||||
log "Mounting ${DRBD_DEVICE} at ${XFS_MOUNT}..."
|
log "Mounting ${DRBD_DEVICE} at ${XFS_MOUNT}..."
|
||||||
mkdir -p "${XFS_MOUNT}"
|
mkdir -p "${XFS_MOUNT}"
|
||||||
mount "${DRBD_DEVICE}" "${XFS_MOUNT}"
|
mountpoint -q "${XFS_MOUNT}" || mount "${DRBD_DEVICE}" "${XFS_MOUNT}"
|
||||||
|
|
||||||
# ── 4. NFS dataset directories ────────────────────────────────────────────
|
# ── 4. NFS dataset directories ────────────────────────────────────────────
|
||||||
log "Creating NFS dataset directories..."
|
log "Creating NFS dataset directories..."
|
||||||
@@ -153,108 +242,130 @@ fi
|
|||||||
|
|
||||||
# ── 6. LIO iSCSI target ───────────────────────────────────────────────────
|
# ── 6. LIO iSCSI target ───────────────────────────────────────────────────
|
||||||
log "Configuring LIO iSCSI target via targetcli..."
|
log "Configuring LIO iSCSI target via targetcli..."
|
||||||
|
# Note: do NOT bind portal to ${VIP} here — the VIP isn't assigned yet (Pacemaker
|
||||||
|
# creates it). The default portal (all IPs, port 3260) is correct; Pacemaker's
|
||||||
|
# VIP resource will make the target reachable at the VIP address.
|
||||||
|
#
|
||||||
|
# Clear any existing LIO state first (idempotent: re-run after a partial failure).
|
||||||
|
# Use specific delete commands — clearconfig does not reliably clear kernel state.
|
||||||
|
if ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -q "${ISCSI_IQN}"; then
|
||||||
|
log "Clearing existing LIO target ${ISCSI_IQN} before reconfiguration..."
|
||||||
|
targetcli "/iscsi delete ${ISCSI_IQN}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if ls /sys/kernel/config/target/core/ 2>/dev/null | grep -q "fileio"; then
|
||||||
|
log "Clearing existing LIO backstore ha-lun0 before reconfiguration..."
|
||||||
|
targetcli "/backstores/fileio delete ha-lun0" 2>/dev/null || true
|
||||||
|
fi
|
||||||
targetcli <<EOF
|
targetcli <<EOF
|
||||||
/backstores/fileio create name=ha-lun0 file_or_dev=${ISCSI_LUN_FILE} size=0 write_back=false
|
/backstores/fileio create name=ha-lun0 file_or_dev=${ISCSI_LUN_FILE} size=0 write_back=false
|
||||||
/iscsi create ${ISCSI_IQN}
|
/iscsi create ${ISCSI_IQN}
|
||||||
/iscsi/${ISCSI_IQN}/tpg1/luns create /backstores/fileio/ha-lun0
|
/iscsi/${ISCSI_IQN}/tpg1/luns create /backstores/fileio/ha-lun0
|
||||||
/iscsi/${ISCSI_IQN}/tpg1/portals create ${VIP}
|
|
||||||
/iscsi/${ISCSI_IQN}/tpg1 set attribute authentication=0
|
/iscsi/${ISCSI_IQN}/tpg1 set attribute authentication=0
|
||||||
/iscsi/${ISCSI_IQN}/tpg1 set attribute demo_mode_write_protect=0
|
/iscsi/${ISCSI_IQN}/tpg1 set attribute demo_mode_write_protect=0
|
||||||
saveconfig /etc/target/saveconfig.json
|
saveconfig /etc/target/saveconfig.json
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
log "Tearing down LIO kernel objects — Pacemaker will restore via targetctl on the Active node..."
|
||||||
|
# LIO holds the backing file open; clear kernel state now so the XFS unmount succeeds.
|
||||||
|
# Use specific delete commands (clearconfig does not reliably clear kernel configfs state).
|
||||||
|
targetcli "/iscsi delete ${ISCSI_IQN}" 2>/dev/null || warn "LIO iscsi delete failed — umount may fail"
|
||||||
|
targetcli "/backstores/fileio delete ha-lun0" 2>/dev/null || warn "LIO backstore delete failed"
|
||||||
|
|
||||||
log "Distributing iSCSI saveconfig to $NODE2..."
|
log "Distributing iSCSI saveconfig to $NODE2..."
|
||||||
scp -q /etc/target/saveconfig.json "root@${NODE2_IP}:/etc/target/saveconfig.json"
|
n2_scp /etc/target/saveconfig.json /etc/target/saveconfig.json
|
||||||
|
|
||||||
|
|
||||||
log "Unmounting ${XFS_MOUNT} — Pacemaker manages it..."
|
log "Unmounting ${XFS_MOUNT} — Pacemaker manages it..."
|
||||||
umount "${XFS_MOUNT}"
|
umount "${XFS_MOUNT}" || { sync; umount -l "${XFS_MOUNT}"; }
|
||||||
|
|
||||||
log "Demoting DRBD to Secondary — Pacemaker manages primary role..."
|
log "Demoting DRBD to Secondary — Pacemaker manages primary role..."
|
||||||
drbdadm secondary ha-data
|
drbdadm role ha-data 2>/dev/null | grep -q "^Primary" && drbdadm secondary ha-data || true
|
||||||
|
|
||||||
# ── 7. Pacemaker resources ────────────────────────────────────────────────
|
# ── 7. Pacemaker resources ────────────────────────────────────────────────
|
||||||
log "Configuring Pacemaker cluster properties..."
|
log "Configuring Pacemaker cluster properties..."
|
||||||
crm_attribute -t crm_config -n stonith-enabled -v false
|
crm_attribute -t crm_config -n stonith-enabled -v false
|
||||||
crm_attribute -t crm_config -n no-quorum-policy -v ignore
|
crm_attribute -t crm_config -n no-quorum-policy -v ignore
|
||||||
|
|
||||||
log "Creating DRBD promotable clone resource..."
|
log "Creating Pacemaker resources via cibadmin..."
|
||||||
cibadmin --replace --scope resources --xml-text "
|
# Use cibadmin --replace with pacemaker-4.0-compatible XML.
|
||||||
<resources>
|
# Key schema rules for pacemaker-4.0:
|
||||||
<clone id=\"ms-drbd0\" globally-unique=\"false\">
|
# - globally-unique must be in <meta_attributes>, not a direct <clone> attribute
|
||||||
<meta_attributes id=\"ms-drbd0-meta\">
|
# - promoted-max / promoted-node-max (not master-max / master-node-max)
|
||||||
<nvpair id=\"ms-drbd0-promotable\" name=\"promotable\" value=\"true\"/>
|
# - constraint with-rsc-role="Promoted" (not "Master")
|
||||||
<nvpair id=\"ms-drbd0-master-max\" name=\"master-max\" value=\"1\"/>
|
cibadmin --replace --scope resources --xml-text '<resources>
|
||||||
<nvpair id=\"ms-drbd0-master-node-max\" name=\"master-node-max\" value=\"1\"/>
|
<clone id="ms-drbd0">
|
||||||
<nvpair id=\"ms-drbd0-clone-max\" name=\"clone-max\" value=\"2\"/>
|
<meta_attributes id="ms-drbd0-meta">
|
||||||
<nvpair id=\"ms-drbd0-clone-node-max\" name=\"clone-node-max\" value=\"1\"/>
|
<nvpair id="ms-drbd0-globally-unique" name="globally-unique" value="false"/>
|
||||||
<nvpair id=\"ms-drbd0-notify\" name=\"notify\" value=\"true\"/>
|
<nvpair id="ms-drbd0-promotable" name="promotable" value="true"/>
|
||||||
<nvpair id=\"ms-drbd0-interleave\" name=\"interleave\" value=\"true\"/>
|
<nvpair id="ms-drbd0-promoted-max" name="promoted-max" value="1"/>
|
||||||
|
<nvpair id="ms-drbd0-promoted-node-max" name="promoted-node-max" value="1"/>
|
||||||
|
<nvpair id="ms-drbd0-clone-max" name="clone-max" value="2"/>
|
||||||
|
<nvpair id="ms-drbd0-clone-node-max" name="clone-node-max" value="1"/>
|
||||||
|
<nvpair id="ms-drbd0-notify" name="notify" value="true"/>
|
||||||
|
<nvpair id="ms-drbd0-interleave" name="interleave" value="true"/>
|
||||||
</meta_attributes>
|
</meta_attributes>
|
||||||
<primitive id=\"drbd0\" class=\"ocf\" type=\"drbd\" provider=\"linbit\">
|
<primitive id="drbd0" class="ocf" type="drbd" provider="linbit">
|
||||||
<instance_attributes id=\"drbd0-attrs\">
|
<instance_attributes id="drbd0-attrs">
|
||||||
<nvpair id=\"drbd0-resource\" name=\"drbd_resource\" value=\"ha-data\"/>
|
<nvpair id="drbd0-resource" name="drbd_resource" value="ha-data"/>
|
||||||
</instance_attributes>
|
</instance_attributes>
|
||||||
<operations>
|
<operations>
|
||||||
<op id=\"drbd0-start\" name=\"start\" interval=\"0\" timeout=\"240s\"/>
|
<op id="drbd0-start" name="start" interval="0" timeout="240s"/>
|
||||||
<op id=\"drbd0-stop\" name=\"stop\" interval=\"0\" timeout=\"120s\"/>
|
<op id="drbd0-stop" name="stop" interval="0" timeout="120s"/>
|
||||||
<op id=\"drbd0-promote\" name=\"promote\" interval=\"0\" timeout=\"90s\"/>
|
<op id="drbd0-promote" name="promote" interval="0" timeout="240s"/>
|
||||||
<op id=\"drbd0-demote\" name=\"demote\" interval=\"0\" timeout=\"90s\"/>
|
<op id="drbd0-demote" name="demote" interval="0" timeout="90s"/>
|
||||||
<op id=\"drbd0-monitor-master\" name=\"monitor\" interval=\"20s\" timeout=\"20s\" role=\"Promoted\"/>
|
<op id="drbd0-monitor-promoted" name="monitor" interval="20s" timeout="20s" role="Promoted"/>
|
||||||
<op id=\"drbd0-monitor-slave\" name=\"monitor\" interval=\"30s\" timeout=\"20s\" role=\"Unpromoted\"/>
|
<op id="drbd0-monitor-unpromoted" name="monitor" interval="30s" timeout="20s" role="Unpromoted"/>
|
||||||
</operations>
|
</operations>
|
||||||
</primitive>
|
</primitive>
|
||||||
</clone>
|
</clone>
|
||||||
<group id=\"ha-group\">
|
<group id="ha-group">
|
||||||
<primitive id=\"xfs-data\" class=\"ocf\" type=\"Filesystem\" provider=\"heartbeat\">
|
<primitive id="xfs-data" class="ocf" type="Filesystem" provider="heartbeat">
|
||||||
<instance_attributes id=\"xfs-data-attrs\">
|
<instance_attributes id="xfs-data-attrs">
|
||||||
<nvpair id=\"xfs-data-device\" name=\"device\" value=\"${DRBD_DEVICE}\"/>
|
<nvpair id="xfs-data-device" name="device" value="/dev/drbd0"/>
|
||||||
<nvpair id=\"xfs-data-directory\" name=\"directory\" value=\"${XFS_MOUNT}\"/>
|
<nvpair id="xfs-data-directory" name="directory" value="/srv/ha-data"/>
|
||||||
<nvpair id=\"xfs-data-fstype\" name=\"fstype\" value=\"xfs\"/>
|
<nvpair id="xfs-data-fstype" name="fstype" value="xfs"/>
|
||||||
<nvpair id=\"xfs-data-options\" name=\"options\" value=\"defaults\"/>
|
<nvpair id="xfs-data-options" name="options" value="defaults"/>
|
||||||
<nvpair id=\"xfs-data-force_unmount\" name=\"force_unmount\" value=\"false\"/>
|
<nvpair id="xfs-data-force_unmount" name="force_unmount" value="true"/>
|
||||||
</instance_attributes>
|
</instance_attributes>
|
||||||
<operations>
|
<operations>
|
||||||
<op id=\"xfs-data-start\" name=\"start\" interval=\"0\" timeout=\"60s\"/>
|
<op id="xfs-data-start" name="start" interval="0" timeout="60s"/>
|
||||||
<op id=\"xfs-data-stop\" name=\"stop\" interval=\"0\" timeout=\"60s\"/>
|
<op id="xfs-data-stop" name="stop" interval="0" timeout="60s"/>
|
||||||
<op id=\"xfs-data-monitor\" name=\"monitor\" interval=\"20s\" timeout=\"40s\"/>
|
<op id="xfs-data-monitor" name="monitor" interval="20s" timeout="40s"/>
|
||||||
</operations>
|
</operations>
|
||||||
</primitive>
|
</primitive>
|
||||||
<primitive id=\"iscsi-target\" class=\"systemd\" type=\"targetctl\">
|
<primitive id="iscsi-target" class="systemd" type="targetctl">
|
||||||
<operations>
|
<operations>
|
||||||
<op id=\"iscsi-start\" name=\"start\" interval=\"0\" timeout=\"60s\"/>
|
<op id="iscsi-start" name="start" interval="0" timeout="60s"/>
|
||||||
<op id=\"iscsi-stop\" name=\"stop\" interval=\"0\" timeout=\"60s\"/>
|
<op id="iscsi-stop" name="stop" interval="0" timeout="60s"/>
|
||||||
<op id=\"iscsi-monitor\" name=\"monitor\" interval=\"20s\" timeout=\"40s\"/>
|
<op id="iscsi-monitor" name="monitor" interval="20s" timeout="40s"/>
|
||||||
</operations>
|
</operations>
|
||||||
</primitive>
|
</primitive>
|
||||||
<primitive id=\"nfs-server\" class=\"systemd\" type=\"nfs-server\">
|
<primitive id="nfs-server" class="systemd" type="nfs-server">
|
||||||
<operations>
|
<operations>
|
||||||
<op id=\"nfs-start\" name=\"start\" interval=\"0\" timeout=\"60s\"/>
|
<op id="nfs-start" name="start" interval="0" timeout="60s"/>
|
||||||
<op id=\"nfs-stop\" name=\"stop\" interval=\"0\" timeout=\"60s\"/>
|
<op id="nfs-stop" name="stop" interval="0" timeout="60s"/>
|
||||||
<op id=\"nfs-monitor\" name=\"monitor\" interval=\"30s\" timeout=\"40s\"/>
|
<op id="nfs-monitor" name="monitor" interval="30s" timeout="40s"/>
|
||||||
</operations>
|
</operations>
|
||||||
</primitive>
|
</primitive>
|
||||||
<primitive id=\"vip\" class=\"ocf\" type=\"IPaddr2\" provider=\"heartbeat\">
|
<primitive id="vip" class="ocf" type="IPaddr2" provider="heartbeat">
|
||||||
<instance_attributes id=\"vip-attrs\">
|
<instance_attributes id="vip-attrs">
|
||||||
<nvpair id=\"vip-ip\" name=\"ip\" value=\"${VIP}\"/>
|
<nvpair id="vip-ip" name="ip" value="192.168.2.229"/>
|
||||||
<nvpair id=\"vip-cidr\" name=\"cidr_netmask\" value=\"24\"/>
|
<nvpair id="vip-cidr" name="cidr_netmask" value="24"/>
|
||||||
</instance_attributes>
|
</instance_attributes>
|
||||||
<operations>
|
<operations>
|
||||||
<op id=\"vip-start\" name=\"start\" interval=\"0\" timeout=\"20s\"/>
|
<op id="vip-start" name="start" interval="0" timeout="20s"/>
|
||||||
<op id=\"vip-stop\" name=\"stop\" interval=\"0\" timeout=\"20s\"/>
|
<op id="vip-stop" name="stop" interval="0" timeout="20s"/>
|
||||||
<op id=\"vip-monitor\" name=\"monitor\" interval=\"10s\" timeout=\"20s\"/>
|
<op id="vip-monitor" name="monitor" interval="10s" timeout="20s"/>
|
||||||
</operations>
|
</operations>
|
||||||
</primitive>
|
</primitive>
|
||||||
</group>
|
</group>
|
||||||
</resources>
|
</resources>'
|
||||||
"
|
|
||||||
|
|
||||||
log "Adding ordering and colocation constraints..."
|
log "Adding Pacemaker ordering and colocation constraints..."
|
||||||
cibadmin --create --scope constraints --xml-text "
|
cibadmin --replace --scope constraints --xml-text '<constraints>
|
||||||
<constraints>
|
<rsc_order id="order-drbd-group" first="ms-drbd0" first-action="promote" then="ha-group" then-action="start" kind="Mandatory"/>
|
||||||
<rsc_order id=\"order-drbd-group\" first=\"ms-drbd0\" first-action=\"promote\" then=\"ha-group\" then-action=\"start\"/>
|
<rsc_colocation id="coloc-group-with-drbd" score="INFINITY" rsc="ha-group" with-rsc="ms-drbd0" with-rsc-role="Promoted"/>
|
||||||
<rsc_colocation id=\"coloc-group-with-drbd\" rsc=\"ha-group\" with-rsc=\"ms-drbd0\" with-rsc-role=\"Master\" score=\"INFINITY\"/>
|
</constraints>'
|
||||||
</constraints>
|
|
||||||
"
|
|
||||||
|
|
||||||
log "Waiting for resources to start..."
|
log "Waiting for resources to start..."
|
||||||
for i in $(seq 1 60); do
|
for i in $(seq 1 60); do
|
||||||
|
|||||||
Executable
+499
@@ -0,0 +1,499 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy.sh — Full lifecycle management for the HA file-server cluster.
|
||||||
|
#
|
||||||
|
# Handles everything from zero (no VMs, no secrets) through a running,
|
||||||
|
# tested cluster, and optionally tears it back down.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/ha/deploy.sh [options]
|
||||||
|
# scripts/ha/deploy.sh --destroy [options]
|
||||||
|
#
|
||||||
|
# Phases (all run by default; skip any with --skip-*):
|
||||||
|
# 1. ensure-bridge Create storage bridge (vmbr1) on the Proxmox node if absent.
|
||||||
|
# 2. sync-keys Generate SSH host keys and register age keys for both nodes.
|
||||||
|
# 3. create-vms Build disk images and create both VMs via create-proxmox-resource.sh.
|
||||||
|
# 4. add-hardware Attach storage NIC (vmbr1) and DRBD data disk to each VM.
|
||||||
|
# 5. boot-wait Start VMs, wait for SSH on both nodes.
|
||||||
|
# 6. cluster-init Form the cluster: DRBD, corosync, Pacemaker, NFS, VIP.
|
||||||
|
# Also encrypts the generated corosync authkey into the repo.
|
||||||
|
# 7. run-tests Run acceptance tests (T1–T7).
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --node <host> Proxmox host to deploy on (default: pve1.sweet.home)
|
||||||
|
# --vmid1 <n> VMID for ha-server-1 (default: 200)
|
||||||
|
# --vmid2 <n> VMID for ha-server-2 (default: 201)
|
||||||
|
# --storage <pool> Proxmox storage pool (default: local-zfs)
|
||||||
|
# --storage-bridge <br> Bridge for HA storage network (default: vmbr1)
|
||||||
|
# --drbd-disk-gb <n> DRBD data disk size in GB (default: 32)
|
||||||
|
# --memory <MB> RAM per node (default: 4096)
|
||||||
|
# --cores <n> vCPUs per node (default: 4)
|
||||||
|
# --skip-ensure-bridge Skip storage bridge creation/check
|
||||||
|
# --skip-sync-keys Skip sync-host-keys.sh (clan vars already exist)
|
||||||
|
# --skip-create-vms Skip VM creation (VMs already exist)
|
||||||
|
# --skip-add-hardware Skip net1/scsi1 attachment (already attached)
|
||||||
|
# --skip-boot-wait Skip boot/SSH wait (VMs already running)
|
||||||
|
# --skip-refresh-sops-keys Skip scanning running VMs for fresh SSH host keys
|
||||||
|
# --skip-cluster-init Skip cluster formation (cluster already configured)
|
||||||
|
# --skip-tests Skip acceptance tests
|
||||||
|
# --force-rebuild Pass --force-rebuild to create-proxmox-resource.sh
|
||||||
|
# --destroy Stop and delete both VMs (skip all other phases)
|
||||||
|
# --dry-run Print what would run without executing
|
||||||
|
# -h|--help Show this message
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - SSH access to the Proxmox node as $PROXMOX_SSH_USER (wayne).
|
||||||
|
# - sops age key in the standard location (used by sync-host-keys.sh).
|
||||||
|
# - For --skip-sync-keys: clan vars already in vars/per-machine/proxmox-ha-server-{1,2}/.
|
||||||
|
# - For full tests: secrets/common.yaml decryptable on both nodes (run
|
||||||
|
# `sops updatekeys secrets/common.yaml` after sync-keys).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=../env.sh
|
||||||
|
source "${REPO_ROOT}/scripts/env.sh"
|
||||||
|
|
||||||
|
# ── Defaults ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
NODE="${PROXMOX_HOST:-$PVE1_HOST}"
|
||||||
|
VMID1=200
|
||||||
|
VMID2=201
|
||||||
|
STORAGE="${PROXMOX_STORAGE:-local-zfs}"
|
||||||
|
STORAGE_BRIDGE="vmbr1"
|
||||||
|
DRBD_DISK_GB=32
|
||||||
|
MEMORY_MB=4096
|
||||||
|
CORES=4
|
||||||
|
|
||||||
|
SKIP_ENSURE_BRIDGE=false
|
||||||
|
SKIP_SYNC_KEYS=false
|
||||||
|
SKIP_CREATE_VMS=false
|
||||||
|
SKIP_ADD_HARDWARE=false
|
||||||
|
SKIP_BOOT_WAIT=false
|
||||||
|
SKIP_REFRESH_SOPS_KEYS=false
|
||||||
|
SKIP_CLUSTER_INIT=false
|
||||||
|
SKIP_TESTS=false
|
||||||
|
FORCE_REBUILD=false
|
||||||
|
DESTROY=false
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# ── Variables from repo ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
NODE1_HOST="ha-server-1"
|
||||||
|
NODE2_HOST="ha-server-2"
|
||||||
|
NODE1_IP="192.168.2.228"
|
||||||
|
NODE2_IP="192.168.2.227"
|
||||||
|
STORAGE_IP1="192.168.4.228"
|
||||||
|
STORAGE_IP2="192.168.4.227"
|
||||||
|
STORAGE_CIDR="192.168.4.0/29"
|
||||||
|
SSH_USER="${PROXMOX_SSH_USER:-wayne}"
|
||||||
|
|
||||||
|
# ── Argument parsing ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '/^# Usage:/,/^[^#]/{ /^#/{ s/^# \?//; p } }' "$0"
|
||||||
|
exit "${1:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--node) NODE="$2"; shift 2 ;;
|
||||||
|
--vmid1) VMID1="$2"; shift 2 ;;
|
||||||
|
--vmid2) VMID2="$2"; shift 2 ;;
|
||||||
|
--storage) STORAGE="$2"; shift 2 ;;
|
||||||
|
--storage-bridge) STORAGE_BRIDGE="$2"; shift 2 ;;
|
||||||
|
--drbd-disk-gb) DRBD_DISK_GB="$2"; shift 2 ;;
|
||||||
|
--memory) MEMORY_MB="$2"; shift 2 ;;
|
||||||
|
--cores) CORES="$2"; shift 2 ;;
|
||||||
|
--skip-ensure-bridge) SKIP_ENSURE_BRIDGE=true; shift ;;
|
||||||
|
--skip-sync-keys) SKIP_SYNC_KEYS=true; shift ;;
|
||||||
|
--skip-create-vms) SKIP_CREATE_VMS=true; shift ;;
|
||||||
|
--skip-add-hardware) SKIP_ADD_HARDWARE=true; shift ;;
|
||||||
|
--skip-boot-wait) SKIP_BOOT_WAIT=true; shift ;;
|
||||||
|
--skip-refresh-sops-keys) SKIP_REFRESH_SOPS_KEYS=true; shift ;;
|
||||||
|
--skip-cluster-init) SKIP_CLUSTER_INIT=true; shift ;;
|
||||||
|
--skip-tests) SKIP_TESTS=true; shift ;;
|
||||||
|
--force-rebuild) FORCE_REBUILD=true; shift ;;
|
||||||
|
--destroy) DESTROY=true; shift ;;
|
||||||
|
--dry-run) DRY_RUN=true; shift ;;
|
||||||
|
-h|--help) usage 0 ;;
|
||||||
|
*) echo "Unknown option: $1" >&2; usage 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
log() { echo "==> $*"; }
|
||||||
|
logn() { echo " $*"; }
|
||||||
|
err() { echo "ERROR: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo "[dry-run] $*"
|
||||||
|
else
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pve() {
|
||||||
|
# Run a command on the Proxmox node via SSH.
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo "[dry-run] ssh ${SSH_USER}@${NODE} sudo $*"
|
||||||
|
else
|
||||||
|
ssh -i ~/.ssh/id_ed25519 "${SSH_USER}@${NODE}" "sudo $*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pve_check() {
|
||||||
|
# Run a read-only probe on the Proxmox node — always executes even in dry-run.
|
||||||
|
ssh -i ~/.ssh/id_ed25519 "${SSH_USER}@${NODE}" "sudo $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
HA_USER="nixos"
|
||||||
|
|
||||||
|
n1() {
|
||||||
|
# Run a command on ha-server-1 via SSH as nixos with sudo.
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=5 "${HA_USER}@${NODE1_IP}" sudo "$@" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
n2() {
|
||||||
|
# Run a command on ha-server-2 via SSH as nixos with sudo.
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=5 "${HA_USER}@${NODE2_IP}" sudo "$@" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_ssh() {
|
||||||
|
local ip="$1" label="$2"
|
||||||
|
if $DRY_RUN; then
|
||||||
|
logn "[dry-run] Skipping SSH wait for ${label} (${ip})"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local deadline=$(( $(date +%s) + 300 ))
|
||||||
|
log "Waiting for SSH on ${label} (${ip}) — up to 5 min..."
|
||||||
|
while [[ $(date +%s) -lt $deadline ]]; do
|
||||||
|
if ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=3 \
|
||||||
|
-o BatchMode=yes "${HA_USER}@${ip}" true 2>/dev/null; then
|
||||||
|
logn "${label} is up."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
err "Timed out waiting for SSH on ${label} (${ip})"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Destroy mode ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if $DESTROY; then
|
||||||
|
log "Destroying HA cluster VMs (${VMID1}=${NODE1_HOST}, ${VMID2}=${NODE2_HOST}) on ${NODE}"
|
||||||
|
for vmid in "$VMID1" "$VMID2"; do
|
||||||
|
STATUS=$(pve "qm status ${vmid} 2>/dev/null" 2>/dev/null || true)
|
||||||
|
if echo "$STATUS" | grep -q "running"; then
|
||||||
|
log "Stopping VMID ${vmid}..."
|
||||||
|
pve "qm stop ${vmid} --skiplock 1"
|
||||||
|
sleep 5
|
||||||
|
fi
|
||||||
|
if $DRY_RUN || pve "qm config ${vmid} >/dev/null 2>&1"; then
|
||||||
|
log "Deleting VMID ${vmid}..."
|
||||||
|
run pve "qm destroy ${vmid} --purge 1"
|
||||||
|
else
|
||||||
|
logn "VMID ${vmid} not found — already gone."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
log "Done — cluster VMs destroyed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 1: Storage bridge ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_ENSURE_BRIDGE; then
|
||||||
|
log "Phase 1: Ensuring storage bridge ${STORAGE_BRIDGE} on ${NODE}"
|
||||||
|
if pve_check "test -d /sys/class/net/${STORAGE_BRIDGE}" &>/dev/null; then
|
||||||
|
logn "${STORAGE_BRIDGE} already exists — skipping."
|
||||||
|
else
|
||||||
|
logn "Creating isolated internal bridge ${STORAGE_BRIDGE} (no upstream port, ${STORAGE_CIDR})"
|
||||||
|
BRIDGE_CONF="auto ${STORAGE_BRIDGE}
|
||||||
|
iface ${STORAGE_BRIDGE} inet manual
|
||||||
|
bridge-ports none
|
||||||
|
bridge-stp off
|
||||||
|
bridge-fd 0"
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo "[dry-run] Would write /etc/network/interfaces.d/${STORAGE_BRIDGE}.conf and ifup it"
|
||||||
|
else
|
||||||
|
ssh -i ~/.ssh/id_ed25519 "${SSH_USER}@${NODE}" \
|
||||||
|
"echo '${BRIDGE_CONF}' | sudo tee /etc/network/interfaces.d/${STORAGE_BRIDGE}.conf > /dev/null && sudo ifup ${STORAGE_BRIDGE}"
|
||||||
|
logn "${STORAGE_BRIDGE} created and brought up."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 2: Sync host keys ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_SYNC_KEYS; then
|
||||||
|
log "Phase 2: Syncing SSH host keys for both HA targets"
|
||||||
|
for target in proxmox-ha-server-1 proxmox-ha-server-2; do
|
||||||
|
CLAN_DIR="${REPO_ROOT}/vars/per-machine/${target}/openssh"
|
||||||
|
if [[ -d "$CLAN_DIR" ]]; then
|
||||||
|
logn "Clan vars for ${target} already exist — skipping."
|
||||||
|
else
|
||||||
|
logn "Generating host keys for ${target}..."
|
||||||
|
run bash "${REPO_ROOT}/scripts/secrets/sync-host-keys.sh" "$target"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 2.5: Prepare Proxmox node for building ─────────────────────────────
|
||||||
|
if ! $SKIP_CREATE_VMS && ! $DRY_RUN; then
|
||||||
|
CURRENT_BRANCH="$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD)"
|
||||||
|
|
||||||
|
# Fix /nix ownership if it exists but belongs to a different UID.
|
||||||
|
# pve1's IPA-enrolled wayne (UID 50002) can't write to a store created by
|
||||||
|
# another UID — passwordless sudo corrects it once.
|
||||||
|
# Use direct SSH (no sudo) for the writability check so we test wayne's own
|
||||||
|
# access, not root's.
|
||||||
|
local_ssh() { ssh -i ~/.ssh/id_ed25519 "${SSH_USER}@${NODE}" "$*"; }
|
||||||
|
if local_ssh "test -d /nix" &>/dev/null && ! local_ssh "test -w /nix" &>/dev/null; then
|
||||||
|
logn "/nix exists but not writable by ${SSH_USER} — fixing ownership with sudo (one-time)..."
|
||||||
|
local_ssh "sudo chown -R ${SSH_USER} /nix"
|
||||||
|
logn "Done."
|
||||||
|
fi
|
||||||
|
unset -f local_ssh
|
||||||
|
|
||||||
|
# Ensure the remote clone is on the correct branch so create-proxmox-resource.sh
|
||||||
|
# builds from the same commits we're deploying.
|
||||||
|
REMOTE_REPO="/home/${SSH_USER}/nixos"
|
||||||
|
if pve_check "test -d ${REMOTE_REPO}/.git" &>/dev/null; then
|
||||||
|
REMOTE_BRANCH=$(ssh -i ~/.ssh/id_ed25519 "${SSH_USER}@${NODE}" \
|
||||||
|
"cd ${REMOTE_REPO} && git rev-parse --abbrev-ref HEAD 2>/dev/null")
|
||||||
|
if [[ "$REMOTE_BRANCH" != "$CURRENT_BRANCH" ]]; then
|
||||||
|
logn "Remote clone is on '${REMOTE_BRANCH}', switching to '${CURRENT_BRANCH}'..."
|
||||||
|
ssh -i ~/.ssh/id_ed25519 "${SSH_USER}@${NODE}" \
|
||||||
|
"cd ${REMOTE_REPO} && git fetch origin && git checkout '${CURRENT_BRANCH}' && git pull --ff-only"
|
||||||
|
logn "Done."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 3: Create VMs ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_CREATE_VMS; then
|
||||||
|
log "Phase 3: Building and creating VMs on ${NODE}"
|
||||||
|
|
||||||
|
CREATE="${REPO_ROOT}/scripts/proxmox/create-proxmox-resource.sh"
|
||||||
|
|
||||||
|
for spec in "${VMID1}:ha-server-1:proxmox-ha-server-1" "${VMID2}:ha-server-2:proxmox-ha-server-2"; do
|
||||||
|
IFS=: read -r vmid host_name flake_target <<< "$spec"
|
||||||
|
log "Creating ${flake_target} (VMID ${vmid}) on ${NODE}..."
|
||||||
|
# Always --force-rebuild: create-proxmox-resource.sh only calls
|
||||||
|
# sync_remote_host_keys (which populates host-keys/ for proxmox.nix to
|
||||||
|
# bake the clan-var SSH key into the disko image) when it actually builds.
|
||||||
|
# Reusing a cached image skips that step, so destroy+recreate would reuse
|
||||||
|
# an image with a stale/random key baked in → sops fails on first boot.
|
||||||
|
run bash "$CREATE" \
|
||||||
|
--type vm \
|
||||||
|
--host "$host_name" \
|
||||||
|
--vmid "$vmid" \
|
||||||
|
--node "$NODE" \
|
||||||
|
--storage "$STORAGE" \
|
||||||
|
--memory "$MEMORY_MB" \
|
||||||
|
--cores "$CORES" \
|
||||||
|
--force-rebuild
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 4: Add storage NIC and DRBD disk ────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_ADD_HARDWARE; then
|
||||||
|
log "Phase 4: Attaching storage NIC (${STORAGE_BRIDGE}) and DRBD disk (${DRBD_DISK_GB}G) to each VM"
|
||||||
|
for vmid in "$VMID1" "$VMID2"; do
|
||||||
|
log " VMID ${vmid}: stopping to add hardware..."
|
||||||
|
pve "qm stop ${vmid} --skiplock 1 2>/dev/null; sleep 3" || true
|
||||||
|
|
||||||
|
logn "Adding net1 (${STORAGE_BRIDGE})..."
|
||||||
|
pve "qm set ${vmid} --net1 virtio,bridge=${STORAGE_BRIDGE},firewall=0"
|
||||||
|
|
||||||
|
logn "Adding scsi1 (${STORAGE}:${DRBD_DISK_GB}G for DRBD)..."
|
||||||
|
pve "qm set ${vmid} --scsi1 ${STORAGE}:${DRBD_DISK_GB},format=raw"
|
||||||
|
|
||||||
|
logn "Starting VMID ${vmid}..."
|
||||||
|
pve "qm start ${vmid}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 5: Wait for SSH ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_BOOT_WAIT; then
|
||||||
|
log "Phase 5: Waiting for both nodes to come up"
|
||||||
|
wait_for_ssh "$NODE1_IP" "$NODE1_HOST"
|
||||||
|
wait_for_ssh "$NODE2_IP" "$NODE2_HOST"
|
||||||
|
logn "Both nodes are SSHable."
|
||||||
|
# Give systemd a few seconds to settle after activation
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 5.5: Refresh sops host-key registrations ───────────────────────────
|
||||||
|
#
|
||||||
|
# Disko builds raw disk images; each new VM boots with a freshly-generated SSH
|
||||||
|
# host key rather than the one pre-seeded in clan vars. This phase scans the
|
||||||
|
# actual running VMs, and if their ed25519 host keys differ from what clan vars
|
||||||
|
# record: updates the clan var pub-key files, rewrites the .sops.yaml age-key
|
||||||
|
# anchors, and re-encrypts all affected sops files so the nodes can decrypt
|
||||||
|
# secrets on the next nixos-rebuild. Safe no-op when keys haven't changed.
|
||||||
|
|
||||||
|
if ! $SKIP_REFRESH_SOPS_KEYS; then
|
||||||
|
if $DRY_RUN; then
|
||||||
|
logn "[dry-run] Would scan VM host keys and refresh .sops.yaml / secrets if needed"
|
||||||
|
else
|
||||||
|
log "Phase 5.5: Refreshing sops host-key registrations (disko key drift fix)"
|
||||||
|
SOPS_UPDATED=false
|
||||||
|
|
||||||
|
for spec in \
|
||||||
|
"${NODE1_IP}:proxmox-ha-server-1:${NODE1_HOST}" \
|
||||||
|
"${NODE2_IP}:proxmox-ha-server-2:${NODE2_HOST}"; do
|
||||||
|
IFS=: read -r node_ip flake_target host_name <<< "$spec"
|
||||||
|
CLAN_PUB="${REPO_ROOT}/vars/per-machine/${flake_target}/openssh/ssh_host_ed25519_key.pub/value"
|
||||||
|
|
||||||
|
logn "Scanning ed25519 host key from ${host_name} (${node_ip})..."
|
||||||
|
RAW=$(ssh-keyscan -t ed25519 "${node_ip}" 2>/dev/null | grep -v "^#") || true
|
||||||
|
if [[ -z "$RAW" ]]; then
|
||||||
|
logn "WARNING: no ed25519 key returned by ssh-keyscan for ${node_ip} — skipping"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# ssh-keyscan returns: <ip> ssh-ed25519 <b64key>
|
||||||
|
SCANNED_TYPE=$(awk '{print $2}' <<< "$RAW")
|
||||||
|
SCANNED_KEY=$(awk '{print $3}' <<< "$RAW")
|
||||||
|
SCANNED_PUBKEY="${SCANNED_TYPE} ${SCANNED_KEY} ${host_name}"
|
||||||
|
|
||||||
|
CURRENT=$(tr -d '\n' < "$CLAN_PUB" 2>/dev/null || true)
|
||||||
|
if [[ "$SCANNED_PUBKEY" == "$CURRENT" ]]; then
|
||||||
|
logn "${host_name}: clan var matches running key — no update needed"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
logn "${host_name}: key drift detected — updating clan var"
|
||||||
|
logn " old: ${CURRENT}"
|
||||||
|
logn " new: ${SCANNED_PUBKEY}"
|
||||||
|
echo "$SCANNED_PUBKEY" > "$CLAN_PUB"
|
||||||
|
SOPS_UPDATED=true
|
||||||
|
|
||||||
|
# Rewrite the .sops.yaml anchor for this host with the new age key.
|
||||||
|
ANCHOR="${flake_target}" # e.g. proxmox-ha-server-1
|
||||||
|
NEW_AGE=$(echo "$SCANNED_PUBKEY" | \
|
||||||
|
nix run --quiet --no-warn-dirty nixpkgs#ssh-to-age 2>/dev/null)
|
||||||
|
if [[ -z "$NEW_AGE" ]]; then
|
||||||
|
err "ssh-to-age produced no output for ${host_name} — check nixpkgs#ssh-to-age"
|
||||||
|
fi
|
||||||
|
logn " new age key: ${NEW_AGE}"
|
||||||
|
sed -i "/&${ANCHOR} /s| age[a-z0-9]*$| ${NEW_AGE}|" "${REPO_ROOT}/.sops.yaml"
|
||||||
|
done
|
||||||
|
|
||||||
|
if $SOPS_UPDATED; then
|
||||||
|
logn "Running sops updatekeys on affected secrets..."
|
||||||
|
SOPS="nix run --quiet --no-warn-dirty nixpkgs#sops --"
|
||||||
|
(cd "${REPO_ROOT}" && \
|
||||||
|
$SOPS updatekeys -y secrets/common.yaml && \
|
||||||
|
$SOPS updatekeys -y secrets/ha-server-1.yaml && \
|
||||||
|
$SOPS updatekeys -y secrets/ha-server-2.yaml && \
|
||||||
|
$SOPS updatekeys -y secrets/ha-server-1.keytab && \
|
||||||
|
$SOPS updatekeys -y secrets/ha-server-2.keytab)
|
||||||
|
# Note: ha-corosync-authkey is re-generated and re-encrypted by cluster-init below.
|
||||||
|
|
||||||
|
logn "Committing refreshed host keys and re-encrypted secrets..."
|
||||||
|
(cd "${REPO_ROOT}" && \
|
||||||
|
git add \
|
||||||
|
vars/per-machine/proxmox-ha-server-1/openssh/ssh_host_ed25519_key.pub/value \
|
||||||
|
vars/per-machine/proxmox-ha-server-2/openssh/ssh_host_ed25519_key.pub/value \
|
||||||
|
.sops.yaml \
|
||||||
|
secrets/common.yaml \
|
||||||
|
secrets/ha-server-1.yaml \
|
||||||
|
secrets/ha-server-2.yaml \
|
||||||
|
secrets/ha-server-1.keytab \
|
||||||
|
secrets/ha-server-2.keytab && \
|
||||||
|
git commit -m "secrets(ha): refresh sops host-key registrations for new VM instances" || true)
|
||||||
|
logn "Sops keys refreshed and committed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 6: Cluster init ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_CLUSTER_INIT; then
|
||||||
|
log "Phase 6: Initialising HA cluster"
|
||||||
|
|
||||||
|
CLUSTER_INIT="${REPO_ROOT}/scripts/ha/cluster-init.sh"
|
||||||
|
[[ -x "$CLUSTER_INIT" ]] || chmod +x "$CLUSTER_INIT"
|
||||||
|
|
||||||
|
if $DRY_RUN; then
|
||||||
|
logn "[dry-run] Would generate temp key, authorise on ${NODE2_HOST}, scp cluster-init.sh to ${NODE1_HOST}, and run it as root via sudo"
|
||||||
|
else
|
||||||
|
# Generate a temp keypair so cluster-init.sh can SSH node1→node2 as ${HA_USER}.
|
||||||
|
# Root on node1 has no keys; a temp key bridging node1→node2 nixos solves this.
|
||||||
|
TEMP_KEY="${REPO_ROOT}/.tmp-cluster-init-key"
|
||||||
|
TEMP_KEY_PUB="${TEMP_KEY}.pub"
|
||||||
|
rm -f "$TEMP_KEY" "$TEMP_KEY_PUB"
|
||||||
|
ssh-keygen -t ed25519 -f "$TEMP_KEY" -N "" -C "cluster-init-temp-$(date +%s)" -q
|
||||||
|
TEMP_PUBKEY=$(cat "$TEMP_KEY_PUB")
|
||||||
|
|
||||||
|
logn "Authorising temp key on ${NODE2_HOST} for ${HA_USER}..."
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${NODE2_IP}" \
|
||||||
|
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '${TEMP_PUBKEY}' >> ~/.ssh/authorized_keys"
|
||||||
|
|
||||||
|
logn "Placing temp key on ${NODE1_HOST} for root..."
|
||||||
|
scp -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no \
|
||||||
|
"$TEMP_KEY" "${HA_USER}@${NODE1_IP}:/tmp/cluster-init-key"
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${NODE1_IP}" \
|
||||||
|
"sudo mkdir -p /root/.ssh && sudo cp /tmp/cluster-init-key /root/.ssh/cluster-init-key && \
|
||||||
|
sudo chmod 600 /root/.ssh/cluster-init-key && rm -f /tmp/cluster-init-key"
|
||||||
|
|
||||||
|
logn "Uploading cluster-init.sh to ${NODE1_HOST}..."
|
||||||
|
scp -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no \
|
||||||
|
"$CLUSTER_INIT" "${HA_USER}@${NODE1_IP}:/tmp/cluster-init.sh"
|
||||||
|
|
||||||
|
logn "Running cluster-init.sh on ${NODE1_HOST}..."
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${NODE1_IP}" \
|
||||||
|
"sudo env NODE1=${NODE1_HOST} NODE2=${NODE2_HOST} \
|
||||||
|
NODE1_IP=${NODE1_IP} NODE2_IP=${NODE2_IP} \
|
||||||
|
VIP=192.168.2.229 XFS_MOUNT=/srv/ha-data \
|
||||||
|
ISCSI_IQN=iqn.2026-01.home.sweet:ha-storage \
|
||||||
|
VMID_NODE1=${VMID1} VMID_NODE2=${VMID2} \
|
||||||
|
HA_USER=${HA_USER} HA_KEY=/root/.ssh/cluster-init-key \
|
||||||
|
bash /tmp/cluster-init.sh"
|
||||||
|
|
||||||
|
logn "Cleaning up temp key from both nodes..."
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${NODE2_IP}" \
|
||||||
|
"sed -i '/cluster-init-temp/d' ~/.ssh/authorized_keys" 2>/dev/null || true
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${NODE1_IP}" \
|
||||||
|
"sudo rm -f /root/.ssh/cluster-init-key" 2>/dev/null || true
|
||||||
|
rm -f "$TEMP_KEY" "$TEMP_KEY_PUB"
|
||||||
|
|
||||||
|
# Encrypt the corosync authkey generated by cluster-init and commit it.
|
||||||
|
log " Encrypting corosync authkey into secrets/ha-corosync-authkey..."
|
||||||
|
AUTHKEY_TMP="${REPO_ROOT}/secrets/ha-corosync-authkey.tmp"
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${NODE1_IP}" \
|
||||||
|
"sudo cat /etc/corosync/authkey" > "$AUTHKEY_TMP"
|
||||||
|
if [[ ! -s "$AUTHKEY_TMP" ]]; then
|
||||||
|
err "corosync authkey on node1 is empty — cluster-init may have failed."
|
||||||
|
fi
|
||||||
|
mv "$AUTHKEY_TMP" "${REPO_ROOT}/secrets/ha-corosync-authkey"
|
||||||
|
(cd "${REPO_ROOT}" && nix run nixpkgs#sops -- -e --input-type binary -i secrets/ha-corosync-authkey)
|
||||||
|
logn "Authkey encrypted. Committing..."
|
||||||
|
(cd "${REPO_ROOT}" && git add secrets/ha-corosync-authkey && \
|
||||||
|
git commit -m "secrets(ha): encrypt corosync authkey generated by cluster-init")
|
||||||
|
logn "Committed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 7: Acceptance tests ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if ! $SKIP_TESTS; then
|
||||||
|
log "Phase 7: Running acceptance tests (T1–T7)"
|
||||||
|
if $DRY_RUN; then
|
||||||
|
logn "[dry-run] Would run acceptance-tests.sh against ${NODE1_HOST}/${NODE2_HOST}"
|
||||||
|
else
|
||||||
|
NODE1="$NODE1_HOST" NODE2="$NODE2_HOST" \
|
||||||
|
NODE1_IP="$NODE1_IP" NODE2_IP="$NODE2_IP" \
|
||||||
|
VIP="192.168.2.229" \
|
||||||
|
bash "${REPO_ROOT}/scripts/ha/acceptance-tests.sh"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Deploy complete."
|
||||||
Regular → Executable
File mode changed.
+133
-115
@@ -5,211 +5,229 @@ sops:
|
|||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrMGd3ZVNlNXdmOUZMUzdQ
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiU2IxZ056SUs3SVZiUTlQ
|
||||||
TW1acEs0NFA2Q01rM2dkc1h0NHkzQmhiWFZZCjMyK202VWdlaGhsZW04MnVwUVdO
|
NXZEYmg3VzJzeWJPRnZXcUtOMGQ1MElBU0dVCm5xSURZZ0sxTjVxQnhpVVNEU092
|
||||||
alA0Q2FETThsYkhSS0hKdHBaS3VaY28KLS0tIG1Gdk8yalREOUtIZTUyY2p1UHlJ
|
bDBHbFAzZ3hzejc5NmM2U0FQTkRNSTQKLS0tIC9OOURPRlcvQXIxMVZXR1JjUWRX
|
||||||
eG5iQnJsaTJBY3Y1dkw1c0VEaDQwdDQKfV04fLy32Lp2ZQ2VnvQ0h/Vsf+qdaJiv
|
bE5ja3FoWXFIZ2ZLTklqWHhMRHBTRWcK2Z4sEcdyvx1rdBIGrbNw7bYvEPXdobvF
|
||||||
DnLXGZ9hE5yzpKWkQIRgqYGBkF8PkH0YC4OIaVkA53wrtjqS4ZHR9Q==
|
h0aNihn817DvtVr8eeucj22iYCS40etElQNcO5i68t5wab4/d3rUbQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjWFNRY2FiK3VkSm1RdHBn
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrN0lNNGtYbE8rRTB0U2hL
|
||||||
cWl1ZStLcTRFZWY5VVI5N0FhODZvR1ltM0FvCnBHZUtTUm9QeHNlbVBoZEx1V3Fa
|
eUNOZUNFRGk1RzE3R1VjV0Q3ckszcDNHWGxJCkhFN25JbDVSaWJIZ09uNzczbkM1
|
||||||
Nk9iMmJKVnhocEpERi9leE1ySUtNMFkKLS0tIDRRYkxnbU90S2RyMHdJNzRJNXBi
|
ZWZ1Y2wrTHZ6VzlnVWRoT2JOdDZHM0UKLS0tIHQ2ZE9BaFRCeDNqbldRQkpLclI4
|
||||||
QjRmZFhVakVic2tYODZHcWtJRmNQTDQK7G8eSJInt11P0DiL9uzNQ/ZHHLVNIYPe
|
dXZqOXNaR2Q2QWJtRlZUa0hLaTh2YlkKeCh3+yIt0sjh2Ig8m/YQifhMDHnTpuZE
|
||||||
bvlhuGkEuQ/+j5sVSKOfSI2Y7CvM7TpE3APyKBcLG3ajYg6F/Ev3SA==
|
zHBE74SUyWFmqLgtd1b8Rx5gn9vBcS/3SMv3yKWispg4DZ3bCOeNFA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age19m0m7vdfg86yqy8l5mmle5jdd0unrn3f55t232w8h5ey42cqw34sfpt32n
|
recipient: age19m0m7vdfg86yqy8l5mmle5jdd0unrn3f55t232w8h5ey42cqw34sfpt32n
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1S3dOcFdwV2NoMW1oMnY1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5c2luYmpYcVVxRHROQlpk
|
||||||
VGdxeXJVR1lsbzNVZHlGb0NGOVo0SndiakVjCmg0QlNnZDV5RlFja2hCbVRXV1VF
|
cTM4cm9uNEtVS21pK2pDRVRySmxYS3NwdGg0CnQvS3g4Q2tSdlpVeWZxbzArcTM1
|
||||||
S1ZtbC9KU0U1ZW9zeVoyR3hxNW1XTFUKLS0tIFUzWTJhTzM4QnpWV3h1OFU0N3BK
|
YWRMcDAxcWgySFRqRC9XSElsTVorVEEKLS0tIFd6YjdrY2ZXQVBaZ01wMTIvV2sv
|
||||||
RnF1N2k0S0lIVitoNDJLUmZqdHRzZVkKUfNg24p8zxb3749v/A1BOKCNw75AUKpf
|
ZEk0TG0ybENlTlhpZnovMXh0OGd5V1kKEbjrmKP6Su16KRDQ4PMqi0tHRS6+PfuT
|
||||||
RUmFCw5DDWF2aNM0mZqcjjVmJ/FRKV2HXwwUGsHPKSOTnKfOUlPNKA==
|
0v5DEyi8EDtIYSMzzdF7Jk2C0h5XuRux45Z6E6JhKW5c788dGQHz/A==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1rrxqea6q6pn39sw8y5te63h2py8jgjl9v0jyper86w3ggtn67upqg3ah39
|
recipient: age1rrxqea6q6pn39sw8y5te63h2py8jgjl9v0jyper86w3ggtn67upqg3ah39
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzazl5MUpUNEVNMTdLWXBp
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1eW5kd1FrSGJaSDkwTzdY
|
||||||
c1hkaVhjcTZXTUNsWS96QVVWb0RVQlZ4VlNvCnE3TFRySU5jTFk2WjBONUQyQUhl
|
SnJhR2hqRkNTTGtSTWsvYmZpSlVWV0xvYkRZCmtqR1FQOTRQR3dGYU8zNFpMdkUr
|
||||||
U1lNTDFTRmZhMUFyZmpVY2xpaUVxRW8KLS0tIE9MQ1M2U2ZXQmRCVll2UGRWL1RG
|
SWN2KzhXaE1rYWZYNHJsOVVvRDY3R00KLS0tIG1aUldSWHlHeHkrcjF2TENvazR3
|
||||||
K2tnU0NOKzU5dkpiekF0Vk1VM0ZZZDgKK13aFypGAqrKWPOr3UwtXI1EoXf1+UzS
|
YXJLUXV3VVNSWEdseFlZQUJPQUF6OFkKOPKH52muUha+MV2leWDiqpuTaIj0ZKzV
|
||||||
rBqcwnX6WPxSKUwWoins4Aojek4QhbhY4R5ei6rRS0KEQeryGxy8bg==
|
keEdyqkIck6KKfIjphNKTW5ujiuKjGQbRliquBknfCRGMnRlMNfmkA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1adur9g330gua4l6ndk8cqjg35qc8yxwgme6wrl2hpylcc7vxm38q05ejuy
|
recipient: age1adur9g330gua4l6ndk8cqjg35qc8yxwgme6wrl2hpylcc7vxm38q05ejuy
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCZUpVT1p4TGZyNC9qMm5G
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmTDExNFpCZHZmWnpwYis4
|
||||||
UzNHV08rWURjY2lqS3FTWFdoc0FYYTFjb0NBCk1mZ2JzaXk5RmExNE9xWGZ5K0pv
|
bFJGZXV5UksyS01MT0d3cXE2Wno2Zzh6OFFnCm9hT3MyZ0hTbnkzdXk3NmN1TUVF
|
||||||
UEtqMkltV0dIWll5eVBUVVRNOUNDWUUKLS0tIGpKNVJudUM1UGNvaGl1UDBOeFA1
|
OGtCZnlJRmZJaWJWaE1pMGcrTzJzZm8KLS0tIFE1L3ZxeUl6WVA2UXN2b0lIcTBl
|
||||||
RjUyRlZ6a0Y4SXNsL21zSURVRk9KTFEKU1L6BQ6ZlYQQtqx3uF/uM5CQ1ercmvRT
|
YmtXZnZaMml6ejY1Y3lRUFFtTEVMS0UKZbcuunA9GRqctCfC0Fh2foKt7n3l8KkE
|
||||||
TL3r2/Y07gE7CjRn3pR9z0co8KndGzxV6YR+ubyWptwBS8KQh5stkw==
|
FNjjxUzL7OQjNG9M1aAtRMMO5KXL7YKXRUJWOPcpiLeLUNvDLeLXvA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age17e89ty6p0fw24daanen57wg8uald9s025t3wwxsw269svwpmgvrshfvfvt
|
recipient: age17e89ty6p0fw24daanen57wg8uald9s025t3wwxsw269svwpmgvrshfvfvt
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsaWxxWS9xQXViY3VnUEx2
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQYlp5U0FvVkdIKzBGZVRl
|
||||||
V09FbTI0WGtNbW0yclhOSGZDbG5NUTNaTkFFCitkcjJ3OE9BSnN4bjFWcE9nYVBk
|
UEtOOG5kVHVLQ0ptVTVpdG9Lb1Y3bzlOODNzCmNYN0Y5ZGNzTGFGYmxJcWFxaExn
|
||||||
ZzMyVHlJQ2wwdU5JOXdCQm9oNkhNd2MKLS0tIE9tRzFYS05vSkUwWFRkaTdtc0k0
|
NkkrT2RLSlZTdmFiNks3RVZXQlBEYnMKLS0tIDdCVlBxUG8vYU05d3c2MXhjQjhM
|
||||||
blVoMWV0QklBVkluT0Z4NHYyS1F0blUKO+Uc0of/V77ZUZOsxTzeH8/LmmAOQt+J
|
TTVJUGxlWUxWalBRYzFKQUQzd1R6dHcKPz1ciNICph7qnqSH5WYmOjUCNHsIAmTX
|
||||||
x/COHxnLCnZ4eWI6q1a0Qn5Br15OJYTxUI2QTV4goTnXBNUDo9wdpQ==
|
lRnbFCZdD/ZIFRK8OYAyCGfQ42UYSTf16kH2jDdTFUy7Fk2+O7lNIA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1hrx8qj02fj2ea6d4g9vqhyj9hl7fppkjqfdx2l37py3h6pdkr95s8n8rvs
|
recipient: age1hrx8qj02fj2ea6d4g9vqhyj9hl7fppkjqfdx2l37py3h6pdkr95s8n8rvs
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsLzFnZkdVdGQ2dHRwcnRt
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiemVqWW9LdlV0RjRuRHpF
|
||||||
Ui8rc1NoQzNKSHVzTk0vYU1vMlRxcjBKZ2hNCmhLYXVGSis4RU9HNGVxZkpvUkd5
|
U2MwN3RDeHU5SWhuQ2l4YStRVVE2eU9XSUc4ClNGMkNXQVdkWFkwcys0aFVhZnZv
|
||||||
L0xvalhDYTQ4N21OcHRheTlkaUxvTkEKLS0tIFVkRGxtLzhQT0paV2U3ZnNScVdn
|
Y3J6RmxwakdaZVo5VWZhb0lVTUdTYzQKLS0tIEQ2NW0yb2lGQ0NLZm1PSWNFVkQ1
|
||||||
alZnaVppeGI3OUVscGpONkk3YTRXd3MK61na8x5qX7+dyMHasDz2dj7yeaUlX8me
|
a0Y1NlYwZ0dnbHhsQVB5eS9kZVdqbmsKgFLUpZMqGywO9Ext5WRwco2bpenVNrzL
|
||||||
N4/SIk1JDBhv9G7mdKLbKhSF1UJrSY7TJqJqx8/dqEc0uG3vptA1ew==
|
XrGCqhn/7TW9DrJlPnFe8r4fl4DFP6GWByn7axDl58Qy1OzeBZwnNg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1e7l8dusgmgfzd2cxrrzwepzjxt69hzqj4epee0cs27u6yg4kxcuqm34ncx
|
recipient: age1e7l8dusgmgfzd2cxrrzwepzjxt69hzqj4epee0cs27u6yg4kxcuqm34ncx
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4ZEtvOUhMU1FRWWpJQjF4
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLRzlzWEcwTUx6M1JNYWgr
|
||||||
NWphNXp5M0dLZXhkZndhT1Y3L09maytHazJVCm1MeEtMWXg1Zjg3bFVnZEorci9J
|
ZWxnVXhFWkhQVjBrSndGTU9oY2MrOTQxTGhFCjhUdERPc3hzQlkzMjBEa3AvaWVX
|
||||||
bkNZQU9Ta1dDTFFHaGFWQVBpK3pYRDQKLS0tIEhPVGliRDR3ZTF2aEl3ZnJEYWtR
|
T0RJWXFFZHdmUVBObGhpQktCQ2NPbWcKLS0tIDN2YkE4bEVtbjJlcndWVmlrQ3ZT
|
||||||
anh0SEpnVW8xdXNkZEZQSjcxU1BHMFEKVRJUA71fi1QawB2TnuTWMYhzQR18u4M2
|
dlVvRmt4RklOdUJyZ25ia0ZoYjU4ZHcK5fL1yvRcty6+EMzfSkwwJM3qvrr6h46d
|
||||||
s1V4j4TwYyyKZFoNvt8kOUayjC499c5OBUufYs6G2ciC6gK2A9E0EQ==
|
d7Zs+EElXrsGDQvL0fevB/zDcICAtudYnVv4nP3Jf8GQnL19zWlwbg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1jcx3yajjhghn8qh8za3yeu8nxykzlg3p4nrv03vnfvzl0mzayg2qmg940e
|
recipient: age1jcx3yajjhghn8qh8za3yeu8nxykzlg3p4nrv03vnfvzl0mzayg2qmg940e
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTV2IzSjBEMjUwZjB0dCtj
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhZElRNXNLK0tVUjQ0ektQ
|
||||||
aTdZVlEvYkVRbTNjUC9ZRVdNZVhPQTJEd1ZZClc3NDJiR1BVYkZkdVVxMVZGc0VN
|
Tzc5NDJReldiYVFFZnRLS3l5dWw3UVQ0WWhJCm1DYVloZGpYeVJtNUNRcEJvVUZE
|
||||||
dWdSSXBFR2xxR0xrV2thRmUwSS96S0UKLS0tIHRRTXVlUi9UYnFRRlhsU21HZVY4
|
Q0xMcmR0WWdVVTBaWko0dGI5V1hqaUkKLS0tIFBBeXdNY1pFai9QT2t3NWFLV2sw
|
||||||
SDFYd0NwVEtVZXNsWUI1a1ZZU2xNRGMKuQUhOq2FRD+PGn5OkdODZItbxCzRKjne
|
cWpRU2RISSs1UXh3Wkl1dWI3VEk1bXMKP6v3Lm0XZkp3Gn3iJkB0K0Y0E5BeilqR
|
||||||
E60UOYtHjanuGjJ1svuR9cYsLZz7lLOwItklecYaQYpMRZEwzzBGCQ==
|
plmPh1rKbT/wkefFwpa8e2Wwmhx80YK+bjn633MH7mLQDf6AiK83Xw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1sweerhrga9yf8x6sv0apz4ed4g48rnlcq34rpv20t0rcelwgpgeqwvndzz
|
recipient: age1sweerhrga9yf8x6sv0apz4ed4g48rnlcq34rpv20t0rcelwgpgeqwvndzz
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrSjJoUVUyd1JqRm1ZQzZx
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMVk8yT1FtMVoxYjF2YVBs
|
||||||
SFJ2cWNUY0E4b3FndVA5Y0hIOEZnZUlVWlhBCm1vY1luOXZBelRUTmF5Y1NMeDBn
|
UTFVbmhzblBCRno2V3YvaERRN0xWcHd2NVE4CmVyK3p1SWtodk5iWjdkQjJRc0dM
|
||||||
cE1BTDErc041UjJCWTBQbnk0Wk80dkEKLS0tIDVSZzd1UktvZGdyanFUMkVORUtl
|
Vm8zZHczYlFFSG91QThmUGNaWHBoN0UKLS0tIDZMMXoxbUM0TUVaNFUzRExLc2VM
|
||||||
eVB5TnJkMlp6dUpXSTlxRlplZ2NxUlEK0AYOxIbswjM0SUASDfmZ7PqcEU844fgI
|
OVFrNGZNL256cjg0Q3ZBQW9xUFcxSUEKv4HqaTcTzNYRJCmNEeUQLFfEoFXyOBEC
|
||||||
ycFWVSEPodwUZ6UFoYXhHlJzHFcgpLvwUd1PMktLHe1qrZ7GOQJIMA==
|
yZrg8D+ROBn98484bxNeoyOjQdcVn7OYiCv1y6utnyeA9sdMUB0ong==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1f7usptjx9rv4rxauasve200gxtdt9jkqhhdqstlf20wvlm7u75rsjfw50m
|
recipient: age1f7usptjx9rv4rxauasve200gxtdt9jkqhhdqstlf20wvlm7u75rsjfw50m
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPakhQcE92ZU0zYmk2QS9D
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1Rkl0UXduS3k3WVU1RzdG
|
||||||
TG0xc1JPZXZCZ0tZOXA3MGNLVnBlZGVtRFRzCmIvMHhQKzFVWCtpMTQrQUhGVGJp
|
N2IybE1TZ1kwVmM4Y2lvUS9DVG0zSkdpZGhRCnFkZnJVS2FBZm9Xa0V4SjRwaGdR
|
||||||
RU1jbldYckw3TXI2SlNpZVBIZHRsWWcKLS0tIGtJTUtJejFxem5jajFQUDFTQWU1
|
TWpaaTh4VEk4cUFrb2pURERzbWtSR0UKLS0tIEsybHdvUWg3UlUvL0lhdHIrUE0y
|
||||||
VnlxYmVlNG04ay9ETi9FRmVYQXVoRkUK9oFNolI7jRjo9RUs1g4ghrx7aYV4U/ce
|
R0pMdnI5QlpIcWVWdGRYQ3JCaElLWTgK5cTfvehj4BPU22tYiR5piZdouUdeh5Gr
|
||||||
ZTc2tFh57+7aKgrDi+2W3jwhfkjvBsThk//p5mLlqEEgw2lwlnhvPA==
|
x/xSbFWqYVauDB6rpzZvhGMI4KCJh2RmDdt7FrlCb/YCB3QoSgvl8w==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age17jqc66x9yeshfgd9v78mj483r4zzarqdtuxtrkxe4x5mw679gphshd94th
|
recipient: age17jqc66x9yeshfgd9v78mj483r4zzarqdtuxtrkxe4x5mw679gphshd94th
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoVnh6dFAwTkY4cHJpaEs4
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwL0NNbGlGS3lTZjEydGJm
|
||||||
QnZPeXZHK0tXYWZPNytmYXVsdGRWQVI3RlhRClFVb2I5OVZzZFNrRXFaa0JTUkRJ
|
MWZRK0Z5YktuNVNudk84MndLUUdXR0RlRFF3ClB4MWRUTFBWYUdwbityeUU2SlR4
|
||||||
OC9GQ1V5K0JhWlhkUjU1WStCa1lPV1kKLS0tIEhzdnBBZkRnK0NtV1FuTkVsNlgv
|
azZMb012YXQzWHUxZnZzeVp3MmdzcU0KLS0tIFJ2SUNyMTBEY1NWejdVQ3hyUytQ
|
||||||
QzVEcEVkQm5NL0Z5dUU1U0ZFaTJITnMKaWE9vlrOpQstr6FGP5ObdilsCYk4kYAj
|
VnlQWHRDYWZFaU1ON2RQTXBwNFNGYUEKwnRUWCdsiStia1jLHJ6UNP/kgEt4Aw8U
|
||||||
/phboR+Ym7QDTyUF9LZXJCU54YJp6vEWkRnlJFqC75UW/v/lgBhBMQ==
|
pA8PKKu6TZVPI8pw7svtI/uvJuW+8ARuNcyb1FdYmX+sOHnOEqHyjg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1px0h5l9zp2dww0m8fncrc82kfdmzplsfv2ltat7sna28xpg09pqqcl3s2k
|
recipient: age1px0h5l9zp2dww0m8fncrc82kfdmzplsfv2ltat7sna28xpg09pqqcl3s2k
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkUHAzWk1KblJxVVZZV3FM
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxM0FyaFlTR2FzMkhGTzZx
|
||||||
WndzRGRtbkw2azRVWnBuZmhKWElqRFk0RENzCk5DMHlMVWpwbXEwVUhkaFZUbkp2
|
d1d3dWNGOG1ESllETEVxRGJlYWkrTUNIdkYwCmI4andLMUUvNVU3R0N2WCsvWWhV
|
||||||
QXNlZFV4SjBEdmR6UEw0N1JOUnhNKzAKLS0tIHo5RkNDUk1ESWRHQmV6bzkvSTlP
|
UmlkcHdMSFA3bFp0ZG5OckE3YmJrejAKLS0tIDhEVEJYOFc5VWVHcGcvMm5yanBl
|
||||||
dk1GQ0Y3V0dTRlByb2xUOERVOTVwbVEKY4sAHyAhvGSYJzPuufWUIQD2xZcSt/nX
|
eGxaNEFxdEN4NWh3cnlGeElBUTJmUzAKvCSJF+LGf1JqEdkzT/Wk6hEzferseyWO
|
||||||
t2ZFXu891/QdEzyUXCIzdwAV+Y/LjvroIlCp5Hkbrk0s7N+ghqsB1A==
|
vAYR8Z+u6GDwQi7JdFz9iZKD21LAs0IMzd7cZqXKIXVedyXuaIhPtg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ufg390ydrmma849t9xfkxxl5xvdkk6mngnlzhmy7mvuaje8sgcmsmnq6l7
|
recipient: age1ufg390ydrmma849t9xfkxxl5xvdkk6mngnlzhmy7mvuaje8sgcmsmnq6l7
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBYVkwYXpYSjdmM1FpbDl3
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZ2M0cG5nMU5nYVliRzlx
|
||||||
QWpycXoyL1AyOEpZUmtpbjl3MFAwTkJoOWpVCmVDd0FBUWxaQmZCU2VmNkZGMk9o
|
TVpDcHpwclhXNkxuc2VKanBvcnRxRW04UXh3CmNJeEl3K0FrWWVHQWRwK3Q4Tzk2
|
||||||
TUdLNGtac2N4REg2eVF1eVh0WnNaTE0KLS0tIDJvcFErSjRiWmhPMmpadjROOHdt
|
MEZQbFV1QURLZEhCdEhaMjBRUzllaVUKLS0tIFhSUTJ4SXJwRHhVbkZFc2Rwb3Fh
|
||||||
NXp6Y1JpdHFlSlRoa3JTaEt3emdnalUKjoFfZAiKMPF3noX+K0+vc3+p/XUHnhic
|
QzAxTnpWSHZaWlF3NUlFMlBHUkRUa2sK3Kim0mp2adSvruI6Necd3UpZHow0EFmT
|
||||||
k888KdUwcZYl2/dAIc8UDSggbMnncJAJgoezoCHLkj97GNNAD7E+gQ==
|
JFOIpIK+3Pc06gsiqrDtDAx2Qtc10Ja4ILTutPlp4va6zzmzrPABiw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age16j42pdc5dr6wnj7xayhkqdj2rny9u68fcqejs50hqq42scssh4gsnrrnlt
|
recipient: age16j42pdc5dr6wnj7xayhkqdj2rny9u68fcqejs50hqq42scssh4gsnrrnlt
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqYnQyYzF5cktZNG5PcThy
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwTUZ1RzJKcVF5bnJGamlU
|
||||||
SEszVEwrUkQ1VVRsM3pSTlRQaHVLN0VuSHhrCmN1Z3pwNlFsbDN2UGI0KzYyallM
|
bWJvcU1sTTJuME94YjhMZWRWRWR0aGJ3UUNvCjh6U1BkaEg0TDlCeTNyUlNTRWV6
|
||||||
SHJ5eklQeEIxSlhiYW5PUlpJcG5KNjQKLS0tIEk0QkpMdlBlRjVYMmJaMzJUbDNm
|
Nnd0NjE3NmplSWFjVWpSRy9qWjBUbnMKLS0tIGdMZUFzbGdMejZoMlFBVUJXSE13
|
||||||
K25pZldwd3JoZi9vdURoa3Myb2RQNG8K6N6bO2YKooPfpKihgsYqilfz/yAYCLZD
|
TW4rUHFjZk5oVkw0UGtpaG5mOHRRbHcKFX4ZyNQ0TUeqwXEaz1qmAXQRIOctDkvz
|
||||||
XJ/THgT4URX2VNvSspvBtN8luOiJUVcchp5WtL2m9jARL5txEcDorA==
|
TIwxJseAjS3qVxCslnxzgaqmCh8DzCfjrRxLUS/HVTnwOaE1YAh0vg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1nruncs4l0ufk7yuc4des8p99c0alfndl0lhsws8tycl5pplfp56s30af5f
|
recipient: age1nruncs4l0ufk7yuc4des8p99c0alfndl0lhsws8tycl5pplfp56s30af5f
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvY0VpeW1nazVvRWJFaGNU
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTWStVRDhlblZDMDJ3ejIr
|
||||||
RGVzVjRmWWV4dnJzRHdsaW9ENVZYck5lWGtVClNtd3ppelowRjZpRFFSMC9EK09n
|
MkwyeHRwcnpWQVBmOXY0a3NCU1JnTjkvVGtVClVvVUpLTVhUNGNNLzY4U0o2SDRD
|
||||||
b2hqNnkzejdrTnhYNGNKblpteDNLRWcKLS0tIE02bWVjazRWNEVKbURITGlQODlR
|
bGZteUU1Uzlzc0NjTW56OVNCcnVpUUEKLS0tIC8wN1V2Nkc0cVU3OHNYa1NBVlhq
|
||||||
cTJJVnBVdGIrdzBoSXExelNrVk1XcEUKc77o2EX7PCm/HjUo5GsUiQdm488WB2mg
|
YTV5QUpsQWppNHRpT1dMb2JDQnpoR3MKCoBngnNg5qpktg5j4CcuTUKiooOVI8IN
|
||||||
wHd/qDbQhF1W75RrVTuIKgtEtrRjZqpmr8toe+aHJizPofcrToUfzw==
|
04plE+1lra8q5ZK8q4r1/K8hBM23poyfq+XushO1GZ17dPtWs9wITg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1k7d2du5mejsmv5rzavm4xwgpthqvcfsehduquv28nzs53zppa3kqngfxq2
|
recipient: age1k7d2du5mejsmv5rzavm4xwgpthqvcfsehduquv28nzs53zppa3kqngfxq2
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSMmZnd1pXRWh5NDVSN2xq
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUU2ZSazhZNWFZVXk0UXdx
|
||||||
MVhTN3N1OFpkcUh0a0tjYzJnWUlINGNVd3k0CmZkTnBac0l1dllxazdLY2l0Rzli
|
Mjh3R1NLMThlaHUvWVFMZEQ4Z0NYOW83dWgwCkVnd2dhdzQrZkxaL1NRbDJodERQ
|
||||||
bE9sNTBVSkJNaWF1T3c0WktoOHl0NU0KLS0tIHEyeWZTUjdQeUN6U2t0d3JwNTBX
|
SkM5S0kxcVBtY3laNWp5OXpXZHhqZ0UKLS0tIHVOT2VUZU5DZjJjVW1kcU5qS3FD
|
||||||
ZE1Za0tXb0gwc1FSakVYdU9OTHkyd28KkwmlzSYP8XofB0VGag+S18+S2TyQjLrM
|
YXNhRkpaREMzRDB5aWgrdVNvUk1qcWsKQnPqE+5xSBGeDMmLfKB0tEBt2eylBOxT
|
||||||
qaXtbBtLzJGNDhe9FhAKTPFcjTLWbohlG69vxcImyCyCns+QQ+gvug==
|
oi26mC+EoO5T2pCIpbD+DODAAlWKLattqCYHneUO9wRk9DnhuEXRBg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age16kqfmvz4e23hmdlqresnyw69ej604s320mmd49h4hm3fhqchtgyqrws0k2
|
recipient: age16kqfmvz4e23hmdlqresnyw69ej604s320mmd49h4hm3fhqchtgyqrws0k2
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2Q2RMWmZVOEwrOC9VcUxp
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZUlFlenlOVDk1NmxQVGUx
|
||||||
VVE4R2NqYlZWZlhKSTBHRks4bDNoaUliMWlJCm5FbWdZS05GY0VLc0sxY2x1U21V
|
UHhVK3ZtMHdzaXdtWmE1TXhOekpsTmUwSlJ3CmdncjMxOUw0alBQZkhZZjA0YzJZ
|
||||||
eExwK29GVVBqYlRPZ0l5RWVXRFhRNlEKLS0tIDl6dVZJQndwVStFVEJnRHRyMW1W
|
bFNVOHUySXRuYW8wVjdHMDN4ZThuQmsKLS0tIGdvUUdHcXlpWlZkL0xBcVpMN1E1
|
||||||
NnFqc1F0SGJqT0xmREpaN21EdnlJK3MKRPE5rfFpVnH5wAOkuB5pNMlMd3omcpku
|
NGZCQURYQVY5ZzdDODNNY0o3SG04eDAKiYVuKsJLaSC7/bQRB51tX282/yqpAjWT
|
||||||
do2hFZwyI7t80jxF4+g3J7EolOx8AGjpc9Ba7Gj6IMDjye728q5N+g==
|
XNnqPQBQ7H3xuK0tDVdCAUMruOSlqAe9YRZwmiou1irpg9vZYwgTPA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1arhf2q45zw6wf2uevju4savp575x3m2tfvved5zzq3ay92ynua9s3cm92c
|
recipient: age1arhf2q45zw6wf2uevju4savp575x3m2tfvved5zzq3ay92ynua9s3cm92c
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhSG94VE96TVlTNkEyclFE
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnUUlPS3ZPS1lSVmxwV0lq
|
||||||
SmdCSkJpeWlVWDFIMDhwUEkxL1RUTDJ5UG5FCjhKRDB6VGtwTVozdUVzbUxGL3BW
|
WWE2QXltb1BzU3p3cFRuMzVieGJta05nWno4Cnk1RG1CUXZjM3dNQzlJK1ZGaHpZ
|
||||||
SnR3cmpSN2RxNnl4QmNvT2lkYmtoVFkKLS0tIHd2V2h2Wk5xOXlISzhjVzBsVkhz
|
b3drMTB2L3hidkpoTXdMQ05vOUtTb28KLS0tIHhyUllpQnlzL093MCs4YWx4VlVn
|
||||||
VVpRenVnSVpHUWJqV0JHNXNWWXJOdW8Kv7PJSTDbwFOAcl7pynALaJiTXU/87bSF
|
S3Q3QnpVNEx6ek54RGx5bmlqUk5hbVEKHVUvfk/xKtlOk2o73LodweIivSB4Oii/
|
||||||
F3HQllYOwOoibGzBCe18H2N+VxyNxoQL9OWe0TvOIR6bgHFIIF0/Dg==
|
6dwnE8wa+Qx/OU0vDJVelKOUE6JiLwckX/0u2YIUOybkx49n+tYs/Q==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age19mn8zrxl8zpps9yvrh4euquvygpp4fp8queg7xc6qhtnl4ng8c9qx02qwn
|
recipient: age19mn8zrxl8zpps9yvrh4euquvygpp4fp8queg7xc6qhtnl4ng8c9qx02qwn
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsMEZPUExRVHFFb3pSVHNO
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3MHlrTTdxYit0MTkxa293
|
||||||
cGJFN1ZzTDFVNEdneVpMZ253ekFJNjVtYkNnCnRGQjU2Q3dsRGRFV25LQ3pCbTJE
|
eFFIcmRLMUpkZjd3eWdsOXYySEljNDB2NUdNCnAyanVkS0dmenZSa2dpSWZTSkZw
|
||||||
OTROaFBiT01xb200S1pUK0NYaTQ3R2sKLS0tIEdiQlZTbi9Vcm0zc2t6bHplZktF
|
Tm5LMkRoWEk3L2ExVktnZDZMM09hZ0EKLS0tIGVGcHFuMjh4OTUxdHNJQSsrR1pq
|
||||||
ekRySENXcjBuR2psdHZSSUJrR0xUdjgKvBsmnC+cbq5TUDFjXCyImIoPKvh8wsjE
|
WU1jVG5UN2hMbW9iQ3JmbHlwc2VveFUK4JC32glesv/9nMWBR0lFeX7MPgIt9sIo
|
||||||
7Shk7Act8Jayrhx0lXBDRmfpHRrB4L16rDSmqO0DTE48VhT3TiFyug==
|
9HsAkOMTxPTI0NZW8q79+G5hq8qG+QvQhwO6wQtwz/KEHPXntKDX+Q==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1jlltcv5jcnm40z5k0q6hv053k2rqpqvemtuecdwn527uw8uqz4es3x7m68
|
recipient: age1jlltcv5jcnm40z5k0q6hv053k2rqpqvemtuecdwn527uw8uqz4es3x7m68
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxNDlya3RmYzNhU2p6RkVw
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRbEZGNmtlUjJVZ2dlVGZC
|
||||||
cHJVWWY0Yi93cW1uZi9FWkVONmtpRWh5SzFzCmtWYTRIY3BkTWU5R2Jsa0ZJK3kz
|
MGVEQXV1SFRpMjFqWks1Y2F6c29TY25oL1MwCjVFRk90Q2dJRXhUMmIrRVdHVFRD
|
||||||
SnZvZ3YwaGtoMVZ3V2laTlBBK3UyTmMKLS0tIFdpZWtqeGlacjlLbmFySHlSUUlj
|
TWgvQS91VWdFWERJWEYrL3VnRXFraUkKLS0tIExUU01VRWlENlpBSGt3V1Izc1dS
|
||||||
RmRqQWVHK0FUT3VDbFhLbXQ5WDhLeEEKcDkgV34lUFJRIHRoLB8F2IOvGAM93sM+
|
RFBhSTVKTGV2KzFidDQ3b3lmUHZ3d0UKHWvWO/Om6ahwBkONreGeENqdFG9UI6JB
|
||||||
AkmaM4+WRcGeYWQKMG2x6cYCUKFaT1lDXuWZ9kI8Fd7b9gTSnQMs6w==
|
fRA3gRHy98EorEiia1him6lGY+ewYkTtVNo6iEt0abncW5x5xMQywQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ug787sgt6st6k82fgkrug2lzltw4qsukrrqqs3w27ewwqj8rg4hsxcmylz
|
recipient: age1ug787sgt6st6k82fgkrug2lzltw4qsukrrqqs3w27ewwqj8rg4hsxcmylz
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjeFM4ajhHeVd4bzlqZmJ1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2dFFpeDU2Y2dtQU12cytZ
|
||||||
MUxYMUpZVjdjR0NGTzVteVB6WFBQeFI0ZkZrCk55TEt0Zjdwbk51RnhYclNzam1H
|
WUJjKzZ5S2RiZmtvYVJScStYVHI5d2FvcmpzCm9COGZmRllyaElqb2NsczhKRjhO
|
||||||
cExKZXQxWFVLa1pDNFpkcGZzcnl6a0kKLS0tIFJ4ZEdJc3JVaEc5RU1aZk1uYm1l
|
ZGhwZCtoaFcrWmNZbGVHMUNLMFQ3Ym8KLS0tIHJQR1JOblZlY0cydnhNUnBCT1Fh
|
||||||
d3RHS3hHSkRKRXFnN21FQmh0TlNtNmcKdc2G/1dhTJen6iT9kUWZM5OzCmDVprgx
|
aHJiZVpMU3Y3NXd0ajc0MGMxSGp4MmcKky1oaS8yFydszcvZE2pAL90ZUKoN9MnJ
|
||||||
WN1Bl3JzYhLsNKn794887bVAICVqbXqkdpEZztNIS5n/Rw6geKsNvQ==
|
1TdqatDSMtmo3PWkdFeHbWxPjTizQGIcKEcsv6GmO/5KCAJDCHDNMg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1529taqdwr6t0w7cvzmty0d5y5593wffl0krt48j6uc4u39k56g2qf6ywtp
|
recipient: age1529taqdwr6t0w7cvzmty0d5y5593wffl0krt48j6uc4u39k56g2qf6ywtp
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYa0gxNnBwNjNNTTgxKzgv
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrd3QwWE4yaVlIakdiYkNX
|
||||||
YkQwTjZxb25tQ053Ny9tSW4wNGVYYnlIaFZNCkVvOFNPOFkwQzhYZGxjb0FOZzJ5
|
bFVmR01yNjdWa0d6V29tcWh4M0hWRmQwR3pBCkFRV2gzSVdPQmQ5dXZYc0F1dW1r
|
||||||
ZjNXam1ZTWZrS1M4cGhhcHZaT1NjblkKLS0tIEdvL1dDTHpWcWF0S3ZqNkxrQW52
|
OFZNTkRwSzRFR0tlUXE2UzFxeU1Ld0kKLS0tIEZ6RlZkMFFiZytPZ05DSEI1Ni9X
|
||||||
VlYwa29sZVloOS9qajJWQWFzY2FKRmsKy074SLdttogXsWycaFX8xso4ek7Cbjph
|
WEtpUlMycVhkMERsWDRGQ2lrNHdQcDAKRqVAOzQY4ZM1uH7LyZSqX1T6+1PwNhBY
|
||||||
MMEhZd/svmnSiYM81nmeaze7qXEUcsZXuSmZCYATTBEGtx/Srll8aA==
|
5M0RxSuI43E8YKC9axcppvKFJMQI1GAGvgfQxg2J0CEpnmPSVsO5DA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1zhfyuzlq40reuqlr34gf77852nhs3t6mqfzrqmas8z6sxk7tcfhsungrm0
|
recipient: age1zhfyuzlq40reuqlr34gf77852nhs3t6mqfzrqmas8z6sxk7tcfhsungrm0
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvb0xNMloyRW5tTUUvZGph
|
||||||
|
YWxIc0ZhSWI5RHNmaEIvdjZJNHFDM2xBMkNzCldlM2RUeFZCNE9tMVp0MFlWM2sr
|
||||||
|
Sm44MjkrWlNkY0U0eUlyNlE2ODl5SFkKLS0tIEdISElQa2VBZlduajhsV0ZaU3dZ
|
||||||
|
ci9COWE4bldUMS9lSVB1eEk4TG1sVjQKYu9zby36DP+41dTV8sRdtDil3Az40pf0
|
||||||
|
vIhqhCTxQymnJHEwEukiEzM/w3Z85R/W/MbOKyzEZmxzG0a5O5svhg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1k73g8x47hs93wcv7qh92n3htz8pl295g49hyvlrf3570mts0hgys5g04d6
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCZlFtOHI0NnlMeWdjZEsr
|
||||||
|
UnQ0UFJXY1pHTm4yLyszY0dBaldtUkIxdzJvCkNQbk9mN0wzK3BtN2pEa3JSWWFk
|
||||||
|
RXNoemFFbDZ1SlUxOEZMUm9mY1RaZW8KLS0tIEk3bCtyaUVGM3MwNUwxS3QyTjA4
|
||||||
|
YVpTTFlOdzljbmI2cnZUNG1nd1NrVFkKxARm3bpBL685Wky4Y06eA18VvUZRkAl3
|
||||||
|
BBYPJMn3lfDgiQPvfXINfhJI6O+bWyjt0WnfodCGFM6EJLARXiTaqA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1fefy6dk8zn5c3edwmrs9vwx79quftnt784m628t9e34q3ft3cehqz8u72r
|
||||||
lastmodified: "2026-07-23T21:15:41Z"
|
lastmodified: "2026-07-23T21:15:41Z"
|
||||||
mac: ENC[AES256_GCM,data:qFhnPra6IE3wyKQ4WKweON0S0YtD5I0adGZVfA0m6BVilN6bX5oC/1j5NK2oHrsz920hSl0SOF8LrpqOrUyGjSRkPsN4kq8qr9bJcrX4URiktP0oRden5LLt6hf+ZRP7WmRXFqixPkPHJnZIoAvkNnTFce7cDq5NEAHkKUEKG7k=,iv:nyblUDGeu3TUfFivYylOn3C/HITj99qiPI2+mh8AGh4=,tag:FrtRzSCylC4wlIoqZdfx7w==,type:str]
|
mac: ENC[AES256_GCM,data:qFhnPra6IE3wyKQ4WKweON0S0YtD5I0adGZVfA0m6BVilN6bX5oC/1j5NK2oHrsz920hSl0SOF8LrpqOrUyGjSRkPsN4kq8qr9bJcrX4URiktP0oRden5LLt6hf+ZRP7WmRXFqixPkPHJnZIoAvkNnTFce7cDq5NEAHkKUEKG7k=,iv:nyblUDGeu3TUfFivYylOn3C/HITj99qiPI2+mh8AGh4=,tag:FrtRzSCylC4wlIoqZdfx7w==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
|
|||||||
@@ -1 +1,22 @@
|
|||||||
STUB: run cluster-init.sh to generate, then: sops -e --input-type binary /etc/corosync/authkey > secrets/ha-corosync-authkey
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:slsTAZVNC7yDJDwqcQV7yTXtW5EJuIGFZ4BSI9sWwDYiPVjGR0hXAUdgp5rQ0h6FlWHl0Ir/Ayoy2ONz0BwWsOtjb+u5Z9piEU7MY2TyWUkED804iiAy30Df5VzWc7cqhDjBPFc3iKwvi35u1QASVl/HW5LX99DJctx1CdA159In2YFUjeEt3nFUqR5ietwPcfWwpjnnKbkK1Cx7hEh/TdPjDOvzEbxKjGZzb+mhL1jAI9bg93VYcI2E02hkIAG2/LbaoVTaFbSmjuNbzNSY9yphKGZZGVCrPDTh/kV345OVVOIkZrGuD3P1WAeDE/NFLCPQ6PfUHFiGNS2DnqDQKw==,iv:Uy22v9N8OodIH71s3Rk5ltGBMpTOUC64yXttokH6rJs=,tag:1Y6vybFk8KeqjnEJRNKmgA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcEl6cC91TTU1QUliWGwz\nUTEzaWhFZ2FMZmJpUmVOZml4Q2xHektPM0ZVCmdVWnVheS9pSjdDcmhFYnF3Y01o\nbENFUkh0a3RzcThRYnhYUDlmVE1uek0KLS0tIEdFajRtTTNvNytpRzNuZkRzOUov\nN200WFZwV3k4U0laUGoramFzTnRlSEkKbDEy25Zr666CfIYPBCKazwmXAhv3tyr7\nqBMO+hvDEilQUhVaIv//C1NKLrlQNsdEj/OKjOrcWek8linV/Uj/Ag==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSdDE1dUdZUENzbE1INitR\neDU0czFKTy82aUxjL0dURWJtZU9LTkFpSFRnCjhSd29FYWpVaUhUd3JkMGFJMGI3\nYXVMNFZDQjVVc0xpWjhOaXgwRzhMQWMKLS0tIFRGaHl4S3hIY04zZW4vU1VsWVgx\ndkE1aWpuYXFLSzBLeXJFL0gzRHFsY2MKILDSAkt4FV6KyExx420OtJQHYMrQKp5m\nOse14ihVqTUYRFoA1OuL8uIGa7x4bUKEQC0FXPyGY/GIl2hsNixdeA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1k73g8x47hs93wcv7qh92n3htz8pl295g49hyvlrf3570mts0hgys5g04d6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2OGNEYW1oR1NFMWl6UVhQ\naEhueEhmT2VETGwvWGxHOEx0M1l3akR4dWlNClA0M2hQQ2kydlk2cExMQnZsWlVR\nZlhvOE0wOWZVVjVkcS9FbEJOaDIxU1EKLS0tIG5Jb21SUk9hUWNjQ3M4Y25ES0VI\nWDJ5bE4zdnRFeXh2Zm4rNUozZEZNM3MKGLeaMwuFNER/jY13LcLfSlkPJNoIfu+F\nnZKlv1e/mYNNCbZ+pW9ZDTPocHzp2KRm6ji3OO1GZC8re5Q7wChkPQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1fefy6dk8zn5c3edwmrs9vwx79quftnt784m628t9e34q3ft3cehqz8u72r"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T13:30:12Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:ZjVCaXOy3Sn+h7i7kCJpcTccE7v1NLLejnZsU8EAoRH9fvUDL681Z6vzv+WDEVDJ04raLAQLnlPJ7gdvq/HVGSdrv8rvl4KNAk34u+njVg/qpeGa1D9NvwxbZlOZTBgy9Xcwy9bPF8jqfchNlUt5naUkk8TlHbgsA1ageG28BkU=,iv:hT1SR7vjGtPX/ATRS/WJvmS0b2z5T35i+OJ6bvVz6AM=,tag:vxk7RRBXU14VziBmWxBjHg==,type:str]",
|
||||||
|
"version": "3.13.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:VpxnzrdX5FY2gfDcZpqg1f2wqL6mAawcrdE5A9A3fQOKr4BWXmCT/hjQEtt8qG+yVOTApdG+vF8SKDj1AKZ25KsZ253WS7W5C4j8oiT/xHo0CtvVani4fZqN60lB/9381h7aUZ+lcBMPaQcNn35zoQqYthuCGrC9PH1qQgD2VVHr0v8J6kceXKp0KTJldtDsQxky6ROC9Zyc44wwkrFPgPss4/yGaRWWqiojDvK5fK8238hmR8HyfD6Tf5KhgbqCsSGS2g6yt1muUhmegaZwtpi/KpoqBmf1XFaObuHGRPKvblq4Fa0x/sSjpPogBZyNuvRKIwRhla0FkKOH1bX4gC9bzOlgiUUfVcRJT6Nr9jocYuhIv9wvIT4TujGbgHyorYaDSQLFPJoJEUa3fgpGq+AC6p1DJJE3b/wqUSngNbdOtAPE/imaEuprOfnbS7cRx+Ti/Yu+ZYZuAlDtLkSVdXi1hQ4fLQ==,iv:rq/BzePXa/w/Gqewz8JfM/NdU+x4ShiH1OuQT7wRyCQ=,tag:IcO3ddoj4M57CkndHEmNkQ==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRdWFEUlRJMlZId2Yvc29F\nNnkwREkwQkllY25vZnRjV3Nob3BvNE1Zd21NCm56UVg5YUFGbEc2ak5MakxDbCs2\nTFVsRFhjVlRwMXdUbGpMKzQ1N2hBUUEKLS0tIEhmdm9hZEJqUEhRN0dsSk80bDY2\nY1JpajNOVFpEejhibjdKZlNHVjE1bGcKycyDfEBZ1WYc4EfAK2y5x/nKRqq7mnb4\nDlpR1Som4bSt7+B+OCZa48mC0Zv05HbP8PnoZdCe83swGNljvRONcQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0SGFVbXYycTE3amR3ZVhi\nVFJHVVhWNWVQQlJKZUJvZ2p4ditqdnYrWnd3ClZaaUVLUXFKaWlDdDQ4QW5SQUZY\nbG05c0RGNnZUenI1QTBlemVjT0szRncKLS0tIHBWTmNpd2FjWHc3MmR3cWt4SCtH\nWS8xZnVON1lGZVc4YzZFRmRBamdSVlUKdlQTlwiExTUVsiY05MXFG2/IQt1bZwKU\nlFDnuy6YCQzawBLQZuSUAp7WSGedupexlZKQKNUzPf+dis26XEYZoA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1k73g8x47hs93wcv7qh92n3htz8pl295g49hyvlrf3570mts0hgys5g04d6"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T11:53:06Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:PMsPSffQkxRmqT6HwFezIoT4WKJ9WDlAcflWynsFLtzbqVTREN0YGOJgMwLd0yBR2sbGBWogG5xAuKjO16aiWUVpCFPRvP97um351679ZoXIlrOL+GlFn4NKwoLH9/eCp8BteB9mv5huhEEN631lqCr+jtYO6yS0ULk6QS4+7/c=,iv:oYWV4J1JUuGryvqntVXkj/HOzRDRr9E2C1RKNvr2bT4=,tag:e4p+I40y1LrgdlK9tLRicA==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.13.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,25 @@
|
|||||||
# STUB — not yet encrypted with sops.
|
beszel-token: ENC[AES256_GCM,data:tT2a1I6AhVgXWfc=,iv:1iXoOMAakHXpI2lYiipxcVSzXODUdpqYmfrpNKZwljg=,tag:Vuxxz7pyIsZYbkg2x6GvgQ==,type:str]
|
||||||
# Bootstrap:
|
sops:
|
||||||
# bash scripts/secrets/sync-host-keys.sh proxmox-ha-server-1
|
age:
|
||||||
# sops updatekeys secrets/common.yaml (allows ha-server-1 to decrypt shared secrets)
|
- enc: |
|
||||||
# sops secrets/ha-server-1.yaml (create with: beszel-token)
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
beszel-token: REPLACE
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLT0oxSDR6WHJGT0VTY211
|
||||||
|
d3hYcU42SzRIQzhvdUxWSnZhRitoNGFlclVBClFEZ0xXVnU2OFV3TmRXZHJyOVp4
|
||||||
|
aDZjQ0g0RlV5bnlzU0Y4ZnYxWm4zK1UKLS0tIDJYZ1F6OG8vdUxnS2NuaG9SL1gv
|
||||||
|
bURjNjRWQXIvQlNyVVZieGpCblV6bmsKQrCQQz7b4+w2WB3E56lEZoZa8/Yx8mn5
|
||||||
|
vWtJa6nvqlE4miJdRpbjQWUeFHghr0A6FsYDg5yf+Mcpf2otTw8psQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5cVFzS3hFVkZpcGRWcUc3
|
||||||
|
YnRqVWUrSU1mUGRaaWhZTFQyRGRLMm9BSWdFCnhzYVhtWExGMFlRdU40K05TV0li
|
||||||
|
L09BK1h6SFlWSGRtQ2F1QW9iM3lDVWMKLS0tIGNQSE9aN0xHNytlOHBzM3MvMVpB
|
||||||
|
VEdyUFc0WWgzVEswRG1XSyt4dThTWGMKRkR+FifFDDKzrMrlJgZj/lbJDjfHsvjI
|
||||||
|
BGfJj1x0whWiozg/SZ6BsYYYlVkcCBwVTmPYx2iQHk4jLqHcMF36dw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1k73g8x47hs93wcv7qh92n3htz8pl295g49hyvlrf3570mts0hgys5g04d6
|
||||||
|
lastmodified: "2026-07-28T06:44:32Z"
|
||||||
|
mac: ENC[AES256_GCM,data:rtDzbONqaNpbEKo1btGiHArAJVCVsStHhgvM8MTgkRMUNGU8p9W4tiuQLmobDpFh81kj/mDnkx4cCpScuiMhQojz9H74l7P9o9b1Wxy0fSiCtGoQhaq6JoIVJoKtEZoHfMStdIio5EBW9DBB5abvjPLTaC0afgHo5pNn3A9qr6g=,iv:SqtJhel/5qW0V7IV6V8dCRWqPnsK6QCzbC15/BKeq1k=,tag:vF7RJJccm0q7BSyNGYnplQ==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.3
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:Ea5AGwloFMyRgmQhJaDNTW19mpDvx0+dSc3ibENEXEniEIBV4Wv135mAwE6jQPbN8wF6f5ki3B5mDXHOMuhDQEqUXg6jcs4uEm2nwz7wAKYXEX4T8p0gUj7FPISiSQrw8O3jwzt4qF7AVcPsNhMtZLgrx9ssMtVmGct5do+E61Y7dFe0XbpJLynfzeiMKYvaBngTbHwQjv1mn9AMpX98apsp3tyzg8avLDR+WXRcMusds2mdov0jCoKuWPLG/srhMwjKOp1Z1Tu+aZCr9lHTT76mytpGIWGSbrP5l3UTp3oPLmy87Kwi/FJStZYdYikMnvmSo5qovePiYsLKVD74dS+d3oFGARyW4TILd9OQdXEjzdDRak5HcPv2/HhR638kvukVMdEdH0hpeJcqcV0h+BoLyIHe1e037iF1ZkCjuXE+A8LtlzRR0wqP202uzBIyg21ca4X5bRiHkkAOX4P3riaEcMgKfw==,iv:IXq4sHHsjnK6maVd3RRVZzhnxx1hOnxfsvYX8IKLn+4=,tag:jRZinAVLH8nGVf3gE4aSKA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByOTkrVVd0TXlBRzdkTktO\nalU3WlVOb2ltc1k5RG5sRE9EN2M2WUlnQVRnClFGZFYvSHdpVlhtNXExSXZMZnU3\ncnl6aHRzU3k2V3R3dU9RVDA3VWp3VkkKLS0tIHcwREM1dDREK0h0UFp0bU5JTW9Q\ncWdyanNMVjY0SnZUWUl3Y2R2SXNyc1UKI1FPIE66to19oQK2TwM5B4snGOVMLFfx\nl6JMEilDVSgy2RR/tSiovmo4NwOsv0hmmF1t34547rbZb+dB5KrMEQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnbWp2UWphSnFibnA4N0ZV\nRHlNUnBNYUNzMzNZakFVc3JXT1d0R3Yyakg0CkowMFlOcEhPcHE5dXd4ZUd3cDAx\nbjQ2T1NqejNRMnFvbTdNRGFGdW1pM2sKLS0tIDhGb1JBQ0RFQ3NxbmpkdXg2WVF6\nS25uZW1hMVo4Q1JGdGNQWWRyWE84b0EK0FmFZB6sN7uZynhzYU932x461zSZIUWU\n8oyDEKNoUuPqpWK8RktgjWlKHtB0oLXC+SLAOJEhtnhWF+GSqloGfw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1fefy6dk8zn5c3edwmrs9vwx79quftnt784m628t9e34q3ft3cehqz8u72r"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T11:53:32Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:EhWEEIamG62xHI6SqO4tzdK6gcEXxUU/UGcF25m+X11kUVXzHd6yLcfiM2DvqcwGLmwmOOf3SpFLABlQUzTka4QRzTMFIAJi7x1rzQV4J0YIGITHk9rgvy0V50TI0loONBb2du+vDt8IlTBNvF9WMbNCki+fHPAjIemyoRBdubo=,iv:wfsx0H4fD3L7WeGzz57SzZtajBr/xTRDljSpz22DD7Y=,tag:naDyGH0WTKkqgd954Ya8jA==,type:str]",
|
||||||
|
"unencrypted_suffix": "_unencrypted",
|
||||||
|
"version": "3.13.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,25 @@
|
|||||||
# STUB — not yet encrypted with sops.
|
beszel-token: ENC[AES256_GCM,data:0wooMaPiytDTGug=,iv:/WATBCEakrIvujt4oAeprw18W1n/WoK2zjpQdlVJl4Y=,tag:lhJa9hJgFqNFSB1Ogj5CtQ==,type:str]
|
||||||
# Bootstrap:
|
sops:
|
||||||
# bash scripts/secrets/sync-host-keys.sh proxmox-ha-server-2
|
age:
|
||||||
# sops updatekeys secrets/common.yaml (allows ha-server-2 to decrypt shared secrets)
|
- enc: |
|
||||||
# sops secrets/ha-server-2.yaml (create with: beszel-token)
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
beszel-token: REPLACE
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlMWJCTUNSVVA0Rk84VWl5
|
||||||
|
emJ5ck0rMTFRMmljUmVOTlpUT1dlM2VFekZnCmRZbWcvL214YjUzakNDYkovQm5q
|
||||||
|
cVJQZnJvVGpIMkpqUnZEenkxZHNDSkkKLS0tIE5tUFl4a0FtZFNNTXFudlFNbjcv
|
||||||
|
a1dEbnl1NTR2T1lWN2wweVZ2ZnErZEEKbn9bLqIsMwSGKBjR97kMfFDuh1l7Hu5o
|
||||||
|
zdLjnAHzCEAsTW6V6wPB1rvi++FO04lqq1RfEiWXJkB2KWSRf+jdTw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDYUhMZWZCVS9MbnlscVBQ
|
||||||
|
aENENGFLTjJ3MVg5SGJCeXZsNVZ2ZFM4aVZZClYyRUhYTFZENEkvQjJqNGp6b3Q2
|
||||||
|
SWthQ01EMDdQYkVBQ0ZRdmRIMTFnUE0KLS0tIEJHV2YzRmw1MDVsVWhHRW9LMnVH
|
||||||
|
cU9qdUQzcjlNWFdQZmVydHdpamdQQ2MKndSN6wQbwyGqnyS819tHJRqDLRryXonC
|
||||||
|
CPuBYrTwxosRPwgh+nyJto4lj1uZqCmFVMXGjn87em8ykY5arp9o8Q==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1fefy6dk8zn5c3edwmrs9vwx79quftnt784m628t9e34q3ft3cehqz8u72r
|
||||||
|
lastmodified: "2026-07-28T06:45:50Z"
|
||||||
|
mac: ENC[AES256_GCM,data:f50JYTzMJiEjB9+fNIs734CXrkN1IRgJKcoJ0GHoI5p75cfLfPxuzUifty9kQ/4DbM7LbWa5w+9occoV/yNOa86BKpPtU0LmlzXf904/SgOCUKhyHqhyO5mp8w71GoyLxx3ld8CbqfBu8t5l4gwbQamftsmQRxnUdU/Oa/CzdZQ=,iv:EewXBjeihw89h8ypH5u/G4YfoKZzkHqRWgSSIXU3/a0=,tag:lEWMvAkebMexDGGpE+wqkg==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.3
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN7IABG7x9ejHSENmHsL/BF9MQadGuBnHZa1eUEFMYnH ha-server-1
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:EhYMNPzlMDyby6g3hfvBKisKJnU+YAxZjf326tPdflQ2PcmPTzZ0GjHqJOPrOWyLU6IyvnsLMBV0JF5/UUi2c/H3p2wvoBgsxDMaaMW0YUK2IZFo+PZpWoYJSMrx40sH90W0qL0VBw17JLwPU6CjeUZrn5+sCN9EhQvncD8NoJ6QwZpWKhWLn9/7mxd/PF1uxjEQXzHYFeuUuVpQdKSUfV/BSJIT3pMM/VdH2203jTMtwUbK1/7UtGKtDWdfFbkEvJ6qsV7hnN3EYU16WtdyAiHEZvvZ3gqg6YK2DemojWG7z7VQkQGbGm40yA6/mYGW2dVK5ULzjxALl05G6lo1vzxODbMHhzsfJZVw6ocGx3r8xwaasiX8S3loq8WNUWJ9QgjbxuEX9uGZja6A3nPhcwVGGZExbUoU+syXUk2m+WfIQ0epplaPDyAxrYO5ZPWLdMYoPRxbDx94rx5fMxA3jst++IEL2kQMTieGQYJy3R8veQIe3Jb074zY+Y79EmRxfo8ruR6ZMqORtbZzJOBcd0OObjUVLUqB4rnC,iv:XkCfOKtmZNz+UTZdElLm8L0PpymkDzCM24VkAa9Y/Qo=,tag:hmr9ik0V7mxEKR0DcDB3/w==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqQ3J3VDVwUVBvUjVXSWNz\nU3YrRXZlT2NLRThCNzd3YWNHVTlHeS9xMHpzCi9HVW5kMXZrdjE4bXdML3NhRnI1\nL0NQQkNQUmg2M0NBYzh2cG1vdHA0clkKLS0tIDJZYUwzK0RGNzJWckJJekI0SFg1\nUXE3N05xWThtdjdCZkdJTFJ2YUx6cm8KVANudVL54WBNc9DK9s9h4WQRLMewUqgN\ntu4LdMOmi5oV3LX06lbxhBq79dmsV5uos/qszhJkVGhEZwD1RGBoZA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T06:42:29Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:Xn3/CWhQJHWtd3QHoqpdwLuJdZTp+oxx43k38j4jspQSBPdVDSG6Ifi7Sb2r1g9YTxpjUto8mv7FH+BqI0wSYUkLfjaxI51xs3dq6e6kl5NWOsvWwIOGmBuhfIwusHGgsfBguxk0J6Ev8Irw674CRFc37jozXTVdpOOzFhPTvFs=,iv:/7OFE9NYkw2kYEetzeZRXah0KXuD5OXmfjQMIeqI75Q=,tag:VamuInZqYGYRwlyU8TtL9g==,type:str]",
|
||||||
|
"version": "3.13.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINWuFwPKW2jwQEj6c//xb95DbtandPkJ7M0ceyTNd35G ha-server-2
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:GEebcQrPPrQxLC6mWXLLtytGkIy6Q2FbcQ/Z+ImGa3gahI/nigdNyR/avIz1DAUsOibpoMVlrk0GQUBMuxiEXulIwUEzeTh7sMI3XTZiugujOFHPyJoFeynlgeRNBbl2EVVjGmEmOsR/cZJGyKaet9aNTHTPDJUhodY5d+D/sE3SgpHoch1DI02/DRnqlnlOYiBoujk++BW71EDHLVyz3n9NsurTDYDK4L0Ch5cMgdlvwIuxGuPKtkhU9z6PtGxPbNlYlNP8U+3jG4XWEEY5hZHBGTVHn+bd5FViGY+sMiigO6yaOmddbUqzJoblKeI6F/rfrL3kXETLgVP7uVm3gw9KPEl5JaTdVqkG4hQ9G9/LvRlyEh619mCp9IRXxabm5SQ4NWFzah1ZwqIKp6GoAtqeWRgZNVqijoF+bnb0YmNFmjwpSrSMZvKsrZb5geyGmqah+NJii0xqVebnufB5p8lW9eytBSNgC/XLjj0olwHMw7CWGmnY6Q7lxF1tNYAD/K3dkbxQYFM3kBnmAdweC8WYc+hDjtk0HJul,iv:H4BonLmf3VoW2S9+IUE+SzPb0qiWHuNuGpVIUgUYScs=,tag:5+bq7vC81DMmOk4jn3u8lg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrQitsaDlqTmlYM2RvR1Ew\nb3NzZTJzR3F2bjM5NHhsa3dCUzZ4TW1pa3pBCkxIeEJtbWdaUFMwMkFyRHpCZ1Bl\nbmM5dGV3b3BDcDEwVlJ4UWV5RFNvRkUKLS0tIFRDd0U3dHBHb0Y3WDhNV3hkd2Ew\nclRDVEZHYjZUSzNVZkFjWGt3SGVQTlUKOULuXiYD9k2uVUmhuC15Kgezrd69rc9P\n7SocPa8kBliffP9IuxxW2S/hPbK8rqEx/sh/Km85ZIi4pG0AUQJ3fA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T06:45:10Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:ZnsqhgELqEdNzlTKJx2+xzcZRMNGTme/0a+PVvpbUv5IApimtif0zkTEC1LJsyLPut3MmO3QGprVlb8ihoRdm/IVvLaQonc36f6vN5cxrpQadru/RWEjLbnge1E8VkN/PVvHqRfLyIOPSq6wtGjAPaC/iHW1scBi50pWr0XO628=,iv:FlqAL1It6JuvoBhZotHWdSz17nckBf2rXnwovl6ZJBA=,tag:tNwcaL1sJakXtgejsB/Sbg==,type:str]",
|
||||||
|
"version": "3.13.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user