Archived
Compare commits
42
Commits
+95
-23
@@ -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 age1nxlnrevqs2msdatze562vz6ym4pgydt2zndye83lwqauy6flggtqrevyw5
|
||||||
|
- &proxmox-ha-server-2 age1scfc8p53q5aq2a87tcmsazmj8sfeft0s8kxg0et4nm3ucxyp3c3s6te82y
|
||||||
|
|
||||||
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:
|
||||||
@@ -63,16 +67,6 @@ creation_rules:
|
|||||||
- *lxc-nix-cache
|
- *lxc-nix-cache
|
||||||
- *proxmox-nix-cache
|
- *proxmox-nix-cache
|
||||||
|
|
||||||
# Host keytab for nix-cache FreeIPA enrollment (binary sops file).
|
|
||||||
# Generate with: sops -e --input-type binary /tmp/nix-cache.keytab > secrets/nix-cache.keytab
|
|
||||||
- path_regex: secrets/nix-cache\.keytab$
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *admin
|
|
||||||
- *linode-nix-cache
|
|
||||||
- *lxc-nix-cache
|
|
||||||
- *proxmox-nix-cache
|
|
||||||
|
|
||||||
- path_regex: secrets/server\.yaml$
|
- path_regex: secrets/server\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
@@ -103,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).
|
||||||
@@ -118,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
|
||||||
|
|
||||||
@@ -127,16 +125,6 @@ creation_rules:
|
|||||||
# scripts/secrets/sync-host-keys.sh yet, so whichever variant is actually
|
# scripts/secrets/sync-host-keys.sh yet, so whichever variant is actually
|
||||||
# deployed next needs its recipient added here (and `sops updatekeys` rerun)
|
# deployed next needs its recipient added here (and `sops updatekeys` rerun)
|
||||||
# before it can decrypt this.
|
# before it can decrypt this.
|
||||||
# Host keytab for tailscale-router FreeIPA enrollment (binary sops file).
|
|
||||||
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
|
||||||
- path_regex: secrets/tailscale-router\.keytab$
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *admin
|
|
||||||
- *lxc-tailscale-router
|
|
||||||
- *proxmox-tailscale-router
|
|
||||||
- *linode-tailscale-router
|
|
||||||
|
|
||||||
- path_regex: secrets/gui\.yaml$
|
- path_regex: secrets/gui\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
@@ -146,12 +134,96 @@ creation_rules:
|
|||||||
- *linode-gui
|
- *linode-gui
|
||||||
- *proxmox-gui
|
- *proxmox-gui
|
||||||
|
|
||||||
# Host keytab for tailscale-router FreeIPA enrollment (binary sops file).
|
# IPA host keytabs (binary sops files).
|
||||||
# Generated by scripts/ipa/create-nixos-ipa-host-account.sh.
|
# Each keytab is encrypted for all platform variants of that host so any
|
||||||
|
# deployed variant can decrypt it at boot. Run
|
||||||
|
# scripts/ipa/create-nixos-ipa-host-account.sh <hostname> to enroll a new
|
||||||
|
# host and produce the keytab; this section is updated by that script.
|
||||||
|
|
||||||
|
- path_regex: secrets/nix-cache\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *linode-nix-cache
|
||||||
|
- *lxc-nix-cache
|
||||||
|
- *proxmox-nix-cache
|
||||||
|
|
||||||
- path_regex: secrets/tailscale-router\.keytab$
|
- path_regex: secrets/tailscale-router\.keytab$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *admin
|
- *admin
|
||||||
|
- *linode-tailscale-router
|
||||||
- *lxc-tailscale-router
|
- *lxc-tailscale-router
|
||||||
- *proxmox-tailscale-router
|
- *proxmox-tailscale-router
|
||||||
- *linode-tailscale-router
|
|
||||||
|
- path_regex: secrets/pxe-boot\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *lxc-pxe-boot
|
||||||
|
- *proxmox-pxe-boot
|
||||||
|
|
||||||
|
# nixos = the workstation (hosts/nixos/host.nix). All gui platform variants
|
||||||
|
# share the hostname "nixos" and must be able to decrypt at boot.
|
||||||
|
- path_regex: secrets/nixos\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *baremetal-gui
|
||||||
|
- *lxc-gui
|
||||||
|
- *proxmox-gui
|
||||||
|
- *linode-gui
|
||||||
|
|
||||||
|
- path_regex: secrets/server\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *linode-server
|
||||||
|
- *lxc-server
|
||||||
|
- *proxmox-server
|
||||||
|
|
||||||
|
- path_regex: secrets/docker\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *linode-docker
|
||||||
|
- *lxc-docker
|
||||||
|
- *proxmox-docker
|
||||||
|
|
||||||
|
- path_regex: secrets/tor-relay\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *lxc-tor-relay
|
||||||
|
|
||||||
|
- path_regex: secrets/nix-minimal\.keytab$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin
|
||||||
|
- *lxc-minimal
|
||||||
|
- *proxmox-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
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
- *proxmox-ha-server-1
|
||||||
|
# proxmox-ha-server-1 added by sync-host-keys.sh
|
||||||
|
=======
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
|
||||||
|
# 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
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
- *proxmox-ha-server-2
|
||||||
|
# proxmox-ha-server-2 added by sync-host-keys.sh
|
||||||
|
=======
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ This repository contains flake-based NixOS configurations for Wayne's LAN
|
|||||||
servers and workstation.
|
servers and workstation.
|
||||||
|
|
||||||
The flake exposes NixOS configurations named `<platform>-<buildtype>`
|
The flake exposes NixOS configurations named `<platform>-<buildtype>`
|
||||||
(platforms: `linode`, `proxmox`, `lxc`; build types: `minimal`, `nix-cache`,
|
(platforms: `linode`, `proxmox`, `lxc`, `baremetal`; build types: `minimal`,
|
||||||
`server`, `docker`, `gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay`), generated from `modules/platforms/*`
|
`nix-cache`, `server`, `docker`, `gui`, `pxe-boot`, `tailscale-router`,
|
||||||
and `modules/build-types/*` by the `mkTarget` function in `flake.nix`. Not
|
`tor-relay`, `ha-server`), generated from `modules/platforms/*` and
|
||||||
every combination is built — `pxe-boot` has no `linode` variant. See
|
`modules/build-types/*` by the `mkTarget` function in `flake.nix`. Not every
|
||||||
`README.md` for the full current target list; treat `flake.nix` as the
|
combination is built — `pxe-boot` has no `linode` variant, `ha-server` only
|
||||||
source of truth since this list can drift.
|
exists on `proxmox`, and `tor-relay` only exists on `lxc`. See `README.md`
|
||||||
|
for the full current target list; treat `flake.nix` as the source of truth
|
||||||
|
since this list can drift.
|
||||||
|
|
||||||
Do not deploy, switch, reboot, repartition, format disks, or run destructive
|
Do not deploy, switch, reboot, repartition, format disks, or run destructive
|
||||||
install commands from this repository unless explicitly asked.
|
install commands from this repository unless explicitly asked.
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ pieces composed in `flake.nix`:
|
|||||||
|
|
||||||
- **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`, `baremetal`
|
- **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`, `baremetal`
|
||||||
- **Build types** (what it's for): `minimal`, `nix-cache`, `server`, `docker`,
|
- **Build types** (what it's for): `minimal`, `nix-cache`, `server`, `docker`,
|
||||||
`gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay`
|
`gui`, `pxe-boot`, `tailscale-router`, `tor-relay`, `ha-server`
|
||||||
|
|
||||||
Not every combination exists — `pxe-boot` has no `linode` variant, since
|
Not every combination exists — `pxe-boot` has no `linode` variant, since
|
||||||
PXE/DHCP/TFTP need LAN L2 adjacency that a Linode VPS doesn't have,
|
PXE/DHCP/TFTP need LAN L2 adjacency that a Linode VPS doesn't have,
|
||||||
`tor-relay` currently only exists as `lxc-tor-relay`, and `baremetal`
|
`tor-relay` and `ha-server` currently only exist on `lxc`/`proxmox`, and
|
||||||
currently only exists as `baremetal-gui` (the real gui-host hardware). The
|
`baremetal` currently only exists as `baremetal-gui` (the real gui-host
|
||||||
full list:
|
hardware). The full list:
|
||||||
|
|
||||||
| Target | Purpose |
|
| Target | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -29,8 +29,9 @@ full list:
|
|||||||
| `linode-gui` / `proxmox-gui` / `lxc-gui` | Cinnamon desktop workstation — previously the flat `nixos` target |
|
| `linode-gui` / `proxmox-gui` / `lxc-gui` | Cinnamon desktop workstation — previously the flat `nixos` target |
|
||||||
| `baremetal-gui` | Same Cinnamon desktop workstation, on the real gui-host hardware — ZFS RAID0 root, systemd-boot |
|
| `baremetal-gui` | Same Cinnamon desktop workstation, on the real gui-host hardware — ZFS RAID0 root, systemd-boot |
|
||||||
| `proxmox-pxe-boot` / `lxc-pxe-boot` | HTTP/iPXE boot asset host — previously the flat `pxe-boot` target |
|
| `proxmox-pxe-boot` / `lxc-pxe-boot` | HTTP/iPXE boot asset host — previously the flat `pxe-boot` target |
|
||||||
| `linode-tailscale-exit-node` / `proxmox-tailscale-exit-node` / `lxc-tailscale-exit-node` | Tailscale exit node |
|
| `linode-tailscale-router` / `proxmox-tailscale-router` / `lxc-tailscale-router` | Tailscale subnet router + MagicDNS forwarder for the LAN |
|
||||||
| `lxc-tor-relay` | Tor middle relay |
|
| `lxc-tor-relay` | Tor middle relay |
|
||||||
|
| `proxmox-ha-server-1` / `proxmox-ha-server-2` | HA file-server cluster nodes — DRBD + XFS + iSCSI + NFS, managed by Corosync + Pacemaker |
|
||||||
|
|
||||||
Which variant of a given buildtype is actually deployed isn't tracked
|
Which variant of a given buildtype is actually deployed isn't tracked
|
||||||
anywhere in this repo — that's live infrastructure state, not something a
|
anywhere in this repo — that's live infrastructure state, not something a
|
||||||
@@ -47,8 +48,7 @@ section for which is which.
|
|||||||
|
|
||||||
Each buildtype's `hosts/<name>/host.nix` carries the per-machine identity
|
Each buildtype's `hosts/<name>/host.nix` carries the per-machine identity
|
||||||
(hostname, hostId, per-machine secrets, `system.stateVersion`) that must stay
|
(hostname, hostId, per-machine secrets, `system.stateVersion`) that must stay
|
||||||
fixed regardless of which platform it's built for — see
|
fixed regardless of which platform it's built for. Every deployed host
|
||||||
`flake-target-refactor-spec.md` for the full rationale. Every deployed host
|
|
||||||
stamps its own active target name into `/etc/flake-target` at build time, so
|
stamps its own active target name into `/etc/flake-target` at build time, so
|
||||||
`nixos-rebuild switch --flake .#$(cat /etc/flake-target)` always picks up the
|
`nixos-rebuild switch --flake .#$(cat /etc/flake-target)` always picks up the
|
||||||
right one even after a platform migration changes the flake attribute name.
|
right one even after a platform migration changes the flake attribute name.
|
||||||
@@ -167,7 +167,6 @@ per-boot host key for sops-nix to derive from on ephemeral media) — see
|
|||||||
(`vars/per-machine/<target>/openssh/`, committed and sops-encrypted) for
|
(`vars/per-machine/<target>/openssh/`, committed and sops-encrypted) for
|
||||||
their SSH host keys.
|
their SSH host keys.
|
||||||
|
|
||||||
This repository's git *history* still contains secrets committed before this
|
This repository's git *history* still contains secrets committed before the
|
||||||
migration (see `remove-sensetive-info-refactor.md`) — those are being
|
sops-nix migration — those are being scrubbed and rotated separately; don't
|
||||||
scrubbed and rotated separately; don't treat the repo as safe to make public
|
treat the repo as safe to make public until that's finished.
|
||||||
until that's finished.
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ see "LXC hosts" immediately below for why those are different.**
|
|||||||
## LXC hosts
|
## LXC hosts
|
||||||
|
|
||||||
`lxc-*` targets (`lxc-minimal`, `lxc-nix-cache`, `lxc-server`, `lxc-docker`,
|
`lxc-*` targets (`lxc-minimal`, `lxc-nix-cache`, `lxc-server`, `lxc-docker`,
|
||||||
`lxc-gui`, `lxc-pxe-boot`, `lxc-tailscale-exit-node`, `lxc-tor-relay`) are **not** installed via `auto-install.sh` — the
|
`lxc-gui`, `lxc-pxe-boot`, `lxc-tailscale-router`, `lxc-tor-relay`) are **not** installed via `auto-install.sh` — the
|
||||||
interactive menu deliberately excludes them. Don't try to select one there;
|
interactive menu deliberately excludes them. Don't try to select one there;
|
||||||
`nixos-install` would bind-mount `/` onto `/mnt` (LXC containers have no raw
|
`nixos-install` would bind-mount `/` onto `/mnt` (LXC containers have no raw
|
||||||
disk to partition) and then refuse to touch the filesystem it's currently
|
disk to partition) and then refuse to touch the filesystem it's currently
|
||||||
@@ -133,13 +133,15 @@ Flake outputs:
|
|||||||
```nix
|
```nix
|
||||||
nixosConfigurations.installer # ISO/netboot installer image
|
nixosConfigurations.installer # ISO/netboot installer image
|
||||||
|
|
||||||
packages.x86_64-linux.iso # installer ISO/netboot image
|
packages.x86_64-linux.iso # installer ISO/netboot image
|
||||||
packages.x86_64-linux.pxe # netboot-ipxe + netboot-initrd + netboot-kernel, bundled
|
packages.x86_64-linux.pxe # auto-installer netboot bundle (kernel + initrd + ipxe script)
|
||||||
|
packages.x86_64-linux.pxe-minimal # vanilla NixOS minimal netboot bundle (no installer wiring)
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix build .#iso
|
nix build .#iso
|
||||||
nix build .#pxe
|
nix build .#pxe
|
||||||
|
nix build .#pxe-minimal
|
||||||
```
|
```
|
||||||
|
|
||||||
There's no `nixosConfigurations.proxmox-lxc` (installer-boots-as-an-LXC-
|
There's no `nixosConfigurations.proxmox-lxc` (installer-boots-as-an-LXC-
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
};
|
};
|
||||||
boot.zfs.forceImportRoot = false;
|
boot.zfs.forceImportRoot = false;
|
||||||
|
|
||||||
|
# Only advertise the LAN interface to IPA DNS. Without this, SSSD registers
|
||||||
|
# every Docker bridge (172.x.x.x) as an A record for docker.sweet.home —
|
||||||
|
# the default dyndns.interface = "*" catches them all.
|
||||||
|
security.ipa.dyndns.interface = vars.lxcLanInterface; # eth0
|
||||||
|
|
||||||
# Preserved from the pre-refactor `docker` target — stateVersion must never
|
# Preserved from the pre-refactor `docker` target — stateVersion must never
|
||||||
# be bumped on an already-installed machine.
|
# be bumped on an already-installed machine.
|
||||||
system.stateVersion = "25.05";
|
system.stateVersion = "25.05";
|
||||||
|
|||||||
@@ -6,15 +6,10 @@
|
|||||||
name = "nix-cache";
|
name = "nix-cache";
|
||||||
sopsFile = ../../secrets/nix-cache.yaml;
|
sopsFile = ../../secrets/nix-cache.yaml;
|
||||||
})
|
})
|
||||||
(import ../../modules/ipa/client.nix {
|
|
||||||
keytabSopsFile = ../../secrets/nix-cache.keytab;
|
|
||||||
caCertFile = ../../certs/ipa-ca.crt;
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
hostName = vars.nixCacheHost;
|
hostName = vars.nixCacheHost;
|
||||||
domain = vars.homeDomain;
|
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
||||||
address = vars.nixCacheIp;
|
address = vars.nixCacheIp;
|
||||||
@@ -25,7 +20,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.beszel.agent.environment = {
|
services.beszel.agent.environment = {
|
||||||
#DOCKER_HOST = "tcp://docker-socket-proxy:2375";
|
|
||||||
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
services.beszel.agent.environment = {
|
services.beszel.agent.environment = {
|
||||||
#DOCKER_HOST = "tcp://docker-socket-proxy:2375";
|
|
||||||
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
|
||||||
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
|
EXTRA_FILESYSTEMS = "${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath}";
|
||||||
LOG_LEVEL = "debug";
|
LOG_LEVEL = "debug";
|
||||||
|
|||||||
@@ -6,15 +6,10 @@
|
|||||||
name = "tailscale-router";
|
name = "tailscale-router";
|
||||||
sopsFile = ../../secrets/tailscale-router.yaml;
|
sopsFile = ../../secrets/tailscale-router.yaml;
|
||||||
})
|
})
|
||||||
(import ../../modules/ipa/client.nix {
|
|
||||||
keytabSopsFile = ../../secrets/tailscale-router.keytab;
|
|
||||||
caCertFile = ../../certs/ipa-ca.crt;
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
hostName = "tailscale-router";
|
hostName = "tailscale-router";
|
||||||
domain = vars.homeDomain;
|
|
||||||
useDHCP = false;
|
useDHCP = false;
|
||||||
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
|
||||||
address = vars.tailscaleRouterIp;
|
address = vars.tailscaleRouterIp;
|
||||||
|
|||||||
@@ -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,17 @@
|
|||||||
# 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
|
||||||
|
# Generates /etc/exports lines for all nfsShares data entries. Shared
|
||||||
|
# pattern with modules/build-types/server.nix — both export the same
|
||||||
|
# set of shares, differing only in the storage root they serve from.
|
||||||
|
mkNfsExports = storageRoot:
|
||||||
|
lib.concatMapStrings
|
||||||
|
(share: " ${storageRoot}/${share.subpath} ${vars.lanCidr}${vars.nfsShares.options}\n")
|
||||||
|
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares));
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../ha/pacemaker-stack.nix
|
../ha/pacemaker-stack.nix
|
||||||
@@ -23,18 +33,13 @@
|
|||||||
../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 = ''
|
exports = mkNfsExports vars.haStorageRoot;
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.dockerConfig.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.dockerVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.dockerDatabases.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.nextcloudData.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.raspiVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.proxmoxIsos.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.proxmoxLxcImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.haStorageRoot}/${vars.nfsShares.pxebootImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Pacemaker controls nfs-server — prevent systemd from starting it at boot
|
# Pacemaker controls nfs-server — prevent systemd from starting it at boot
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ let
|
|||||||
lib.concatMap (share: ancestors share.subpath)
|
lib.concatMap (share: ancestors share.subpath)
|
||||||
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares))
|
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
# Generates /etc/exports lines for all nfsShares data entries (every
|
||||||
|
# attrset value — excludes the bare `options` string). Both server and
|
||||||
|
# ha-server export the same share set from different storage roots, so
|
||||||
|
# this helper is the single source of truth for the export line format.
|
||||||
|
mkNfsExports = storageRoot:
|
||||||
|
lib.concatMapStrings
|
||||||
|
(share: " ${storageRoot}/${share.subpath} ${vars.lanCidr}${vars.nfsShares.options}\n")
|
||||||
|
(lib.filter builtins.isAttrs (lib.attrValues vars.nfsShares));
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -95,16 +104,7 @@ in
|
|||||||
|
|
||||||
services.nfs.server = {
|
services.nfs.server = {
|
||||||
enable = true;
|
enable = true;
|
||||||
exports = ''
|
exports = mkNfsExports vars.storageRoot;
|
||||||
${vars.storageRoot}/${vars.nfsShares.dockerConfig.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.dockerVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.dockerDatabases.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.nextcloudData.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.raspiVolumes.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.proxmoxIsos.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.proxmoxLxcImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
${vars.storageRoot}/${vars.nfsShares.pxebootImages.subpath} ${vars.lanCidr}${vars.nfsShares.options}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# mountd (20048) is needed for showmount/NFSv3 mount protocol — without it
|
# mountd (20048) is needed for showmount/NFSv3 mount protocol — without it
|
||||||
|
|||||||
@@ -1,50 +1,7 @@
|
|||||||
{ config, pkgs, lib, vars, ... }:
|
_:
|
||||||
|
|
||||||
let
|
|
||||||
# Flake attribute names are now <platform>-<buildtype> (e.g. proxmox-docker)
|
|
||||||
# and no longer match networking.hostName, since a host's hostname stays
|
|
||||||
# fixed while the platform backing it can change. Each nixosConfiguration
|
|
||||||
# stamps its own active target name into /etc/flake-target at build time.
|
|
||||||
mySwitchCmd = ''
|
|
||||||
sudo nixos-rebuild switch \
|
|
||||||
--no-write-lock-file \
|
|
||||||
--refresh \
|
|
||||||
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
|
||||||
'';
|
|
||||||
myTestCmd = ''
|
|
||||||
sudo nixos-rebuild test \
|
|
||||||
--no-write-lock-file \
|
|
||||||
--refresh \
|
|
||||||
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
|
||||||
'';
|
|
||||||
|
|
||||||
# lxc-* hosts pre-seed their SSH host key at build time (see
|
|
||||||
# modules/platforms/lxc.nix) so sops-nix's .sops.yaml recipient matches on
|
|
||||||
# first boot -- without it, secrets permanently fail to decrypt (see that
|
|
||||||
# file's comment for the confirmed failure). That requires --impure plus
|
|
||||||
# NIXOS_HOST_KEYS_DIR pointing at the repo's host-keys/ dir, same pattern
|
|
||||||
# docs/auto-installer.md uses for the installer ISO. A function, not a
|
|
||||||
# shellAlias, since the target name has to interpolate into the middle of
|
|
||||||
# the flake attribute path, not just append after it. Must be run from the
|
|
||||||
# repo root, same as every other host-keys/ command in this repo.
|
|
||||||
buildImageFn = ''
|
|
||||||
buildImage() {
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo "usage: buildImage <flake-target> (e.g. lxc-docker)" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build --impure \
|
|
||||||
".#nixosConfigurations.$1.config.system.build.tarball"
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
programs.bash = {
|
# Switch-nix, Test-nix, and buildImage are defined system-wide in
|
||||||
enable = true;
|
# modules/common/configuration.nix so all users (including IPA accounts)
|
||||||
shellAliases = {
|
# get them. Add any Home-Manager-only per-user shell config here.
|
||||||
"Switch-nix" = mySwitchCmd;
|
|
||||||
"Test-nix" = myTestCmd;
|
|
||||||
};
|
|
||||||
initExtra = buildImageFn;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,45 @@
|
|||||||
{ config, lib, pkgs, vars, ... }:
|
{ config, lib, pkgs, vars, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
switchCmd = ''
|
||||||
|
sudo nixos-rebuild switch \
|
||||||
|
--no-write-lock-file \
|
||||||
|
--refresh \
|
||||||
|
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
||||||
|
'';
|
||||||
|
testCmd = ''
|
||||||
|
sudo nixos-rebuild test \
|
||||||
|
--no-write-lock-file \
|
||||||
|
--refresh \
|
||||||
|
--flake git+https://${vars.lanDomain}/beatzaplenty/nixos.git#$(cat /etc/flake-target)
|
||||||
|
'';
|
||||||
|
buildImageFn = ''
|
||||||
|
buildImage() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "usage: buildImage <flake-target> (e.g. lxc-docker)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build --impure \
|
||||||
|
".#nixosConfigurations.$1.config.system.build.tarball"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[
|
./set-locale.nix
|
||||||
# Include the results of the hardware scan.
|
../ipa/client.nix
|
||||||
# ./hardware-configuration.nix
|
];
|
||||||
./set-locale.nix
|
|
||||||
];
|
|
||||||
# Use the GRUB 2 boot loader.
|
|
||||||
# boot.loader.grub.enable = true;
|
|
||||||
#boot.loader.grub.device = "/dev/sda"; # or "nodev" for efi only
|
|
||||||
|
|
||||||
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default.
|
# System-wide shell config so all users (including IPA accounts) get the
|
||||||
|
# same management aliases as the local nixos user's Home Manager provides.
|
||||||
|
programs.bash = {
|
||||||
|
shellAliases = {
|
||||||
|
"Switch-nix" = switchCmd;
|
||||||
|
"Test-nix" = testCmd;
|
||||||
|
};
|
||||||
|
interactiveShellInit = buildImageFn;
|
||||||
|
};
|
||||||
|
networking.networkmanager.enable = true;
|
||||||
|
|
||||||
# Recommended over the true default (bypasses ZFS's own import safeguards)
|
# Recommended over the true default (bypasses ZFS's own import safeguards)
|
||||||
# per the option's own docs; matches hosts/docker/host.nix and
|
# per the option's own docs; matches hosts/docker/host.nix and
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Shared activation-script logic to preserve the SSH host key across
|
||||||
|
# nixos-rebuild on platforms that embed the key via environment.etc (lxc and
|
||||||
|
# proxmox). When NIXOS_HOST_KEYS_DIR is not set the key is absent from
|
||||||
|
# environment.etc, and NixOS's etc activation removes any /etc file not in
|
||||||
|
# the new generation — which would destroy the live key and break sops-nix
|
||||||
|
# decryption permanently. These scripts save the key to /run before etc
|
||||||
|
# removes it, then restore it afterward.
|
||||||
|
#
|
||||||
|
# Explicit deps enforce the correct ordering: without them the topological
|
||||||
|
# sort places preserveSshHostKey after etc (confirmed live on lxc-tor-relay:
|
||||||
|
# position 7 vs etc's position 5), so the key is gone before it can be saved.
|
||||||
|
_: {
|
||||||
|
system.activationScripts = {
|
||||||
|
preserveSshHostKey = ''
|
||||||
|
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
||||||
|
cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp
|
||||||
|
cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
restoreSshHostKey = {
|
||||||
|
deps = [ "etc" ];
|
||||||
|
text = ''
|
||||||
|
if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then
|
||||||
|
install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key
|
||||||
|
install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub
|
||||||
|
fi
|
||||||
|
rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
etc = { deps = [ "preserveSshHostKey" ]; };
|
||||||
|
setupSecrets = { deps = [ "restoreSshHostKey" ]; };
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,23 +1,45 @@
|
|||||||
{ pkgs, vars, ... }:
|
{ lib, pkgs, vars, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
gid = toString vars.dockerAccessGid;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
# virtualisation.docker.enable = true;
|
|
||||||
virtualisation.docker = {
|
virtualisation.docker = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.docker;
|
package = pkgs.docker;
|
||||||
# listenOptions = [
|
|
||||||
# "unix:///var/run/docker.sock"
|
|
||||||
# "tcp://0.0.0.0:2375"
|
|
||||||
#];
|
|
||||||
|
|
||||||
# daemon.settings = {
|
|
||||||
# metrics-addr = "0.0.0.0:9323";
|
|
||||||
# experimental = true;
|
|
||||||
# };
|
|
||||||
};
|
};
|
||||||
|
# Pin the docker group GID to match the IPA "docker-access" group so that
|
||||||
|
# IPA group membership alone grants access to the Docker socket. Any user
|
||||||
|
# whose supplementary groups (resolved by SSSD from IPA) include GID
|
||||||
|
# vars.dockerAccessGid will pass the socket group-permission check without
|
||||||
|
# any per-host users.groups.docker.members entry.
|
||||||
|
users.groups.docker.gid = lib.mkForce vars.dockerAccessGid;
|
||||||
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
|
users.users.${vars.primaryUser}.extraGroups = [ "docker" ];
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
docker-compose
|
docker-compose
|
||||||
docker-buildx
|
docker-buildx
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# NixOS's group activation uses plain `groupmod` without --non-unique.
|
||||||
|
# When SSSD is active it exposes the IPA "docker-access" group at
|
||||||
|
# vars.dockerAccessGid via NSS, so groupmod sees that GID as already in
|
||||||
|
# use and silently skips the change (warning: "not applying GID change").
|
||||||
|
# This script runs after the normal "groups" step and applies the change
|
||||||
|
# with --non-unique (which lets the local docker group share the GID with
|
||||||
|
# the SSSD-provided IPA group). If the GID actually changed it also
|
||||||
|
# restarts docker.socket so the socket is recreated with the new GID.
|
||||||
|
system.activationScripts.docker-group-gid = {
|
||||||
|
deps = [ "groups" ];
|
||||||
|
text = ''
|
||||||
|
current=$(grep "^docker:" /etc/group | cut -d: -f3)
|
||||||
|
if [ "$current" != "${gid}" ]; then
|
||||||
|
${pkgs.shadow}/bin/groupmod --non-unique -g ${gid} docker
|
||||||
|
if ${pkgs.systemd}/bin/systemctl is-active --quiet docker.socket; then
|
||||||
|
${pkgs.systemd}/bin/systemctl stop docker.service docker.socket
|
||||||
|
rm -f /var/run/docker.sock
|
||||||
|
${pkgs.systemd}/bin/systemctl start docker.socket docker.service
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,32 @@
|
|||||||
# 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 -" ];
|
||||||
|
|
||||||
services.drbd = {
|
services.drbd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
config = ''
|
config = ''
|
||||||
@@ -37,6 +55,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
|
||||||
|
|||||||
+168
-61
@@ -1,34 +1,15 @@
|
|||||||
# Fully declarative FreeIPA domain membership.
|
# Fully declarative FreeIPA domain membership.
|
||||||
#
|
#
|
||||||
# Configures security.ipa (SSSD, Kerberos, PAM, NSSwitch) and places a
|
# Imported by modules/common/configuration.nix — no per-host wiring needed.
|
||||||
# pre-provisioned host keytab via sops-nix so no imperative ipa-client-install
|
# Enables itself automatically on any host that has a sops-encrypted keytab
|
||||||
# step is needed after deployment.
|
# at secrets/<hostname>.keytab; is a no-op for all other hosts.
|
||||||
#
|
#
|
||||||
# Usage (in a host.nix imports list):
|
# To enroll a new host:
|
||||||
# (import ../../modules/ipa/client.nix {
|
# 0. scripts/secrets/sync-host-keys.sh <flake-target>
|
||||||
# keytabSopsFile = ../../secrets/<hostname>.keytab;
|
# 1. scripts/ipa/create-nixos-ipa-host-account.sh [--ip <addr>] <hostname>
|
||||||
# caCertFile = ../../certs/ipa-ca.crt; # already committed — do not re-fetch
|
# (adds .sops.yaml rule, runs ipa host-add, encrypts keytab in one step)
|
||||||
# })
|
# 2. git add secrets/<hostname>.keytab .sops.yaml && git commit
|
||||||
#
|
# 3. Deploy — no further steps required.
|
||||||
# The host.nix networking block must also set:
|
|
||||||
# networking.domain = vars.homeDomain; # needed for Kerberos FQDN
|
|
||||||
# networking.nameservers = [ vars.domainControllerIp ]; # IPA DNS
|
|
||||||
#
|
|
||||||
# One-time operator setup per host (do this BEFORE deploying):
|
|
||||||
#
|
|
||||||
# 0. Generate SSH host keys and the host's age key for sops:
|
|
||||||
# scripts/secrets/sync-host-keys.sh <flake-target>
|
|
||||||
# This must run before step 1 so the host age key is in .sops.yaml
|
|
||||||
# and the keytab can be encrypted for the host to read at boot.
|
|
||||||
#
|
|
||||||
# 1. Add the IPA host account and produce the sops-encrypted keytab:
|
|
||||||
# scripts/ipa/create-nixos-ipa-host-account.sh [--ip <addr>] <hostname>
|
|
||||||
# The script handles ipa host-add, ipa-getkeytab, .sops.yaml patching,
|
|
||||||
# and sops encryption in one step. See the script header for details.
|
|
||||||
#
|
|
||||||
# 2. Wire up the host (see "Usage" above), then deploy:
|
|
||||||
# nixos-rebuild switch (or create-proxmox-resource.sh)
|
|
||||||
# No further manual enrollment steps are required after deployment.
|
|
||||||
#
|
#
|
||||||
# Manual fallback (if the script isn't usable):
|
# Manual fallback (if the script isn't usable):
|
||||||
# a. On the FreeIPA server: ipa host-add <fqdn> [--ip-address=<ip>] --force
|
# a. On the FreeIPA server: ipa host-add <fqdn> [--ip-address=<ip>] --force
|
||||||
@@ -38,61 +19,146 @@
|
|||||||
# sops -e --input-type binary -i secrets/<host>.keytab
|
# sops -e --input-type binary -i secrets/<host>.keytab
|
||||||
# d. Commit secrets/<host>.keytab and the updated .sops.yaml, then deploy.
|
# d. Commit secrets/<host>.keytab and the updated .sops.yaml, then deploy.
|
||||||
#
|
#
|
||||||
# vars dependencies: homeDomain, ipaServer, domainControllerIp
|
# vars dependencies: homeDomain, ipaServer, domainControllerIp, ipaUser
|
||||||
|
|
||||||
{ keytabSopsFile, caCertFile }:
|
|
||||||
{ config, lib, pkgs, vars, ... }:
|
{ config, lib, pkgs, vars, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
keytabPath = ../../secrets + "/${config.networking.hostName}.keytab";
|
||||||
|
enabled = builtins.pathExists keytabPath;
|
||||||
|
|
||||||
realm = lib.strings.toUpper vars.homeDomain;
|
realm = lib.strings.toUpper vars.homeDomain;
|
||||||
fqdn = "${config.networking.hostName}.${vars.homeDomain}";
|
fqdn = "${config.networking.hostName}.${vars.homeDomain}";
|
||||||
# "sweet.home" -> "dc=sweet,dc=home"
|
# "sweet.home" -> "dc=sweet,dc=home"
|
||||||
basedn = lib.strings.concatMapStringsSep "," (c: "dc=${c}") (lib.strings.splitString "." vars.homeDomain);
|
basedn = lib.strings.concatMapStringsSep "," (c: "dc=${c}") (lib.strings.splitString "." vars.homeDomain);
|
||||||
# security.ipa.certificate expects a derivation (package), not a raw path.
|
# security.ipa.certificate expects a derivation (package), not a raw path.
|
||||||
caCertPkg = pkgs.writeText "ipa-ca.crt" (builtins.readFile caCertFile);
|
caCertPkg = pkgs.writeText "ipa-ca.crt" (builtins.readFile ../../certs/ipa-ca.crt);
|
||||||
in
|
in
|
||||||
{
|
lib.mkIf enabled {
|
||||||
security.ipa = {
|
networking.domain = lib.mkDefault vars.homeDomain;
|
||||||
enable = true;
|
networking.nameservers = lib.mkDefault [ vars.domainControllerIp ];
|
||||||
domain = vars.homeDomain;
|
|
||||||
realm = realm;
|
security = {
|
||||||
server = vars.ipaServer;
|
ipa = {
|
||||||
certificate = caCertPkg;
|
enable = true;
|
||||||
basedn = basedn;
|
domain = vars.homeDomain;
|
||||||
ipaHostname = fqdn;
|
inherit realm;
|
||||||
offlinePasswords = true;
|
server = vars.ipaServer;
|
||||||
cacheCredentials = true;
|
certificate = caCertPkg;
|
||||||
|
inherit basedn;
|
||||||
|
ipaHostname = fqdn;
|
||||||
|
offlinePasswords = true;
|
||||||
|
cacheCredentials = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Create the home directory on first login if it doesn't exist yet.
|
||||||
|
# IPA users have no pre-created home on the host; without this sshd
|
||||||
|
# opens a session to a non-existent directory and resets the connection.
|
||||||
|
# lightdm also needs this so the GUI login path can create the home dir
|
||||||
|
# if it was not pre-seeded by the tmpfiles rule above (e.g. on first boot
|
||||||
|
# before SSSD has resolved the user).
|
||||||
|
pam.services = {
|
||||||
|
sshd.makeHomeDir = true;
|
||||||
|
lightdm.makeHomeDir = true;
|
||||||
|
|
||||||
|
# pam_unix returns PAM_AUTHINFO_UNAVAIL without prompting when the local
|
||||||
|
# stub has "!" in shadow (account locked), so PAM_AUTHTOK is never set
|
||||||
|
# and pam_sss's use_first_pass fails with "No authentication token".
|
||||||
|
# Changing to try_first_pass makes pam_sss prompt independently when no
|
||||||
|
# prior module has set the token, restoring IPA password login via
|
||||||
|
# LightDM and su.
|
||||||
|
login.rules.auth.sss.settings = lib.mkForce { try_first_pass = true; };
|
||||||
|
su.rules.auth.sss.settings = lib.mkForce { try_first_pass = true; };
|
||||||
|
};
|
||||||
|
|
||||||
|
# HM with useUserPackages = true (flake.nix) sets users.users.${ipaUser}.packages,
|
||||||
|
# which forces the stub into /etc/passwd. pam_sss.so with the "localusers" flag
|
||||||
|
# (added by NixOS when SSSD is enabled) then skips SSSD for any user it finds in
|
||||||
|
# local /etc/passwd — including this stub — falling through to pam_unix, which has
|
||||||
|
# no password for the stub → sudo auth always fails.
|
||||||
|
#
|
||||||
|
# Fix: NOPASSWD for the IPA user. The IPA user already authenticated to reach a
|
||||||
|
# shell (SSH public key from IPA or Kerberos), so re-prompting via a broken PAM
|
||||||
|
# path is security theater on a single-admin homelab.
|
||||||
|
sudo.extraRules = [{
|
||||||
|
users = [ vars.ipaUser ];
|
||||||
|
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
|
||||||
|
}];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Fetch SSH public keys from IPA so users can log in with the key stored
|
systemd = {
|
||||||
# in their IPA profile rather than needing ~/.ssh/authorized_keys on every
|
# Fetch SSH public keys from IPA so users can log in with the key stored
|
||||||
# host. sss_ssh_authorizedkeys queries SSSD (which queries IPA LDAP).
|
# in their IPA profile rather than needing ~/.ssh/authorized_keys on every
|
||||||
#
|
# host. sss_ssh_authorizedkeys queries SSSD (which queries IPA LDAP).
|
||||||
# /nix/store is 1775 (group-writable by nixbld). OpenSSH 10.0+ rejects
|
#
|
||||||
# AuthorizedKeysCommand binaries whose path contains any group-writable
|
# /nix/store is 1775 (group-writable by nixbld). OpenSSH 10.0+ rejects
|
||||||
# component, silently skipping the command. Copy to /usr/local/bin (all
|
# AuthorizedKeysCommand binaries whose path contains any group-writable
|
||||||
# components root-owned, 755) so the path passes sshd's safety check.
|
# component, silently skipping the command. Copy to /usr/local/bin (all
|
||||||
systemd.tmpfiles.rules = [
|
# components root-owned, 755) so the path passes sshd's safety check.
|
||||||
"d /usr/local 0755 root root - -"
|
tmpfiles.rules = [
|
||||||
"d /usr/local/bin 0755 root root - -"
|
"d /usr/local 0755 root root - -"
|
||||||
"C+ /usr/local/bin/sss_ssh_authorizedkeys 0555 root root - ${pkgs.sssd}/bin/sss_ssh_authorizedkeys"
|
"d /usr/local/bin 0755 root root - -"
|
||||||
];
|
"C+ /usr/local/bin/sss_ssh_authorizedkeys 0555 root root - ${pkgs.sssd}/bin/sss_ssh_authorizedkeys"
|
||||||
|
# Pre-create the IPA user's home dir so Home Manager activation succeeds
|
||||||
|
# even before their first login. On a fresh system SSSD may not have
|
||||||
|
# resolved the user yet — tmpfiles warns and skips in that case (non-fatal),
|
||||||
|
# and pam_mkhomedir covers the first-login path as a fallback.
|
||||||
|
"d /home/${vars.ipaUser} 0700 ${vars.ipaUser} ${vars.ipaUser} - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
# security.ipa enables Kerberos (security.krb5) which causes systemd to
|
||||||
|
# start auth-rpcgss-module.service and rpc-gssd.service for Kerberos NFS
|
||||||
|
# authentication. LXC containers can't load the auth_rpcgss kernel module
|
||||||
|
# and don't have /var/lib/nfs/rpc_pipefs, so both services fail.
|
||||||
|
#
|
||||||
|
# The NixOS IPA module already adds a drop-in for auth-rpcgss-module.service
|
||||||
|
# with ConditionPathExists=/etc/krb5.keytab. We use lib.mkForce to win the
|
||||||
|
# text conflict and add ConditionVirtualization=!container alongside it so
|
||||||
|
# the service is skipped (not failed) in containers that do have a keytab.
|
||||||
|
# Same fix for rpc-gssd.service which also fails in containers.
|
||||||
|
units = lib.mkIf config.boot.isContainer {
|
||||||
|
"auth-rpcgss-module.service" = {
|
||||||
|
overrideStrategy = "asDropinIfExists";
|
||||||
|
text = lib.mkForce ''
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=
|
||||||
|
ConditionPathExists=/etc/krb5.keytab
|
||||||
|
ConditionVirtualization=!container
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
# rpc-gssd also has ConditionPathExists from the NixOS IPA module (and an
|
||||||
|
# X-Restart-Triggers store path from systemd.nix). Use mkForce to win;
|
||||||
|
# omit X-Restart-Triggers since this service is skipped in containers anyway.
|
||||||
|
"rpc-gssd.service" = {
|
||||||
|
overrideStrategy = "asDropinIfExists";
|
||||||
|
text = lib.mkForce ''
|
||||||
|
[Unit]
|
||||||
|
ConditionPathExists=
|
||||||
|
ConditionPathExists=/etc/krb5.keytab
|
||||||
|
ConditionVirtualization=!container
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# home-manager-<user>.service fails on first enrollment because /home/wayne
|
||||||
|
# doesn't exist until the user's first login (pam_mkhomedir creates it then).
|
||||||
|
# ConditionPathExists makes systemd skip the service (exit 0, condition not
|
||||||
|
# met) instead of failing. After first login the dir exists and subsequent
|
||||||
|
# rebuilds activate HM normally.
|
||||||
|
services."home-manager-${vars.ipaUser}".unitConfig.ConditionPathExists =
|
||||||
|
"/home/${vars.ipaUser}";
|
||||||
|
};
|
||||||
|
|
||||||
services.openssh.extraConfig = ''
|
services.openssh.extraConfig = ''
|
||||||
AuthorizedKeysCommand /usr/local/bin/sss_ssh_authorizedkeys %u
|
AuthorizedKeysCommand /usr/local/bin/sss_ssh_authorizedkeys %u
|
||||||
AuthorizedKeysCommandUser nobody
|
AuthorizedKeysCommandUser nobody
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Create the home directory on first login if it doesn't exist yet.
|
|
||||||
# IPA users have no pre-created home on the host; without this sshd
|
|
||||||
# opens a session to a non-existent directory and resets the connection.
|
|
||||||
security.pam.services.sshd.makeHomeDir = true;
|
|
||||||
|
|
||||||
# Host keytab: pre-provisioned on the IPA server, sops-encrypted binary.
|
# Host keytab: pre-provisioned on the IPA server, sops-encrypted binary.
|
||||||
# Placed at /etc/krb5.keytab before SSSD starts so the host authenticates
|
# Placed at /etc/krb5.keytab before SSSD starts so the host authenticates
|
||||||
# to IPA without running ipa-client-install.
|
# to IPA without running ipa-client-install.
|
||||||
sops.secrets."ipa-host-keytab" = {
|
sops.secrets."ipa-host-keytab" = {
|
||||||
sopsFile = keytabSopsFile;
|
sopsFile = keytabPath;
|
||||||
format = "binary";
|
format = "binary";
|
||||||
path = "/etc/krb5.keytab";
|
path = "/etc/krb5.keytab";
|
||||||
owner = "root";
|
owner = "root";
|
||||||
@@ -100,4 +166,45 @@ in
|
|||||||
mode = "0600";
|
mode = "0600";
|
||||||
restartUnits = [ "sssd.service" ];
|
restartUnits = [ "sssd.service" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# NixOS requires isNormalUser/isSystemUser + group on any entry in
|
||||||
|
# users.users. HM with useUserPackages = true (set in flake.nix) adds a stub
|
||||||
|
# entry for each HM user so it can install packages to
|
||||||
|
# /etc/profiles/per-user/<name>/. This definition satisfies those assertions.
|
||||||
|
# With security.ipa setting "passwd: sss files" in nsswitch, SSSD's IPA entry
|
||||||
|
# takes priority for NSS lookups — this local stub is only a fallback when
|
||||||
|
# SSSD is unreachable (at which point auth fails anyway).
|
||||||
|
users.users.${vars.ipaUser} = {
|
||||||
|
isNormalUser = true;
|
||||||
|
group = "users";
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
createHome = false;
|
||||||
|
# "!" is not a password hash — it is the standard "account locked" marker.
|
||||||
|
# It cannot authenticate anyone locally. It exists solely so NixOS generates
|
||||||
|
# a shadow entry for this stub user; without one pam_unix returns
|
||||||
|
# PAM_AUTHINFO_UNAVAIL before prompting, which means PAM_AUTHTOK is never
|
||||||
|
# set and the subsequent pam_sss use_first_pass call has nothing to work
|
||||||
|
# with — blocking LightDM and su logins even when IPA/SSSD auth succeeds.
|
||||||
|
hashedPassword = "!";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Home Manager config for the IPA primary user, applied on every enrolled
|
||||||
|
# host. Manages what IPA doesn't: dotfiles, user-scoped packages, session
|
||||||
|
# variables. Switch-nix/Test-nix/buildImage are system-wide (configuration.nix)
|
||||||
|
# so they don't need to be repeated here.
|
||||||
|
#
|
||||||
|
# homeDirectory uses mkForce because HM's NixOS integration module sets it to
|
||||||
|
# "/var/empty" for users not found in config.users.users at eval time (SSSD
|
||||||
|
# users aren't visible there).
|
||||||
|
home-manager.users.${vars.ipaUser} = { pkgs, ... }: {
|
||||||
|
home = {
|
||||||
|
username = vars.ipaUser;
|
||||||
|
homeDirectory = lib.mkForce "/home/${vars.ipaUser}";
|
||||||
|
stateVersion = "26.05";
|
||||||
|
packages = with pkgs; [ tmux sshfs ];
|
||||||
|
sessionVariables.EDITOR = lib.mkDefault "nano";
|
||||||
|
};
|
||||||
|
programs.home-manager.enable = true;
|
||||||
|
programs.bash.enable = true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ in
|
|||||||
# LXC container does).
|
# LXC container does).
|
||||||
imports = [
|
imports = [
|
||||||
(modulesPath + "/virtualisation/proxmox-lxc.nix")
|
(modulesPath + "/virtualisation/proxmox-lxc.nix")
|
||||||
|
../common/preserve-ssh-host-key.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
proxmoxLXC = {
|
proxmoxLXC = {
|
||||||
@@ -105,49 +106,6 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# NixOS's etc activation removes any /etc file that was in the previous
|
|
||||||
# generation's environment.etc but is absent from the current one — even
|
|
||||||
# real (non-symlink) copies. On every routine nixos-rebuild switch/test that
|
|
||||||
# lacks NIXOS_HOST_KEYS_DIR the key is absent from environment.etc, so it
|
|
||||||
# gets removed as "obsolete". sops-nix derives its age decryption key from
|
|
||||||
# /etc/ssh/ssh_host_ed25519_key; deletion cascades into every sops secret
|
|
||||||
# failing with "Error getting data key: 0 successful groups required, got 0".
|
|
||||||
#
|
|
||||||
# Fix: activation scripts that bracket the etc step, with explicit deps
|
|
||||||
# to enforce the correct ordering. Without deps the topological sort places
|
|
||||||
# preserveSshHostKey AFTER etc (confirmed live on a deployed lxc-tor-relay:
|
|
||||||
# position 7 vs etc's position 5) -- the key is already gone by the time it
|
|
||||||
# tries to save it. The etc/setupSecrets entries ADD to existing deps
|
|
||||||
# (types.listOf concatenates across module definitions).
|
|
||||||
system.activationScripts = {
|
|
||||||
# Saves the live key to /run before etc can delete it.
|
|
||||||
preserveSshHostKey = ''
|
|
||||||
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Reinstalls the key after etc runs if it was removed as "obsolete".
|
|
||||||
# The resulting file is not registered in environment.etc for either
|
|
||||||
# generation, so subsequent rebuilds leave it alone permanently.
|
|
||||||
restoreSshHostKey = {
|
|
||||||
deps = [ "etc" ];
|
|
||||||
text = ''
|
|
||||||
if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then
|
|
||||||
install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
fi
|
|
||||||
rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# Force etc to wait until the key is saved, and sops to wait until the
|
|
||||||
# key is restored. Without these the topological sort breaks the chain.
|
|
||||||
etc = { deps = [ "preserveSshHostKey" ]; };
|
|
||||||
setupSecrets = { deps = [ "restoreSshHostKey" ]; };
|
|
||||||
};
|
|
||||||
|
|
||||||
# virtualisation/proxmox-lxc.nix (imported above) registers the Nix
|
# virtualisation/proxmox-lxc.nix (imported above) registers the Nix
|
||||||
# store DB via a systemd service (register-nix-paths) -- it never runs
|
# store DB via a systemd service (register-nix-paths) -- it never runs
|
||||||
# an activation script at all. Confirmed live this means neither
|
# an activation script at all. Confirmed live this means neither
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ in
|
|||||||
../hardware-configuration/vm/proxmox.nix
|
../hardware-configuration/vm/proxmox.nix
|
||||||
../boot/efi.nix
|
../boot/efi.nix
|
||||||
../disko/proxmox.nix
|
../disko/proxmox.nix
|
||||||
|
../common/preserve-ssh-host-key.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
environment.etc = lib.mkIf hasKeyForThisTarget {
|
environment.etc = lib.mkIf hasKeyForThisTarget {
|
||||||
@@ -46,36 +47,4 @@ in
|
|||||||
mode = "0644";
|
mode = "0644";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# NixOS's etc activation removes any /etc file that was in the previous
|
|
||||||
# generation's environment.etc but is absent from the current one. Since
|
|
||||||
# the SSH key is only in environment.etc during the --impure build (when
|
|
||||||
# NIXOS_HOST_KEYS_DIR is set), normal rebuilds would remove it as
|
|
||||||
# "obsolete". These scripts mirror lxc.nix's approach: save the live key
|
|
||||||
# before etc runs, restore it after. Without the explicit deps, the
|
|
||||||
# topological sort places preserveSshHostKey after etc (confirmed live on
|
|
||||||
# lxc-tor-relay: position 7 vs etc's position 5), so the key is gone
|
|
||||||
# before it can be saved.
|
|
||||||
system.activationScripts = {
|
|
||||||
preserveSshHostKey = ''
|
|
||||||
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key /run/sshd-host-key-preserve.tmp
|
|
||||||
cp /etc/ssh/ssh_host_ed25519_key.pub /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
restoreSshHostKey = {
|
|
||||||
deps = [ "etc" ];
|
|
||||||
text = ''
|
|
||||||
if [ ! -f /etc/ssh/ssh_host_ed25519_key ] && [ -f /run/sshd-host-key-preserve.tmp ]; then
|
|
||||||
install -m 0600 /run/sshd-host-key-preserve.tmp /etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -m 0644 /run/sshd-host-key-preserve.pub.tmp /etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
fi
|
|
||||||
rm -f /run/sshd-host-key-preserve.tmp /run/sshd-host-key-preserve.pub.tmp
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
etc = { deps = [ "preserveSshHostKey" ]; };
|
|
||||||
setupSecrets = { deps = [ "restoreSshHostKey" ]; };
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 "ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -c iqn" || 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 "ls /sys/kernel/config/target/iscsi/ 2>/dev/null | grep -c iqn" || 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
+134
-84
@@ -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,32 @@ 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
|
||||||
|
|
||||||
|
# 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,13 +99,13 @@ 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 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
|
||||||
|
|
||||||
# ── 1. Corosync quorum ────────────────────────────────────────────────────
|
# ── 1. Corosync quorum ────────────────────────────────────────────────────
|
||||||
@@ -105,15 +136,12 @@ if ! drbdadm dstate ha-data 2>/dev/null | grep -q "UpToDate\|Inconsistent\|Diskl
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
log "Initialising DRBD metadata on $NODE2..."
|
log "Initialising DRBD metadata on $NODE2..."
|
||||||
ssh "root@${NODE2_IP}" "
|
# Use grep -E for ERE alternation inside the remote bash -c string (avoids \| quoting issues).
|
||||||
if ! drbdadm dstate ha-data 2>/dev/null | grep -q 'UpToDate\|Inconsistent\|Diskless'; then
|
n2_ssh "bash -c 'drbdadm dstate ha-data 2>/dev/null | grep -qE \"UpToDate|Inconsistent|Diskless\" || drbdadm create-md ha-data --force'"
|
||||||
drbdadm create-md ha-data --force
|
|
||||||
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 "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 +165,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 +181,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)" == "Primary/Secondary" ]] && 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
+462
@@ -0,0 +1,462 @@
|
|||||||
|
#!/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-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_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-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}"
|
||||||
|
|
||||||
|
REBUILD_FLAG=""
|
||||||
|
$FORCE_REBUILD && REBUILD_FLAG="--force-rebuild"
|
||||||
|
|
||||||
|
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}..."
|
||||||
|
run bash "$CREATE" \
|
||||||
|
--type vm \
|
||||||
|
--host "$host_name" \
|
||||||
|
--vmid "$vmid" \
|
||||||
|
--node "$NODE" \
|
||||||
|
--storage "$STORAGE" \
|
||||||
|
--memory "$MEMORY_MB" \
|
||||||
|
--cores "$CORES" \
|
||||||
|
${REBUILD_FLAG}
|
||||||
|
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 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"
|
||||||
|
|
||||||
|
# Fix targetctl.service on both nodes: the iscsi-target.nix module bakes
|
||||||
|
# pkgs.targetcli-fb for the targetctl binary, but targetctl is actually in
|
||||||
|
# rtslib-fb (a different store path). Apply a runtime dropin that corrects
|
||||||
|
# both ExecStart and ExecStop before Pacemaker ever touches the service.
|
||||||
|
# The fixed iscsi-target.nix module will make this redundant on next rebuild.
|
||||||
|
logn "Patching targetctl.service on both nodes..."
|
||||||
|
_patch_targetctl() {
|
||||||
|
local ip="$1"
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no "${HA_USER}@${ip}" sudo bash << 'PATCH'
|
||||||
|
set -euo pipefail
|
||||||
|
TC=$(find /nix/store -maxdepth 4 -path '*/python3*env/bin/targetctl' 2>/dev/null | head -1)
|
||||||
|
PY=$(find /nix/store -maxdepth 4 -path '*/python3*env/bin/python3' -name 'python3' 2>/dev/null | \
|
||||||
|
while IFS= read -r p; do "$p" -c "import rtslib_fb" 2>/dev/null && echo "$p" && break; done | head -1)
|
||||||
|
[[ -n "$TC" && -n "$PY" ]] || { echo "targetctl or python3+rtslib_fb not found"; exit 1; }
|
||||||
|
# Write stop script that saves LIO config then tears down kernel state
|
||||||
|
"$PY" - "$TC" "$PY" << 'PYEOF'
|
||||||
|
import sys, os, stat
|
||||||
|
tc, py = sys.argv[1], sys.argv[2]
|
||||||
|
script = f"""#!{py}
|
||||||
|
import subprocess, sys, rtslib_fb
|
||||||
|
root = rtslib_fb.RTSRoot()
|
||||||
|
targets = list(root.targets)
|
||||||
|
if targets:
|
||||||
|
r = subprocess.run(["{tc}", "save", "/etc/target/saveconfig.json"], capture_output=True)
|
||||||
|
print(f"saved {{len(targets)}} target(s); rc={{r.returncode}}")
|
||||||
|
else:
|
||||||
|
print("no active LIO targets")
|
||||||
|
for t in targets:
|
||||||
|
try:
|
||||||
|
for tpg in list(t.tpgs): tpg.enable = False
|
||||||
|
t.delete()
|
||||||
|
except Exception as e: print(f"warn: {{e}}", file=sys.stderr)
|
||||||
|
for so in list(root.storage_objects):
|
||||||
|
try: so.delete()
|
||||||
|
except Exception as e: print(f"warn: {{e}}", file=sys.stderr)
|
||||||
|
print("LIO kernel target cleared")
|
||||||
|
"""
|
||||||
|
path = "/run/ha-targetctl-stop.py"
|
||||||
|
with open(path, "w") as f: f.write(script)
|
||||||
|
os.chmod(path, 0o755)
|
||||||
|
print(f"wrote {path}")
|
||||||
|
PYEOF
|
||||||
|
mkdir -p /run/systemd/system/targetctl.service.d
|
||||||
|
printf '[Service]\nExecStart=\nExecStart=%s restore /etc/target/saveconfig.json\nExecStop=\nExecStop=/run/ha-targetctl-stop.py\n' \
|
||||||
|
"$TC" > /run/systemd/system/targetctl.service.d/fix-exec.conf
|
||||||
|
systemctl daemon-reload
|
||||||
|
echo "patched on $(hostname)"
|
||||||
|
PATCH
|
||||||
|
}
|
||||||
|
_patch_targetctl "${NODE1_IP}"
|
||||||
|
_patch_targetctl "${NODE2_IP}"
|
||||||
|
|
||||||
|
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."
|
||||||
@@ -81,6 +81,14 @@ if [[ -z "${TARGET}" ]]; then
|
|||||||
usage 1
|
usage 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Reject FQDNs passed by mistake — the script appends HOME_DOMAIN itself.
|
||||||
|
# "nixos.sweet.home" → FQDN would become "nixos.sweet.home.sweet.home".
|
||||||
|
if [[ "${TARGET}" == *"."* ]]; then
|
||||||
|
echo "Error: <hostname> must be the short name (e.g. 'nixos'), not a FQDN." >&2
|
||||||
|
echo " The FQDN is derived automatically as ${TARGET}.${HOME_DOMAIN}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
FQDN="${TARGET}.${HOME_DOMAIN}"
|
FQDN="${TARGET}.${HOME_DOMAIN}"
|
||||||
KEYTAB_SECRET="${REPO_ROOT}/secrets/${TARGET}.keytab"
|
KEYTAB_SECRET="${REPO_ROOT}/secrets/${TARGET}.keytab"
|
||||||
# Temp path on the domain controller — use a name that won't collide.
|
# Temp path on the domain controller — use a name that won't collide.
|
||||||
|
|||||||
+133
-115
@@ -5,211 +5,229 @@ sops:
|
|||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrMGd3ZVNlNXdmOUZMUzdQ
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJR3NKa2kxZGRHd3RWdWRR
|
||||||
TW1acEs0NFA2Q01rM2dkc1h0NHkzQmhiWFZZCjMyK202VWdlaGhsZW04MnVwUVdO
|
WDkreDcxZzE1RVZ5NFFUN1pLUktzVHZSUVd3CmJkMzVsRVVraGZWVEZLWkQydVJI
|
||||||
alA0Q2FETThsYkhSS0hKdHBaS3VaY28KLS0tIG1Gdk8yalREOUtIZTUyY2p1UHlJ
|
NGF5Ui9ranRZU1lqS2RKZzRmWGJVYmMKLS0tIGdIRlRMOFBDMnNnT3hZdjcvUVA3
|
||||||
eG5iQnJsaTJBY3Y1dkw1c0VEaDQwdDQKfV04fLy32Lp2ZQ2VnvQ0h/Vsf+qdaJiv
|
cUxXa0tjWWk3K0ZTNUx2OU9MRHpobzQKMZLQH+z8o27s1bAXyJI8HD8jHnU5JaZV
|
||||||
DnLXGZ9hE5yzpKWkQIRgqYGBkF8PkH0YC4OIaVkA53wrtjqS4ZHR9Q==
|
WLHwwptGYz7pYyCkWc25IkA0nR3KR4lfHT2o5eGn2BBivpI5o/QQMw==
|
||||||
-----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+IFgyNTUxOSBpOVVzNDZvcE1Kb1Zua2pF
|
||||||
cWl1ZStLcTRFZWY5VVI5N0FhODZvR1ltM0FvCnBHZUtTUm9QeHNlbVBoZEx1V3Fa
|
cjllRWZkTDZUb0JtVW84Z01OOHF0Q3JsZW1RCmswV0lxUW02NEZzY3lWeHNXQ1Ez
|
||||||
Nk9iMmJKVnhocEpERi9leE1ySUtNMFkKLS0tIDRRYkxnbU90S2RyMHdJNzRJNXBi
|
K0VILzFhemZjTTFLbEpVZVdJY21HTTAKLS0tIHVqZ2E5aU1NVk10bEJ5UXkvMVFK
|
||||||
QjRmZFhVakVic2tYODZHcWtJRmNQTDQK7G8eSJInt11P0DiL9uzNQ/ZHHLVNIYPe
|
dXdSWEx2OXBSZG9aM0VGNVQzTjVRUkUKYgZjGG7I0ea9I+gG4Ah1VSkiONNAHdDQ
|
||||||
bvlhuGkEuQ/+j5sVSKOfSI2Y7CvM7TpE3APyKBcLG3ajYg6F/Ev3SA==
|
5zG9LnKQZ5faY4tWsX7JHFWDX2Y83mXbHV6jMCutAfQG2zt/5psTMw==
|
||||||
-----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+IFgyNTUxOSAxS3VuaHlaVGJObVNPb3Rh
|
||||||
VGdxeXJVR1lsbzNVZHlGb0NGOVo0SndiakVjCmg0QlNnZDV5RlFja2hCbVRXV1VF
|
UEdBMk9meDQvWi85NVRHR3dXSlhkcFZVa1RBClZDR1MxOUdwVjhzSjFaWVFWelVX
|
||||||
S1ZtbC9KU0U1ZW9zeVoyR3hxNW1XTFUKLS0tIFUzWTJhTzM4QnpWV3h1OFU0N3BK
|
cUgzMWNrUzl3L1R6elc1ZTRGamxDU2MKLS0tIDFwUUJFQ0hIUWNTVmFwdGt0czZi
|
||||||
RnF1N2k0S0lIVitoNDJLUmZqdHRzZVkKUfNg24p8zxb3749v/A1BOKCNw75AUKpf
|
V3VGSndCT0tQb2Nzd2w2TWxBbEFBeUEK8kQQVqISb3h0snOtqM0w/mhWEpjIxlhk
|
||||||
RUmFCw5DDWF2aNM0mZqcjjVmJ/FRKV2HXwwUGsHPKSOTnKfOUlPNKA==
|
N84QzgxhD40tvutfO+57HsfOIqjKi4Yhdmbd8+iW9GzGnrbfrDuVGw==
|
||||||
-----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+IFgyNTUxOSBoYXBpZlpjT2oxMURTcW9t
|
||||||
c1hkaVhjcTZXTUNsWS96QVVWb0RVQlZ4VlNvCnE3TFRySU5jTFk2WjBONUQyQUhl
|
NTdNWkMyT0d0SVBCSFBqVC9vNkxYUUk5Wnd3CmlyNUhIa0FmR0lwSFEwK2QzRjlO
|
||||||
U1lNTDFTRmZhMUFyZmpVY2xpaUVxRW8KLS0tIE9MQ1M2U2ZXQmRCVll2UGRWL1RG
|
ajNKMitzZGlDaXpRS1pNNDhhaFdPYjgKLS0tIFVtSjNXSWdlQTVwOEUzcjdEVExu
|
||||||
K2tnU0NOKzU5dkpiekF0Vk1VM0ZZZDgKK13aFypGAqrKWPOr3UwtXI1EoXf1+UzS
|
aklINzU2QUFsZmxPaUI5aFR2Z3V0bXMK/rDuNYW2r022tHjuk4KqIEOxqvjQsBIg
|
||||||
rBqcwnX6WPxSKUwWoins4Aojek4QhbhY4R5ei6rRS0KEQeryGxy8bg==
|
D2lG4ih5h3idm++gnhCSlP/9tKSaVHoq+BpunsmFObrrWzZRhKI6jw==
|
||||||
-----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+IFgyNTUxOSArbkhEaEhoUk1SUHNjVm5C
|
||||||
UzNHV08rWURjY2lqS3FTWFdoc0FYYTFjb0NBCk1mZ2JzaXk5RmExNE9xWGZ5K0pv
|
Q2svcmpselJ1eU5qMVJKV2JWV1J0eUpSWlV3CnhIWDF2NXFMM0VMbDQyeHdkNDJV
|
||||||
UEtqMkltV0dIWll5eVBUVVRNOUNDWUUKLS0tIGpKNVJudUM1UGNvaGl1UDBOeFA1
|
aForNXJ0UFd0aXFGZzZhekNTdUd2U3cKLS0tIFljclNTa2I5RHJTUU1yMm5XakVo
|
||||||
RjUyRlZ6a0Y4SXNsL21zSURVRk9KTFEKU1L6BQ6ZlYQQtqx3uF/uM5CQ1ercmvRT
|
amlHVmZzSDZRTndFSEpjQlVOUTVhRVEKz4Tx0PceUCxw8MsREB2HRPDyC/lIskhs
|
||||||
TL3r2/Y07gE7CjRn3pR9z0co8KndGzxV6YR+ubyWptwBS8KQh5stkw==
|
/HWmseMlnsoEMu8E1OIO0TpWY3qowHrdFFr4njpxMJUTXusGvRK8Qw==
|
||||||
-----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+IFgyNTUxOSBXbm1jWW1DZDVNdnVZOE5E
|
||||||
V09FbTI0WGtNbW0yclhOSGZDbG5NUTNaTkFFCitkcjJ3OE9BSnN4bjFWcE9nYVBk
|
bTRLTzRhckVMNjV5VkMrcllLZk9NeVRiT0NrCkh3RDRicXFTQTNqSmw4VEQ0QlNt
|
||||||
ZzMyVHlJQ2wwdU5JOXdCQm9oNkhNd2MKLS0tIE9tRzFYS05vSkUwWFRkaTdtc0k0
|
cGJNRlFTSjU5YWFXWmM1MjBjVGZSeVkKLS0tIHFPOERUTGVOVSs5U0VMalAwUXcx
|
||||||
blVoMWV0QklBVkluT0Z4NHYyS1F0blUKO+Uc0of/V77ZUZOsxTzeH8/LmmAOQt+J
|
QTYvK2RScDErVENFdUhpYUVsejBYR0UKCNBN3n4q2X1YLturxpjDv4IlnLXoPtOM
|
||||||
x/COHxnLCnZ4eWI6q1a0Qn5Br15OJYTxUI2QTV4goTnXBNUDo9wdpQ==
|
DqXMNEgGubagOBhfVFrOjpFxonP7JThSfzEJT/pmL+S7bCvgyosYXA==
|
||||||
-----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+IFgyNTUxOSBXdDRCMlVyaTdPWFQ5VVhh
|
||||||
Ui8rc1NoQzNKSHVzTk0vYU1vMlRxcjBKZ2hNCmhLYXVGSis4RU9HNGVxZkpvUkd5
|
UWMzUTBkZVYrSDRQWUpWLytDeHVmcVh0L0g0CjVIZE1jZHA0N3RtSmZKVThrcmt5
|
||||||
L0xvalhDYTQ4N21OcHRheTlkaUxvTkEKLS0tIFVkRGxtLzhQT0paV2U3ZnNScVdn
|
N3VrenFGTS9LUDdZRHBiNDhORE4wa2sKLS0tIGdieDQxQ0lWZnVMZUZzSExKWEJU
|
||||||
alZnaVppeGI3OUVscGpONkk3YTRXd3MK61na8x5qX7+dyMHasDz2dj7yeaUlX8me
|
azl2Q01YdHZacDRSaElpZ0h3SG9xcGsK4s30qRC2eXbuKqPUHfRUJrE8FCMdz5EQ
|
||||||
N4/SIk1JDBhv9G7mdKLbKhSF1UJrSY7TJqJqx8/dqEc0uG3vptA1ew==
|
25UhdrspVBadt0G92hHV06Uwm/KKnG4Mi5crLKIMI+HAF+5Uxrh/xA==
|
||||||
-----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+IFgyNTUxOSBwL2xrRnJyOWxRT01YbHI0
|
||||||
NWphNXp5M0dLZXhkZndhT1Y3L09maytHazJVCm1MeEtMWXg1Zjg3bFVnZEorci9J
|
OG8vU2xQaEJySkZkQnp6ck90dmkzQlV1RER3CmtGc3duTGRxZXhlbDlHd2hGUDdH
|
||||||
bkNZQU9Ta1dDTFFHaGFWQVBpK3pYRDQKLS0tIEhPVGliRDR3ZTF2aEl3ZnJEYWtR
|
QmxYTjBVbkVWRVlIcEhpaml2MlVzc0kKLS0tIFRWTnZGQm94VDhJK0J6SS9oa2FB
|
||||||
anh0SEpnVW8xdXNkZEZQSjcxU1BHMFEKVRJUA71fi1QawB2TnuTWMYhzQR18u4M2
|
aGpJUGFydUhvQlpFaURWSm9VS1AzM28KWvU5knwB/ViUrSxbP0zPR4iUE4LXxYi3
|
||||||
s1V4j4TwYyyKZFoNvt8kOUayjC499c5OBUufYs6G2ciC6gK2A9E0EQ==
|
qraWrbv3jXZwu4Sgv0H+/k6St/Xo5RU+nIqSOCKEy/kpACwk4BhMXg==
|
||||||
-----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+IFgyNTUxOSB3K2tIY25TZCtReTQ2Zkc5
|
||||||
aTdZVlEvYkVRbTNjUC9ZRVdNZVhPQTJEd1ZZClc3NDJiR1BVYkZkdVVxMVZGc0VN
|
d2w2aGZqYXNBSmZ2VVc5WWtZNDU4YlJpNm5VCkpGUkNOdGVSeXU1WG1YV2NQYjhu
|
||||||
dWdSSXBFR2xxR0xrV2thRmUwSS96S0UKLS0tIHRRTXVlUi9UYnFRRlhsU21HZVY4
|
c2NKd0czSm5FUWxmNTdHVGVVWGlUMWcKLS0tIDJmRy9vbGJDNGxMVDM3b25TRVVI
|
||||||
SDFYd0NwVEtVZXNsWUI1a1ZZU2xNRGMKuQUhOq2FRD+PGn5OkdODZItbxCzRKjne
|
blpZTkZsTzNrdmFwUGQzR2RIOVJINlkKwa06bE6TmYtXPx0daHadbwL9/u1iYuGm
|
||||||
E60UOYtHjanuGjJ1svuR9cYsLZz7lLOwItklecYaQYpMRZEwzzBGCQ==
|
n62kEwTKdyfncODl895qqjWkiA3JnbwQxzsaVDtEGfauzUph9DPY2g==
|
||||||
-----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+IFgyNTUxOSA1ZlVxYkZ4UFJmRVBHeExi
|
||||||
SFJ2cWNUY0E4b3FndVA5Y0hIOEZnZUlVWlhBCm1vY1luOXZBelRUTmF5Y1NMeDBn
|
OWZ0akNwc0dMenlHQ2dUSDlERGxSNzZtdFJJCnRYK2cwNHJRK0xtY0NnYlh1a1pG
|
||||||
cE1BTDErc041UjJCWTBQbnk0Wk80dkEKLS0tIDVSZzd1UktvZGdyanFUMkVORUtl
|
aTk0WURSYVdUY1ZTSUhBNVpZZUFyaUUKLS0tIHZKTFZmS2ZsNlZ6UjZUWmhNK0Q4
|
||||||
eVB5TnJkMlp6dUpXSTlxRlplZ2NxUlEK0AYOxIbswjM0SUASDfmZ7PqcEU844fgI
|
RC9TREh3ZWZRTzc0WmFkQm5qVlZJcFkKUngIUXCVV1CKpUKfAlal1KjoJwk83mq5
|
||||||
ycFWVSEPodwUZ6UFoYXhHlJzHFcgpLvwUd1PMktLHe1qrZ7GOQJIMA==
|
1MvU9mjx8Oq/suUlW4axFfAO99mUc9yCwb8TCMUZDhQp802v5FjBcw==
|
||||||
-----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+IFgyNTUxOSBRM0hrc0I3UCtBOXJWUm1H
|
||||||
TG0xc1JPZXZCZ0tZOXA3MGNLVnBlZGVtRFRzCmIvMHhQKzFVWCtpMTQrQUhGVGJp
|
MW5QWHAwV3lETFp2TGtPWVZHZ2s0ZHJmQ2w4CnBlQmlTVmEvRTlLMjVXMkRJeWIw
|
||||||
RU1jbldYckw3TXI2SlNpZVBIZHRsWWcKLS0tIGtJTUtJejFxem5jajFQUDFTQWU1
|
QUR3aXpPUndocCt4aWUybndxK3RUN00KLS0tIDJrNkY0d1Jqby9pa0tXWVVuTER5
|
||||||
VnlxYmVlNG04ay9ETi9FRmVYQXVoRkUK9oFNolI7jRjo9RUs1g4ghrx7aYV4U/ce
|
YTlVNklTakFlaGQxdjJqRks5cUZoK3MKkV199oB7lPBzNYd30nSY0J6Cd09ViBA8
|
||||||
ZTc2tFh57+7aKgrDi+2W3jwhfkjvBsThk//p5mLlqEEgw2lwlnhvPA==
|
uZWlmMGKlveBQ98qOt/vDTouRDxedASz0ZGg7+jxUXkUKbujkCYYXw==
|
||||||
-----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+IFgyNTUxOSBwVmlDOHh6dUFaR1YxOHJM
|
||||||
QnZPeXZHK0tXYWZPNytmYXVsdGRWQVI3RlhRClFVb2I5OVZzZFNrRXFaa0JTUkRJ
|
N09LdWxscStGSTYvMm1QaWRkZVVod1JTTlJnCkp0Z1pSa3lVU2JqUnU0ZnAzWWEv
|
||||||
OC9GQ1V5K0JhWlhkUjU1WStCa1lPV1kKLS0tIEhzdnBBZkRnK0NtV1FuTkVsNlgv
|
bHc3S2ZjK3E5Z1BRL2pYWklvSFoxSnMKLS0tIGxTd29MK0dRWFFnaGVwa2NVQXM2
|
||||||
QzVEcEVkQm5NL0Z5dUU1U0ZFaTJITnMKaWE9vlrOpQstr6FGP5ObdilsCYk4kYAj
|
TjRQdHRZNDFTTWNWR2FvZXhQMVZORncKdAcSOB50bNQsaTGtAqnvjYyFpcjdMGzO
|
||||||
/phboR+Ym7QDTyUF9LZXJCU54YJp6vEWkRnlJFqC75UW/v/lgBhBMQ==
|
NvwI5ZquoT/B37Xsg4NQ7++/ZkemzkuX5VYoXLVAQ9WJwQxewoMFKg==
|
||||||
-----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+IFgyNTUxOSA3V0M5bnBzamdGemU4Y0g0
|
||||||
WndzRGRtbkw2azRVWnBuZmhKWElqRFk0RENzCk5DMHlMVWpwbXEwVUhkaFZUbkp2
|
TEdZdzQvUVpYSkozZlRsMWM4MStmN2lxckYwCms0ZTF3cTB5OTRzWWE0a1FXWkhG
|
||||||
QXNlZFV4SjBEdmR6UEw0N1JOUnhNKzAKLS0tIHo5RkNDUk1ESWRHQmV6bzkvSTlP
|
SGptVCtiOHptV3V0aVNQUUJ4NkE0dnMKLS0tIEEvK0lwa1pnc1YwWXhkYmh3cy94
|
||||||
dk1GQ0Y3V0dTRlByb2xUOERVOTVwbVEKY4sAHyAhvGSYJzPuufWUIQD2xZcSt/nX
|
S2E0SUk4RDdSWktQTjZoTkNUallLTVEKNqTmhQo74Q00ZhrSrD+/JB3TwxL9OQdC
|
||||||
t2ZFXu891/QdEzyUXCIzdwAV+Y/LjvroIlCp5Hkbrk0s7N+ghqsB1A==
|
XSONu6XXzPzfhllNIwe3BnmRVJM/1ru8rvllk98Cvle0k4K5+xBJ/g==
|
||||||
-----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+IFgyNTUxOSBKYnFtM29UUzVRYWpTN0w2
|
||||||
QWpycXoyL1AyOEpZUmtpbjl3MFAwTkJoOWpVCmVDd0FBUWxaQmZCU2VmNkZGMk9o
|
MDVmQk83THEwR3liN0hmemxBZlF3eDVZV0RRCjRFcjZKQk8yaXViWm0vTUdrSlM3
|
||||||
TUdLNGtac2N4REg2eVF1eVh0WnNaTE0KLS0tIDJvcFErSjRiWmhPMmpadjROOHdt
|
UDNnemoyMG0wbnpzK0FRYXNiRTRnaW8KLS0tIHd1QVYvWWl2QlcxVGZzbFBLbjlu
|
||||||
NXp6Y1JpdHFlSlRoa3JTaEt3emdnalUKjoFfZAiKMPF3noX+K0+vc3+p/XUHnhic
|
djV4dVpnQzF6LzFiWTREaTYrMHAreUkK82l9Njt3B7HJBLtdLjNSnQ4TDTvBgG9/
|
||||||
k888KdUwcZYl2/dAIc8UDSggbMnncJAJgoezoCHLkj97GNNAD7E+gQ==
|
iUEn3fd9OmvfSGcbp4hhwB7q0UJqb7AP47ctwDOEg/FYeF7eSBvVgg==
|
||||||
-----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+IFgyNTUxOSBoZnVZekVieVVRT2ViWUpN
|
||||||
SEszVEwrUkQ1VVRsM3pSTlRQaHVLN0VuSHhrCmN1Z3pwNlFsbDN2UGI0KzYyallM
|
bFk3ckJUU1RHRUJrbkNkTUZkbVEzRGd1VWtRCk03MFEweENBOWtOS3gvM3ZpNTF2
|
||||||
SHJ5eklQeEIxSlhiYW5PUlpJcG5KNjQKLS0tIEk0QkpMdlBlRjVYMmJaMzJUbDNm
|
WG1UWTM3OFp3RERNNC9iV05aOTZnWmMKLS0tIFZqZ0NhZVhxODMyWENhVlI3SUR0
|
||||||
K25pZldwd3JoZi9vdURoa3Myb2RQNG8K6N6bO2YKooPfpKihgsYqilfz/yAYCLZD
|
b2doaGp1VHBOUVFacG51Rk02d2o1MWMKnNYktA42DgSWWGuy0XTOtUN9ry49WOBd
|
||||||
XJ/THgT4URX2VNvSspvBtN8luOiJUVcchp5WtL2m9jARL5txEcDorA==
|
NaGq27ke16XhQ0ZC+r2a1g7YAFaL+qWJxpaH+0ojhUjti3x9O3pWqw==
|
||||||
-----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+IFgyNTUxOSBRTkQ0cm9MSTMzZjF5T0Vh
|
||||||
RGVzVjRmWWV4dnJzRHdsaW9ENVZYck5lWGtVClNtd3ppelowRjZpRFFSMC9EK09n
|
TWlOdVJzZ1JvQWdLTzZsYWZ6djJhTjk2dnpFCmxUZkhrOW5OTWtrS2FLOUdsWm9F
|
||||||
b2hqNnkzejdrTnhYNGNKblpteDNLRWcKLS0tIE02bWVjazRWNEVKbURITGlQODlR
|
WVB2VzhTdGwxNzZIZ2lSRmRielAzZkUKLS0tIEd1RlIwQXhocmJkVWtvVGMvTlVB
|
||||||
cTJJVnBVdGIrdzBoSXExelNrVk1XcEUKc77o2EX7PCm/HjUo5GsUiQdm488WB2mg
|
WWtyVjUrWjYrWDFmc3VtdzJUaEFPeGMKvWONIbN6B9Ims3f0l/lfUTU116TFPXew
|
||||||
wHd/qDbQhF1W75RrVTuIKgtEtrRjZqpmr8toe+aHJizPofcrToUfzw==
|
K6kAvyySbh9Z9JK8Uyp31WLSbqVy56eGr2Su8Hj6rAGIoJdo0v5NoQ==
|
||||||
-----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+IFgyNTUxOSBlSWtxbHJsQllITit4NVZv
|
||||||
MVhTN3N1OFpkcUh0a0tjYzJnWUlINGNVd3k0CmZkTnBac0l1dllxazdLY2l0Rzli
|
b2Z3YzJZTHFOTGU3VzkwWEFZUjdWTC96UjNVCi83SkYyaFdoTVN3eFhLQnA0bDVa
|
||||||
bE9sNTBVSkJNaWF1T3c0WktoOHl0NU0KLS0tIHEyeWZTUjdQeUN6U2t0d3JwNTBX
|
OExZRFl1M28yV0VTUC9hZUJJR3dYTWMKLS0tIGNuc00rZDI2dHFqWEc2R1ZVb1E5
|
||||||
ZE1Za0tXb0gwc1FSakVYdU9OTHkyd28KkwmlzSYP8XofB0VGag+S18+S2TyQjLrM
|
Vm45YUpvWlMvMXUxdDB0NHpncWpIMjgKcMIZK2ww/VPuuBXlL8klSTm8ySME2njC
|
||||||
qaXtbBtLzJGNDhe9FhAKTPFcjTLWbohlG69vxcImyCyCns+QQ+gvug==
|
vbhCzg9VIqIV3q2i8WEbWFmo7uyiyF89Z52xf/3uUc/TUEqjVVDnZQ==
|
||||||
-----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+IFgyNTUxOSBZRkRvN2dBZ1ErZmNhSktm
|
||||||
VVE4R2NqYlZWZlhKSTBHRks4bDNoaUliMWlJCm5FbWdZS05GY0VLc0sxY2x1U21V
|
LzNLdzJLR3RhR2VqS0xCdlVSVUJtRnpLTkVNCjFmZlp3U05Vb2FwbDBBdmhjVUJt
|
||||||
eExwK29GVVBqYlRPZ0l5RWVXRFhRNlEKLS0tIDl6dVZJQndwVStFVEJnRHRyMW1W
|
Uk01dDVzS1lWeEZueDlmcUw4MlFFTHMKLS0tIGJ1bENFSFZPa0tNNFZsblFOM05x
|
||||||
NnFqc1F0SGJqT0xmREpaN21EdnlJK3MKRPE5rfFpVnH5wAOkuB5pNMlMd3omcpku
|
THAyY3draFB5eHpydk94Um1qU1BZb0UKUVszMlSqUPsEj3vVseGksI+SEEjEblNo
|
||||||
do2hFZwyI7t80jxF4+g3J7EolOx8AGjpc9Ba7Gj6IMDjye728q5N+g==
|
zK5fwkYH0aDubsotkVcJjoXc2ZArHyygIMImLNpRBJYIScDGzzTBRA==
|
||||||
-----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+IFgyNTUxOSBic1JIWi9xKzVkVUtqdHBx
|
||||||
SmdCSkJpeWlVWDFIMDhwUEkxL1RUTDJ5UG5FCjhKRDB6VGtwTVozdUVzbUxGL3BW
|
OVU1djJDbDcxYTFCSEQ0MTVRaS8zYm5QWFhRCjVNNFE0RUtPTlE5U3pRTlh2eVNy
|
||||||
SnR3cmpSN2RxNnl4QmNvT2lkYmtoVFkKLS0tIHd2V2h2Wk5xOXlISzhjVzBsVkhz
|
OUU0cnpLNlluemdkVVNjWGxhV1NTRUEKLS0tIDNkWmhoSTNkWExWek4zdjduaFBB
|
||||||
VVpRenVnSVpHUWJqV0JHNXNWWXJOdW8Kv7PJSTDbwFOAcl7pynALaJiTXU/87bSF
|
UEJjYnZwYVN3L3p5Vk9sRGVQM3dld0kKsFjQKYOClBjWBaU3kCEP5yYGBphUfgOO
|
||||||
F3HQllYOwOoibGzBCe18H2N+VxyNxoQL9OWe0TvOIR6bgHFIIF0/Dg==
|
E1epPU6UI3asa2AC/svfFBZI3u/7EpxDH9jaSSpPec4QTwkheiVVrw==
|
||||||
-----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+IFgyNTUxOSBEb0VlcGQrWnZHWlM4YjhG
|
||||||
cGJFN1ZzTDFVNEdneVpMZ253ekFJNjVtYkNnCnRGQjU2Q3dsRGRFV25LQ3pCbTJE
|
NlN6c2I3UnpSdkhYTXg4NlM5aG82K3YzUGwwCkdsM0JLUm03RTk0cm5SSitzR1ls
|
||||||
OTROaFBiT01xb200S1pUK0NYaTQ3R2sKLS0tIEdiQlZTbi9Vcm0zc2t6bHplZktF
|
bVBLZ3kzaElDaTRHR1Q3bjdXYm9BMk0KLS0tIEpHNE9MTDJuMDEzV002YTVpT2J5
|
||||||
ekRySENXcjBuR2psdHZSSUJrR0xUdjgKvBsmnC+cbq5TUDFjXCyImIoPKvh8wsjE
|
Rm9nem5FSnA3M1NzNi91NnBtRi9XMmMKaGB7uE6HCE2cYHfI1VscO8meeq6O5JPi
|
||||||
7Shk7Act8Jayrhx0lXBDRmfpHRrB4L16rDSmqO0DTE48VhT3TiFyug==
|
7bTansMUKCPKhDTBYlSnxxjlDE3DRB8ZdxW7LiG+8iVI7HUbsjTejQ==
|
||||||
-----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+IFgyNTUxOSBlSnYrVVFObDRmUE5UN0xY
|
||||||
cHJVWWY0Yi93cW1uZi9FWkVONmtpRWh5SzFzCmtWYTRIY3BkTWU5R2Jsa0ZJK3kz
|
K0ZIMzNtUWM5RE8xMmlhTm5pcHNEemVwYVZNCnVLWGRObkxPcWdaOGVMcExqRlRw
|
||||||
SnZvZ3YwaGtoMVZ3V2laTlBBK3UyTmMKLS0tIFdpZWtqeGlacjlLbmFySHlSUUlj
|
VngrNGJFWXBHZGh3YXVBQmtSZXBsTzgKLS0tIFovVG43V0FBbmRJYW1qRGJCRUZp
|
||||||
RmRqQWVHK0FUT3VDbFhLbXQ5WDhLeEEKcDkgV34lUFJRIHRoLB8F2IOvGAM93sM+
|
RENmSmFuMmhFV0xHUHB2N2xWbHVvSWcKJXaXCfz3Zh4SRgTJkMuPabOq3laRorIo
|
||||||
AkmaM4+WRcGeYWQKMG2x6cYCUKFaT1lDXuWZ9kI8Fd7b9gTSnQMs6w==
|
F4R4bJDkao5L5QWDH1BWSi97kTTLz3QgmsEGPHm/SpODM7Llx7EPMQ==
|
||||||
-----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+IFgyNTUxOSAyR3VNVisxbnoyQXZGaTJV
|
||||||
MUxYMUpZVjdjR0NGTzVteVB6WFBQeFI0ZkZrCk55TEt0Zjdwbk51RnhYclNzam1H
|
Y0lMemtYK1F1QjAyWFVqSm9VRzhhbHRCRFRJCnZINVpiS0ZCRXUrVEJhcVlSZk9H
|
||||||
cExKZXQxWFVLa1pDNFpkcGZzcnl6a0kKLS0tIFJ4ZEdJc3JVaEc5RU1aZk1uYm1l
|
TnhaeDU4b0loQjdOazJVbkpEbDZ4K3cKLS0tIGFKRjd0Rkh3RTk2WG9uQUVpWU1K
|
||||||
d3RHS3hHSkRKRXFnN21FQmh0TlNtNmcKdc2G/1dhTJen6iT9kUWZM5OzCmDVprgx
|
c3IxVmU0Tnc5dnBBWUZhYUgxV0NaaE0K+00bh1AiHdTL3gsA98fvFI2/IDsnWiiK
|
||||||
WN1Bl3JzYhLsNKn794887bVAICVqbXqkdpEZztNIS5n/Rw6geKsNvQ==
|
3tOBK4UB5OE5PRupqVXlpEK3ZqKXju/MQbu0/KdaVwHxlL+WXdjAGA==
|
||||||
-----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+IFgyNTUxOSBjSHFHbmU0WGg2a1poMzRF
|
||||||
YkQwTjZxb25tQ053Ny9tSW4wNGVYYnlIaFZNCkVvOFNPOFkwQzhYZGxjb0FOZzJ5
|
Ry85ZWZCYVdVWkVoNnRWWTZXWWNqQURtendJCndHcmtIaFNPRGMrblJndElMKy9Q
|
||||||
ZjNXam1ZTWZrS1M4cGhhcHZaT1NjblkKLS0tIEdvL1dDTHpWcWF0S3ZqNkxrQW52
|
NHlaeDVOS2k1OSt1bWs0RlU3a0dJTFEKLS0tIFRrMGJWYThKbVQ5SzdjLzlQbk9a
|
||||||
VlYwa29sZVloOS9qajJWQWFzY2FKRmsKy074SLdttogXsWycaFX8xso4ek7Cbjph
|
b0xlZGFvTVJGRjZHQk5XM3VObFRnaUkKMavcISNlQh+5yHpA1M5JIkQEF2qasnXH
|
||||||
MMEhZd/svmnSiYM81nmeaze7qXEUcsZXuSmZCYATTBEGtx/Srll8aA==
|
Pd/JWKhnvk3Lyd45ZBJEqFV2wOknJF7v4Z0jdbo4WiUaLh5shqOp2A==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1zhfyuzlq40reuqlr34gf77852nhs3t6mqfzrqmas8z6sxk7tcfhsungrm0
|
recipient: age1zhfyuzlq40reuqlr34gf77852nhs3t6mqfzrqmas8z6sxk7tcfhsungrm0
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3ckNVU2lUQS94RkRGeTFo
|
||||||
|
aWNJRTdpcWxUOWZEOHF5ODIrRkZ0MkpnRndBClREbDNNUW9RZWFSRHdON3pOT3VZ
|
||||||
|
bUlFZnFGTnJpSzhFVjFLMVhKZTNDWUkKLS0tIG44Ui9YL2RPYzJLck1UVVBiRTRJ
|
||||||
|
MWxURWJlVEFBQ3RSZzZFYWJvaW1FVW8KX8/o/4LP+Wp/qOfF4wt7cTt0O+kAZbWR
|
||||||
|
Xu+dthboRmZBV21zzrEvzKGRFBj2T3EMyGDqqPcfX/vuhbVEJJgZyg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1nxlnrevqs2msdatze562vz6ym4pgydt2zndye83lwqauy6flggtqrevyw5
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYck0zNDJaMmhLZUxxVUxR
|
||||||
|
OGJqdTBHMFpzb2tQV3NsUVhPc1JTeGxCWWkwCmtWeWhqRXJFNVRoVVBTcjJtWlM2
|
||||||
|
UmQ1OXh3SnJVQWQ2NnloVjRCdUZnaDQKLS0tIGZITFdwcjJFMU5iRHhvRllKaTA0
|
||||||
|
dW1xN2ZhUmZ3VlNzWFprYWNOVG90aVUKH8Gd6gkbDIZydnb1mL0tpujmucLhbPbm
|
||||||
|
sUm8KZpNUfWpP+t7SK5M91HFaAZiqGJOARb31oszwqLGcNDliV40MQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1scfc8p53q5aq2a87tcmsazmj8sfeft0s8kxg0et4nm3ucxyp3c3s6te82y
|
||||||
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
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data://9IEHIVCfHjyMNao5sCu2zNlZ/CaW+JyxVpGpD/vab2qvURunCUY7eMfSOyvOx/2WPXnWWlkoVJPCR1ec/yUg09EaSMfxrvqlu4UJI3Sxvu9xNuDszMwMMD/sCulJDiMWFNLp8qaYt7UGzexp4+GlGiqWDxk3ZEu/iLmSApzBrpciTF0lfehT4qblDovo9QXG2KDWFhCt2SwEKmHJ61Yl3pVAQnPLyTWaNhwWD/mYL76mIiDVKq7DJlvi9MBxzi3aYh/ttuHgRCvnCL0C9oUvOyT2cljwra0LXuOrum7FhPIXXboA7WTEbTHJm1LB5yLfxDrnWCrGxQzFQAwNixVHIcjuvjjvA/LifTvbj5FYM8x7an+DwXPfPhruFrDew1paAWsEGNNYXSmAlju2QLI/OwLHFFLuDyLnBKQ6RLtZbCEAOUKf2h4iZj7nH86eb/aGU=,iv:rj76+MJBCpiYyx2Ogut5UxJj3Gn+bygvu2mtuY5hFLA=,tag:yQmTvWVu8ZLug/7fo6RnwA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqcURBS2tIMGl1aWxLMHFQ\nS3ZGVTE0cnYzTmpnQ0daL1F3QnlpemRCMmtjClJiZzIrc2syN29sRXVoaXlPRUNF\nakw4RndmbEduTFg5ZVRKUTVwRWpGRjgKLS0tIHZ5ZFlyODlGWTJoNGpXR3RhY0ZH\nOGpPdDZQVmt6UWZXbHkxQTBoeW1JencKbsfH1V1lUj8mmHyLNj36VaRDgaBojcDU\ndoQWmSEXxjticJqdadbVKb3UABpvzAZxASCy81sa3wH0gT+7zLaNyQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrbFdaMmRjcHgyV3Y0eWZL\neG1uMEtpQVUxQmNTU0Z3aC95Zi9uQlgvMEhRCmJiVlRTR2NocGlnbVU1UmlLVVA2\neERiMXpiQlVPNVhRU09XVGxrY0Ixa2sKLS0tIFRuOHdoelJxOXNRRHBJNnlhSE9j\nUUZHZmQzOVFwRmhFV3pvdnpRMTMraGcKRHBuSUpbHaEzH2tuSBE5MsLJDCuH3vUx\nO0jnDldCWkCw7Wvr/tQAkaDI8axZcYVDUkCEk+xqAdxDozLCPhEO6g==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age17e89ty6p0fw24daanen57wg8uald9s025t3wwxsw269svwpmgvrshfvfvt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZMW5taXh2QkJkT0JjaVpq\nQlBjVFpFYUhXTHhOT3prbFcvUFB0YnRGUzMwCjhHT01lY3dPWDE2dDZWZnJza1hN\nS1AvZWIvdjZoYmRjWlliK1hiOEdrT00KLS0tIDljWWY0NlQveS9VR2hOSUNyTUtH\nK2gvbEVibmZSdktPemdEQ2p5U0Y4d2MKKitoTi2vbxJ41IoWMlj4vO91Ahpj0hHP\nrKCPyx7ws/IUMmKGyvDLpZ3pYqHD9jl5pLfB05Hh4Emhv4lA1qtwwQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age17jqc66x9yeshfgd9v78mj483r4zzarqdtuxtrkxe4x5mw679gphshd94th"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3K2dVV1BHbDk2Z3FhNmdV\nZEN2Um94K0pma1k4b1V5ZUJxQ1hWU1grZjJNCi9iRVVqOVpEMmtBUi80NThlYldY\nTUtZOVErT2VSWk43NndpVzBlL1lQaTgKLS0tIEpCM3Q4NjM3N1lpUE56N04rTXN0\nZkZ6TkV1TU9OV3dpc25RNWRpVnprM00KItUzKBdShakOfX8Sr+k906nsvYPl8QLb\nge//1GA+ukGsaS9rcChOY89vFdm61JDmj1jXSJ0CN4wLMW9/eblZRw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1arhf2q45zw6wf2uevju4savp575x3m2tfvved5zzq3ay92ynua9s3cm92c"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:28Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:efFXIqbauOURR7lrVpK7kqRIPgYjgWfenBYoX7mUrQ/thFc+ApcAx58Fi1zg4biwIs7NarJAgDqAxZi+yKb2Sll8H/wlsEjWDU4iQlLJdIQw7wey9eDW8pgBLd+6E7FXrgn1Vh49dS5GXlC6clAYk1lCiB4NvfzPDy5Ktpl+Chs=,iv:+zCqj5I1MLJfRRiIrqgocYB49PZnlV45PUTH4gYlfrQ=,tag:WxY1sF4kFOTEzbSFcfJFbQ==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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:olqZm/3yxtVnlDeYzAWnXjUP9pgkruFEpeS6qONHSfdrj0lJOch44zn1pbG6Jg5QYcksny0rHki9PJEZSbRIyrZ2mSKfTJing3n9M5ODm9xPMbNYn8cpVB4AzZmCSAH7mN4p/98+5hJ1qzopGwCgKTcOOJ9DMUl9AStXORY=,iv:R2K3OehIfyKZXRFH3Y9KYnvEp6CjHPT4Ki2P1YrC6+I=,tag:1FM/E0n9teARAZ1HZM4b3A==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3RFRoRW1rRklxbXYybUtu\nL1lEOXJBMDNCN1paU1k5N0hQRTJYQ1dQcTBVCk9OYnloOFZad2toOHo0UXFYSzNR\nZDUwV00vKzVnckMwSE9MMnE4RUozZTgKLS0tIFByL3Z3SzJFeEVNZjhuRDNTMzFo\nNU5tZ2tQU1ZORE5qNm1JUEJrTGdzUHMKypYeJz6BeUUY4aPKazB1nxncOA3DGkal\nfemLr9uwCJw+D3xfXzrIKrI0w3OH7bu2LmWqrNDSz3Bwa5VDuHoLqQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqTzhtOHM1Q1lWRXdadHB6\nZ1VrMXFsQWZEbDBMMnQvYnZSN090VnpyQnd3CkdJaHBsbzQ4SERmU0dBS2RZMUpi\nZVYrZmtSNXhiaDNTZkNMVEZud3ZzT3cKLS0tIFhETER5eWJRK05ValFhenRnSGg1\nc1prdWVTVmMyWWt2UkNXWVRLcWRHcm8KjVn1faGmsWiFzcNg4PnxZQfeQONFKz/i\nyJGJc7w2KSZ3La+44jfitMziJQ3AFRUlAhGDX25kWZJ7PtjmFvYwuQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1nxlnrevqs2msdatze562vz6ym4pgydt2zndye83lwqauy6flggtqrevyw5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkVXFQazlvUEx2NzVUNlF1\nN3FQNTdFL1FtSDhCdmNQclRMdG1vRVhvcEhzClF4Ykh1ZS9JekFzOHkvOVphejVy\nd2s0a0IrZ1d4V3diOGNvMlBHUVlwTmMKLS0tIEFhTE13Q2F1di9ESXdJVDhJUVl4\na1kzNDBLcHhlRGg2Y3hSSmdEc2k5MlEKpU2sIv5Pq0LaKkvqA/fRkiB9PcvsSjvq\no4iEFzGNDi63QzXqftWnzengEy/6nWGNA31fzScQftESesGlKfYYlA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1scfc8p53q5aq2a87tcmsazmj8sfeft0s8kxg0et4nm3ucxyp3c3s6te82y"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T06:46:00Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:xJHyh4JuY+qnKljS3mtfN4Cy0pGPUszhU4ud2L8JxTFGUHTXhZzwQ3s1vM88efLTwM7yDK7oBMBUH2xligAzglDnqkE1T0l7R9k+hyldm4nYqOK6Y8GyPt/aald6oLYGCx3J7Ap0Oqq/crU+s4NiY9enf+R+08jliFyrPBHuRSY=,iv:FGtURIfSrf1tRGqpPfn40GqybhNYFBcvR7n1YkcAMGY=,tag:ir0TU7YuxpPuew+nyC9/eg==,type:str]",
|
||||||
|
"version": "3.13.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
"data": "ENC[AES256_GCM,data:4Plrw4SFb7Qx7ofyHwXkHHiCaY+54QT+XLnqoMullnxoTEwf4aZGmjZKx+J5eNnXO/+aPcheg50pe6vXggIgA02a736l3xkIU8mmRdqDSXwROwy7DWBfpcsT5bcL5umGpJ3tnJUAoA8TWVvxoj14J2k=,iv:IwoLgVeWJ+1E1yjeVLjMGEcEDNK0MD1caaP0hcbYJhs=,tag:KPT4mFN2MitDcWAggBV7Mg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIaDJyeGJKeDhVNU9xYzhk\nb2Z5K0VTbnM2OExVWWZPaVBoYkNrOVVwbjJBCnBmQlRsdjdPZHZydU14UDl2Nm5G\nUkJWd1N0SXljTW9Kb2VMVXpETGZlS1kKLS0tIEZaWkdrWkVqMENQUmpQOERSY0hQ\ncHU5anBjeUQ0U2ZFYmFaa3ozSmpGWXcKU8QpWAk7Zkc3N/ZmWn5xLsOSTAtMxU5W\nIQOvdDNzIn7Ecoj4zQ91T9puAhaO6PZ1wWjo+rAN0TyWjM+TAwTuFw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMbGg2WW1nOUw0RzhaNXNa\nbk1iWmRFWng5SlgwY1V1ZXZIcEp1SWlKYnlBCmJ1Q3Z1KzJoY2ZZWi9UckFOTWZC\nUGxZeUlJZDdRci9ZbzhsdTM4cXk0SjgKLS0tIEhWUy91bTZkTyt2ZFRIb2VKL1Rn\nMm0wN2RjUUJ3dHAxQU1nd0VRTGlQbUEKsb6AFoMZw+FxZW1A1rf4YUlNaODUgbS3\npZfGZIocS2y9uMTaXWQ56Y078zDD/F40wLYsuknm0ECVfNysSVacVw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1nxlnrevqs2msdatze562vz6ym4pgydt2zndye83lwqauy6flggtqrevyw5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T06:44:56Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:N95kOsHFH2nKVhr79EB/zILMpJ0rgh+QZ1weYbQJgvxRYjLMl+AK/ALjtx7Mrj9ufOnIWnndrUpD7X2cod2oPrlAPT3c8P0IY8LaJTNDx6TUljKHFe9Nvm/JcnI9i5skioY0X8sRC1oosL0loVTmVb9yaf5xsf+wursIsj7pI3A=,iv:kQLx4FLeiKZIevOOOPS3NAu2NUUhnRw/jpws3FZ6zcM=,tag:rCp0h+wujci4N5VFuzCuLA==,type:str]",
|
||||||
|
=======
|
||||||
|
"data": "ENC[AES256_GCM,data:LbqG6wmya62HfWjTiQad1+k0XwR/iwgoxvvBaCfbY8NsjXYdh8rsl20r9OUFkp4G0DaEjfoNXburz5Jled7OEWReXymt9oYqD4V8JM06x3xwdY2Zotxit13FjcgDqclv7tX3eewgge4F7K+qxXSQaoCLt4VgDswAWX+mz7GNUSD8WnRt/n57VSLKaG/mce53S/hH77sMStosRhy47R8zlKrnmp5T33r1Xz7Y9cOZozxid3QsDp0MGZjYEYjOYkmTLYno62DeTxiGQ1sABW+RpGlZuSyDmk840pXqTk8oikSsfjkEWZH62CYelNzyhVXFe9K/NscX5C8U//AkpHHK/9Od8C4Wts66Q9K3dLIQbsJvuVyx30xEL1MF6FaHlvROkzsbgYN1wJMHw+B0H+gLI6H6UYojg3VIrBQxGHMzajX5UEQviLmaxjHKVsfC3+fFQCXBQkP4fOjoA/WKPjTAT13wz2bYVg==,iv:OuO1VFB4koP1kPRmT6MWIv9gouUGB9LLnhkL+FwRUM4=,tag:1dv+YprSKLTGN8tKM5A4Yg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6S25jcEwwYkEyTkhjSlgr\naWljSXNVQ0ZHOVkxQXgwZDYvTi9YOStsNUJJClB3Qk1hTUpvZVVJOTBpN05vcmdi\nbENuMlhsLzFuUTdoTXhYK2JIZ3IwVmsKLS0tIFZLbmx4R1IrS0pQVmVCdDZPZjBy\na0JIL0MvOXhhTloxZVpBQlY3TWVZemMK2XMLHNm6wDrX9VqIOPErQWIUwXLhhNJN\nlWEQvkZWjDY9PQ06CYpF/B/VE1ukJFtuslzNc3a2yuXMx7BtAhiX7A==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T07:50:04Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:M90tmlmYarfx6UcS3rFedpyMBtefZ802ecouExpGczsWTLtAqDtfdsendiXDgq0aV9D4cCA7KecstFvKAFIICIXF9FHCxHPtHMUWW3RsSjH0N1IIu5oeERSfzR+EIYwE9/bE+bx3dVXRMBdnuGgMXKPt7UatBNBAw43xAjl3FkA=,iv:JfVKxfpk7QYtK7XrdzpHouO8z0nRGgVlxyzB/0JwVRM=,tag:pUHX4zsX4clkQU+TeTJa1g==,type:str]",
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
"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+IFgyNTUxOSAzeEVTTVZ1ZHdVNWNNUW53
|
||||||
|
amN2d1NhQ2lydVNJUXdyaG52ditNTHNJZmdZCnljakFpaW92a0xEbjNZSEhXdDcr
|
||||||
|
ZUN1MmN2dlA0T2lYTVNibjB6UExqU1UKLS0tIGliYmVicTBrWmE3dFZWVzhwUm9r
|
||||||
|
T2dUUUdUQUVzTWRvbzlWSExKRUorTGMKktkw3uuydGeChy+9pxtysHLvssZ5LKoK
|
||||||
|
8i+CNr76/nyKCRns6tHj5lHYefY/B8IpdmlQ4fXQKSrna/dDjkdWTA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0dmd5ZkdDcEM1akJna3Yv
|
||||||
|
RnZFYVJNZ1IvY2R0QnZ6WC9PQnFmVkdWNHpFClVJWnNaRFV1UVBQTWlXb1NEZEVp
|
||||||
|
Z0ZiUC8yTXVyMFdmRnB5OWdwQVVTSUEKLS0tIFFxLzdyNlZ2WlFPdEJFTTl0Mnhh
|
||||||
|
TXVMdVIvKzRTeGRIRXBqTUNxM0Z1cmsKMLHPN6n2i18LHkgjinq033qZsrt7BdFG
|
||||||
|
NuQA4EbCqg5uiHPCSGU6/EVk2XmfROrJz5eyMWIwrwCbt53jsUNTFw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1nxlnrevqs2msdatze562vz6ym4pgydt2zndye83lwqauy6flggtqrevyw5
|
||||||
|
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,31 @@
|
|||||||
|
{
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
"data": "ENC[AES256_GCM,data:Gi6HP0EuWQcTESaFOEq7wgAvuvlyevuFIKAnShDXJIUBg7xNNlGJ3PmBfx+YyFvChCqOqThAS8TxStogMVQqyWxm0vtCoVshkHG+9Q9xHV72AiuTcmJdsZ0DiSf9jwJYLZ80x1VI9a++Mqg0yLs5few=,iv:H9YYUQ4nOhQTBaonA/H7F7KroMRyU7kGul/bL/AnQ2Y=,tag:7ESjzSEH+XTqvvtxpinKJg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1QVJsNHBYaUt6WWFDRjFX\nYVkrMHRtd1pTVFJsano1TG9hVyt4NGZyUEY0Ck1RU2U3MTQ1YTVkaXVhNlM5clg5\nWDZKZ1duTHZrM3FxWldnVXB1alJBeGsKLS0tIC9wUmZWcG1yLzBIL05qQ3lVV0xp\nS2lhWWVaTU9NWjd0K2hEaTcwNWE5YjgKmy3SHvYur/2sn6PjVLZFYzHnVMTFe66r\nm7MsWeUXU6zs4o4RWPPNBrpsVXIGZJx5Gbl+lp9Zubn61Wt/KVqyfw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRNG5SQ0FvWWVvaGhOTmI1\naTE2anlXbTJDcHVBdWlxamlBam9jQlVyL2kwCmVJMkx2NnU4dno2T2dQOGUrRFRp\nQnl3TkJ2R0ZOS3Q2K2g3VDZQZ2VWS2cKLS0tIHgvdjJ3WURmcHVSQytHUGlwSnpm\nc1k2UHZORWt5TlJvaVp0YlpzWDdob00KKJ4bSM2CQD675b0ceEv9tz+wBtCIx/wz\n7Nr2CJoY68cB8KjM1eMzioZI1Rlt49tTQuD4XSs2/9Ej/dDDbP8p6A==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1scfc8p53q5aq2a87tcmsazmj8sfeft0s8kxg0et4nm3ucxyp3c3s6te82y"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T06:45:59Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:13BJaaM1Zrcg8/SZToXdlEM8J4GtFZnkoVt6vBWoeLU58wsGi58jdEAqMzWpnT3Vf2Sb8OjNUB1cbBVv9uMitbffFR27y06diKkAICnVc4shS0czoO/6u9O0k9gXicU04jUr/FKLL+sSiNi2ypCFSZT1C3WY44sTB15RNYQR7XQ=,iv:/4owaf5HYndcojBy5rZv6LZp/ipe3UuXy34dq3XI4Qc=,tag:i3Qk8jdl5hUmLScLCirm9w==,type:str]",
|
||||||
|
=======
|
||||||
|
"data": "ENC[AES256_GCM,data:YGR1qsYnOOJyaRx49FmR8Qs5pTZtxUu3izJxl6y+Zp9EJ2ERnJKBhQ7d7wxWb/Nd/SjHh4abAVPzX5ALoUkqo3avl2j9pJnAiBGtlQKwzwZrTwf+OBmM3GCvv0Myx3TPvo0vH9QuZnwdDQRI3uWgHeZ+KMRODMpKqzxiJNtwI0mHhz/eaIK8lIWM8ssrPc/g7Q5pb8K+Jz+duLX5M9COd5ohIoJWwGCuuWnCA4yrKoioizTHuJMDlFUip4kkdJoXUXJL7TL38BZU12TUM0wkpScxojel94xezw7tyUh9/ak9IDA2n5+LrUVwXYcJUT4BN19Pxy0zQnTWAWA/9p0tUa4m1dnYzkYOM6YPFpGuV/310ZHbjMp65gw9nSojBgpnvflQZqpNn5TEzc2ox63Y8uMLiEjf6Qcb/yA32Oy/lfEZwPFDEZel2MAefb9kYKC4EEkPUxywqS80Yzg8UhI3H/HwSMUe+Q==,iv:Bbn8oDmFHUY4/apwGd7cZyKnVeZCrTT5/IVgciyL7mQ=,tag:e4H6PO5KfRysRltjkpDY3w==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5N3F6N1paaUFkek44SUdW\ncDIvVVNPN0R6M1VVS1BHMUJaL3ZHUU44U2tJCjI3cHZqMmU5M1hvamtDVnpBdGhV\nRG9mdnROaXVTNEN2d0tmTUxkUjRhTVEKLS0tIENqeHRjZ3NLd1BpSHRHQzArQmlw\naVVxYXlXUXFoWXpGOGxzeTBsRFhXRUUKkGZzx82vm2kkw6nM5pnOkDXwLxuh0Cq0\nW/rdAwHwQtskgsL2A7mXd46bDdtPLiT+6ajLsCighJ9EZgaP2TfUBw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T07:50:16Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:jRj0folhDTavBa+J4VivfHFpkqJBihdodMwu6EZ9G1hmJP782AzSWoVkeoPK7UhBzipki8aDzYS0uWpRxvSeFFAnkPmif0z2yRL4Z0MahXuEb2RHnexbqOjfHT2k4u1UKzEJ/s0IlXby61HXXG1uoB1PXeDauDgGVnrvIo/Bsjg=,iv:DJ2zXvDW2FKb+F7XxMCHtWA6ICkyfWbNJOB4WshEdis=,tag:M8GqOQq343OksrFK+sKkdw==,type:str]",
|
||||||
|
>>>>>>> Stashed changes
|
||||||
|
"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+IFgyNTUxOSBhdmNmSFlmei9KbDRWU3lj
|
||||||
|
TVJoSW9FRE9sWUdBRkNIMTB3aVNERUp0U0c4CnZUUzIvYm1xL1JNMGFKcWlBK2xT
|
||||||
|
SCt4TTdlU0ZralArcUVFZEh1YTRQaWcKLS0tIEtKbnBZVlkrU2U3VzlycS9odXVi
|
||||||
|
VjhXeWtZdDhpTG12cUZwVDhKeWZPMmsKOtXLVB8Z0faO05BPCVQXBn8gbMOZeHxC
|
||||||
|
sHmt3vMWO0jrxM0WLql59VWAFsczNwb1fw89NdZMfd/XoVq2EUV8dA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBucCtBR3N3Qm9JbTlLdTZT
|
||||||
|
eTlxWEsxUWZsUnMrWi9zSDdRVWZWejBUWFNVCnpyVEVLbTllVHg1a2RXRkVRZ05z
|
||||||
|
ZWlsSTBDWFF3bzkzaTFMVDQrVW1yczgKLS0tIFhKMll2OVdzWTcyeWdoaU85Vmla
|
||||||
|
WWEwRFgxNDF5czA3Q01LZ0NjY2toaTQKKawEiHUWss0NPuJng6UimekEOlxipiyC
|
||||||
|
ielJbLxma8WPLeLwBukFOBOTyNFxgsVBvwmBJ2k1iMIg1lCjhLD7RA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1scfc8p53q5aq2a87tcmsazmj8sfeft0s8kxg0et4nm3ucxyp3c3s6te82y
|
||||||
|
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,30 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:apiijrtrqd77CTizITg0R35BfCi8PBnufpxIyC+hLYqwoBzP//3z/yjFyHPLG98m/c/qywoi3Kn+zsaTT7MjP++9OMhhX94YKlSHV1/cHB76OkwsNc+ClqWxl6vpaFX29Qvh3gFX9c/NR3xvYQutYwrIrQ9NR+t/M52IMC8hvtR1LQy0ak3VIuXJlSnG2r4kF2Ym1iP7phjuq39Gd245Axzw8OB7yGvOjNxSdTPxW/qL0fMlzNcMrjr9hw15WlqnZfWPOsB1+gZjHXpGfPD5BCbAAMoTRJd75vhKKXP/ERhIffewuuH2x/QHfSFvXVB3QyhBQMxd2b8QEEE5cjvcExOST3tkj6QARkzoUpRT7AE3jhl3XZ0uA2qu9SwyrSvbr0tBRKxCdK0g2E2/hqwcK/Tck5GB1eKb4aN+UkqxOblNDH+B1RfDoyNAuN+KEg==,iv:0p+ScrKpP4kQvO52gBAlwAis6oAzZ0EHFnU74hYPrn4=,tag:ON7qOjztF52xsJWAou7ogg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlYXB3cVAzb2xEZ2pGa1RJ\nbS9ZVTc4Ums5eUZJUjEvd1g1aGVyNUNramowCnptZXFOZVB3MFRFcUtzSXBEZk1B\neEtKcDdLS0h0b1h3VjRjRXRvV3V5V3MKLS0tIHBDemkyUnV6ZXhTeE5VOVVOMlky\nWWMzVGVzZlAxMjZYUGpQUCs5QmxiYkkKcuBshCgWX4TwfVlQ5lHikzvwWdLEXWD1\n/uSiy0J6yMSiu8u6cg2SxeFrlKJ3j47dDlT6WHCxS0PfeEA0bJb3LA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGa2VTc2NWdkFRNUJxelVR\nWFk4RWxoelYzNHo1UFVhU2ZkLzEySlRWN2xNCmNmcmJod2crL3NMRlVsSmpmVkU2\nMjlXMktjc3piUVNhUXlTdnVGTWJkUTQKLS0tIGQrMUxrNDlNTkRCSUtFWkxRdXgw\nRlV4ZmtYSGhPQU84eWtiQXVqTmxUK3cKk5fn72UZPH68t5ZappfAhZJwzpLkfKmT\ny9TbUPIr4Pbrexau6YiH43QIbDQFdwYPfkBjGkd57zCg8AVo1+MBRw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1adur9g330gua4l6ndk8cqjg35qc8yxwgme6wrl2hpylcc7vxm38q05ejuy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArNWkwQXFWT1RpaDNvbzYy\nQWc1aHhmNHFEUXVsQjZqb0EzM0wrV0dwN1hnCjFsUFJiT3REK05uSGRWTEw2SFE4\nY1FleE1XVjhBbndiMmZxTWNTYmhYeVEKLS0tIEJiZzJvS3BsYzB3cHIxa2k5N1Ro\nenFFZDVaODNnVGdBZTBOYWJwRjQzc1kKlXJgee8wTSN4Beq4P0t9cYbk0BWHCseQ\nyaWpiPT9aZBEGLFmuEd3zKABc8lrilX/ySTmOG49vRg6CPmr7cT0Wg==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1rrxqea6q6pn39sw8y5te63h2py8jgjl9v0jyper86w3ggtn67upqg3ah39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEa0JYenFMNmNzWnVmcXdz\ndG90ZUZ0WWlIU0FCZG9OaWpBM3ZDWnFhZFhNCjhuV1FTOTJ2WVJGa2RuNVV2MjR0\naVNXa3diaWxWUlJtdkNOQXZ2R2NsQkUKLS0tIDcyYXh3N3B2QmNiK3dzemFFMGV1\nNTZpTk5yNGV5YVo3cGswK0NLWFQxQlEKIe0N5OxooWXzt1cUViBmjihmGEe3G6/f\nkz2/IscnG78ZvNgYKjdoG1jlsyje/3zI4C8aWXLq2DnIyxUyAhPgsQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age19mn8zrxl8zpps9yvrh4euquvygpp4fp8queg7xc6qhtnl4ng8c9qx02qwn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2b0NVVm9QNm9waUUzcXBi\nWnZWU0JETjZRZmprZVRUL1h6ZHB4cXkrdm5rCm9GZ0VnTXB3S1BYSmlGWFJVcDhJ\naUl3RjR0ak9BRmQvVk1GRnQxNmtYM00KLS0tIFlTU1p2OHhWUGlOWngwbE56NEhF\nRW5QSkVVUWZpdDZXWEIxZ1BkbzVwclEK2P25nBgf8255vaKW/+T97aNTecRgNjLu\nedIUiPdXbFATCe3v/YRo6sqzFwIsvM6Bl9yHh/SXo6Ftc7eWZZd8zQ==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1hrx8qj02fj2ea6d4g9vqhyj9hl7fppkjqfdx2l37py3h6pdkr95s8n8rvs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:24Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:J0D8bEs5mHLraLS6TvYuCgfiNU1xKM2Yfb5Y0f/q/4wM4LzXufNzv3+SWDHumTe328U8UnNXLqjNHEKL0bZi0coxpU5hVM+BvPcmqD72vscETzbQ2hnU05sfW+XjfZhcN8/ke0bpLt7nP0crD5hsZv3esV1E2UWvzjEiYtWzFHY=,iv:lcmXYG2H469UKBYDndWKMO+GP0mSGLztenm+kBaUdYI=,tag:ujiXbLM9CUsuoFwQQWI84Q==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:Q++XWxg9tvY7ugT8+8FWCC5jgOfQ1+LYLsnqN0/WGxTf5XT2VmoXvszcE/ow2BUOBG3qBTXS3OHYFPwmj1GgaBHB8rpdXX6+LveSBE2gmx1VR+NUDTxy+0/DBq411MK9n/J9eIYcybdFIE11biFSAob9EgfxBF5roCDXIPDPyVCSe6LyhNvqYPnGQsfHCbejSewLTcRQEiguP9BX96CpMPIpmaB9fHN25t5RWCgMI7MtacrRxRsyKg44+2FZstXdZrp2Wv9u86BxqdAFqtZE8qpPeGdrdzluZx9jhnw0wZPzHdKg5wS7/UrLCb0UxIQiDxDMDuuBuLGkKXfAhb6SGZU41wWAWYk548iGRUaG+79BO4HhRZNObOFvfsjpMXEfcH/Vbv/wHVY4OpJUPe/ZWK4wpL9YrY4nT0t62HH7MirOy1uhwLE50h2+6dHKj6ur5G7thkSqgzVWXQ==,iv:zaRBwS+gfXLhH30havn6Q2+oPWuLV3qBfbOj00kewlQ=,tag:Vr5iEQ2u+9YNahryhgzwSw==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArTGJnaVhleUtsWnlITE1s\nNmRCaW1QWTFNSG5LRFdmb1lzRlV5NWl3YTBNCmF6eUN6RkJBZzc0MmJJa0dKTW01\ncVh4K1VLR2lURUpKQXpxNGpQNnpSUlUKLS0tIFJONnJFWkNCR3pqalRsUW9POVBj\nQ0pFQ3ltKzBETTVXTW5sV1ppWTFJc1kKzxUboNZO+Nwn2eTWy11VP9w1pRswCHaJ\nE2dYU0oUOClVzc0oSuIJxraG6TPj1N4WGC24gS+UmpkmSuCiOeZBsw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDSmxNR1I3dUFKT0xUVG9h\nWkRIakNVeWRQOEN3blNRVjZlWHF4K2NRa0hBClRiOXlmTTJ4T2JTUEw2c1l0R2N2\nMnRwdDA5bEZlQWJRTm9vUmNKclBSU1EKLS0tIFcyeDFjbTZyVEVDUjN1VzU1VHly\nNWNDMW9rTXY2bHNWYVR0SmtMckovUzQKhTWr6yFVW9am3okCiIswwqR5+/p9OLmB\nWCgPtwoFaBt1RjUXPK4/eS4LlucR2K6V/mNMn4xVsnkIl193U9632g==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age16j42pdc5dr6wnj7xayhkqdj2rny9u68fcqejs50hqq42scssh4gsnrrnlt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2ME9CbmVGcENRWksyL0pW\nOEJyUGhpbGMwWlBhVXBSeXQ2MW1EWnFuR0E4CnMwU0pjdk1YMzF5ZEhTVFlBaHZq\nam94UWVGbjhZSEx6VHBmem9JRWgwYzAKLS0tIHFJZWRyRjRHNzhXdDJSYWN3bDlR\nYVp3eGJWWkh3Y09ZWElyclZQN1ZSVFkKjR32//EcFAdMjVlNgky5zvVkwXwEN68D\nrkTuHKjiO5aV7yAQGPkdNw0UM0oRGF0u4YF3oOUcZfSvnKgDeoi2Zw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1ug787sgt6st6k82fgkrug2lzltw4qsukrrqqs3w27ewwqj8rg4hsxcmylz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T00:27:46Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:AbJIHYcFpeanQsJ3x7RPL9Yjlg5BJgkepKax0fL9L/PpA03Antab93iUNG95Mp6k/duovp8Jm445lbuppDZq1dh9ij/deBa8GbzJ50wwEe9zMc3EwRKScpqZEhRPF7KlJsIjsHJyd8NkcI5ji49XkHb4Ae1//8zG5HpVgy+3b04=,iv:uxQCbMwMIfP5S1dbsvIx3F79YWEguxwox8T0YZvUBdc=,tag:3utjmBGDmPc8q4JjaXvCkA==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:aqlMnoVkGtH9z3fJRweeC0OYf7LGqJU2sWA9Q25dKK6NuNyJd4BvjPtpfeg/WhVtJsaOtcbwVm4WAhVK9FARE8g8j+vmq0f6BAU4s6mx0ZIhl+mP+/hIpt//LOdd+9YezelJxdpzUyZbdAngU99rsTluLRe2XmZ7Fquxd8yH/OHenSDY6dizp9+5jfEi8EU+EmuXvuWMPY59xnlnqYNPfSFxs43/pS402LzJoJ5H+cBPprddkUBVzy4cBQvMnrRFUSjnqp74ovZkfIWFqDWQ5YgSU2PjatBg18oulZ7wNRhQ6OLqj6gsu+xrMjNFwnp7rMlA3X//hIidxTkVcYITycXd8KzuMIaofUpnwoyT34fy6+H35/39iiEyG4LRTrOOKRDzXkY2rhJUxFSZ8GlhNhMd0RlkmLngVYrtjsswJ9meIwoAFLPYt7BC61PJf0TXdtk=,iv:mCA819J9LpAOj8QxFAkrHI9wFJIy8qVxv31D6IwWFnk=,tag:qqMtM0eyHbQEyl6ND/wf7g==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOZVpRYmNMQ1E4ZlFXMDVq\nbW1FcnYzWTBHQmdhSjlsZUtZK3dzNUZjY1FvCkV4VXU4MTcwNVZoZUpVVS9JeHE5\nbGk3UzE3YmpZQ1JISHV0RHJqSG9ZNXcKLS0tIHhBdnIvMjJkeWlVRE9Fb0FhWjNX\nUjBVWDltK2w0akkwOTJaTGNSWWNrRXMKqZRNnHiXvn1QBoSGdABp7vOqNlsEN6Xr\nDp3NByXow6PuRuWvQXHzd+WC+ADkwNaaiT6TUrbZcd/Pl8Ges9kcZg==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2VFBxc2ZMUFp0YkJhVFhW\nc0ZNbnJUcGtkT1VLR2YrVGhRRFVTZFlyZXlJCmVnblNBNGxIdHh4Q0IweWR1b0dq\nRWFyOURuWkdkRE1RTnJRMEpXdk9HaUUKLS0tIFhKV3FoWW1zRk1pMlhuWWlhV2E5\nQzJTRHAyc0JtSjd2NHlJODZVbndaVDgKFA4565X/4FqNq/fZDZTg81/55hZi4c7b\nTti2AnyE3OcY/kurXJFHRinVMqURQf1fx9MxqUYRitiCz4qe5zFF+A==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1sweerhrga9yf8x6sv0apz4ed4g48rnlcq34rpv20t0rcelwgpgeqwvndzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwRWlKTFNocnBRN01pZ0hm\na3pyRUkxMWM3dE1iTkZZcGRYUFcyb1RDb2h3Ckt4blpGZHBjcnZ4SVE5UnF4cmY4\nWW0rZG5wUkZINVk5a2lmSzN2L3JJY1kKLS0tIExpWGlBRFZJVGpGbFhxRE5ZYjBo\nQVliTlFIZ1U5dE9xbDhHMUtxenpBSm8KY6sIFEfK8p+70IXsC4Jwb9Lm/pd9+V6K\n4JAzGrpA6mAuIwwSNnbdcA5j8FmBhCpK6nLBWmFhGm9Y+MRTaM7Jrw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1nruncs4l0ufk7yuc4des8p99c0alfndl0lhsws8tycl5pplfp56s30af5f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoV0pIUXlyOFFTUHVTdllu\neG10VENOYWl5Wk0ycnpLUytTbnlrQ25pMkFRCnhyRU5xWW9vQXJJOHdFMGZqMkR1\nSktqT3lOdVVPRUN4YTlveGp0NXpqd0kKLS0tIGRaTktra0ZtSVpzSHBrY3VSSUph\nRVRZOSsxTTNmMmltMlJnVy9oT2VEWU0Krxf49B1BsrWn05fqg+cZ0k0PtfJJNfn0\nUL44RUWXWbK2igQHaIct9DfYe7DEonBJeROuxDYm8g7yNOv15S+P4Q==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1529taqdwr6t0w7cvzmty0d5y5593wffl0krt48j6uc4u39k56g2qf6ywtp"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:26Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:eyOSn03dzHSgkshPzLVwc95382eEFaDQarHs9l83dtcsb1Ui9CjkKipl2DVSUb6bdMUH1qKYmXqJhwFnAZbFZFjT4VTKtutNtM+OkhVXfT+fJs+1+u7k+ZUYFL9HuxKA6AWpwX3eJ8vmJDJZaAayJbm4PRzOyJywvKeneQqdUS0=,iv:QRKltR4qzVofo/Elt2Us/lrHlD7BenX605X31x+Ng78=,tag:SZ97CcfgMJeu3yqNk8Y/cA==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:QvViw/s1N7eIN3CoD27llEjriuSrohOou4Cv310nigcW8xMnA2SDN2id3H3AoKii1JlJ+qWpKn+gUmt5HOM0UlbeNe3os2RVwiX38O7eN9xFH9F5kA3TFs6Umqq3EoI586PwIVmB2LyxDnTeEEXVd7v5PFkBcfu7u8YIcNF7lpcj+6rOyHMB8uxPhrGep3yiKawFd9c9wWD0hlSSatV5tMHA1qmdK8VmDbCU/iuGwIoMzN1eZwGAXzG6LkCA63bUfdxU6yGuTboD+kN2Wbo+GZB0EACmiZoofl2wqlXuiw6qwTvlXkyauc9O5EG//PUkIECzDwiXcX+qSOM9DIBlZNth4ebhtic/PskyF09etL/gICz5YvV4ph5lyrWHq41KxljSU7QXOkGhzagruuMrYzhZb35wFH4Tie1ee2DXbGhreJr8V3Zse/zTMaD+iM57V8bvNcarTzOFXyKfp7Y=,iv:XBPhj2wT0k/yRCRHU4d+BQA/k00ZHWSKOucnZ5+PGys=,tag:Nh5MOxZIUXAYVZY5SZh/JA==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0aUtETXlBMmRlTC9vK3NF\nRW4zS1FrSmo1dDN0OGN4SmV5ODd5MTRVejFJCnpPQkM2MVJ5WFlYRW1NVjduV1hv\nUWUzY0hHV05LV1BROVZVUlM2NldsTlEKLS0tIHFsTzI2SVZzYUtJWTM0MmFiUlVQ\neHEvUXgzc1pxSU1OZFo0cXhSZDdGUVEKpLVfzQEnntluUGsblnkHZJ9Jezu8tFte\nxEoV96GVHxUca6TFWpTLMqdR7NtuQGCkx295W3i1tkp58DP1OzRTUA==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4ZW5ybVBvTkxibWJMWGRL\nZ1lnWEkxZ3l6d0FWbmxMY2VsTVlzOHJJM3owClNYd3BlQi9pL1lIdzJmekJUVEVi\ncWhSK3ZkZEMyeTNoZlpTT0NMUXZFWVEKLS0tIHVkVlhmQXdRQktTK3J5dXZ4aTNt\ncUw3WCt5dXJhTkdUbVpmeWoxWkoxNnMKj4XtdwmgFVOiVsIJs2Du7QJ09A9tv/Lf\nkFOq8y4tlZe0nCwRjq43sVz7hdCTdQ0rsaWjBGY90LLkJbOA+f+Wrw==\n-----END AGE ENCRYPTED FILE-----\n",
|
||||||
|
"recipient": "age16kqfmvz4e23hmdlqresnyw69ej604s320mmd49h4hm3fhqchtgyqrws0k2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-07-28T01:44:30Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:zR2WLWX7NaHA15gi4kX0jDvzUIe9jtz5bMCAggbPW+IXOEedPrddAHZ8OfPErVMfx8O1pJKkAKSzoPTAbEle54FisSLMHXp8fI0297MByJrF9pOsMFpVcDy/L4Q+pBzmB7aS9r7+u7KRVVTZT3QwG1rFWZaDs5dFTP80RhtCbWQ=,iv:ZzPoO+h7ebS+jsSH7tWMx6QK8umpa2/HFQmx9dnJN+Y=,tag:vG9+ifxw4HaBE6YsmOwXcg==,type:str]",
|
||||||
|
"version": "3.13.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-1
@@ -16,7 +16,7 @@
|
|||||||
serverIp = "192.168.2.226"; # server (NFS/ZFS) Proxmox VM LAN IP
|
serverIp = "192.168.2.226"; # server (NFS/ZFS) Proxmox VM LAN IP
|
||||||
dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP
|
dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP
|
||||||
pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed)
|
pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed)
|
||||||
domainControllerIp = "192.168.2.253"; # FreeIPA domain controller / primary DNS (not NixOS-managed)
|
domainControllerIp = "192.168.2.253"; # FreeIPA domain controller — authoritative DNS for sweet.home (not NixOS-managed)
|
||||||
ipaServer = "domain-controller.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP)
|
ipaServer = "domain-controller.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP)
|
||||||
|
|
||||||
# Cross-host references (LAN hostnames/users other hosts reach over the network)
|
# Cross-host references (LAN hostnames/users other hosts reach over the network)
|
||||||
@@ -80,6 +80,17 @@
|
|||||||
# one-line change.
|
# one-line change.
|
||||||
primaryUser = "nixos";
|
primaryUser = "nixos";
|
||||||
|
|
||||||
|
# Primary IPA/domain user. Home Manager is configured for this user on every
|
||||||
|
# IPA-enrolled host (see modules/ipa/client.nix) to manage the environment
|
||||||
|
# that IPA itself doesn't cover: dotfiles, user packages, session variables.
|
||||||
|
ipaUser = "wayne";
|
||||||
|
|
||||||
|
# GID of the IPA "docker-access" group (GID 50010 on the IPA server).
|
||||||
|
# The local "docker" group is pinned to this GID on every host that runs
|
||||||
|
# Docker so that IPA group membership alone grants docker socket access -
|
||||||
|
# no per-host users.groups.docker.members entry for the IPA user needed.
|
||||||
|
dockerAccessGid = 50010;
|
||||||
|
|
||||||
# HA file server cluster
|
# HA file server cluster
|
||||||
# LAN IPs (vmbr0 / ens18) — client-facing: iSCSI initiators, NFS, management.
|
# LAN IPs (vmbr0 / ens18) — client-facing: iSCSI initiators, NFS, management.
|
||||||
# Storage IPs (vmbr1 / ens19) — isolated internal bridge, used for DRBD
|
# Storage IPs (vmbr1 / ens19) — isolated internal bridge, used for DRBD
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILdvVsj5cw/y6s2q5cPqKnULgnkEnsIQahrhm9DyCdd/ proxmox-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 AAAAC3NzaC1lZDI1NTE5AAAAILjgkgUFZfpTLkFQeEbqXlvktE641EexWLv2I4iVKyQi proxmox-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