Archived
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60fd64f0f7 | ||
|
|
d35b7c4b57 |
-49
@@ -12,21 +12,6 @@ keys:
|
|||||||
- &lxc-gui age190htw7prp4vln076dxjx3gxxaq06h0zl0te7cqgpx79vl3lhkaes8suy05
|
- &lxc-gui age190htw7prp4vln076dxjx3gxxaq06h0zl0te7cqgpx79vl3lhkaes8suy05
|
||||||
- &proxmox-server age1ukpqxzl44mnjpy5r96sfuc5sqzm47u4k8ujjh5qdgy6jvl9uqgpspymqfk
|
- &proxmox-server age1ukpqxzl44mnjpy5r96sfuc5sqzm47u4k8ujjh5qdgy6jvl9uqgpspymqfk
|
||||||
- &vm-server age15kh7akxlx7zn00tey79rq2g8lgs4j5y77rcnyfxrxap8ckfu0a9sqvtdhh
|
- &vm-server age15kh7akxlx7zn00tey79rq2g8lgs4j5y77rcnyfxrxap8ckfu0a9sqvtdhh
|
||||||
- &baremetal-gui age1ehkswwz2pqaz4svzh7ela5tdnssl8kn6d4vwwxd6zwg8exfpd43syyrrjp
|
|
||||||
- &lxc-tor-relay age1vvev5m3shgckl62awa64rtug3lyact7jgxehkuu3vn3wpzulhans75w65s
|
|
||||||
- &linode-docker age1eu65wsmez68gegnufl0gqrs0e6w2409mypjlajlq383d7l2e3pjqy5kn6v
|
|
||||||
- &linode-gui age17pwyghxr6lq06fw46gwqzhc9ut4paz28rpwx5pmv3cxwak6rgyjsw7lk3w
|
|
||||||
- &linode-minimal age1jukmg69cqxnjd0lp5f534jhqe65rxew2hufcmyjxa3rkw6ayef6s2ylcmy
|
|
||||||
- &linode-nix-cache age1tzsrtwd3p3lrr9g7nv3z5nvmzsz54t2uc6tfqwutp6usmav83s2sck25cc
|
|
||||||
- &linode-server age1rf4kj99wuq59k7w8ar326djmgmpl9hcwlnuag07f8gauq8c3y5mqne87s4
|
|
||||||
- &linode-tailscale-exit-node age1d0zhx7u3mfs3nktl67npey87cze4dwsfvfvgaje4rh3gwv500yssckcf6u
|
|
||||||
- &lxc-server age1jc6wx33hdhgwhk6nzy5rr8fkgmqxk9um639tk5h632nqfyaw8czskdptj9
|
|
||||||
- &lxc-tailscale-exit-node age1pgykvq4pmxhhjrqupcp99fyad2uht40pt79dkzg66cfvsjy5apjqls8u68
|
|
||||||
- &proxmox-docker age19ht95nv8uhz2shjmakeut8mc3l5spvrcxs3thhe85an7u02r6sysyv457n
|
|
||||||
- &proxmox-gui age1mhaze5tgvc9lwjpml6dnp3xc292337wgm6376yh6tq4fn492ndjq9h23dq
|
|
||||||
- &proxmox-nix-cache age15me6sx0f8r58xh9v7aqrj6e99n7eu555jkpw422txpkqt4r02v0qpahlyq
|
|
||||||
- &proxmox-pxe-boot age1q3hu6eh3mt4saey7dc3yu04s7knnk2ygpm9xt6mg6rqtem6l0uyq4wzul5
|
|
||||||
- &proxmox-tailscale-exit-node age1tczst3x7thwtcz4vce4rg6kmlsszzm6j45kn2nv860z9wa93a92s05ppmc
|
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
# Shared across every currently-deployed host: root/nixos password hash,
|
# Shared across every currently-deployed host: root/nixos password hash,
|
||||||
@@ -49,21 +34,6 @@ creation_rules:
|
|||||||
- *lxc-gui
|
- *lxc-gui
|
||||||
- *proxmox-server
|
- *proxmox-server
|
||||||
- *vm-server
|
- *vm-server
|
||||||
- *baremetal-gui
|
|
||||||
- *lxc-tor-relay
|
|
||||||
- *linode-docker
|
|
||||||
- *linode-gui
|
|
||||||
- *linode-minimal
|
|
||||||
- *linode-nix-cache
|
|
||||||
- *linode-server
|
|
||||||
- *linode-tailscale-exit-node
|
|
||||||
- *lxc-server
|
|
||||||
- *lxc-tailscale-exit-node
|
|
||||||
- *proxmox-docker
|
|
||||||
- *proxmox-gui
|
|
||||||
- *proxmox-nix-cache
|
|
||||||
- *proxmox-pxe-boot
|
|
||||||
- *proxmox-tailscale-exit-node
|
|
||||||
|
|
||||||
- path_regex: secrets/nix-cache\.yaml$
|
- path_regex: secrets/nix-cache\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
@@ -71,8 +41,6 @@ creation_rules:
|
|||||||
- *admin
|
- *admin
|
||||||
- *nix-cache
|
- *nix-cache
|
||||||
- *lxc-nix-cache
|
- *lxc-nix-cache
|
||||||
- *linode-nix-cache
|
|
||||||
- *proxmox-nix-cache
|
|
||||||
|
|
||||||
- path_regex: secrets/server\.yaml$
|
- path_regex: secrets/server\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
@@ -81,26 +49,9 @@ creation_rules:
|
|||||||
- *server
|
- *server
|
||||||
- *proxmox-server
|
- *proxmox-server
|
||||||
- *vm-server
|
- *vm-server
|
||||||
- *linode-server
|
|
||||||
- *lxc-server
|
|
||||||
|
|
||||||
- path_regex: secrets/docker\.yaml$
|
- path_regex: secrets/docker\.yaml$
|
||||||
key_groups:
|
key_groups:
|
||||||
- age:
|
- age:
|
||||||
- *admin
|
- *admin
|
||||||
- *docker
|
- *docker
|
||||||
|
|
||||||
# gui-host-specific secrets (currently: wifi-password, see
|
|
||||||
# modules/networking/wifi.nix). Only *lxc-gui has a registered key today
|
|
||||||
# -- proxmox-gui/linode-gui/baremetal-gui haven't been provisioned via
|
|
||||||
# scripts/secrets/sync-host-keys.sh yet, so whichever variant is actually
|
|
||||||
# deployed next needs its recipient added here (and `sops updatekeys` rerun)
|
|
||||||
# before it can decrypt this.
|
|
||||||
- path_regex: secrets/gui\.yaml$
|
|
||||||
key_groups:
|
|
||||||
- age:
|
|
||||||
- *admin
|
|
||||||
- *lxc-gui
|
|
||||||
- *baremetal-gui
|
|
||||||
- *linode-gui
|
|
||||||
- *proxmox-gui
|
|
||||||
|
|||||||
@@ -164,39 +164,13 @@ before committing.
|
|||||||
|
|
||||||
Beyond `codex-setup.sh`/`codex-maintenance.sh` above, `scripts/` is
|
Beyond `codex-setup.sh`/`codex-maintenance.sh` above, `scripts/` is
|
||||||
organized by purpose: `scripts/secrets/` (sops/age + SSH host-key
|
organized by purpose: `scripts/secrets/` (sops/age + SSH host-key
|
||||||
management), `scripts/proxmox/` (Proxmox deployment), `scripts/installer/`
|
management), `scripts/proxmox/` (Proxmox deployment), `scripts/lib/`
|
||||||
(the auto-installer's own shell script, templated into the image — see
|
(shared helpers, sourced by the scripts below — not run directly), and a
|
||||||
below), `scripts/lib/` (shared helpers, sourced by the scripts below — not
|
handful of repo-wide scripts left at the top level (`env.sh`,
|
||||||
run directly), and a handful of repo-wide scripts left at the top level
|
`bump-nixpkgs-release.sh`, plus `codex-setup.sh`/`codex-maintenance.sh`
|
||||||
(`env.sh`, `bump-nixpkgs-release.sh`, plus `codex-setup.sh`/
|
above). When adding a new script, put it in the matching subfolder rather
|
||||||
`codex-maintenance.sh` above). When adding a new script, put it in the
|
than the top level, and if it duplicates logic another script already has,
|
||||||
matching subfolder rather than the top level, and if it duplicates logic
|
lift the shared part into `scripts/lib/` instead of copying it.
|
||||||
another script already has, lift the shared part into `scripts/lib/`
|
|
||||||
instead of copying it.
|
|
||||||
|
|
||||||
### `scripts/installer/`
|
|
||||||
|
|
||||||
- `scripts/installer/auto-install.sh` — the interactive install script
|
|
||||||
baked into the auto-installer image (see `docs/auto-installer.md`), kept
|
|
||||||
as a real, version-controlled shell file rather than inline in
|
|
||||||
`modules/installer/common.nix`'s Nix. It sources `scripts/env.sh` itself
|
|
||||||
for `LAN_DOMAIN` (`export LAN_DOMAIN`/`: "${LAN_DOMAIN:=...}"`, matching
|
|
||||||
`variables.nix`'s `lanDomain` — manually kept in sync, same pattern as
|
|
||||||
`NIX_CACHE_HOST` mirroring `nixCacheHost`), rather than Nix-level string
|
|
||||||
substitution — that's what makes it work identically whether run
|
|
||||||
straight from a git checkout or from inside the built installer image.
|
|
||||||
`common.nix` bakes `scripts/env.sh` in alongside it at a matching
|
|
||||||
relative path (`/etc/nixos-installer/env.sh` next to
|
|
||||||
`/etc/nixos-installer/installer/auto-install.sh`) so the script's own
|
|
||||||
`source "$(dirname ...)/../env.sh"` line resolves the same way in both
|
|
||||||
contexts — this is also why it's invoked from
|
|
||||||
`/etc/nixos-installer/installer/auto-install.sh` rather than a flat
|
|
||||||
`/etc/auto-install.sh`. `#!/usr/bin/env bash`, not
|
|
||||||
`#!/run/current-system/sw/bin/bash`: the latter only resolves on an
|
|
||||||
already-activated NixOS system, breaking the checked-out-file case
|
|
||||||
entirely (confirmed live: "cannot execute: required file not found" on
|
|
||||||
a non-NixOS box); `/usr/bin/env` is reliably present on both NixOS
|
|
||||||
(`environment.usrbinenv`'s own default) and any normal Linux distro.
|
|
||||||
|
|
||||||
### `scripts/secrets/`
|
### `scripts/secrets/`
|
||||||
|
|
||||||
@@ -282,6 +256,33 @@ instead of copying it.
|
|||||||
`/etc/ssh/ssh_known_hosts`. Idempotent (re-running replaces its own
|
`/etc/ssh/ssh_known_hosts`. Idempotent (re-running replaces its own
|
||||||
marked block rather than duplicating it); restarts `nix-daemon` by
|
marked block rather than duplicating it); restarts `nix-daemon` by
|
||||||
default so the change takes effect immediately.
|
default so the change takes effect immediately.
|
||||||
|
- `scripts/proxmox/clone-pve1-to-pve-test.sh --vmid <n> [options]` — ad hoc
|
||||||
|
copy of a single VM/CT from one node to another via `vzdump` +
|
||||||
|
`qmrestore`/`pct restore`, relayed through this machine
|
||||||
|
(`ssh src cat ... | ssh dst cat > ...`, no on-disk staging copy).
|
||||||
|
Defaults `pve1` → `pve-test` (see "Two Proxmox nodes" above); source is
|
||||||
|
left running throughout (`--mode snapshot` default) and untouched
|
||||||
|
beyond the vzdump + a typed-VMID confirm, restored on the target with a
|
||||||
|
fresh MAC (`--unique 1`, always) since the source is expected to still
|
||||||
|
be live on the same LAN. Backup archives are deleted from both nodes
|
||||||
|
afterward unless `--keep-backup`. `--dry-run` throughout.
|
||||||
|
- `scripts/proxmox/migrate-vm.sh --vmid <n> [options]` — moves (rather
|
||||||
|
than clones) a single VM/CT between nodes, built on the same
|
||||||
|
vzdump/relay/restore pattern as `clone-pve1-to-pve-test.sh` above, plus
|
||||||
|
a cutover step (stop the guest on the source node, start it on the
|
||||||
|
target) and, optionally, `--remove-source` to destroy it on the source
|
||||||
|
node afterward (its own separate typed confirmation, since that step is
|
||||||
|
irreversible) — the default instead leaves the stopped source guest
|
||||||
|
intact as a safety net. Restores with the *original* MAC (no
|
||||||
|
`--unique`) unless `--unique` is passed, since the source is expected to
|
||||||
|
go away rather than keep running alongside the target. **Not** a true
|
||||||
|
`qm migrate`-style live migration — pve1/pve-test aren't a clustered
|
||||||
|
pair with shared storage, so this is vzdump-based and, with the default
|
||||||
|
`--mode snapshot`, any writes on the source between the snapshot and
|
||||||
|
cutover are lost (use `--mode stop` if that's not acceptable). Generic
|
||||||
|
node-to-node (not pve1/pve-test-specific) via
|
||||||
|
`--source-node`/`--target-node`, defaulting to pve1 → pve-test like the
|
||||||
|
clone script. `--dry-run` throughout.
|
||||||
|
|
||||||
### `scripts/lib/`
|
### `scripts/lib/`
|
||||||
|
|
||||||
@@ -308,9 +309,8 @@ Sourced by the scripts above, never run directly:
|
|||||||
### Top level
|
### Top level
|
||||||
|
|
||||||
- `scripts/env.sh` — shared config (`PROXMOX_HOST`, storage pool, bridge,
|
- `scripts/env.sh` — shared config (`PROXMOX_HOST`, storage pool, bridge,
|
||||||
default cores/memory, `NIX_CACHE_HOST`, `LAN_DOMAIN`) sourced by
|
default cores/memory) sourced by `create-proxmox-resource.sh`. Add new
|
||||||
`create-proxmox-resource.sh` and `scripts/installer/auto-install.sh`. Add
|
cross-script config here instead of duplicating it per-script.
|
||||||
new cross-script config here instead of duplicating it per-script.
|
|
||||||
- `scripts/bump-nixpkgs-release.sh` — bumps `flake.nix`'s `nixpkgs.url`/
|
- `scripts/bump-nixpkgs-release.sh` — bumps `flake.nix`'s `nixpkgs.url`/
|
||||||
`home-manager.url` in place. Exists because flake input URLs can't
|
`home-manager.url` in place. Exists because flake input URLs can't
|
||||||
reference `variables.nix` (confirmed empirically — `nix flake metadata`
|
reference `variables.nix` (confirmed empirically — `nix flake metadata`
|
||||||
@@ -348,14 +348,11 @@ nixosSystem {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Platforms: `linode`, `proxmox`, `lxc`, `baremetal`. Build types: `minimal`,
|
Platforms: `linode`, `proxmox`, `lxc`. Build types: `minimal`, `nix-cache`,
|
||||||
`nix-cache`, `server`, `docker`, `gui`, `pxe-boot`, `tailscale-exit-node`,
|
`server`, `docker`, `gui`, `pxe-boot`, `tailscale-exit-node`, `tor-relay`. Not
|
||||||
`tor-relay`. Not every combination is built — e.g. `pxe-boot` has no `linode`
|
every combination is built — e.g. `pxe-boot` has no `linode` variant
|
||||||
variant (PXE/DHCP/TFTP need LAN L2 adjacency a Linode VPS doesn't have),
|
(PXE/DHCP/TFTP need LAN L2 adjacency a Linode VPS doesn't have), and
|
||||||
`tor-relay` currently only exists as `lxc-tor-relay`, and `baremetal`
|
`tor-relay` currently only exists as `lxc-tor-relay`. Treat `flake.nix`'s
|
||||||
currently only exists as `baremetal-gui` (the real gui-host hardware —
|
|
||||||
see `hosts/nixos/host.nix` and `modules/platforms/baremetal.nix`). Treat
|
|
||||||
`flake.nix`'s
|
|
||||||
`generatedTargets` as the source
|
`generatedTargets` as the source
|
||||||
of truth for which hosts exist — `README.md`, `AGENTS.md`,
|
of truth for which hosts exist — `README.md`, `AGENTS.md`,
|
||||||
`docs/flake-lock-automation.md`, and the CI eval workflows
|
`docs/flake-lock-automation.md`, and the CI eval workflows
|
||||||
@@ -371,16 +368,12 @@ removing a host.
|
|||||||
of their own beyond narrow parameterized helpers (see
|
of their own beyond narrow parameterized helpers (see
|
||||||
`modules/beszel/host-token.nix` below) — all shared behavior comes from the
|
`modules/beszel/host-token.nix` below) — all shared behavior comes from the
|
||||||
platform/build-type modules composed in `flake.nix`, not from the host file.
|
platform/build-type modules composed in `flake.nix`, not from the host file.
|
||||||
- `modules/platforms/{linode,proxmox,lxc,baremetal}.nix` — platform-specific
|
- `modules/platforms/{linode,proxmox,lxc}.nix` — platform-specific config:
|
||||||
config: boot method, guest tooling, and the hardware config, imported
|
boot method, guest tooling, and (for linode/proxmox) the hypervisor-specific
|
||||||
directly by the platform module itself — **not** wired in from
|
hardware config, imported directly by the platform module itself
|
||||||
`flake.nix`. VM platforms use `../hardware-configuration/vm/{proxmox,linode}.nix`;
|
(`../hardware-configuration/vm/{proxmox,linode}.nix`) — **not** wired in
|
||||||
`baremetal.nix` uses `../hardware-configuration/baremetal.nix` (adapted
|
from `flake.nix`. `lxc.nix` has no hardware-configuration counterpart since
|
||||||
from a real `nixos-generate-config` run on the actual hardware, not a
|
containers share the host kernel; instead it imports nixpkgs' own
|
||||||
vm/ file, since it isn't a VM) plus `hardware.enableRedistributableFirmware
|
|
||||||
= true` for real wifi/GPU/microcode firmware that VMs never needed.
|
|
||||||
`lxc.nix` has no hardware-configuration counterpart since containers
|
|
||||||
share the host kernel; instead it imports nixpkgs' own
|
|
||||||
`virtualisation/proxmox-lxc.nix`, which gives every `lxc-*` host a
|
`virtualisation/proxmox-lxc.nix`, which gives every `lxc-*` host a
|
||||||
`config.system.build.tarball` output — a plain rootfs tarball, used as a
|
`config.system.build.tarball` output — a plain rootfs tarball, used as a
|
||||||
`pct create ... vztmpl` CT template (**not** `pct restore`, which expects
|
`pct create ... vztmpl` CT template (**not** `pct restore`, which expects
|
||||||
@@ -404,16 +397,6 @@ removing a host.
|
|||||||
boots, so this declares them with `destroy = false` (disko never wipes
|
boots, so this declares them with `destroy = false` (disko never wipes
|
||||||
them) and a bare `filesystem`/`swap` content type instead of a partition
|
them) and a bare `filesystem`/`swap` content type instead of a partition
|
||||||
table — idempotent against an already-provisioned disk, never destructive.
|
table — idempotent against an already-provisioned disk, never destructive.
|
||||||
- `modules/disko/baremetal.nix` — `baremetal-gui`'s disko config: a ZFS
|
|
||||||
RAID0 (striped, no redundancy — disko's zpool `mode` defaults to `""`,
|
|
||||||
which is a plain stripe rather than `"mirror"`/`"raidz"`) root pool
|
|
||||||
across two disks, ESP + systemd-boot on the first. Device paths
|
|
||||||
(`vars.guiRootDisk1`/`guiRootDisk2`) are placeholders — fill in stable
|
|
||||||
`/dev/disk/by-id/...` paths before running disko for real.
|
|
||||||
`modules/platforms/baremetal.nix` also imports
|
|
||||||
`modules/services/zfs/enable-service.nix` for this (the `zfs_unstable`
|
|
||||||
package, autoScrub/autoSnapshot/trim) — the only other importer today is
|
|
||||||
`server`'s NFS data pool, an unrelated non-root ZFS use.
|
|
||||||
- `modules/boot/efi.nix` — systemd-boot + EFI vars, paired with the disko module.
|
- `modules/boot/efi.nix` — systemd-boot + EFI vars, paired with the disko module.
|
||||||
- `modules/installer/` — the auto-installer environment (ISO, also served as
|
- `modules/installer/` — the auto-installer environment (ISO, also served as
|
||||||
PXE netboot): `common.nix` (shared config + the generated
|
PXE netboot): `common.nix` (shared config + the generated
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ workstation.
|
|||||||
Targets are named `<platform>-<buildtype>`, generated from two orthogonal
|
Targets are named `<platform>-<buildtype>`, generated from two orthogonal
|
||||||
pieces composed in `flake.nix`:
|
pieces composed in `flake.nix`:
|
||||||
|
|
||||||
- **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`, `baremetal`
|
- **Platforms** (what it runs on): `linode`, `proxmox`, `lxc`
|
||||||
- **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-exit-node`, `tor-relay`
|
||||||
|
|
||||||
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, and
|
||||||
`tor-relay` currently only exists as `lxc-tor-relay`, and `baremetal`
|
`tor-relay` currently only exists as `lxc-tor-relay`. The full list:
|
||||||
currently only exists as `baremetal-gui` (the real gui-host hardware). The
|
|
||||||
full list:
|
|
||||||
|
|
||||||
| Target | Purpose |
|
| Target | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -27,7 +25,6 @@ full list:
|
|||||||
| `linode-server` / `proxmox-server` / `lxc-server` | Storage, NFS, backup, and monitoring exporter host — previously the flat `server` target |
|
| `linode-server` / `proxmox-server` / `lxc-server` | Storage, NFS, backup, and monitoring exporter host — previously the flat `server` target |
|
||||||
| `linode-docker` / `proxmox-docker` / `lxc-docker` | Docker host for the main container stack — previously the flat `docker` target |
|
| `linode-docker` / `proxmox-docker` / `lxc-docker` | Docker host for the main container stack — previously the flat `docker` target |
|
||||||
| `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 |
|
|
||||||
| `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-exit-node` / `proxmox-tailscale-exit-node` / `lxc-tailscale-exit-node` | Tailscale exit node |
|
||||||
| `lxc-tor-relay` | Tor middle relay |
|
| `lxc-tor-relay` | Tor middle relay |
|
||||||
@@ -67,7 +64,7 @@ nix eval --json .#nixosConfigurations --apply builtins.attrNames | jq -r '.[]'
|
|||||||
| `variables.nix` | Single source of truth for shared values (LAN domain/CIDR, hostnames, timezone, primary username, storage root, NFS share subpaths/mountpoints, service ports, ...) — passed to every module and Home Manager config as the `vars` argument via `specialArgs`/`extraSpecialArgs` |
|
| `variables.nix` | Single source of truth for shared values (LAN domain/CIDR, hostnames, timezone, primary username, storage root, NFS share subpaths/mountpoints, service ports, ...) — passed to every module and Home Manager config as the `vars` argument via `specialArgs`/`extraSpecialArgs` |
|
||||||
| `hosts/<name>/host.nix` | Per-machine identity: hostname, hostId, per-machine secrets, `system.stateVersion` |
|
| `hosts/<name>/host.nix` | Per-machine identity: hostname, hostId, per-machine secrets, `system.stateVersion` |
|
||||||
| `hosts/nixos/home.nix` | Workstation-specific Home Manager config (used by the `gui` build type) |
|
| `hosts/nixos/home.nix` | Workstation-specific Home Manager config (used by the `gui` build type) |
|
||||||
| `modules/platforms/` | Platform-specific config: virtualisation guest tools, boot method, hardware config (`linode.nix`, `proxmox.nix`, `lxc.nix`, `baremetal.nix`) |
|
| `modules/platforms/` | Platform-specific config: virtualisation guest tools, boot method, hardware config (`linode.nix`, `proxmox.nix`, `lxc.nix`) |
|
||||||
| `modules/build-types/` | Build-type-specific config: what makes a system minimal/server/docker/gui/pxe-boot/nix-cache |
|
| `modules/build-types/` | Build-type-specific config: what makes a system minimal/server/docker/gui/pxe-boot/nix-cache |
|
||||||
| `modules/common/` | Shared NixOS config, Home Manager, aliases imported by every host |
|
| `modules/common/` | Shared NixOS config, Home Manager, aliases imported by every host |
|
||||||
| `modules/nix-cache/` | Binary cache and remote builder client/server modules |
|
| `modules/nix-cache/` | Binary cache and remote builder client/server modules |
|
||||||
|
|||||||
+5
-26
@@ -8,13 +8,10 @@ lives here.
|
|||||||
The installer provides a small NixOS install environment (ISO, or the same
|
The installer provides a small NixOS install environment (ISO, or the same
|
||||||
image netbooted via PXE) with SSH access, Git support, and an interactive
|
image netbooted via PXE) with SSH access, Git support, and an interactive
|
||||||
installation script.
|
installation script.
|
||||||
Logging in as any user (root or `nixos`) runs
|
Logging in as any user (root or `nixos`) runs `/etc/auto-install.sh`,
|
||||||
`/etc/nixos-installer/installer/auto-install.sh` (the same file as
|
discovers available hosts from this same flake, lets the operator choose a
|
||||||
`scripts/installer/auto-install.sh` in this repo — see "Installer process"
|
target, applies that host's Disko storage configuration, installs NixOS, and
|
||||||
below for why it's baked in at that path rather than a flat
|
reboots.
|
||||||
`/etc/auto-install.sh`), discovers available hosts from this same flake,
|
|
||||||
lets the operator choose a target, applies that host's Disko storage
|
|
||||||
configuration, installs NixOS, and reboots.
|
|
||||||
|
|
||||||
**This applies to every `nixosConfigurations` target except `lxc-*` hosts —
|
**This applies to every `nixosConfigurations` target except `lxc-*` hosts —
|
||||||
see "LXC hosts" immediately below for why those are different.**
|
see "LXC hosts" immediately below for why those are different.**
|
||||||
@@ -195,10 +192,6 @@ default.
|
|||||||
`auto-install.sh` still supports the older manual path as a fallback: if a
|
`auto-install.sh` still supports the older manual path as a fallback: if a
|
||||||
host's key isn't baked in (`/etc/host-keys`), it checks `/root/host-keys`
|
host's key isn't baked in (`/etc/host-keys`), it checks `/root/host-keys`
|
||||||
next, where you can `scp` a key in after boot, same as before this migration.
|
next, where you can `scp` a key in after boot, same as before this migration.
|
||||||
If neither has it and the script is running interactively (an actual
|
|
||||||
operator at the other end of stdin, not an unattended run), it prompts for
|
|
||||||
an arbitrary directory to check (a mounted USB stick, another filesystem,
|
|
||||||
etc.) and copies the key pair into `/root/host-keys` from there if found.
|
|
||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
|
|
||||||
@@ -224,21 +217,7 @@ entirely (see "LXC hosts" above), so it never reaches this code path.
|
|||||||
|
|
||||||
## Installer process
|
## Installer process
|
||||||
|
|
||||||
`scripts/installer/auto-install.sh` is a real, version-controlled shell
|
`/etc/auto-install.sh`:
|
||||||
script — not an inline Nix string. It sources `scripts/env.sh` for
|
|
||||||
`LAN_DOMAIN` itself (same as every other script in `scripts/`), so it
|
|
||||||
behaves identically whether it's run straight from a git checkout (e.g.
|
|
||||||
manually, from a stock NixOS ISO that isn't this repo's own installer
|
|
||||||
image) or from inside the built installer image. That's also why it's
|
|
||||||
baked in at `/etc/nixos-installer/installer/auto-install.sh` rather than a
|
|
||||||
flat `/etc/auto-install.sh` — `modules/installer/common.nix` bakes
|
|
||||||
`scripts/env.sh` in alongside it at `/etc/nixos-installer/env.sh`,
|
|
||||||
preserving the same relative layout (`installer/auto-install.sh` ->
|
|
||||||
`../env.sh`) the checked-out repo has, so the script's own
|
|
||||||
`source ".../env.sh"` line resolves correctly in both places without any
|
|
||||||
Nix-level templating.
|
|
||||||
|
|
||||||
Once running, it:
|
|
||||||
|
|
||||||
1. Queries `nixosConfigurations` from this flake over the network (`git+https://<lanDomain>/beatzaplenty/nixos.git`) — this happens at *install* time, not build time, so a generic installer image always sees whatever hosts are currently committed, without needing a rebuild.
|
1. Queries `nixosConfigurations` from this flake over the network (`git+https://<lanDomain>/beatzaplenty/nixos.git`) — this happens at *install* time, not build time, so a generic installer image always sees whatever hosts are currently committed, without needing a rebuild.
|
||||||
2. Presents them as a menu; confirms the choice.
|
2. Presents them as a menu; confirms the choice.
|
||||||
|
|||||||
Generated
+6
-6
@@ -95,11 +95,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784350909,
|
"lastModified": 1783740085,
|
||||||
"narHash": "sha256-ZWyzLbS1yKUTeFJLmdVuWNnHttL333/ldJbEE+KzCrM=",
|
"narHash": "sha256-qajyHfZY29G2oEQk+uHxmsJcRoBUBXP9maTpFlwP/dI=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "4ce190229c73d44536caa7072f6308fb2d8feeb3",
|
"rev": "3cd22efe6471dc7365c822bd9ad73a21e55f38fb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -147,11 +147,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784432872,
|
"lastModified": 1784011430,
|
||||||
"narHash": "sha256-n3gKTBIV4ZA5VQpUakffBe3KGu4+mhPoA34rrqS0GkA=",
|
"narHash": "sha256-lDebytrYdd47IBLwvNOD+6AGeoqZ78CIKlp70hzW280=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "fd1462031fdee08f65fd0b4c6b64e22239a77870",
|
"rev": "8eeec934ae0dbeca3d7868c059568a65c08b2fc3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -91,14 +91,13 @@
|
|||||||
linode-gui = mkTarget { platform = "linode"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
linode-gui = mkTarget { platform = "linode"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
||||||
proxmox-gui = mkTarget { platform = "proxmox"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
proxmox-gui = mkTarget { platform = "proxmox"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
||||||
lxc-gui = mkTarget { platform = "lxc"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
lxc-gui = mkTarget { platform = "lxc"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
||||||
baremetal-gui = mkTarget { platform = "baremetal"; buildType = "gui"; hostPath = ./hosts/nixos/host.nix; homeFile = ./hosts/nixos/home.nix; };
|
|
||||||
|
|
||||||
proxmox-pxe-boot = mkTarget { platform = "proxmox"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; };
|
proxmox-pxe-boot = mkTarget { platform = "proxmox"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; };
|
||||||
lxc-pxe-boot = mkTarget { platform = "lxc"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; };
|
lxc-pxe-boot = mkTarget { platform = "lxc"; buildType = "pxe-boot"; hostPath = ./hosts/pxe-boot/host.nix; };
|
||||||
|
|
||||||
linode-tailscale-subnet-router = mkTarget { platform = "linode"; buildType = "tailscale-subnet-router"; hostPath = ./hosts/tailscale-subnet-router/host.nix; };
|
linode-tailscale-exit-node = mkTarget { platform = "linode"; buildType = "tailscale-exit-node"; hostPath = ./hosts/tailscale-exit-node/host.nix; };
|
||||||
proxmox-tailscale-subnet-router = mkTarget { platform = "proxmox"; buildType = "tailscale-subnet-router"; hostPath = ./hosts/tailscale-subnet-router/host.nix; };
|
proxmox-tailscale-exit-node = mkTarget { platform = "proxmox"; buildType = "tailscale-exit-node"; hostPath = ./hosts/tailscale-exit-node/host.nix; };
|
||||||
lxc-tailscale-subnet-router = mkTarget { platform = "lxc"; buildType = "tailscale-subnet-router"; hostPath = ./hosts/tailscale-subnet-router/host.nix; };
|
lxc-tailscale-exit-node = mkTarget { platform = "lxc"; buildType = "tailscale-exit-node"; hostPath = ./hosts/tailscale-exit-node/host.nix; };
|
||||||
|
|
||||||
lxc-tor-relay = mkTarget { platform = "lxc"; buildType = "tor-relay"; hostPath = ./hosts/tor-relay/host.nix; };
|
lxc-tor-relay = mkTarget { platform = "lxc"; buildType = "tor-relay"; hostPath = ./hosts/tor-relay/host.nix; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,15 +19,11 @@
|
|||||||
nextcloud-client
|
nextcloud-client
|
||||||
# vscode
|
# vscode
|
||||||
chromium
|
chromium
|
||||||
claude-code
|
|
||||||
fish
|
|
||||||
sops
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Optional: set environment vars
|
# Optional: set environment vars
|
||||||
sessionVariables = {
|
sessionVariables = {
|
||||||
EDITOR = "vim";
|
EDITOR = "vim";
|
||||||
SOPS_AGE_KEY_FILE = "/home/nixos/Nextcloud/Filing Cabinet/keys/nixos-sops-age-key-txt";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
file = {
|
file = {
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
_:
|
_:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
|
||||||
../../modules/networking/wifi.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = "nixos";
|
networking.hostName = "nixos";
|
||||||
|
|
||||||
# Only needed now that baremetal-gui exists (ZFS root) -- harmless on the
|
|
||||||
# ext4-rooted linode/proxmox/lxc-gui variants, so set unconditionally
|
|
||||||
# rather than only on the baremetal platform.
|
|
||||||
networking.hostId = "de6a9ffc";
|
|
||||||
|
|
||||||
# Preserved from the pre-refactor `nixos` target — stateVersion must never
|
# Preserved from the pre-refactor `nixos` 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";
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
_:
|
||||||
|
|
||||||
|
{
|
||||||
|
networking.hostName = "exit-node";
|
||||||
|
|
||||||
|
# No networking.hostId: only ZFS-touching hosts (server, docker) need one
|
||||||
|
# for pool-import safety, and this host does neither.
|
||||||
|
|
||||||
|
# A genuinely new host (not a pre-refactor carry-over), so it tracks the
|
||||||
|
# flake's current nixpkgs release rather than being pinned to an older one.
|
||||||
|
system.stateVersion = "26.05";
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
_:
|
|
||||||
|
|
||||||
{
|
|
||||||
networking.hostName = "tailscale-router";
|
|
||||||
|
|
||||||
# No networking.hostId: only ZFS-touching hosts (server, docker) need one
|
|
||||||
# for pool-import safety, and this host does neither.
|
|
||||||
|
|
||||||
system.stateVersion = "26.05";
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
boot.loader.grub.useOSProber = true;
|
boot.loader.grub.useOSProber = true;
|
||||||
programs.direnv.enable = true;
|
|
||||||
services = {
|
services = {
|
||||||
xserver = {
|
xserver = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -1,74 +1,12 @@
|
|||||||
{ vars, lib, pkgs, ... }:
|
{ vars, lib, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
poolName = lib.removePrefix "/" vars.storageRoot;
|
|
||||||
|
|
||||||
# For each NFS share subpath, generate every ancestor path so ZFS datasets
|
|
||||||
# are created parent-first. e.g. "docker/config" → ["docker" "docker/config"]
|
|
||||||
ancestors = path:
|
|
||||||
let parts = lib.splitString "/" path;
|
|
||||||
in lib.imap1 (i: _: lib.concatStringsSep "/" (lib.take i parts)) parts;
|
|
||||||
|
|
||||||
poolDatasets = lib.unique (
|
|
||||||
lib.concatMap (share: ancestors share.subpath) (lib.attrValues vars.nfsShares)
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../beszel/enable-agent.nix
|
../beszel/enable-agent.nix
|
||||||
../services/zfs/enable-service.nix
|
../services/zfs/enable-service.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.zfs.extraPools = [ poolName ];
|
boot.zfs.extraPools = [ (lib.removePrefix "/" vars.storageRoot) ];
|
||||||
|
|
||||||
# On a fresh image deploy the data disk (scsi1) starts blank — no pool
|
|
||||||
# exists yet, so zfs-import-tank.service would spin for 60 s and fail.
|
|
||||||
# This service runs first: if the pool is already present it exits instantly;
|
|
||||||
# otherwise it creates it (with all required datasets) so the standard
|
|
||||||
# import service finds it ready on the very first boot.
|
|
||||||
systemd.services."zfs-init-${poolName}" = {
|
|
||||||
description = "Initialize '${poolName}' ZFS pool on first boot if not present";
|
|
||||||
wantedBy = [ "zfs-import-${poolName}.service" ];
|
|
||||||
before = [ "zfs-import-${poolName}.service" ];
|
|
||||||
after = [ "systemd-udev-settle.service" ];
|
|
||||||
unitConfig.DefaultDependencies = false;
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
};
|
|
||||||
path = [ pkgs.zfs_unstable ];
|
|
||||||
script = ''
|
|
||||||
# Already imported — nothing to do.
|
|
||||||
if zpool list "${poolName}" >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Pool exists on a device but not yet imported — let the standard
|
|
||||||
# zfs-import-${poolName}.service handle it normally.
|
|
||||||
if zpool import -d /dev/disk/by-id -N "${poolName}" 2>/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# No pool found at all. Create it on the Proxmox data disk (scsi1),
|
|
||||||
# which appears as /dev/disk/by-id/scsi-*drive-scsi1 inside the VM.
|
|
||||||
DATA_DISK=""
|
|
||||||
for candidate in /dev/disk/by-id/scsi-*drive-scsi1; do
|
|
||||||
[[ "$candidate" == *-part* ]] && continue
|
|
||||||
[ -b "$candidate" ] && DATA_DISK="$candidate" && break
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -z "$DATA_DISK" ]; then
|
|
||||||
echo "zfs-init-${poolName}: no data disk found (expected /dev/disk/by-id/scsi-*drive-scsi1)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "zfs-init-${poolName}: creating pool on $DATA_DISK"
|
|
||||||
zpool create -f "${poolName}" "$DATA_DISK"
|
|
||||||
${lib.concatMapStrings (ds: ''
|
|
||||||
zfs create "${poolName}/${ds}"
|
|
||||||
'') poolDatasets}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.nfs-server = {
|
systemd.services.nfs-server = {
|
||||||
after = [ "zfs-mount.service" ];
|
after = [ "zfs-mount.service" ];
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{ ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../tailscale/exit-node.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
# "server", not "both": this build type only ever advertises itself as an
|
||||||
|
# exit node (see ../tailscale/exit-node.nix) -- it doesn't advertise LAN
|
||||||
|
# subnet routes, so it doesn't need the "client"-side loose reverse-path
|
||||||
|
# filtering that "both" would also turn on. Deliberately left unbundled
|
||||||
|
# from LAN-subnet-route advertisement so this build type stays valid on
|
||||||
|
# every platform, including linode (a remote VPS with no network path to
|
||||||
|
# the home LAN at all).
|
||||||
|
services.tailscale.useRoutingFeatures = "server";
|
||||||
|
|
||||||
|
# Forwarded exit-node traffic arrives on tailscale0 already
|
||||||
|
# tailscale-authenticated -- the firewall's normal per-port allow-list
|
||||||
|
# would otherwise drop it. Standard NixOS/Tailscale exit-node guidance.
|
||||||
|
networking.firewall.trustedInterfaces = [ "tailscale0" ];
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../tailscale/subnet-router.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
# "server", not "both": this build type advertises LAN subnet routes but
|
|
||||||
# doesn't use another tailscale exit node itself, so it doesn't need the
|
|
||||||
# "client"-side loose reverse-path filtering that "both" would also enable.
|
|
||||||
# Deliberately kept explicit here (not just relying on subnet-router.nix's
|
|
||||||
# own setting) so the intent is clear at the build-type level.
|
|
||||||
services.tailscale.useRoutingFeatures = "server";
|
|
||||||
|
|
||||||
# Forwarded subnet-router traffic arrives on tailscale0 already
|
|
||||||
# tailscale-authenticated -- the firewall's normal per-port allow-list
|
|
||||||
# would otherwise drop it. Standard NixOS/Tailscale subnet-router guidance.
|
|
||||||
networking.firewall.trustedInterfaces = [ "tailscale0" ];
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,5 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
../tor/enable-relay.nix
|
../tor/enable-relay.nix
|
||||||
../beszel/enable-agent.nix
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
btop
|
btop
|
||||||
git
|
git
|
||||||
gcr
|
gcr
|
||||||
jq
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Secrets shared by every host, decrypted at activation via each host's
|
# Secrets shared by every host, decrypted at activation via each host's
|
||||||
@@ -77,7 +76,6 @@
|
|||||||
openssh.authorizedKeys.keys = [
|
openssh.authorizedKeys.keys = [
|
||||||
vars.adminSshKey
|
vars.adminSshKey
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICMJhrfFayLBG+gWtO6oAvgambw5nWWgztiTFEaaaVRH debian@surface"
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICMJhrfFayLBG+gWtO6oAvgambw5nWWgztiTFEaaaVRH debian@surface"
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGygkCljN6uKpdJbHTOQtn8ZnH+wKXDLAwrDFbLrE/65 nixos@nixos"
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
{ vars, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# ZFS RAID0 (striped, no redundancy) root pool for the bare-metal gui
|
|
||||||
# host — two disks, each contributing its own top-level vdev. disko's
|
|
||||||
# zpool `mode` defaults to "" (plain stripe) when left unset, which is
|
|
||||||
# what gives RAID0 semantics here rather than mirror/raidz.
|
|
||||||
#
|
|
||||||
# Device paths are placeholders until the real hardware profile lands —
|
|
||||||
# fill in vars.guiRootDisk1/guiRootDisk2 (stable /dev/disk/by-id/...
|
|
||||||
# paths, not /dev/sdX) before running disko against real hardware. Swap
|
|
||||||
# is deliberately left out for now — sizing that sensibly needs the
|
|
||||||
# box's actual RAM size, which comes with the hardware profile too.
|
|
||||||
#
|
|
||||||
# Not yet imported anywhere: this awaits the new bare-metal platform
|
|
||||||
# module (alongside modules/boot/efi.nix for systemd-boot, matching
|
|
||||||
# modules/platforms/proxmox.nix's pattern) once the hardware config is
|
|
||||||
# in hand.
|
|
||||||
disko.devices = {
|
|
||||||
disk = {
|
|
||||||
disk1 = {
|
|
||||||
type = "disk";
|
|
||||||
device = vars.guiRootDisk1;
|
|
||||||
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
|
|
||||||
partitions = {
|
|
||||||
esp = {
|
|
||||||
priority = 1;
|
|
||||||
name = "ESP";
|
|
||||||
size = "512M";
|
|
||||||
type = "EF00";
|
|
||||||
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
mountOptions = [ "umask=0077" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
zfs = {
|
|
||||||
size = "100%";
|
|
||||||
|
|
||||||
content = {
|
|
||||||
type = "zfs";
|
|
||||||
pool = "rpool";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
disk2 = {
|
|
||||||
type = "disk";
|
|
||||||
device = vars.guiRootDisk2;
|
|
||||||
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
|
|
||||||
partitions = {
|
|
||||||
zfs = {
|
|
||||||
size = "100%";
|
|
||||||
|
|
||||||
content = {
|
|
||||||
type = "zfs";
|
|
||||||
pool = "rpool";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
zpool.rpool = {
|
|
||||||
type = "zpool";
|
|
||||||
|
|
||||||
rootFsOptions = {
|
|
||||||
compression = "zstd";
|
|
||||||
"com.sun:auto-snapshot" = "false";
|
|
||||||
};
|
|
||||||
mountpoint = "/";
|
|
||||||
options.ashift = "12";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Adapted from the output of `nixos-generate-config`, run from a live GUI
|
|
||||||
# ISO boot on the actual gui-host hardware (AMD CPU). fileSystems and
|
|
||||||
# swapDevices are deliberately omitted -- the live ISO had no formatted
|
|
||||||
# disks to detect, and disko (modules/disko/baremetal.nix) generates both
|
|
||||||
# from the declarative zpool layout anyway.
|
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports =
|
|
||||||
[
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
boot = {
|
|
||||||
initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
|
|
||||||
initrd.kernelModules = [ ];
|
|
||||||
kernelModules = [ "kvm-amd" ];
|
|
||||||
extraModulePackages = [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
}
|
|
||||||
+133
-14
@@ -45,23 +45,142 @@
|
|||||||
disko
|
disko
|
||||||
];
|
];
|
||||||
|
|
||||||
# Auto-install script, kept as a real, version-controlled shell file at
|
# Write auto-install script to /root
|
||||||
# scripts/installer/auto-install.sh rather than an inline Nix string.
|
etc."auto-install.sh" = {
|
||||||
# It sources scripts/env.sh itself (for LAN_DOMAIN, same as every other
|
text = ''
|
||||||
# script in this repo) rather than relying on Nix-level templating, so
|
#!/run/current-system/sw/bin/bash
|
||||||
# it behaves identically whether it's run straight from a git checkout
|
set -eux
|
||||||
# or from here -- baking scripts/env.sh in alongside it at a matching
|
|
||||||
# relative path (installer/auto-install.sh -> ../env.sh) is what makes
|
set -euo pipefail
|
||||||
# that resolve correctly in both places.
|
|
||||||
etc = {
|
export FLAKE_BASE_URL="git+https://${vars.lanDomain}/beatzaplenty/nixos.git"
|
||||||
"nixos-installer/env.sh".source = ../../scripts/env.sh;
|
|
||||||
|
echo "Fetching available NixOS hosts from flake..."
|
||||||
|
# Two categories deliberately excluded from the menu:
|
||||||
|
# lxc-* — these build a config.system.build.tarball meant for
|
||||||
|
# `pct restore` on Proxmox directly, not an install.
|
||||||
|
# Running nixos-install against one here would
|
||||||
|
# bind-mount / onto /mnt and then refuse to touch the
|
||||||
|
# filesystem it's currently running on — see
|
||||||
|
# docs/auto-installer.md.
|
||||||
|
# installer — this *is* the installer image's own flake target,
|
||||||
|
# not a deployable host; "installing" it means
|
||||||
|
# nixos-install-ing a copy of the installer into
|
||||||
|
# itself.
|
||||||
|
mapfile -t options < <(
|
||||||
|
nix eval --json --no-use-registries --no-accept-flake-config --extra-experimental-features "flakes nix-command" \
|
||||||
|
"''${FLAKE_BASE_URL}#nixosConfigurations" \
|
||||||
|
--apply builtins.attrNames \
|
||||||
|
| jq -r '.[]
|
||||||
|
| select(startswith("lxc-") | not)
|
||||||
|
| select(. != "installer")'
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ ''${#options[@]} -eq 0 ]]; then
|
||||||
|
echo "ERROR: No NixOS hosts found in ''${FLAKE_BASE_URL}#nixosConfigurations" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Note: lxc-* targets aren't installed this way — build them with"
|
||||||
|
echo " nix build .#nixosConfigurations.<name>.config.system.build.tarball"
|
||||||
|
echo "and 'pct restore' the result on Proxmox directly. See docs/auto-installer.md."
|
||||||
|
|
||||||
|
echo "Choose the flake profile to install:"
|
||||||
|
select choice in "''${options[@]}"; do
|
||||||
|
if [[ -n "$choice" ]]; then
|
||||||
|
echo "You selected: $choice"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "Invalid selection. Try again."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Starting install with flake: ''${FLAKE_BASE_URL}#''${choice}"
|
||||||
|
|
||||||
|
# Optional: confirm before proceeding
|
||||||
|
read -rp "Proceed with installation? (y/N): " confirm
|
||||||
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A nix-cache host is *the* substituter/remote-builder for every other
|
||||||
|
# host once installed (its own config explicitly excludes itself from
|
||||||
|
# using either — see buildType != "nix-cache" in the nixos flake.nix).
|
||||||
|
# Installing one shouldn't depend on a nix-cache substituter either,
|
||||||
|
# for the same reason — plus in practice "nix-cache" only resolves over
|
||||||
|
# Tailscale, which a fresh installer environment was never connected to
|
||||||
|
# anyway, so it's dead weight even for non-nix-cache installs until
|
||||||
|
# that's sorted out. Override it away here specifically for nix-cache
|
||||||
|
# targets to keep install-time behaviour consistent with run-time.
|
||||||
|
nix_extra_opts=()
|
||||||
|
if [[ "''${choice}" == *-nix-cache ]]; then
|
||||||
|
echo "Installing a nix-cache host — skipping the nix-cache substituter."
|
||||||
|
nix_extra_opts+=(--option substituters "https://cache.nixos.org/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Every host reachable through this menu has a Disko config (lxc-*
|
||||||
|
# is filtered out above, and is the only category that doesn't —
|
||||||
|
# see docs/auto-installer.md), so this can run unconditionally: no
|
||||||
|
# need to probe the flake first and branch on whether Disko applies.
|
||||||
|
disko --mode destroy,format,mount \
|
||||||
|
--flake "''${FLAKE_BASE_URL}#''${choice}" "''${nix_extra_opts[@]}" --yes-wipe-all-disks
|
||||||
|
|
||||||
|
# sops-nix derives this host's decryption key from its own SSH host key
|
||||||
|
# at *activation* time, which runs before systemd would otherwise
|
||||||
|
# generate one on first boot. Without pre-seeding it here, secrets
|
||||||
|
# (including the login password) fail to decrypt on first boot.
|
||||||
|
# Generate the key with scripts/secrets/prepare-host-key.sh first.
|
||||||
|
#
|
||||||
|
# Two places a key can come from, checked in order:
|
||||||
|
# /etc/host-keys — baked into this image at build time (see
|
||||||
|
# modules/installer/host-keys.nix; only present
|
||||||
|
# if built with NIXOS_HOST_KEYS_DIR set)
|
||||||
|
# /root/host-keys — scp'd in manually after boot (older fallback,
|
||||||
|
# still supported for images built without keys)
|
||||||
|
mkdir -p /root/host-keys
|
||||||
|
if [[ -f "/etc/host-keys/''${choice}_ssh_host_ed25519_key" ]]; then
|
||||||
|
echo "Found baked-in SSH host key for ''${choice}, installing to target..."
|
||||||
|
install -D -m 0600 "/etc/host-keys/''${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
||||||
|
install -D -m 0644 "/etc/host-keys/''${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
||||||
|
elif [[ -f "/root/host-keys/''${choice}_ssh_host_ed25519_key" ]]; then
|
||||||
|
echo "Found pre-seeded SSH host key for ''${choice}, installing to target..."
|
||||||
|
install -D -m 0600 "/root/host-keys/''${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
||||||
|
install -D -m 0644 "/root/host-keys/''${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
||||||
|
else
|
||||||
|
echo "WARNING: no SSH host key found for ''${choice} (checked /etc/host-keys and /root/host-keys)"
|
||||||
|
echo "sops-nix secrets (including the login password) will NOT decrypt on first boot."
|
||||||
|
echo "Run scripts/secrets/prepare-host-key.sh for host ''${choice} on your admin workstation first,"
|
||||||
|
echo "then either rebuild this image with NIXOS_HOST_KEYS_DIR set, or scp the result to"
|
||||||
|
echo "/root/host-keys/ on this machine."
|
||||||
|
read -rp "Continue without a pre-seeded key anyway? (y/N): " skip_key
|
||||||
|
if [[ ! "$skip_key" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p /mnt/install-tmp
|
||||||
|
export TMPDIR=/mnt/install-tmp
|
||||||
|
|
||||||
|
nixos-install \
|
||||||
|
--flake "''${FLAKE_BASE_URL}#''${choice}" \
|
||||||
|
"''${nix_extra_opts[@]}" \
|
||||||
|
--no-root-password
|
||||||
|
|
||||||
|
|
||||||
|
rm -rf /mnt/install-tmp
|
||||||
|
# Redundant copy of the host's private key — the real one is now at
|
||||||
|
# /etc/ssh/ssh_host_ed25519_key. Nothing NixOS-managed ever cleans this
|
||||||
|
# up on its own since it was written imperatively, not declaratively.
|
||||||
|
rm -rf /root/host-keys
|
||||||
|
sleep 10
|
||||||
|
reboot
|
||||||
|
'';
|
||||||
|
|
||||||
"nixos-installer/installer/auto-install.sh" = {
|
|
||||||
source = ../../scripts/installer/auto-install.sh;
|
|
||||||
mode = "0755";
|
mode = "0755";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
programs.git.enable = true;
|
programs.git.enable = true;
|
||||||
|
|
||||||
@@ -73,7 +192,7 @@
|
|||||||
# file-copying/chown.
|
# file-copying/chown.
|
||||||
programs.bash.loginShellInit = ''
|
programs.bash.loginShellInit = ''
|
||||||
if [ -n "$PS1" ] && [ ! -e "$HOME/.auto_install_ran" ]; then
|
if [ -n "$PS1" ] && [ ! -e "$HOME/.auto_install_ran" ]; then
|
||||||
sudo /etc/nixos-installer/installer/auto-install.sh
|
sudo /etc/auto-install.sh
|
||||||
touch "$HOME/.auto_install_ran"
|
touch "$HOME/.auto_install_ran"
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
{ config, lib, vars, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Prestages a NetworkManager connection profile for vars.wifiSsid so the
|
|
||||||
# host associates on first boot with no manual nmtui/nmcli step. Guarded
|
|
||||||
# on a non-empty SSID so leaving the placeholder blank in variables.nix
|
|
||||||
# is a no-op rather than an empty, broken profile — fill it in once the
|
|
||||||
# network is known.
|
|
||||||
#
|
|
||||||
# The password itself lives in secrets/gui.yaml, not variables.nix --
|
|
||||||
# NetworkManager's ensureProfiles renders `psk = "$WIFI_PASSWORD"`
|
|
||||||
# literally into the store (see nixpkgs' own ensureProfiles example,
|
|
||||||
# which does the same for exactly this reason) and its systemd service
|
|
||||||
# envsubst-expands it from environmentFiles at activation time, so the
|
|
||||||
# real value only ever touches /run (root-only, UMask 0177), never the
|
|
||||||
# Nix store.
|
|
||||||
sops.secrets."wifi-password" = lib.mkIf (vars.wifiSsid != "") {
|
|
||||||
sopsFile = ../../secrets/gui.yaml;
|
|
||||||
};
|
|
||||||
|
|
||||||
sops.templates."wifi-password.env" = lib.mkIf (vars.wifiSsid != "") {
|
|
||||||
content = "WIFI_PASSWORD=${config.sops.placeholder."wifi-password"}";
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.networkmanager.ensureProfiles = lib.mkIf (vars.wifiSsid != "") {
|
|
||||||
environmentFiles = [ config.sops.templates."wifi-password.env".path ];
|
|
||||||
|
|
||||||
profiles.${vars.wifiSsid} = {
|
|
||||||
connection = {
|
|
||||||
id = vars.wifiSsid;
|
|
||||||
type = "wifi";
|
|
||||||
};
|
|
||||||
wifi = {
|
|
||||||
mode = "infrastructure";
|
|
||||||
ssid = vars.wifiSsid;
|
|
||||||
};
|
|
||||||
wifi-security = {
|
|
||||||
key-mgmt = "wpa-psk";
|
|
||||||
psk = "$WIFI_PASSWORD";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../hardware-configuration/baremetal.nix
|
|
||||||
../boot/efi.nix
|
|
||||||
../disko/baremetal.nix
|
|
||||||
../services/zfs/enable-service.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
# Needed for real wifi/bluetooth/GPU firmware blobs and CPU microcode
|
|
||||||
# updates (hardware-configuration/baremetal.nix's amd.updateMicrocode
|
|
||||||
# keys off this) -- irrelevant on the linode/proxmox/lxc platforms,
|
|
||||||
# which are all VMs with no real hardware to load firmware for.
|
|
||||||
hardware.enableRedistributableFirmware = true;
|
|
||||||
|
|
||||||
# AMD GPU: the amdgpu kernel driver autoloads from the PCI ID with no
|
|
||||||
# extra boot.kernelModules entry needed; this is the userspace half --
|
|
||||||
# the dedicated Xorg driver (not just the generic modesetting fallback)
|
|
||||||
# plus Mesa OpenGL/Vulkan (amdgpu/RADV), same firmware blobs as above.
|
|
||||||
# 32-bit support is for compatibility with 32-bit apps/games.
|
|
||||||
services.xserver.videoDrivers = [ "amdgpu" ];
|
|
||||||
|
|
||||||
hardware.graphics = {
|
|
||||||
enable = true;
|
|
||||||
enable32Bit = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# The systemd-based initrd (default here since this host has a ZFS root --
|
|
||||||
# see modules/disko/baremetal.nix) locks the root account by default, so
|
|
||||||
# sulogin refuses to hand over a shell if something in the initrd (e.g.
|
|
||||||
# the ZFS pool import) fails and it drops to emergency mode -- confirmed
|
|
||||||
# live: it just loops re-entering the target instead of prompting. This
|
|
||||||
# only affects the pre-switch-root initrd shell, not the installed
|
|
||||||
# system's own login, and is worth the tradeoff on a box already reachable
|
|
||||||
# at the physical console.
|
|
||||||
boot.initrd.systemd.emergencyAccess = true;
|
|
||||||
}
|
|
||||||
@@ -106,40 +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: two activation scripts that bracket the etc step.
|
|
||||||
# preserveSshHostKey — no deps, runs before etc — saves the live key to
|
|
||||||
# /run (tmpfs) before etc can delete it.
|
|
||||||
# restoreSshHostKey — deps=[etc], runs after etc — reinstalls the key via
|
|
||||||
# `install` (atomic, sets mode) if etc removed it.
|
|
||||||
# The resulting file is not registered in environment.etc
|
|
||||||
# for either the previous or current generation, so
|
|
||||||
# subsequent rebuilds leave it alone permanently.
|
|
||||||
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
|
|
||||||
'';
|
|
||||||
|
|
||||||
system.activationScripts.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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
_:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [ ./enable-service.nix ];
|
||||||
|
|
||||||
|
services.tailscale = {
|
||||||
|
# Enables the sysctl forwarding settings exit nodes/subnet routers need;
|
||||||
|
# without this, --advertise-exit-node has no effect.
|
||||||
|
useRoutingFeatures = "server";
|
||||||
|
|
||||||
|
# Lets peers reach this node directly over the tailscale UDP port
|
||||||
|
# instead of relaying through DERP.
|
||||||
|
openFirewall = true;
|
||||||
|
|
||||||
|
# extraSetFlags (tailscale set, via the always-on tailscaled-set
|
||||||
|
# service), not extraUpFlags -- extraUpFlags is only ever applied by
|
||||||
|
# tailscaled-autoconnect, which itself only runs when
|
||||||
|
# services.tailscale.authKeyFile is set (nothing in this repo sets one,
|
||||||
|
# so tailscale up is a manual, one-time operator step on every host that
|
||||||
|
# uses this service). extraSetFlags has no such gate, so
|
||||||
|
# --advertise-exit-node self-reapplies on every boot once the operator
|
||||||
|
# has authenticated the node once.
|
||||||
|
extraSetFlags = [
|
||||||
|
"--advertise-exit-node"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
_:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [ ./enable-service.nix ];
|
|
||||||
|
|
||||||
services.tailscale = {
|
|
||||||
# Enables the sysctl forwarding settings subnet routers need;
|
|
||||||
# without this, --advertise-routes has no effect.
|
|
||||||
useRoutingFeatures = "server";
|
|
||||||
|
|
||||||
# Lets peers reach this node directly over the tailscale UDP port
|
|
||||||
# instead of relaying through DERP.
|
|
||||||
openFirewall = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -16,14 +16,6 @@
|
|||||||
#
|
#
|
||||||
# --dry-run: adds `nix build --dry-run --no-link` for whatever scope is
|
# --dry-run: adds `nix build --dry-run --no-link` for whatever scope is
|
||||||
# active (changed-files scope by default, full scope under --full-check).
|
# active (changed-files scope by default, full scope under --full-check).
|
||||||
#
|
|
||||||
# Per-host/per-package eval and dry-run build calls run concurrently (see
|
|
||||||
# scripts/lib/nix-parallel.sh) since they're independent of each other.
|
|
||||||
# Concurrency defaults to core count capped by available memory (~1GB/job)
|
|
||||||
# rather than plain core count, since each concurrent `nix eval` evaluates a
|
|
||||||
# whole NixOS system closure and can OOM a small/memory-constrained CI
|
|
||||||
# runner otherwise; override via NIX_PARALLEL_JOBS if a runner has more (or
|
|
||||||
# less) room than that estimate assumes.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -31,8 +23,6 @@ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
source "${script_dir}/lib/nix-bootstrap.sh"
|
source "${script_dir}/lib/nix-bootstrap.sh"
|
||||||
# shellcheck source=lib/nix-eval.sh
|
# shellcheck source=lib/nix-eval.sh
|
||||||
source "${script_dir}/lib/nix-eval.sh"
|
source "${script_dir}/lib/nix-eval.sh"
|
||||||
# shellcheck source=lib/nix-parallel.sh
|
|
||||||
source "${script_dir}/lib/nix-parallel.sh"
|
|
||||||
|
|
||||||
repo_root="$(cd "${script_dir}/.." && pwd)"
|
repo_root="$(cd "${script_dir}/.." && pwd)"
|
||||||
cd "$repo_root"
|
cd "$repo_root"
|
||||||
@@ -256,64 +246,66 @@ echo
|
|||||||
if [[ ${#hosts[@]} -eq 0 ]]; then
|
if [[ ${#hosts[@]} -eq 0 ]]; then
|
||||||
echo "No hosts affected by changed files; skipping host eval."
|
echo "No hosts affected by changed files; skipping host eval."
|
||||||
else
|
else
|
||||||
echo "Evaluating host toplevel derivations (${scope_desc}, up to ${NIX_PARALLEL_JOBS} at a time)..."
|
echo "Evaluating host toplevel derivations (${scope_desc})..."
|
||||||
|
for host in "${hosts[@]}"; do
|
||||||
|
echo "==> $host"
|
||||||
|
nix eval --raw "${NIX_EVAL_FLAGS[@]}" ".#nixosConfigurations.${host}.config.system.build.toplevel.drvPath"
|
||||||
|
|
||||||
# lxc-* hosts deploy via a directly pct-restore-able tarball instead of
|
# lxc-* hosts deploy via a directly pct-restore-able tarball instead of
|
||||||
# nixos-install (see docs/auto-installer.md); proxmox-* hosts can
|
# nixos-install (see docs/auto-installer.md); proxmox-* hosts can
|
||||||
# alternatively be built as a standalone disk image (see
|
# alternatively be built as a standalone disk image (see
|
||||||
# docs/proxmox-images.md). Both are otherwise-unvalidated buildable
|
# docs/proxmox-images.md). Both are otherwise-unvalidated buildable
|
||||||
# surface, easy to silently break without this.
|
# surface, easy to silently break without this.
|
||||||
declare -a host_eval_jobs=()
|
|
||||||
for host in "${hosts[@]}"; do
|
|
||||||
host_eval_jobs+=("${host}${NIX_PARALLEL_SEP}.#nixosConfigurations.${host}.config.system.build.toplevel.drvPath")
|
|
||||||
case "$host" in
|
case "$host" in
|
||||||
lxc-*)
|
lxc-*)
|
||||||
host_eval_jobs+=("${host} (tarball)${NIX_PARALLEL_SEP}.#nixosConfigurations.${host}.config.system.build.tarball.drvPath")
|
echo "==> $host (tarball)"
|
||||||
|
nix eval --raw "${NIX_EVAL_FLAGS[@]}" ".#nixosConfigurations.${host}.config.system.build.tarball.drvPath"
|
||||||
;;
|
;;
|
||||||
proxmox-*)
|
proxmox-*)
|
||||||
host_eval_jobs+=("${host} (diskoImagesScript)${NIX_PARALLEL_SEP}.#nixosConfigurations.${host}.config.system.build.diskoImagesScript.drvPath")
|
echo "==> $host (diskoImagesScript)"
|
||||||
|
nix eval --raw "${NIX_EVAL_FLAGS[@]}" ".#nixosConfigurations.${host}.config.system.build.diskoImagesScript.drvPath"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
run_nix_parallel host_eval_jobs eval --raw "${NIX_EVAL_FLAGS[@]}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if ! $eval_packages; then
|
if ! $eval_packages; then
|
||||||
echo "No packages affected by changed files; skipping package eval."
|
echo "No packages affected by changed files; skipping package eval."
|
||||||
else
|
else
|
||||||
echo "Evaluating buildable packages (up to ${NIX_PARALLEL_JOBS} at a time)..."
|
echo "Evaluating buildable packages..."
|
||||||
declare -a package_eval_jobs=()
|
|
||||||
for pkg in "${all_packages[@]}"; do
|
for pkg in "${all_packages[@]}"; do
|
||||||
package_eval_jobs+=("packages.x86_64-linux.${pkg}${NIX_PARALLEL_SEP}.#packages.x86_64-linux.${pkg}")
|
echo "==> packages.x86_64-linux.${pkg}"
|
||||||
|
nix eval --raw "${NIX_EVAL_FLAGS[@]}" ".#packages.x86_64-linux.${pkg}"
|
||||||
done
|
done
|
||||||
run_nix_parallel package_eval_jobs eval --raw "${NIX_EVAL_FLAGS[@]}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if $dry_run; then
|
if $dry_run; then
|
||||||
echo
|
echo
|
||||||
echo "Running dry-run builds for the active scope (up to ${NIX_PARALLEL_JOBS} at a time). This will not create result symlinks."
|
echo "Running dry-run builds for the active scope. This will not create result symlinks."
|
||||||
declare -a host_build_jobs=()
|
|
||||||
for host in "${hosts[@]:-}"; do
|
for host in "${hosts[@]:-}"; do
|
||||||
host_build_jobs+=("Dry-run build: ${host}${NIX_PARALLEL_SEP}.#nixosConfigurations.${host}.config.system.build.toplevel")
|
echo "==> Dry-run build: $host"
|
||||||
|
nix build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}" ".#nixosConfigurations.${host}.config.system.build.toplevel"
|
||||||
|
|
||||||
case "$host" in
|
case "$host" in
|
||||||
lxc-*)
|
lxc-*)
|
||||||
host_build_jobs+=("Dry-run build: ${host} (tarball)${NIX_PARALLEL_SEP}.#nixosConfigurations.${host}.config.system.build.tarball")
|
echo "==> Dry-run build: $host (tarball)"
|
||||||
|
nix build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}" ".#nixosConfigurations.${host}.config.system.build.tarball"
|
||||||
;;
|
;;
|
||||||
proxmox-*)
|
proxmox-*)
|
||||||
host_build_jobs+=("Dry-run build: ${host} (diskoImagesScript)${NIX_PARALLEL_SEP}.#nixosConfigurations.${host}.config.system.build.diskoImagesScript")
|
echo "==> Dry-run build: $host (diskoImagesScript)"
|
||||||
|
nix build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}" ".#nixosConfigurations.${host}.config.system.build.diskoImagesScript"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
run_nix_parallel host_build_jobs build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}"
|
|
||||||
|
|
||||||
if $eval_packages; then
|
if $eval_packages; then
|
||||||
echo
|
echo
|
||||||
echo "Running dry-run builds for packages."
|
echo "Running dry-run builds for packages."
|
||||||
declare -a package_build_jobs=()
|
|
||||||
for pkg in "${all_packages[@]}"; do
|
for pkg in "${all_packages[@]}"; do
|
||||||
package_build_jobs+=("Dry-run build: packages.x86_64-linux.${pkg}${NIX_PARALLEL_SEP}.#packages.x86_64-linux.${pkg}")
|
echo "==> Dry-run build: packages.x86_64-linux.${pkg}"
|
||||||
|
nix build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}" ".#packages.x86_64-linux.${pkg}"
|
||||||
done
|
done
|
||||||
run_nix_parallel package_build_jobs build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ cat > "$HOME/.config/nix/nix.conf" <<'EOF'
|
|||||||
experimental-features = nix-command flakes
|
experimental-features = nix-command flakes
|
||||||
accept-flake-config = false
|
accept-flake-config = false
|
||||||
warn-dirty = false
|
warn-dirty = false
|
||||||
build-users-group =
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Nix version:"
|
echo "Nix version:"
|
||||||
|
|||||||
+2
-8
@@ -22,7 +22,7 @@
|
|||||||
: "${PVE1_HOST:=pve1.sweet.home}"
|
: "${PVE1_HOST:=pve1.sweet.home}"
|
||||||
: "${PVE_TEST_HOST:=pve-test.sweet.home}"
|
: "${PVE_TEST_HOST:=pve-test.sweet.home}"
|
||||||
: "${PROXMOX_HOST:=$PVE1_HOST}"
|
: "${PROXMOX_HOST:=$PVE1_HOST}"
|
||||||
: "${PROXMOX_SSH_USER:=wayne}"
|
: "${PROXMOX_SSH_USER:=root}"
|
||||||
|
|
||||||
# Where this flake repo lives on the Proxmox node itself.
|
# Where this flake repo lives on the Proxmox node itself.
|
||||||
# scripts/proxmox/create-proxmox-resource.sh builds images directly on the node
|
# scripts/proxmox/create-proxmox-resource.sh builds images directly on the node
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
# (from this checkout's own `origin` remote) the first time it doesn't
|
# (from this checkout's own `origin` remote) the first time it doesn't
|
||||||
# find it, installing build tooling via scripts/codex-setup.sh, then
|
# find it, installing build tooling via scripts/codex-setup.sh, then
|
||||||
# `git pull`s it before every subsequent build.
|
# `git pull`s it before every subsequent build.
|
||||||
: "${PROXMOX_REMOTE_REPO_DIR:=/home/${PROXMOX_SSH_USER}/nixos}"
|
: "${PROXMOX_REMOTE_REPO_DIR:=/root/nixos}"
|
||||||
|
|
||||||
# Storage pool names -- Proxmox's own stock-install defaults, but this
|
# Storage pool names -- Proxmox's own stock-install defaults, but this
|
||||||
# varies a lot by setup (ZFS pool name, custom LVM-thin volume, etc.).
|
# varies a lot by setup (ZFS pool name, custom LVM-thin volume, etc.).
|
||||||
@@ -82,12 +82,6 @@ export PVE1_HOST PVE_TEST_HOST PROXMOX_HOST PROXMOX_SSH_USER PROXMOX_STORAGE \
|
|||||||
: "${NIX_CACHE_HOST:=nix-cache}"
|
: "${NIX_CACHE_HOST:=nix-cache}"
|
||||||
export NIX_CACHE_HOST
|
export NIX_CACHE_HOST
|
||||||
|
|
||||||
# Matches variables.nix's lanDomain (the Gitea host this flake's own repo
|
|
||||||
# is served from -- see scripts/installer/auto-install.sh's FLAKE_BASE_URL)
|
|
||||||
# -- update both if it ever changes.
|
|
||||||
: "${LAN_DOMAIN:=gitea.lan.ddnsgeek.com}"
|
|
||||||
export LAN_DOMAIN
|
|
||||||
|
|
||||||
# nix_extra_opts: call as a plain statement (NOT inside $(...)/<(...) --
|
# nix_extra_opts: call as a plain statement (NOT inside $(...)/<(...) --
|
||||||
# that forks a subshell, and the whole point is exporting a decision back
|
# that forks a subshell, and the whole point is exporting a decision back
|
||||||
# into *this* shell) to populate the global NIX_OPTS array with whatever
|
# into *this* shell) to populate the global NIX_OPTS array with whatever
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
#!/usr/bin/env nix-shell
|
|
||||||
#!nix-shell -i bash -p jq disko nixos-install-tools zfs
|
|
||||||
# shellcheck shell=bash
|
|
||||||
# The only genuinely external tools this script calls directly: `jq`
|
|
||||||
# (parsing the `nix eval` host list), `disko`/`nixos-install` (the
|
|
||||||
# install itself), and `zpool` (exporting a ZFS root pool before reboot,
|
|
||||||
# see the comment above that call below). Everything disko shells out to
|
|
||||||
# internally (parted/sgdisk/mkfs.*/zfs/...) is self-contained -- disko's
|
|
||||||
# own generated scripts hardcode absolute Nix store paths for those, they
|
|
||||||
# don't rely on this script's PATH at all (confirmed by inspecting a
|
|
||||||
# generated system.build.formatScript). The built installer image
|
|
||||||
# (modules/installer/common.nix, plus the upstream
|
|
||||||
# installation-cd-minimal.nix it imports via iso.nix) already has all
|
|
||||||
# four in environment.systemPackages, so this nix-shell wrapper is a
|
|
||||||
# fast no-op there; it's what makes the script also work standalone
|
|
||||||
# (e.g. run directly from a checkout on a stock ISO), where they aren't
|
|
||||||
# guaranteed.
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# shellcheck source=../env.sh
|
|
||||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/env.sh"
|
|
||||||
|
|
||||||
export FLAKE_BASE_URL="git+https://${LAN_DOMAIN}/beatzaplenty/nixos.git"
|
|
||||||
|
|
||||||
echo "Fetching available NixOS hosts from flake..."
|
|
||||||
# Two categories deliberately excluded from the menu:
|
|
||||||
# lxc-* — these build a config.system.build.tarball meant for
|
|
||||||
# `pct restore` on Proxmox directly, not an install.
|
|
||||||
# Running nixos-install against one here would
|
|
||||||
# bind-mount / onto /mnt and then refuse to touch the
|
|
||||||
# filesystem it's currently running on — see
|
|
||||||
# docs/auto-installer.md.
|
|
||||||
# installer — this *is* the installer image's own flake target,
|
|
||||||
# not a deployable host; "installing" it means
|
|
||||||
# nixos-install-ing a copy of the installer into
|
|
||||||
# itself.
|
|
||||||
mapfile -t options < <(
|
|
||||||
nix eval --json --no-use-registries --no-accept-flake-config --extra-experimental-features "flakes nix-command" \
|
|
||||||
"${FLAKE_BASE_URL}#nixosConfigurations" \
|
|
||||||
--apply builtins.attrNames \
|
|
||||||
| jq -r '.[]
|
|
||||||
| select(startswith("lxc-") | not)
|
|
||||||
| select(. != "installer")'
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ ${#options[@]} -eq 0 ]]; then
|
|
||||||
echo "ERROR: No NixOS hosts found in ${FLAKE_BASE_URL}#nixosConfigurations" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Note: lxc-* targets aren't installed this way — build them with"
|
|
||||||
echo " nix build .#nixosConfigurations.<name>.config.system.build.tarball"
|
|
||||||
echo "and 'pct restore' the result on Proxmox directly. See docs/auto-installer.md."
|
|
||||||
|
|
||||||
echo "Choose the flake profile to install:"
|
|
||||||
select choice in "${options[@]}"; do
|
|
||||||
if [[ -n "$choice" ]]; then
|
|
||||||
echo "You selected: $choice"
|
|
||||||
break
|
|
||||||
else
|
|
||||||
echo "Invalid selection. Try again."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Starting install with flake: ${FLAKE_BASE_URL}#${choice}"
|
|
||||||
|
|
||||||
# Optional: confirm before proceeding
|
|
||||||
read -rp "Proceed with installation? (y/N): " confirm
|
|
||||||
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Aborted."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# A nix-cache host is *the* substituter/remote-builder for every other
|
|
||||||
# host once installed (its own config explicitly excludes itself from
|
|
||||||
# using either — see buildType != "nix-cache" in the nixos flake.nix).
|
|
||||||
# Installing one shouldn't depend on a nix-cache substituter either,
|
|
||||||
# for the same reason — plus in practice "nix-cache" only resolves over
|
|
||||||
# Tailscale, which a fresh installer environment was never connected to
|
|
||||||
# anyway, so it's dead weight even for non-nix-cache installs until
|
|
||||||
# that's sorted out. Override it away here specifically for nix-cache
|
|
||||||
# targets to keep install-time behaviour consistent with run-time.
|
|
||||||
nix_extra_opts=()
|
|
||||||
if [[ "${choice}" == *-nix-cache ]]; then
|
|
||||||
echo "Installing a nix-cache host — skipping the nix-cache substituter."
|
|
||||||
nix_extra_opts+=(--option substituters "https://cache.nixos.org/")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Every host reachable through this menu has a Disko config (lxc-*
|
|
||||||
# is filtered out above, and is the only category that doesn't —
|
|
||||||
# see docs/auto-installer.md), so this can run unconditionally: no
|
|
||||||
# need to probe the flake first and branch on whether Disko applies.
|
|
||||||
disko --mode destroy,format,mount \
|
|
||||||
--flake "${FLAKE_BASE_URL}#${choice}" "${nix_extra_opts[@]}" --yes-wipe-all-disks
|
|
||||||
|
|
||||||
# sops-nix derives this host's decryption key from its own SSH host key
|
|
||||||
# at *activation* time, which runs before systemd would otherwise
|
|
||||||
# generate one on first boot. Without pre-seeding it here, secrets
|
|
||||||
# (including the login password) fail to decrypt on first boot.
|
|
||||||
# Generate the key with scripts/secrets/prepare-host-key.sh first.
|
|
||||||
#
|
|
||||||
# Two places a key can come from, checked in order:
|
|
||||||
# /etc/host-keys — baked into this image at build time (see
|
|
||||||
# modules/installer/host-keys.nix; only present
|
|
||||||
# if built with NIXOS_HOST_KEYS_DIR set)
|
|
||||||
# /root/host-keys — scp'd in manually after boot (older fallback,
|
|
||||||
# still supported for images built without keys)
|
|
||||||
mkdir -p /root/host-keys
|
|
||||||
if [[ -f "/etc/host-keys/${choice}_ssh_host_ed25519_key" ]]; then
|
|
||||||
echo "Found baked-in SSH host key for ${choice}, installing to target..."
|
|
||||||
install -D -m 0600 "/etc/host-keys/${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -D -m 0644 "/etc/host-keys/${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
elif [[ -f "/root/host-keys/${choice}_ssh_host_ed25519_key" ]]; then
|
|
||||||
echo "Found pre-seeded SSH host key for ${choice}, installing to target..."
|
|
||||||
install -D -m 0600 "/root/host-keys/${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -D -m 0644 "/root/host-keys/${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
else
|
|
||||||
# Third place a key can come from: an arbitrary path the operator
|
|
||||||
# points at interactively (e.g. a USB stick, a mount from another
|
|
||||||
# machine) -- only offered when there's an actual human at the other
|
|
||||||
# end of stdin to ask, never in a non-interactive run.
|
|
||||||
key_copied=0
|
|
||||||
if [[ -t 0 ]]; then
|
|
||||||
echo "No SSH host key found for ${choice} (checked /etc/host-keys and /root/host-keys)."
|
|
||||||
read -rp "Path to a directory containing ${choice}_ssh_host_ed25519_key(.pub) (blank to skip): " key_src_dir
|
|
||||||
if [[ -n "$key_src_dir" && -f "${key_src_dir}/${choice}_ssh_host_ed25519_key" && -f "${key_src_dir}/${choice}_ssh_host_ed25519_key.pub" ]]; then
|
|
||||||
cp "${key_src_dir}/${choice}_ssh_host_ed25519_key" "${key_src_dir}/${choice}_ssh_host_ed25519_key.pub" /root/host-keys/
|
|
||||||
key_copied=1
|
|
||||||
elif [[ -n "$key_src_dir" ]]; then
|
|
||||||
echo "WARNING: ${choice}_ssh_host_ed25519_key(.pub) not found in ${key_src_dir}."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$key_copied" -eq 1 ]]; then
|
|
||||||
echo "Copied SSH host key for ${choice} from ${key_src_dir}, installing to target..."
|
|
||||||
install -D -m 0600 "/root/host-keys/${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -D -m 0644 "/root/host-keys/${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
else
|
|
||||||
echo "WARNING: no SSH host key found for ${choice} (checked /etc/host-keys and /root/host-keys)"
|
|
||||||
echo "sops-nix secrets (including the login password) will NOT decrypt on first boot."
|
|
||||||
echo "Run scripts/secrets/prepare-host-key.sh for host ${choice} on your admin workstation first,"
|
|
||||||
echo "then either rebuild this image with NIXOS_HOST_KEYS_DIR set, scp the result to"
|
|
||||||
echo "/root/host-keys/ on this machine, or point at it when prompted above."
|
|
||||||
read -rp "Continue without a pre-seeded key anyway? (y/N): " skip_key
|
|
||||||
if [[ ! "$skip_key" =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Aborted."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p /mnt/install-tmp
|
|
||||||
export TMPDIR=/mnt/install-tmp
|
|
||||||
|
|
||||||
nixos-install \
|
|
||||||
--flake "${FLAKE_BASE_URL}#${choice}" \
|
|
||||||
"${nix_extra_opts[@]}" \
|
|
||||||
--no-root-password
|
|
||||||
|
|
||||||
|
|
||||||
rm -rf /mnt/install-tmp
|
|
||||||
# Redundant copy of the host's private key — the real one is now at
|
|
||||||
# /etc/ssh/ssh_host_ed25519_key. Nothing NixOS-managed ever cleans this
|
|
||||||
# up on its own since it was written imperatively, not declaratively.
|
|
||||||
rm -rf /root/host-keys
|
|
||||||
|
|
||||||
# disko's --mode ...,mount left any ZFS root pool imported (that's what
|
|
||||||
# let nixos-install write into /mnt). If we reboot with it still
|
|
||||||
# imported, it isn't just "not exported" -- it's stamped with *this*
|
|
||||||
# live installer environment's hostid, which almost never matches the
|
|
||||||
# target's own networking.hostId (see hosts/*/host.nix; the installer
|
|
||||||
# itself sets none). modules/services/zfs/enable-service.nix and
|
|
||||||
# modules/common/configuration.nix both set boot.zfs.forceImportRoot =
|
|
||||||
# false deliberately (the safe option per that setting's own docs), so
|
|
||||||
# the freshly-installed system's first real boot sees a pool "in use by
|
|
||||||
# another system" and refuses to import it without -f -- which is what
|
|
||||||
# makes boot stall waiting on the ZFS import. Exporting here (a no-op
|
|
||||||
# if the chosen host has no ZFS root, e.g. proxmox-*/linode-*) clears
|
|
||||||
# that in-use state so the next import, from any hostid, succeeds.
|
|
||||||
#
|
|
||||||
# Anything still mounted under /mnt -- nixos-install's own leftover
|
|
||||||
# chroot bind mounts for running the target's activation script
|
|
||||||
# (/mnt/dev, /mnt/proc, /mnt/sys, /mnt/run), and disko's own /mnt/boot
|
|
||||||
# ESP mount (modules/disko/baremetal.nix) -- blocks ZFS from unmounting
|
|
||||||
# its root dataset at /mnt, the same way any nested mount blocks
|
|
||||||
# unmounting its parent. Confirmed live: zpool export failed with
|
|
||||||
# "cannot unmount '/mnt': pool or dataset busy" even after handling the
|
|
||||||
# chroot mounts alone, because /mnt/boot was still mounted too. Because
|
|
||||||
# of this script's `set -e`, that killed the script before it ever
|
|
||||||
# reached reboot, silently defeating the whole point of exporting first.
|
|
||||||
# Unmounting everything under /mnt up front (recursively, so nested
|
|
||||||
# mounts like /mnt/dev/pts come along for free) sidesteps needing to
|
|
||||||
# enumerate every mount disko/nixos-install might leave behind.
|
|
||||||
if mountpoint -q /mnt; then
|
|
||||||
umount -R /mnt
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$(zpool list -H -o name 2>/dev/null)" ]]; then
|
|
||||||
echo "Exporting ZFS pool(s) before reboot..."
|
|
||||||
zpool export -a
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 10
|
|
||||||
reboot
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Shared parallel-nix-invocation helper for scripts/codex-maintenance.sh.
|
|
||||||
# Source alongside nix-eval.sh:
|
|
||||||
# source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/nix-parallel.sh"
|
|
||||||
#
|
|
||||||
# The per-host/per-package `nix eval`/`nix build --dry-run` calls in
|
|
||||||
# codex-maintenance.sh are independent of each other, so running them one at
|
|
||||||
# a time leaves most cores idle for most of the sweep -- run_nix_parallel
|
|
||||||
# fans a batch of them out across up to NIX_PARALLEL_JOBS processes instead.
|
|
||||||
|
|
||||||
# NIX_PARALLEL_JOBS: how many `nix` invocations run_nix_parallel runs at
|
|
||||||
# once. Defaults to core count capped by available memory (~1GB/job,
|
|
||||||
# floor 1) rather than plain `nproc` -- each concurrent `nix eval` here
|
|
||||||
# evaluates a whole NixOS system closure from scratch, and on a small/
|
|
||||||
# memory-constrained CI runner, `nproc` concurrent evals can OOM-kill each
|
|
||||||
# other (confirmed empirically: on a 4GB/6-core box, 5-6 concurrent evals
|
|
||||||
# started getting killed while 3-4 ran clean and were still ~2x faster than
|
|
||||||
# serial). Override via env if a given machine/CI runner has room to spare
|
|
||||||
# or needs a tighter cap.
|
|
||||||
default_nix_parallel_jobs() {
|
|
||||||
local cores mem_avail_kb mem_cap
|
|
||||||
cores="$(nproc 2>/dev/null || echo 4)"
|
|
||||||
mem_avail_kb="$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo 2>/dev/null)"
|
|
||||||
if [[ -z "$mem_avail_kb" ]]; then
|
|
||||||
echo "$cores"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
mem_cap=$((mem_avail_kb / 1024 / 1024))
|
|
||||||
((mem_cap < 1)) && mem_cap=1
|
|
||||||
((mem_cap < cores)) && echo "$mem_cap" || echo "$cores"
|
|
||||||
}
|
|
||||||
NIX_PARALLEL_JOBS="${NIX_PARALLEL_JOBS:-$(default_nix_parallel_jobs)}"
|
|
||||||
|
|
||||||
# Separator between a job's label and its flake attr in the arrays
|
|
||||||
# run_nix_parallel takes -- a control character so it can't collide with
|
|
||||||
# anything a label or attr path would plausibly contain.
|
|
||||||
NIX_PARALLEL_SEP=$'\x1f'
|
|
||||||
|
|
||||||
# run_nix_parallel <jobs_array_name> <nix subcommand + flags...>
|
|
||||||
#
|
|
||||||
# jobs_array_name: name of an already-populated bash array whose entries are
|
|
||||||
# "<label>${NIX_PARALLEL_SEP}<attr>" pairs, e.g.
|
|
||||||
# jobs=("proxmox-docker${NIX_PARALLEL_SEP}.#nixosConfigurations.proxmox-docker...drvPath")
|
|
||||||
# Remaining args are passed to `nix` before the attr, e.g.:
|
|
||||||
# run_nix_parallel jobs eval --raw "${NIX_EVAL_FLAGS[@]}"
|
|
||||||
# run_nix_parallel jobs build --dry-run --no-link "${NIX_EVAL_FLAGS[@]}"
|
|
||||||
#
|
|
||||||
# Prints "==> <label>" followed by that job's stdout+stderr for every job,
|
|
||||||
# in submission order (not completion order) so a run stays readable and
|
|
||||||
# diffable across invocations even though the work itself doesn't finish in
|
|
||||||
# that order. Returns non-zero if any job failed, only after every job has
|
|
||||||
# finished and been printed -- same "surface everything, then fail" contract
|
|
||||||
# a `set -e` caller gets, just parallelized instead of stopping at the first
|
|
||||||
# failure.
|
|
||||||
run_nix_parallel() {
|
|
||||||
local -n jobs_ref="$1"
|
|
||||||
shift
|
|
||||||
local -a nix_args=("$@")
|
|
||||||
|
|
||||||
local n=${#jobs_ref[@]}
|
|
||||||
[[ $n -eq 0 ]] && return 0
|
|
||||||
|
|
||||||
local tmp_dir
|
|
||||||
tmp_dir="$(mktemp -d)"
|
|
||||||
|
|
||||||
local i=0 running=0
|
|
||||||
for job in "${jobs_ref[@]}"; do
|
|
||||||
local attr="${job#*"${NIX_PARALLEL_SEP}"}"
|
|
||||||
printf '%s\n' "${job%%"${NIX_PARALLEL_SEP}"*}" >"${tmp_dir}/${i}.label"
|
|
||||||
(
|
|
||||||
if nix "${nix_args[@]}" "$attr" >"${tmp_dir}/${i}.out" 2>&1; then
|
|
||||||
echo 0 >"${tmp_dir}/${i}.status"
|
|
||||||
else
|
|
||||||
echo 1 >"${tmp_dir}/${i}.status"
|
|
||||||
fi
|
|
||||||
) &
|
|
||||||
i=$((i + 1))
|
|
||||||
running=$((running + 1))
|
|
||||||
if ((running >= NIX_PARALLEL_JOBS)); then
|
|
||||||
wait -n
|
|
||||||
running=$((running - 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
|
|
||||||
local failed=0 j
|
|
||||||
for ((j = 0; j < n; j++)); do
|
|
||||||
echo "==> $(cat "${tmp_dir}/${j}.label")"
|
|
||||||
cat "${tmp_dir}/${j}.out"
|
|
||||||
[[ "$(cat "${tmp_dir}/${j}.status")" -ne 0 ]] && failed=1
|
|
||||||
done
|
|
||||||
|
|
||||||
rm -rf "$tmp_dir"
|
|
||||||
return $failed
|
|
||||||
}
|
|
||||||
@@ -6,31 +6,27 @@
|
|||||||
#
|
#
|
||||||
# This is the non-NixOS equivalent of modules/nix-cache/client.nix +
|
# This is the non-NixOS equivalent of modules/nix-cache/client.nix +
|
||||||
# modules/nix-cache/remote-builder-client.nix -- those two only apply to
|
# modules/nix-cache/remote-builder-client.nix -- those two only apply to
|
||||||
# hosts built from this flake. A plain Debian box with Nix installed has no
|
# hosts built from this flake. A plain Debian box with Nix installed
|
||||||
# NixOS module system to pick that config up, so this edits nix.conf by hand.
|
# (single- or multi-user install, nix-daemon running) has no NixOS module
|
||||||
#
|
# system to pick that config up, so this edits /etc/nix/nix.conf by hand
|
||||||
# Two modes depending on who runs it:
|
# instead. Run this ON the target Debian machine, as root.
|
||||||
#
|
|
||||||
# root (multi-user / daemon install):
|
|
||||||
# Writes /etc/nix/nix.conf, /etc/ssh/ssh_known_hosts, restarts nix-daemon.
|
|
||||||
# Requires /etc/nix/nix.conf to already exist (i.e. nix-daemon is set up).
|
|
||||||
# Run as: sudo ./configure-nix-cache-client.sh [options]
|
|
||||||
#
|
|
||||||
# non-root (single-user install):
|
|
||||||
# Writes ~/.config/nix/nix.conf, ~/.ssh/known_hosts. No daemon to restart.
|
|
||||||
# Run as: ./configure-nix-cache-client.sh [options]
|
|
||||||
#
|
#
|
||||||
# The values below mirror variables.nix / modules/nix-cache/client.nix in
|
# The values below mirror variables.nix / modules/nix-cache/client.nix in
|
||||||
# this repo -- update both if nix-cache is ever rebuilt with a new host
|
# this repo -- update both if nix-cache is ever rebuilt with a new host
|
||||||
# key or the cache signing key is rotated (see docs/nix-cache.md).
|
# key or the cache signing key is rotated (see docs/nix-cache.md).
|
||||||
#
|
#
|
||||||
# REMOTE_BUILDER_KEY defaults to the running user's default SSH identity
|
# REMOTE_BUILDER_KEY defaults to this machine's own default root SSH
|
||||||
# (root: /root/.ssh/id_ed25519, other user: ~/.ssh/id_ed25519). That key
|
# identity (matches modules/nix-cache/remote-builder-client.nix's
|
||||||
# must be listed in vars.remoteBuilderAuthorizedKeys in this repo and
|
# convention for real NixOS clients: authenticate as nixremote with the
|
||||||
# nix-cache rebuilt before remote building works.
|
# host's own default key, added individually to
|
||||||
|
# vars.remoteBuilderAuthorizedKeys, rather than a separately-named or
|
||||||
|
# shared keypair) -- generate one with
|
||||||
|
# `ssh-keygen -t ed25519 -N '' -f /root/.ssh/id_ed25519` if this machine
|
||||||
|
# doesn't have one yet, then add its .pub to vars.remoteBuilderAuthorizedKeys
|
||||||
|
# and rebuild nix-cache.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./configure-nix-cache-client.sh [--dry-run] [--no-remote-builder] [--no-restart]
|
# sudo ./configure-nix-cache-client.sh [--dry-run] [--no-remote-builder] [--no-restart]
|
||||||
#
|
#
|
||||||
# Env overrides (defaults match variables.nix):
|
# Env overrides (defaults match variables.nix):
|
||||||
# NIX_CACHE_HOST, NIX_CACHE_HOST_KEY, REMOTE_BUILDER_USER, REMOTE_BUILDER_KEY
|
# NIX_CACHE_HOST, NIX_CACHE_HOST_KEY, REMOTE_BUILDER_USER, REMOTE_BUILDER_KEY
|
||||||
@@ -40,28 +36,17 @@ set -euo pipefail
|
|||||||
: "${NIX_CACHE_HOST:=nix-cache}"
|
: "${NIX_CACHE_HOST:=nix-cache}"
|
||||||
: "${NIX_CACHE_HOST_KEY:=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPeWgMsdaiz4axT/deFc1+0B5bN+GX/NOeW9bbQ0c/IT lxc-nix-cache}"
|
: "${NIX_CACHE_HOST_KEY:=ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPeWgMsdaiz4axT/deFc1+0B5bN+GX/NOeW9bbQ0c/IT lxc-nix-cache}"
|
||||||
: "${REMOTE_BUILDER_USER:=nixremote}"
|
: "${REMOTE_BUILDER_USER:=nixremote}"
|
||||||
|
: "${REMOTE_BUILDER_KEY:=/root/.ssh/id_ed25519}"
|
||||||
|
|
||||||
CACHE_PUB_KEY="cache.local-1:usoWYanY3Kpq2+kDIS2nhWoLZiRxanmdysdzqCFBHW4="
|
CACHE_PUB_KEY="cache.local-1:usoWYanY3Kpq2+kDIS2nhWoLZiRxanmdysdzqCFBHW4="
|
||||||
FALLBACK_URL="https://cache.nixos.org/"
|
FALLBACK_URL="https://cache.nixos.org/"
|
||||||
FALLBACK_PUB_KEY="cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
FALLBACK_PUB_KEY="cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||||
|
|
||||||
|
NIX_CONF="/etc/nix/nix.conf"
|
||||||
|
KNOWN_HOSTS="/etc/ssh/ssh_known_hosts"
|
||||||
MARKER_BEGIN="# BEGIN nix-cache client config (configure-nix-cache-client.sh)"
|
MARKER_BEGIN="# BEGIN nix-cache client config (configure-nix-cache-client.sh)"
|
||||||
MARKER_END="# END nix-cache client config"
|
MARKER_END="# END nix-cache client config"
|
||||||
|
|
||||||
# Mode: root uses system-wide paths and restarts the daemon; non-root uses
|
|
||||||
# user-level paths and has no daemon to restart.
|
|
||||||
if [[ "$EUID" -eq 0 ]]; then
|
|
||||||
install_mode="multi"
|
|
||||||
NIX_CONF="/etc/nix/nix.conf"
|
|
||||||
KNOWN_HOSTS="/etc/ssh/ssh_known_hosts"
|
|
||||||
: "${REMOTE_BUILDER_KEY:=/root/.ssh/id_ed25519}"
|
|
||||||
else
|
|
||||||
install_mode="single"
|
|
||||||
NIX_CONF="${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf"
|
|
||||||
KNOWN_HOSTS="$HOME/.ssh/known_hosts"
|
|
||||||
: "${REMOTE_BUILDER_KEY:=$HOME/.ssh/id_ed25519}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
dry_run=0
|
dry_run=0
|
||||||
with_remote_builder=1
|
with_remote_builder=1
|
||||||
restart_daemon=1
|
restart_daemon=1
|
||||||
@@ -72,7 +57,7 @@ for arg in "$@"; do
|
|||||||
--no-remote-builder) with_remote_builder=0 ;;
|
--no-remote-builder) with_remote_builder=0 ;;
|
||||||
--no-restart) restart_daemon=0 ;;
|
--no-restart) restart_daemon=0 ;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
sed -n '2,37p' "$0"
|
sed -n '2,20p' "$0"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -82,22 +67,21 @@ for arg in "$@"; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [[ "$dry_run" -eq 0 && "$EUID" -ne 0 ]]; then
|
||||||
|
echo "ERROR: must run as root (writes $NIX_CONF and, unless --no-remote-builder, $KNOWN_HOSTS)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if ! command -v nix >/dev/null 2>&1; then
|
if ! command -v nix >/dev/null 2>&1; then
|
||||||
echo "ERROR: no 'nix' binary on PATH -- install the Nix package manager first." >&2
|
echo "ERROR: no 'nix' binary on PATH -- install the Nix package manager first." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$install_mode" == "multi" && ! -f "$NIX_CONF" ]]; then
|
if [[ ! -f "$NIX_CONF" ]]; then
|
||||||
echo "ERROR: $NIX_CONF not found -- expected an existing multi-user Nix install." >&2
|
echo "ERROR: $NIX_CONF not found -- expected an existing multi-user Nix install." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Single-user: create the config file if it doesn't exist yet.
|
|
||||||
if [[ "$install_mode" == "single" && "$dry_run" -eq 0 ]]; then
|
|
||||||
mkdir -p "$(dirname "$NIX_CONF")"
|
|
||||||
[[ -f "$NIX_CONF" ]] || touch "$NIX_CONF"
|
|
||||||
fi
|
|
||||||
|
|
||||||
builder_line=""
|
builder_line=""
|
||||||
if [[ "$with_remote_builder" -eq 1 ]]; then
|
if [[ "$with_remote_builder" -eq 1 ]]; then
|
||||||
if [[ -f "$REMOTE_BUILDER_KEY" ]]; then
|
if [[ -f "$REMOTE_BUILDER_KEY" ]]; then
|
||||||
@@ -133,7 +117,7 @@ fi
|
|||||||
block="${block}
|
block="${block}
|
||||||
$MARKER_END"
|
$MARKER_END"
|
||||||
|
|
||||||
echo "== nix.conf block to install ($NIX_CONF) =="
|
echo "== nix.conf block to install =="
|
||||||
echo "$block"
|
echo "$block"
|
||||||
echo "================================"
|
echo "================================"
|
||||||
|
|
||||||
@@ -175,8 +159,7 @@ if [[ "$with_remote_builder" -eq 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Only restart the daemon for multi-user installs -- single-user has no daemon.
|
if [[ "$dry_run" -eq 0 && "$restart_daemon" -eq 1 ]]; then
|
||||||
if [[ "$dry_run" -eq 0 && "$restart_daemon" -eq 1 && "$install_mode" == "multi" ]]; then
|
|
||||||
if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet nix-daemon 2>/dev/null; then
|
if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet nix-daemon 2>/dev/null; then
|
||||||
systemctl restart nix-daemon
|
systemctl restart nix-daemon
|
||||||
echo "Restarted nix-daemon to pick up the new config."
|
echo "Restarted nix-daemon to pick up the new config."
|
||||||
|
|||||||
@@ -193,16 +193,6 @@ done
|
|||||||
|
|
||||||
ssh_target="${PROXMOX_SSH_USER}@${node}"
|
ssh_target="${PROXMOX_SSH_USER}@${node}"
|
||||||
|
|
||||||
# Proxmox tools (pvesh, qm, pct) require root access to the cluster IPC
|
|
||||||
# socket. When SSH-ing as a non-root user with sudo, prefix every remote
|
|
||||||
# Proxmox command with sudo.
|
|
||||||
sudo_prefix=""
|
|
||||||
sudo_display=""
|
|
||||||
if [[ "$PROXMOX_SSH_USER" != "root" ]]; then
|
|
||||||
sudo_prefix="sudo"
|
|
||||||
sudo_display="sudo "
|
|
||||||
fi
|
|
||||||
|
|
||||||
remote() {
|
remote() {
|
||||||
if [[ "$dry_run" -eq 1 ]]; then
|
if [[ "$dry_run" -eq 1 ]]; then
|
||||||
echo "[dry-run] ssh ${ssh_target} -- $*"
|
echo "[dry-run] ssh ${ssh_target} -- $*"
|
||||||
@@ -224,10 +214,10 @@ cmd_modify() {
|
|||||||
|
|
||||||
echo "Looking up VMID ${vmid} on ${node}..."
|
echo "Looking up VMID ${vmid} on ${node}..."
|
||||||
local kind current_cores current_memory disk_key
|
local kind current_cores current_memory disk_key
|
||||||
if ssh "$ssh_target" "${sudo_prefix} qm status ${vmid}" >/dev/null 2>&1; then
|
if ssh "$ssh_target" "qm status ${vmid}" >/dev/null 2>&1; then
|
||||||
kind="vm"
|
kind="vm"
|
||||||
disk_key="scsi0"
|
disk_key="scsi0"
|
||||||
elif ssh "$ssh_target" "${sudo_prefix} pct status ${vmid}" >/dev/null 2>&1; then
|
elif ssh "$ssh_target" "pct status ${vmid}" >/dev/null 2>&1; then
|
||||||
kind="lxc"
|
kind="lxc"
|
||||||
disk_key="rootfs"
|
disk_key="rootfs"
|
||||||
else
|
else
|
||||||
@@ -235,8 +225,8 @@ cmd_modify() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local config_cmd="${sudo_prefix} qm config ${vmid}"
|
local config_cmd="qm config ${vmid}"
|
||||||
[[ "$kind" == "lxc" ]] && config_cmd="${sudo_prefix} pct config ${vmid}"
|
[[ "$kind" == "lxc" ]] && config_cmd="pct config ${vmid}"
|
||||||
local current_config
|
local current_config
|
||||||
current_config="$(ssh "$ssh_target" "$config_cmd")"
|
current_config="$(ssh "$ssh_target" "$config_cmd")"
|
||||||
current_cores="$(echo "$current_config" | grep -oP '^cores:\s*\K\S+' || echo '?')"
|
current_cores="$(echo "$current_config" | grep -oP '^cores:\s*\K\S+' || echo '?')"
|
||||||
@@ -260,9 +250,9 @@ cmd_modify() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local set_cmd="${sudo_prefix} qm set"
|
local set_cmd="qm set"
|
||||||
local resize_cmd="${sudo_prefix} qm resize"
|
local resize_cmd="qm resize"
|
||||||
[[ "$kind" == "lxc" ]] && set_cmd="${sudo_prefix} pct set" && resize_cmd="${sudo_prefix} pct resize"
|
[[ "$kind" == "lxc" ]] && set_cmd="pct set" && resize_cmd="pct resize"
|
||||||
|
|
||||||
if [[ -n "$cores" || -n "$memory" ]]; then
|
if [[ -n "$cores" || -n "$memory" ]]; then
|
||||||
local args=""
|
local args=""
|
||||||
@@ -369,15 +359,14 @@ else
|
|||||||
echo
|
echo
|
||||||
echo "==> Checking ${node} for an existing VM/CT identified as '${host}'..."
|
echo "==> Checking ${node} for an existing VM/CT identified as '${host}'..."
|
||||||
ssh_check_status=0
|
ssh_check_status=0
|
||||||
existing="$(ssh "$ssh_target" bash -s -- "$host" "$sudo_prefix" <<'REMOTE_SCRIPT'
|
existing="$(ssh "$ssh_target" bash -s -- "$host" <<'REMOTE_SCRIPT'
|
||||||
target="$1"
|
target="$1"
|
||||||
sudo_pfx="$2"
|
for id in $(qm list 2>/dev/null | awk 'NR>1{print $1}'); do
|
||||||
for id in $($sudo_pfx qm list 2>/dev/null | awk 'NR>1{print $1}'); do
|
n="$(qm config "$id" 2>/dev/null | grep -oP '^name:\s*\K\S+' || true)"
|
||||||
n="$($sudo_pfx qm config "$id" 2>/dev/null | grep -oP '^name:\s*\K\S+' || true)"
|
|
||||||
[[ "$n" == "$target" ]] && echo "vm ${id} ${n}"
|
[[ "$n" == "$target" ]] && echo "vm ${id} ${n}"
|
||||||
done
|
done
|
||||||
for id in $($sudo_pfx pct list 2>/dev/null | awk 'NR>1{print $1}'); do
|
for id in $(pct list 2>/dev/null | awk 'NR>1{print $1}'); do
|
||||||
n="$($sudo_pfx pct config "$id" 2>/dev/null | grep -oP '^hostname:\s*\K\S+' || true)"
|
n="$(pct config "$id" 2>/dev/null | grep -oP '^hostname:\s*\K\S+' || true)"
|
||||||
[[ "$n" == "$target" ]] && echo "lxc ${id} ${n}"
|
[[ "$n" == "$target" ]] && echo "lxc ${id} ${n}"
|
||||||
done
|
done
|
||||||
exit 0
|
exit 0
|
||||||
@@ -464,12 +453,12 @@ REMOTE_SCRIPT
|
|||||||
if [[ "$kind" == "vm" ]]; then
|
if [[ "$kind" == "vm" ]]; then
|
||||||
# qm destroy has no --force to stop-then-destroy in one call (pct's
|
# qm destroy has no --force to stop-then-destroy in one call (pct's
|
||||||
# does) -- stop explicitly first if it's running.
|
# does) -- stop explicitly first if it's running.
|
||||||
if ssh "$ssh_target" "${sudo_prefix} qm status ${id}" 2>/dev/null | grep -q running; then
|
if ssh "$ssh_target" "qm status ${id}" 2>/dev/null | grep -q running; then
|
||||||
ssh "$ssh_target" "${sudo_prefix} qm stop ${id}"
|
ssh "$ssh_target" "qm stop ${id}"
|
||||||
fi
|
fi
|
||||||
ssh "$ssh_target" "${sudo_prefix} qm destroy ${id} --purge 1"
|
ssh "$ssh_target" "qm destroy ${id} --purge 1"
|
||||||
else
|
else
|
||||||
ssh "$ssh_target" "${sudo_prefix} pct destroy ${id} --force 1 --purge 1"
|
ssh "$ssh_target" "pct destroy ${id} --force 1 --purge 1"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -498,7 +487,7 @@ if [[ -z "$vmid" ]]; then
|
|||||||
vmid="<next-free-vmid>"
|
vmid="<next-free-vmid>"
|
||||||
echo "[dry-run] would ask ${node} for the next free VMID (pvesh get /cluster/nextid)"
|
echo "[dry-run] would ask ${node} for the next free VMID (pvesh get /cluster/nextid)"
|
||||||
else
|
else
|
||||||
vmid="$(ssh "$ssh_target" "${sudo_prefix} pvesh get /cluster/nextid" | tr -d '[:space:]')"
|
vmid="$(ssh "$ssh_target" "pvesh get /cluster/nextid" | tr -d '[:space:]')"
|
||||||
echo "Auto-assigned VMID: ${vmid}"
|
echo "Auto-assigned VMID: ${vmid}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
@@ -512,8 +501,8 @@ if [[ "$dry_run" -eq 0 ]]; then
|
|||||||
# both. Any success here means something is already using this ID --
|
# both. Any success here means something is already using this ID --
|
||||||
# refuse to go anywhere near it. (Reconfiguring an existing resource is
|
# refuse to go anywhere near it. (Reconfiguring an existing resource is
|
||||||
# --modify's job, not this one's.)
|
# --modify's job, not this one's.)
|
||||||
if ssh "$ssh_target" "${sudo_prefix} qm status ${vmid}" >/dev/null 2>&1 \
|
if ssh "$ssh_target" "qm status ${vmid}" >/dev/null 2>&1 \
|
||||||
|| ssh "$ssh_target" "${sudo_prefix} pct status ${vmid}" >/dev/null 2>&1; then
|
|| ssh "$ssh_target" "pct status ${vmid}" >/dev/null 2>&1; then
|
||||||
echo "ERROR: VMID ${vmid} already exists on ${node}. Refusing to touch an" >&2
|
echo "ERROR: VMID ${vmid} already exists on ${node}. Refusing to touch an" >&2
|
||||||
echo "existing resource here -- use --modify to reconfigure it, pick a" >&2
|
echo "existing resource here -- use --modify to reconfigure it, pick a" >&2
|
||||||
echo "different --vmid, or omit it to auto-assign." >&2
|
echo "different --vmid, or omit it to auto-assign." >&2
|
||||||
@@ -683,11 +672,11 @@ if [[ "$image_already_remote" -eq 0 && -z "$local_image" ]]; then
|
|||||||
# hands the result to the remote shell to re-split, which would
|
# hands the result to the remote shell to re-split, which would
|
||||||
# otherwise scatter NIX_EXTRA_OPTS (itself several space-separated,
|
# otherwise scatter NIX_EXTRA_OPTS (itself several space-separated,
|
||||||
# %q-quoted tokens) across the wrong positional parameters below.
|
# %q-quoted tokens) across the wrong positional parameters below.
|
||||||
printf -v remote_cmd 'bash -s -- %q %q %q %q %q %q' \
|
printf -v remote_cmd 'bash -s -- %q %q %q %q %q' \
|
||||||
"$remote_repo_dir" "$flake_target" "$remote_dir" "$remote_filename" "$NIX_EXTRA_OPTS" "$sudo_prefix"
|
"$remote_repo_dir" "$flake_target" "$remote_dir" "$remote_filename" "$NIX_EXTRA_OPTS"
|
||||||
ssh "$ssh_target" "$remote_cmd" <<'REMOTE_SCRIPT'
|
ssh "$ssh_target" "$remote_cmd" <<'REMOTE_SCRIPT'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
repo_dir="$1"; target="$2"; dest_dir="$3"; dest_name="$4"; nix_extra_opts_str="$5"; sudo_pfx="$6"
|
repo_dir="$1"; target="$2"; dest_dir="$3"; dest_name="$4"; nix_extra_opts_str="$5"
|
||||||
declare -a NIX_OPTS=()
|
declare -a NIX_OPTS=()
|
||||||
[[ -n "$nix_extra_opts_str" ]] && eval "NIX_OPTS=(${nix_extra_opts_str})"
|
[[ -n "$nix_extra_opts_str" ]] && eval "NIX_OPTS=(${nix_extra_opts_str})"
|
||||||
cd "$repo_dir"
|
cd "$repo_dir"
|
||||||
@@ -705,14 +694,23 @@ if [[ -z "$built" ]]; then
|
|||||||
echo "ERROR: no tarball found under result-${target}/tarball after build." >&2
|
echo "ERROR: no tarball found under result-${target}/tarball after build." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
$sudo_pfx mkdir -p "$dest_dir"
|
mkdir -p "$dest_dir"
|
||||||
$sudo_pfx cp "$built" "${dest_dir}/${dest_name}"
|
cp "$built" "${dest_dir}/${dest_name}"
|
||||||
echo "Built and staged: ${dest_dir}/${dest_name}"
|
echo "Built and staged: ${dest_dir}/${dest_name}"
|
||||||
REMOTE_SCRIPT
|
REMOTE_SCRIPT
|
||||||
local_image="$remote_path"
|
local_image="$remote_path"
|
||||||
echo "Built on ${node}: ${remote_path}"
|
echo "Built on ${node}: ${remote_path}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
# PROXMOX_SSH_USER defaults to root (env.sh), which needs no sudo and
|
||||||
|
# can't assume it's even installed on a minimal node -- only shell out
|
||||||
|
# through sudo when actually running as a non-root SSH user.
|
||||||
|
sudo_prefix="sudo"
|
||||||
|
sudo_display="sudo "
|
||||||
|
if [[ "$PROXMOX_SSH_USER" == "root" ]]; then
|
||||||
|
sudo_prefix=""
|
||||||
|
sudo_display=""
|
||||||
|
fi
|
||||||
if [[ "$dry_run" -eq 1 ]]; then
|
if [[ "$dry_run" -eq 1 ]]; then
|
||||||
echo "[dry-run] would build on ${node}: nix build --no-use-registries --no-accept-flake-config${nix_opts_display} \\"
|
echo "[dry-run] would build on ${node}: nix build --no-use-registries --no-accept-flake-config${nix_opts_display} \\"
|
||||||
echo "[dry-run] .#nixosConfigurations.${flake_target}.config.system.build.diskoImagesScript"
|
echo "[dry-run] .#nixosConfigurations.${flake_target}.config.system.build.diskoImagesScript"
|
||||||
@@ -730,7 +728,7 @@ REMOTE_SCRIPT
|
|||||||
"$remote_repo_dir" "$flake_target" "$remote_dir" "$remote_filename" "$NIX_EXTRA_OPTS" "$sudo_prefix"
|
"$remote_repo_dir" "$flake_target" "$remote_dir" "$remote_filename" "$NIX_EXTRA_OPTS" "$sudo_prefix"
|
||||||
ssh "$ssh_target" "$remote_cmd" <<'REMOTE_SCRIPT'
|
ssh "$ssh_target" "$remote_cmd" <<'REMOTE_SCRIPT'
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
repo_dir="$1"; target="$2"; dest_dir="$3"; dest_name="$4"; nix_extra_opts_str="$5"; sudo_pfx="$6"
|
repo_dir="$1"; target="$2"; dest_dir="$3"; dest_name="$4"; nix_extra_opts_str="$5"; sudo_prefix="$6"
|
||||||
declare -a NIX_OPTS=()
|
declare -a NIX_OPTS=()
|
||||||
[[ -n "$nix_extra_opts_str" ]] && eval "NIX_OPTS=(${nix_extra_opts_str})"
|
[[ -n "$nix_extra_opts_str" ]] && eval "NIX_OPTS=(${nix_extra_opts_str})"
|
||||||
cd "$repo_dir"
|
cd "$repo_dir"
|
||||||
@@ -739,7 +737,7 @@ ensure_nix_profile
|
|||||||
nix build --no-use-registries --no-accept-flake-config "${NIX_OPTS[@]}" \
|
nix build --no-use-registries --no-accept-flake-config "${NIX_OPTS[@]}" \
|
||||||
".#nixosConfigurations.${target}.config.system.build.diskoImagesScript" \
|
".#nixosConfigurations.${target}.config.system.build.diskoImagesScript" \
|
||||||
--out-link "result-${target}"
|
--out-link "result-${target}"
|
||||||
$sudo_pfx "./result-${target}" \
|
$sudo_prefix "./result-${target}" \
|
||||||
--pre-format-files "host-keys/${target}_ssh_host_ed25519_key" /etc/ssh/ssh_host_ed25519_key \
|
--pre-format-files "host-keys/${target}_ssh_host_ed25519_key" /etc/ssh/ssh_host_ed25519_key \
|
||||||
--pre-format-files "host-keys/${target}_ssh_host_ed25519_key.pub" /etc/ssh/ssh_host_ed25519_key.pub \
|
--pre-format-files "host-keys/${target}_ssh_host_ed25519_key.pub" /etc/ssh/ssh_host_ed25519_key.pub \
|
||||||
--build-memory 2048
|
--build-memory 2048
|
||||||
@@ -748,8 +746,8 @@ if [[ -z "$built" ]]; then
|
|||||||
echo "ERROR: no .raw image found in ${repo_dir} after build." >&2
|
echo "ERROR: no .raw image found in ${repo_dir} after build." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
$sudo_pfx mkdir -p "$dest_dir"
|
mkdir -p "$dest_dir"
|
||||||
$sudo_pfx mv "$built" "${dest_dir}/${dest_name}"
|
mv "$built" "${dest_dir}/${dest_name}"
|
||||||
echo "Built and staged: ${dest_dir}/${dest_name}"
|
echo "Built and staged: ${dest_dir}/${dest_name}"
|
||||||
REMOTE_SCRIPT
|
REMOTE_SCRIPT
|
||||||
local_image="$remote_path"
|
local_image="$remote_path"
|
||||||
@@ -814,9 +812,9 @@ if [[ "$type" == "lxc" ]]; then
|
|||||||
# hands the whole string to `ssh` as a single command for the *remote*
|
# hands the whole string to `ssh` as a single command for the *remote*
|
||||||
# shell to parse -- unquoted, that `;` would be read as a remote
|
# shell to parse -- unquoted, that `;` would be read as a remote
|
||||||
# command separator and silently truncate this into two commands.
|
# command separator and silently truncate this into two commands.
|
||||||
create_cmd="${sudo_prefix} pct create ${vmid} ${iso_storage}:vztmpl/${remote_filename} --unprivileged ${unprivileged_flag} --features '${PROXMOX_DEFAULT_LXC_FEATURES}' --rootfs ${storage}:${local_disk_size} --hostname ${name} --cores ${cores} --memory ${memory} --swap ${local_swap} --net0 name=eth0,bridge=${bridge},ip=dhcp"
|
create_cmd="pct create ${vmid} ${iso_storage}:vztmpl/${remote_filename} --unprivileged ${unprivileged_flag} --features '${PROXMOX_DEFAULT_LXC_FEATURES}' --rootfs ${storage}:${local_disk_size} --hostname ${name} --cores ${cores} --memory ${memory} --swap ${local_swap} --net0 name=eth0,bridge=${bridge},ip=dhcp"
|
||||||
remote "$create_cmd"
|
remote "$create_cmd"
|
||||||
remote "${sudo_prefix} pct start ${vmid}"
|
remote "pct start ${vmid}"
|
||||||
else
|
else
|
||||||
echo "==> Creating VM ${vmid} (${name})..."
|
echo "==> Creating VM ${vmid} (${name})..."
|
||||||
# pre-enrolled-keys=0 disables OVMF's Secure Boot key pre-enrollment --
|
# pre-enrolled-keys=0 disables OVMF's Secure Boot key pre-enrollment --
|
||||||
@@ -827,29 +825,29 @@ else
|
|||||||
# without this flag Proxmox never creates the channel it listens on, so
|
# without this flag Proxmox never creates the channel it listens on, so
|
||||||
# `qm guest exec`/`qm agent` and the UI's IP-address display silently
|
# `qm guest exec`/`qm agent` and the UI's IP-address display silently
|
||||||
# never work for any VM this script creates.
|
# never work for any VM this script creates.
|
||||||
remote "${sudo_prefix} qm create ${vmid} --name ${name} --memory ${memory} --cores ${cores} \
|
remote "qm create ${vmid} --name ${name} --memory ${memory} --cores ${cores} \
|
||||||
--net0 virtio,bridge=${bridge} --bios ovmf --machine q35 --scsihw virtio-scsi-pci \
|
--net0 virtio,bridge=${bridge} --bios ovmf --machine q35 --scsihw virtio-scsi-pci \
|
||||||
--efidisk0 ${storage}:1,efitype=4m,pre-enrolled-keys=0 --agent enabled=1"
|
--efidisk0 ${storage}:1,efitype=4m,pre-enrolled-keys=0 --agent enabled=1"
|
||||||
|
|
||||||
if [[ "$dry_run" -eq 1 ]]; then
|
if [[ "$dry_run" -eq 1 ]]; then
|
||||||
echo "[dry-run] ssh ${ssh_target} -- ${sudo_display}qm importdisk ${vmid} ${remote_path} ${storage}"
|
echo "[dry-run] ssh ${ssh_target} -- qm importdisk ${vmid} ${remote_path} ${storage}"
|
||||||
echo "[dry-run] (would parse the resulting disk identifier from that output)"
|
echo "[dry-run] (would parse the resulting disk identifier from that output)"
|
||||||
echo "[dry-run] ssh ${ssh_target} -- ${sudo_display}qm set ${vmid} --scsi0 ${storage}:<parsed-disk-id>"
|
echo "[dry-run] ssh ${ssh_target} -- qm set ${vmid} --scsi0 ${storage}:<parsed-disk-id>"
|
||||||
else
|
else
|
||||||
importdisk_output="$(ssh "$ssh_target" "${sudo_prefix} qm importdisk ${vmid} ${remote_path} ${storage}")"
|
importdisk_output="$(ssh "$ssh_target" "qm importdisk ${vmid} ${remote_path} ${storage}")"
|
||||||
echo "$importdisk_output"
|
echo "$importdisk_output"
|
||||||
disk_id="$(echo "$importdisk_output" | grep -oP "(?<=Successfully imported disk as ')[^']+" | sed 's/^unused[0-9]*://')"
|
disk_id="$(echo "$importdisk_output" | grep -oP "(?<=Successfully imported disk as ')[^']+" | sed 's/^unused[0-9]*://')"
|
||||||
if [[ -z "$disk_id" ]]; then
|
if [[ -z "$disk_id" ]]; then
|
||||||
echo "ERROR: couldn't parse the imported disk identifier from qm importdisk's output above." >&2
|
echo "ERROR: couldn't parse the imported disk identifier from qm importdisk's output above." >&2
|
||||||
echo "The VM shell (${vmid}) and imported disk both exist -- finish attaching it by hand:" >&2
|
echo "The VM shell (${vmid}) and imported disk both exist -- finish attaching it by hand:" >&2
|
||||||
echo " ssh ${ssh_target} -- ${sudo_display}qm set ${vmid} --scsi0 ${storage}:<disk-id-from-output-above>" >&2
|
echo " ssh ${ssh_target} -- qm set ${vmid} --scsi0 ${storage}:<disk-id-from-output-above>" >&2
|
||||||
echo " ssh ${ssh_target} -- ${sudo_display}qm set ${vmid} --boot order=scsi0" >&2
|
echo " ssh ${ssh_target} -- qm set ${vmid} --boot order=scsi0" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
remote "${sudo_prefix} qm set ${vmid} --scsi0 ${disk_id}"
|
remote "qm set ${vmid} --scsi0 ${disk_id}"
|
||||||
fi
|
fi
|
||||||
remote "${sudo_prefix} qm set ${vmid} --boot order=scsi0"
|
remote "qm set ${vmid} --boot order=scsi0"
|
||||||
remote "${sudo_prefix} qm start ${vmid}"
|
remote "qm start ${vmid}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
Executable
+347
@@ -0,0 +1,347 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Move a single VM/CT from one Proxmox node to another via vzdump +
|
||||||
|
# qmrestore/pct restore -- the same relay pattern as
|
||||||
|
# clone-pve1-to-pve-test.sh, but for a *migration* (the guest ends up
|
||||||
|
# living on the target node only) rather than a *clone* (a disposable
|
||||||
|
# copy, source untouched, fresh MAC).
|
||||||
|
#
|
||||||
|
# There is no real `qm migrate` here: that command only works between
|
||||||
|
# nodes in the same Proxmox cluster with shared/replicated storage, which
|
||||||
|
# pve1 and pve-test are not (see CLAUDE.md's "Two Proxmox nodes" section --
|
||||||
|
# they're deliberately separate, unclustered nodes). This script is the
|
||||||
|
# closest equivalent across two independent nodes, built out of the same
|
||||||
|
# primitives as the clone script. It is NOT a true live migration -- read
|
||||||
|
# the caveat below before using it for anything where losing a few
|
||||||
|
# seconds/minutes of writes is unacceptable.
|
||||||
|
#
|
||||||
|
# Flow:
|
||||||
|
# 1. vzdump the resource on the source node (--mode snapshot by
|
||||||
|
# default, so the source keeps running throughout the bulk copy --
|
||||||
|
# this is the part that's "live": the guest stays reachable while
|
||||||
|
# its disk data is captured and shipped to the target).
|
||||||
|
# 2. Stream the archive straight from the source node to the target
|
||||||
|
# node (ssh source cat ... | ssh target cat > ...), same relay this
|
||||||
|
# machine does in the clone script -- no separate on-disk staging
|
||||||
|
# copy here either.
|
||||||
|
# 3. qmrestore / pct restore it on the target node under the same VMID
|
||||||
|
# by default (--new-vmid to pick a different one). Restored WITHOUT
|
||||||
|
# --unique (i.e. original MAC preserved) unless --unique is passed
|
||||||
|
# explicitly -- unlike the clone script, the source is being retired,
|
||||||
|
# not left running alongside a copy, so there's no address collision
|
||||||
|
# to avoid. qmrestore/pct restore don't start the guest, so nothing
|
||||||
|
# is live on the target node yet.
|
||||||
|
# 4. Cutover: stop the guest on the source node, then (unless --no-start)
|
||||||
|
# start it on the target node. This is the only real downtime window
|
||||||
|
# -- everything before this point runs with the source guest still
|
||||||
|
# up.
|
||||||
|
# 5. Unless --remove-source is passed, the now-stopped guest is LEFT ON
|
||||||
|
# the source node as a safety net (config + disk intact, just
|
||||||
|
# powered off) -- easy to start back up if the target copy turns out
|
||||||
|
# to be broken. Pass --remove-source to actually destroy it there
|
||||||
|
# once you've verified the target is good; this step gets its own
|
||||||
|
# typed confirmation since qm destroy/pct destroy is irreversible.
|
||||||
|
# 6. Delete the vzdump archive from both nodes' storage (unless
|
||||||
|
# --keep-backup), same as the clone script -- neither node
|
||||||
|
# accumulates ad hoc backup files from this script.
|
||||||
|
#
|
||||||
|
# --- IMPORTANT CAVEAT: this is not byte-perfect live migration ---
|
||||||
|
# vzdump is not incremental. With the default --mode snapshot, the source
|
||||||
|
# guest keeps running (and can keep writing to disk) for the entire time
|
||||||
|
# between when the snapshot is taken and when this script stops it at
|
||||||
|
# cutover. Any writes in that window are NOT captured in the archive and
|
||||||
|
# will NOT exist on the target. For a large disk this window can be
|
||||||
|
# minutes. If the workload can't tolerate that:
|
||||||
|
# - use --mode stop (or --mode suspend) instead, which makes the source
|
||||||
|
# guest go down *before* vzdump reads its disk, so the archive is
|
||||||
|
# exactly the state being migrated and cutover has nothing left to
|
||||||
|
# lose -- at the cost of the guest being down for the whole backup
|
||||||
|
# duration instead of just the final cutover.
|
||||||
|
# - or don't use this script -- put both nodes in an actual Proxmox
|
||||||
|
# cluster with shared storage and use `qm migrate --online` instead,
|
||||||
|
# which is the real thing this script is only approximating.
|
||||||
|
#
|
||||||
|
# This script's own defaults are pve1 -> pve-test, matching
|
||||||
|
# clone-pve1-to-pve-test.sh and CLAUDE.md's "Two Proxmox nodes" section --
|
||||||
|
# but unlike that script, this one is a generic node-to-node mover: pass
|
||||||
|
# --source-node/--target-node for any other pair. Regardless of node
|
||||||
|
# names, the source resource is only ever touched here after typing the
|
||||||
|
# source VMID back to confirm, and destroying it on the source node
|
||||||
|
# (--remove-source) requires a second, separate typed confirmation.
|
||||||
|
#
|
||||||
|
# See --help for the full option list.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
# shellcheck source=../env.sh
|
||||||
|
source "${repo_root}/scripts/env.sh"
|
||||||
|
# shellcheck source=../lib/confirm.sh
|
||||||
|
source "${repo_root}/scripts/lib/confirm.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 --vmid <n> [options]
|
||||||
|
|
||||||
|
--vmid <n> Required: VMID on the source node to migrate.
|
||||||
|
Kind (qemu VM vs LXC CT) is auto-detected.
|
||||||
|
--new-vmid <n> VMID to restore as on the target node
|
||||||
|
(default: same as --vmid).
|
||||||
|
--mode snapshot|suspend|stop
|
||||||
|
vzdump backup mode (default: snapshot -- the
|
||||||
|
source resource keeps running until cutover;
|
||||||
|
requires snapshot-capable storage, e.g.
|
||||||
|
ZFS/LVM-thin/Ceph/qcow2). Use "stop" for a
|
||||||
|
byte-perfect migration with no post-snapshot
|
||||||
|
write gap (source goes down for the whole
|
||||||
|
backup duration instead of just cutover) --
|
||||||
|
see the caveat at the top of this script.
|
||||||
|
--source-node <host> (default: \$PVE1_HOST, ${PVE1_HOST})
|
||||||
|
--target-node <host> (default: \$PVE_TEST_HOST, ${PVE_TEST_HOST})
|
||||||
|
--source-storage <pool> Where vzdump writes the backup on the
|
||||||
|
source node (default: local).
|
||||||
|
--target-storage <pool> Where the restored disk/rootfs lands on
|
||||||
|
the target node (default: \$PROXMOX_STORAGE, ${PROXMOX_STORAGE}).
|
||||||
|
--unique Restore with a fresh MAC address
|
||||||
|
(--unique 1), as the clone script always
|
||||||
|
does. Off by default here since the
|
||||||
|
source is being retired, not left
|
||||||
|
running alongside the target.
|
||||||
|
--no-start Don't start the guest on the target
|
||||||
|
node after cutover (default: start
|
||||||
|
it).
|
||||||
|
--remove-source Destroy the guest on the source
|
||||||
|
node (qm destroy/pct destroy) after
|
||||||
|
a successful cutover, instead of
|
||||||
|
just leaving it stopped there.
|
||||||
|
Irreversible -- prompts for its own
|
||||||
|
typed confirmation unless --yes.
|
||||||
|
--keep-backup Don't delete the vzdump archive from
|
||||||
|
either node afterward (debugging aid).
|
||||||
|
--yes Skip all typed confirmations (initial
|
||||||
|
backup, cutover, and --remove-source
|
||||||
|
if passed).
|
||||||
|
--dry-run Print the full plan and skip every
|
||||||
|
mutating step (vzdump, transfer,
|
||||||
|
restore, cutover, destroy, delete)
|
||||||
|
and every confirm prompt. Still makes
|
||||||
|
read-only SSH calls to look up the
|
||||||
|
source kind and check the target
|
||||||
|
VMID is free -- harmless on either
|
||||||
|
node.
|
||||||
|
-h, --help
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
vmid=""
|
||||||
|
new_vmid=""
|
||||||
|
mode="snapshot"
|
||||||
|
source_node="$PVE1_HOST"
|
||||||
|
target_node="$PVE_TEST_HOST"
|
||||||
|
source_storage="local"
|
||||||
|
target_storage="$PROXMOX_STORAGE"
|
||||||
|
unique=0
|
||||||
|
start_target=1
|
||||||
|
remove_source=0
|
||||||
|
keep_backup=0
|
||||||
|
skip_confirm=0
|
||||||
|
dry_run=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--vmid) vmid="$2"; shift 2 ;;
|
||||||
|
--new-vmid) new_vmid="$2"; shift 2 ;;
|
||||||
|
--mode) mode="$2"; shift 2 ;;
|
||||||
|
--source-node) source_node="$2"; shift 2 ;;
|
||||||
|
--target-node) target_node="$2"; shift 2 ;;
|
||||||
|
--source-storage) source_storage="$2"; shift 2 ;;
|
||||||
|
--target-storage) target_storage="$2"; shift 2 ;;
|
||||||
|
--unique) unique=1; shift ;;
|
||||||
|
--no-start) start_target=0; shift ;;
|
||||||
|
--remove-source) remove_source=1; shift ;;
|
||||||
|
--keep-backup) keep_backup=1; shift ;;
|
||||||
|
--yes) skip_confirm=1; shift ;;
|
||||||
|
--dry-run) dry_run=1; shift ;;
|
||||||
|
-h | --help) usage; exit 0 ;;
|
||||||
|
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$vmid" ]]; then
|
||||||
|
echo "ERROR: --vmid is required." >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$mode" != "snapshot" && "$mode" != "suspend" && "$mode" != "stop" ]]; then
|
||||||
|
echo "ERROR: --mode must be snapshot, suspend, or stop." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
[[ -z "$new_vmid" ]] && new_vmid="$vmid"
|
||||||
|
|
||||||
|
source_target="${PROXMOX_SSH_USER}@${source_node}"
|
||||||
|
target_target="${PROXMOX_SSH_USER}@${target_node}"
|
||||||
|
|
||||||
|
# No dry-run wrapper needed for the calls below: every mutating step
|
||||||
|
# (vzdump, transfer, restore, cutover, destroy, delete) is reached only
|
||||||
|
# after the --dry-run early-exit further down, so a plain `ssh` call is
|
||||||
|
# never in the dry-run path.
|
||||||
|
|
||||||
|
# --- identify the resource kind on the source node -----------------------
|
||||||
|
echo "==> Looking up VMID ${vmid} on ${source_node}..."
|
||||||
|
kind=""
|
||||||
|
if ssh "$source_target" "qm status ${vmid}" >/dev/null 2>&1; then
|
||||||
|
kind="vm"
|
||||||
|
elif ssh "$source_target" "pct status ${vmid}" >/dev/null 2>&1; then
|
||||||
|
kind="lxc"
|
||||||
|
else
|
||||||
|
echo "ERROR: VMID ${vmid} doesn't exist on ${source_node} as either a VM or CT." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "VMID ${vmid} on ${source_node} is a ${kind}."
|
||||||
|
|
||||||
|
# --- refuse to clobber an existing resource on the target node -----------
|
||||||
|
if ssh "$target_target" "qm status ${new_vmid}" >/dev/null 2>&1 \
|
||||||
|
|| ssh "$target_target" "pct status ${new_vmid}" >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: VMID ${new_vmid} already exists on ${target_node}. Pass --new-vmid" >&2
|
||||||
|
echo "with a free ID, or remove the existing resource there first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Plan:"
|
||||||
|
echo " source: ${kind} VMID ${vmid} on ${source_node} (storage: ${source_storage}, mode: ${mode})"
|
||||||
|
echo " target: VMID ${new_vmid} on ${target_node} (storage: ${target_storage}," \
|
||||||
|
"$([[ "$unique" -eq 1 ]] && echo "fresh MAC via --unique" || echo "original MAC preserved"))"
|
||||||
|
echo " cutover: stop VMID ${vmid} on ${source_node}," \
|
||||||
|
"$([[ "$start_target" -eq 1 ]] && echo "then start VMID ${new_vmid} on ${target_node}" || echo "target left stopped (--no-start)")"
|
||||||
|
if [[ "$remove_source" -eq 1 ]]; then
|
||||||
|
echo " after cutover: DESTROY VMID ${vmid} on ${source_node} (--remove-source, irreversible)"
|
||||||
|
else
|
||||||
|
echo " after cutover: source VMID ${vmid} left stopped (but intact) on ${source_node}"
|
||||||
|
fi
|
||||||
|
[[ "$keep_backup" -eq 1 ]] && echo " backup archives are kept on both nodes afterward (--keep-backup)"
|
||||||
|
|
||||||
|
if [[ "$dry_run" -eq 1 ]]; then
|
||||||
|
echo
|
||||||
|
echo "[dry-run] No backup, transfer, restore, cutover, destroy, or delete was performed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$skip_confirm" -ne 1 ]]; then
|
||||||
|
echo
|
||||||
|
if ! confirm_typed "$vmid" "Type the source VMID (${vmid}) to confirm backing it up from ${source_node} for migration: "; then
|
||||||
|
echo "Cancelled -- input didn't match ${vmid}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- vzdump on the source node --------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "==> Backing up VMID ${vmid} on ${source_node} (mode=${mode}, storage=${source_storage})..."
|
||||||
|
vzdump_log="$(ssh "$source_target" \
|
||||||
|
"vzdump ${vmid} --mode ${mode} --storage ${source_storage} --compress zstd" 2>&1)" \
|
||||||
|
|| {
|
||||||
|
echo "$vzdump_log" >&2
|
||||||
|
echo "ERROR: vzdump failed on ${source_node}." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "$vzdump_log"
|
||||||
|
|
||||||
|
archive="$(echo "$vzdump_log" | grep -oP "creating vzdump archive '\K[^']+" | tail -n1)"
|
||||||
|
if [[ -z "$archive" ]]; then
|
||||||
|
echo "ERROR: couldn't find the archive path in vzdump's output above." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
archive_basename="$(basename "$archive")"
|
||||||
|
target_tmp_archive="/var/tmp/${archive_basename}"
|
||||||
|
echo "Archive: ${archive}"
|
||||||
|
|
||||||
|
# Always clean up the relayed copy on the target node, success or failure
|
||||||
|
# -- it's only ever a working copy, restored or not.
|
||||||
|
cleanup_target_tmp() {
|
||||||
|
if [[ "$keep_backup" -ne 1 ]]; then
|
||||||
|
ssh "$target_target" "rm -f '${target_tmp_archive}'" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap cleanup_target_tmp EXIT
|
||||||
|
|
||||||
|
# --- relay the archive from source to target ------------------------------
|
||||||
|
echo
|
||||||
|
echo "==> Transferring archive to ${target_node}..."
|
||||||
|
ssh "$source_target" "cat '${archive}'" | ssh "$target_target" "cat > '${target_tmp_archive}'"
|
||||||
|
|
||||||
|
# --- restore on the target node --------------------------------------------
|
||||||
|
echo
|
||||||
|
echo "==> Restoring as VMID ${new_vmid} on ${target_node} (storage=${target_storage})..."
|
||||||
|
restore_unique_flag=0
|
||||||
|
[[ "$unique" -eq 1 ]] && restore_unique_flag=1
|
||||||
|
if [[ "$kind" == "vm" ]]; then
|
||||||
|
ssh "$target_target" "qmrestore '${target_tmp_archive}' ${new_vmid} --storage ${target_storage} --unique ${restore_unique_flag}"
|
||||||
|
else
|
||||||
|
ssh "$target_target" "pct restore ${new_vmid} '${target_tmp_archive}' --storage ${target_storage} --unique ${restore_unique_flag}"
|
||||||
|
fi
|
||||||
|
echo "Restored on ${target_node}. Source VMID ${vmid} on ${source_node} is still up --" \
|
||||||
|
"the guest hasn't moved yet."
|
||||||
|
|
||||||
|
# --- cutover: stop source, start target -----------------------------------
|
||||||
|
if [[ "$skip_confirm" -ne 1 ]]; then
|
||||||
|
echo
|
||||||
|
if ! confirm_typed "$vmid" "Type the source VMID (${vmid}) again to confirm CUTOVER (stop it on ${source_node}$([[ "$start_target" -eq 1 ]] && echo ", start VMID ${new_vmid} on ${target_node}")): "; then
|
||||||
|
echo "Cancelled before cutover -- input didn't match ${vmid}." >&2
|
||||||
|
echo "The restored (but not started) copy remains on ${target_node} as VMID ${new_vmid};" >&2
|
||||||
|
echo "the source on ${source_node} is untouched. Re-run cutover manually, or clean up" >&2
|
||||||
|
echo "the target copy if you no longer want it." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Stopping VMID ${vmid} on ${source_node}..."
|
||||||
|
if [[ "$kind" == "vm" ]]; then
|
||||||
|
ssh "$source_target" "qm stop ${vmid}"
|
||||||
|
else
|
||||||
|
ssh "$source_target" "pct stop ${vmid}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$start_target" -eq 1 ]]; then
|
||||||
|
echo
|
||||||
|
echo "==> Starting VMID ${new_vmid} on ${target_node}..."
|
||||||
|
if [[ "$kind" == "vm" ]]; then
|
||||||
|
ssh "$target_target" "qm start ${new_vmid}"
|
||||||
|
else
|
||||||
|
ssh "$target_target" "pct start ${new_vmid}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- optionally destroy the now-stopped source resource -------------------
|
||||||
|
if [[ "$remove_source" -eq 1 ]]; then
|
||||||
|
if [[ "$skip_confirm" -ne 1 ]]; then
|
||||||
|
echo
|
||||||
|
if ! confirm_typed "$vmid" "Type the source VMID (${vmid}) one more time to permanently DESTROY it on ${source_node}: "; then
|
||||||
|
echo "Cancelled -- input didn't match ${vmid}." >&2
|
||||||
|
echo "VMID ${vmid} on ${source_node} is left stopped (not destroyed). VMID ${new_vmid}" >&2
|
||||||
|
echo "on ${target_node} is up and running." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "==> Destroying VMID ${vmid} on ${source_node}..."
|
||||||
|
if [[ "$kind" == "vm" ]]; then
|
||||||
|
ssh "$source_target" "qm destroy ${vmid} --purge"
|
||||||
|
else
|
||||||
|
ssh "$source_target" "pct destroy ${vmid} --purge"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- clean up backup archives now that the migration succeeded -----------
|
||||||
|
if [[ "$keep_backup" -ne 1 ]]; then
|
||||||
|
echo
|
||||||
|
echo "==> Deleting backup archive from ${source_node}'s ${source_storage} storage..."
|
||||||
|
ssh "$source_target" "rm -f '${archive}' '${archive}.notes' '${archive}.log'" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Done. VMID ${new_vmid} (${kind}) is now on ${target_node}, migrated from" \
|
||||||
|
"VMID ${vmid} on ${source_node}."
|
||||||
|
if [[ "$remove_source" -ne 1 ]]; then
|
||||||
|
echo "The source copy is stopped but still present on ${source_node} -- re-run with" \
|
||||||
|
"--remove-source once you've verified the target, or remove it manually."
|
||||||
|
fi
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# recover-hosts.sh — Fix sops/SSH-key/GitHub-token issues on deployed NixOS hosts
|
|
||||||
# and trigger a Switch-nix rebuild on each.
|
|
||||||
#
|
|
||||||
# Run from the repo root on the workstation (nixos@nixos):
|
|
||||||
# bash scripts/recover-hosts.sh [<hostname> ...]
|
|
||||||
#
|
|
||||||
# With no args it discovers and checks every known hostname.
|
|
||||||
# With args it checks only those hostnames:
|
|
||||||
# bash scripts/recover-hosts.sh tor-relay
|
|
||||||
#
|
|
||||||
# Fixes applied automatically (then prompts before rebuilding):
|
|
||||||
# 1. SSH host key drift — live key no longer matches host-keys/<target>_ssh_host_ed25519_key
|
|
||||||
# Fix: scp the registered key back and restore it (needs sudo once per host).
|
|
||||||
# 2. Stale/invalid GitHub access token — the rendered nix-github-token.conf has
|
|
||||||
# a token GitHub rejects (401), blocking any rebuild that fetches disko or
|
|
||||||
# other public GitHub flake inputs.
|
|
||||||
# Fix: empty the rendered file so nix makes unauthenticated requests instead.
|
|
||||||
# Public repos (disko, nixpkgs, etc.) work fine without auth. sops-nix
|
|
||||||
# re-renders the correct new token automatically after the first successful
|
|
||||||
# rebuild.
|
|
||||||
#
|
|
||||||
# Both fixes need one interactive sudo session per host. The script opens a
|
|
||||||
# single ssh -t per broken host so you enter the password once and all steps
|
|
||||||
# run in sequence.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
source scripts/env.sh 2>/dev/null || true
|
|
||||||
|
|
||||||
SSH_OPTS=(-o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5)
|
|
||||||
SSH_USER=nixos
|
|
||||||
|
|
||||||
# Known flake-target → ssh hostname map for all currently-defined hosts.
|
|
||||||
# Add new hosts here as they are deployed.
|
|
||||||
declare -A TARGET_HOST=(
|
|
||||||
[lxc-docker]=docker
|
|
||||||
[lxc-nix-cache]=nix-cache
|
|
||||||
[lxc-pxe-boot]=pxe-boot
|
|
||||||
[lxc-tor-relay]=tor-relay
|
|
||||||
[lxc-minimal]=nix-minimal
|
|
||||||
[proxmox-server]=server
|
|
||||||
[baremetal-gui]=nixos
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
info() { echo " [✓] $*"; }
|
|
||||||
warn() { echo " [!] $*"; }
|
|
||||||
step() { echo "==> $*"; }
|
|
||||||
|
|
||||||
ssh_host_age() {
|
|
||||||
ssh-keyscan -t ed25519 "$1" 2>/dev/null \
|
|
||||||
| nix shell nixpkgs#ssh-to-age --command ssh-to-age 2>/dev/null \
|
|
||||||
| head -1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
registered_age() {
|
|
||||||
local keyfile="host-keys/${1}_ssh_host_ed25519_key.pub"
|
|
||||||
[ -f "$keyfile" ] || return 0
|
|
||||||
nix shell nixpkgs#ssh-to-age --command ssh-to-age < "$keyfile" 2>/dev/null \
|
|
||||||
| head -1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
github_token_valid() {
|
|
||||||
local host=$1
|
|
||||||
local raw token code
|
|
||||||
raw=$(ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" \
|
|
||||||
"cat /run/secrets/rendered/nix-github-token.conf 2>/dev/null || true")
|
|
||||||
token=$(echo "$raw" | grep -oP '(?<=github\.com=)\S+' || true)
|
|
||||||
if [ -z "$token" ]; then
|
|
||||||
return 0 # no token = unauthenticated, works for public repos
|
|
||||||
fi
|
|
||||||
code=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
||||||
-H "Authorization: token $token" \
|
|
||||||
"https://api.github.com/repos/nix-community/disko" 2>/dev/null || echo 000)
|
|
||||||
[ "$code" = "200" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── discover hosts ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
if [ $# -gt 0 ]; then
|
|
||||||
HOSTNAMES=("$@")
|
|
||||||
else
|
|
||||||
HOSTNAMES=()
|
|
||||||
seen=()
|
|
||||||
for target in "${!TARGET_HOST[@]}"; do
|
|
||||||
h="${TARGET_HOST[$target]}"
|
|
||||||
# deduplicate (e.g. proxmox-server and lxc-server both map to "server")
|
|
||||||
if [[ ! " ${seen[*]:-} " =~ " $h " ]]; then
|
|
||||||
seen+=("$h")
|
|
||||||
if ssh "${SSH_OPTS[@]}" "$SSH_USER@$h" "true" 2>/dev/null; then
|
|
||||||
HOSTNAMES+=("$h")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ${#HOSTNAMES[@]} -eq 0 ]; then
|
|
||||||
echo "No reachable hosts found. Pass hostnames explicitly or check SSH."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Hosts to check: ${HOSTNAMES[*]}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# ── check phase ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
NEEDS_FIX=()
|
|
||||||
|
|
||||||
for host in "${HOSTNAMES[@]}"; do
|
|
||||||
step "$host"
|
|
||||||
|
|
||||||
if ! ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" "true" 2>/dev/null; then
|
|
||||||
warn "SSH unreachable — clearing stale known_hosts entry"
|
|
||||||
ssh-keygen -R "$host" 2>/dev/null || true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
flake_target=$(ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" \
|
|
||||||
"cat /etc/flake-target 2>/dev/null || true")
|
|
||||||
echo " flake-target: ${flake_target:-unknown}"
|
|
||||||
|
|
||||||
host_broken=false
|
|
||||||
|
|
||||||
# SSH host key
|
|
||||||
if [ -n "$flake_target" ] && [ -f "host-keys/${flake_target}_ssh_host_ed25519_key.pub" ]; then
|
|
||||||
live=$(ssh_host_age "$host")
|
|
||||||
want=$(registered_age "$flake_target")
|
|
||||||
if [ "$live" = "$want" ]; then
|
|
||||||
info "SSH host key OK"
|
|
||||||
else
|
|
||||||
warn "SSH host key MISMATCH (live ≠ host-keys/)"
|
|
||||||
echo " live: $live"
|
|
||||||
echo " registered: $want"
|
|
||||||
host_broken=true
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " [~] No host-keys/ entry for ${flake_target:-unknown} — skipping key check"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# GitHub token
|
|
||||||
if github_token_valid "$host"; then
|
|
||||||
info "GitHub token OK"
|
|
||||||
else
|
|
||||||
warn "GitHub token invalid (rebuild will fail with 401)"
|
|
||||||
host_broken=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# sops-nix result
|
|
||||||
sops_result=$(ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" \
|
|
||||||
"systemctl show sops-nix --property=Result --value 2>/dev/null || echo unknown")
|
|
||||||
if [ "$sops_result" = "success" ]; then
|
|
||||||
info "sops-nix: success"
|
|
||||||
else
|
|
||||||
warn "sops-nix: $sops_result"
|
|
||||||
fi
|
|
||||||
|
|
||||||
$host_broken && NEEDS_FIX+=("$host")
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
# ── fix phase ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
if [ ${#NEEDS_FIX[@]} -eq 0 ]; then
|
|
||||||
echo "All hosts healthy — nothing to fix."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Hosts needing fixes: ${NEEDS_FIX[*]}"
|
|
||||||
echo ""
|
|
||||||
echo "Each fix requires one sudo session per host. You will be prompted for"
|
|
||||||
echo "the nixos sudo password once per host; all steps run in that session."
|
|
||||||
echo ""
|
|
||||||
read -r -p "Proceed with fixes + Switch-nix on each broken host? [y/N] " confirm
|
|
||||||
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
for host in "${NEEDS_FIX[@]}"; do
|
|
||||||
step "Fixing $host"
|
|
||||||
flake_target=$(ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" \
|
|
||||||
"cat /etc/flake-target 2>/dev/null || true")
|
|
||||||
|
|
||||||
fix_script=""
|
|
||||||
|
|
||||||
# Fix 1: restore SSH host key
|
|
||||||
live=$(ssh_host_age "$host")
|
|
||||||
want=$(registered_age "${flake_target:-}")
|
|
||||||
if [ -n "$want" ] && [ "$live" != "$want" ]; then
|
|
||||||
echo " Uploading registered SSH host key (private + public)..."
|
|
||||||
scp -o StrictHostKeyChecking=no \
|
|
||||||
"host-keys/${flake_target}_ssh_host_ed25519_key" \
|
|
||||||
"$SSH_USER@$host:/tmp/recover_ed25519_key"
|
|
||||||
scp -o StrictHostKeyChecking=no \
|
|
||||||
"host-keys/${flake_target}_ssh_host_ed25519_key.pub" \
|
|
||||||
"$SSH_USER@$host:/tmp/recover_ed25519_key.pub"
|
|
||||||
fix_script+='
|
|
||||||
echo "[fix] Restoring SSH host key..."
|
|
||||||
install -m 0600 /tmp/recover_ed25519_key /etc/ssh/ssh_host_ed25519_key
|
|
||||||
install -m 0644 /tmp/recover_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub
|
|
||||||
rm -f /tmp/recover_ed25519_key /tmp/recover_ed25519_key.pub
|
|
||||||
echo " Done."
|
|
||||||
'
|
|
||||||
ssh-keygen -R "$host" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fix 2: clear invalid GitHub token
|
|
||||||
if ! github_token_valid "$host"; then
|
|
||||||
fix_script+='
|
|
||||||
echo "[fix] Clearing stale GitHub token (nix will use unauthenticated access)..."
|
|
||||||
echo "" > /run/secrets/rendered/nix-github-token.conf
|
|
||||||
systemctl restart nix-daemon 2>/dev/null || true
|
|
||||||
echo " Done."
|
|
||||||
'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Fix 3: rebuild
|
|
||||||
fix_script+='
|
|
||||||
echo "[fix] Running nixos-rebuild switch..."
|
|
||||||
nixos-rebuild switch \
|
|
||||||
--no-write-lock-file \
|
|
||||||
--refresh \
|
|
||||||
--flake "git+https://gitea.lan.ddnsgeek.com/beatzaplenty/nixos.git#$(cat /etc/flake-target)"
|
|
||||||
echo "[fix] Rebuild complete."
|
|
||||||
'
|
|
||||||
|
|
||||||
echo " Opening SSH session (enter sudo password when prompted)..."
|
|
||||||
if ssh -t -o StrictHostKeyChecking=no "$SSH_USER@$host" \
|
|
||||||
"sudo bash -s" <<< "$fix_script"; then
|
|
||||||
echo ""
|
|
||||||
info "$host fixed and rebuilt"
|
|
||||||
else
|
|
||||||
rc=$?
|
|
||||||
echo ""
|
|
||||||
warn "$host: rebuild exited with code $rc (may still have succeeded — check sops-nix below)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Verify: re-check sops-nix result post-rebuild
|
|
||||||
sops_result_after=$(ssh "${SSH_OPTS[@]}" "$SSH_USER@$host" \
|
|
||||||
"systemctl show sops-nix --property=Result --value 2>/dev/null || echo unknown" 2>/dev/null || echo "ssh-failed")
|
|
||||||
if [ "$sops_result_after" = "success" ]; then
|
|
||||||
info "$host sops-nix: success post-rebuild"
|
|
||||||
else
|
|
||||||
warn "$host sops-nix: $sops_result_after post-rebuild (may need another pass)"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Recovery complete."
|
|
||||||
+69
-204
@@ -1,261 +1,126 @@
|
|||||||
root-hashedPassword: ENC[AES256_GCM,data:Kp0nOZI7vDoLhJHiOJBwJn0rQZ5yhnwapGnAcA+qh8vlDETtFs/iQdetF/2ZxmANf62SviTNd+Ag0q5JIF1996x7onZGXqxgSMCuVzZLBdUlsO5IR0BslWWz47khYGTe4WkUg4NB1itBfQ==,iv:5Sra5vJ79V8hxQT3g9qJ+dOj2W2sumIhqpitqnHjJdk=,tag:3Igu0+8GeUZHqS3fKUVwog==,type:str]
|
root-hashedPassword: ENC[AES256_GCM,data:Kp0nOZI7vDoLhJHiOJBwJn0rQZ5yhnwapGnAcA+qh8vlDETtFs/iQdetF/2ZxmANf62SviTNd+Ag0q5JIF1996x7onZGXqxgSMCuVzZLBdUlsO5IR0BslWWz47khYGTe4WkUg4NB1itBfQ==,iv:5Sra5vJ79V8hxQT3g9qJ+dOj2W2sumIhqpitqnHjJdk=,tag:3Igu0+8GeUZHqS3fKUVwog==,type:str]
|
||||||
nixos-hashedPassword: ENC[AES256_GCM,data:pT7tVRN6X4a+DNUgB7fIUUE3CbnetkjxmoSL1PxSU+ktsFU+fB0mEvJjA1uujsGH5Rcztg7YM815+M0Z67ILmHaXbza5DtFacrqhi4/b277xly0SHRX4yOvBwQh6mJG1jn/0O/wvUUIYdw==,iv:bp2nfhC8nFbk6o5iWDAugvbzu7J/a1xayFnBEtkhNpE=,tag:HqWgkIpSrSM/K9OK2WO+VQ==,type:str]
|
nixos-hashedPassword: ENC[AES256_GCM,data:pT7tVRN6X4a+DNUgB7fIUUE3CbnetkjxmoSL1PxSU+ktsFU+fB0mEvJjA1uujsGH5Rcztg7YM815+M0Z67ILmHaXbza5DtFacrqhi4/b277xly0SHRX4yOvBwQh6mJG1jn/0O/wvUUIYdw==,iv:bp2nfhC8nFbk6o5iWDAugvbzu7J/a1xayFnBEtkhNpE=,tag:HqWgkIpSrSM/K9OK2WO+VQ==,type:str]
|
||||||
nix-github-token: ENC[AES256_GCM,data:k1vYz7SqVhzpWa6jTL6NUD8lKOCpHCgTm+HT4IcnbzbSTUZP/bJUYw==,iv:UqAULZnr/4+VcioUDfTwvOSuwM8K9JgGhiApvYQPyoc=,tag:1LKHXhWAO/AHPDIZFBb04A==,type:str]
|
nix-github-token: ENC[AES256_GCM,data:OfNRGJg16Ede6EilWUetCs9za+xk5/Lsa3SpVajsqz8PMdA1xQNeCWdX7ZAMdijHClpBhU6ETFGsXvt41O9aORS951uijeGSW7/NH35/bnPISrKdYeBx/+xEiqwH,iv:QGU3v7xOy89uzRTCb1U9ICyJ8XYIpXrUsDt12aL3g2Y=,tag:Bde2wcWNv8H4WLxSEUAodg==,type:str]
|
||||||
sops:
|
sops:
|
||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBndmw0dXdSd3paLzdlYXBv
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQUFhWVFVlVlBnNE5FTnMz
|
||||||
K25ZMy9sZ3Ftci8yckZzOFdITmFEaXpiamlzCnZFSDZqWnVKQzNZUXBITXZmOGF5
|
VkxkTmxpRXlzZ3pSNTVZWFUrSllsYWo0alRnCkJSc25TYktSTFFJdkQydHcxOUlj
|
||||||
ZUFwdTVuNWptS0lyei9XTWJiQkppenMKLS0tIFZRa255YVBKcTBCV3ZBSUN4NWdv
|
ajhQU1ZIb1lodEpHTnVhQjJ6WEthaDQKLS0tIDJCY1E2UVBaU3BoMzhXUXlIdnMv
|
||||||
UG5vV3FDaUNWM0Zwb1BrWnpDZXZCek0KhuUPg0MwDO9qGlCHhft3tmuvmjAVsWMu
|
djZTcE1rcWNTOXFPMmFDYTVoRGo4ZTQKYy8g6pqP3VpTKDIBPbnC8NzCdDvOCKnL
|
||||||
eO3S1tANZ/YesQylxXQl+pDYC4J1X4BZmtf9tXGLTQoC0nxSTbqQEA==
|
14kSrKmKlzefTrbkVyriz2Jdl2s0F374yfQQFreZ3m4AffSACCxziQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2U0VHSzh0SllHaFBoNzVU
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVQnpVWC9wcnIreG9GeE9U
|
||||||
UlJtcE1YdG9tYWN6eitqSzFMR1IzbGtISFZzCmo2VEkrWlFlRW9lbmFpSUM1WkJl
|
UWhuRytkc2Flc0hyQm5yMjZnelNwaXhlWWc4CjBkWnd4cHNRRXQ0UXFkZGp4QlR1
|
||||||
aHN1djk1eWMyaWdJUXZjQVo3QWNYUUUKLS0tIHVQaUI1Zzd6Ly9kYVoySjVYYVM2
|
eW5NNnE1WFhnb054M1pac2ZidFg4Y3MKLS0tIFdTNmk2V1l2WC9rUk8yd0ZnOEJS
|
||||||
L3FxMmVJOUFkdXVQa1A2U2svZjRtUjQKMQPIeUzyKIaqZqONldMNoqACcQgXFV2D
|
VkNnejVGVUZPZkorQkltVEplN2FmdTAKRY7DPP5HeFQntn2f/fXLjU6M1V6iug86
|
||||||
0B0Rn1bgDOKskMjUc5ffp4PztNGKr/Y3oVqzEChQc3V78WvzAGcjyQ==
|
BD09PI+T2DbIBQPotRZisw8IzHu9gY/O3+h0TccyIsXjI9wy/XPCAQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age19gfn2yedg76dmztm4hncr7vf3r3c9j0qpt4rap7y7gersjk4m3ks2lhd0e
|
recipient: age19gfn2yedg76dmztm4hncr7vf3r3c9j0qpt4rap7y7gersjk4m3ks2lhd0e
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArbGVOaXlmb0hhUFpiSnBE
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkczFSTVhxWHlIWjhRcWlV
|
||||||
OVJFdFpSeEpNLzhYNzFBVnExY0FPM1RzckVRCkVRQmgzTUFHRnl5MkljNFZVRFJY
|
V2JPQXd5Wnk5R3NwWC81T3Z0MW4vYnd5S1ZZClV1NlU1Tzd6UkxPQ2M4MmhLV01G
|
||||||
OVZ6REkyNkprWGV6Y1QrSHVCUlJlYXcKLS0tIGltTk9HekdHSGpHZUVtU2g1MHpJ
|
d3VIb0RhR1RiNTZqNjlQcmg2YjdPeGsKLS0tIDQ1RTFTWGN4MnEvWkRUR3VnN204
|
||||||
TEkrYmlvNWMvcmtrMXh4YXZjU3lKREUKV/QdP8vXM8qjiwdLrZFpBe8EBm2j3LT9
|
WVdFOXdmNC9FVFhBSGNEUUgyYWpYYzAKfdpeaFL/RrIbqpD9hNj8L7UxpmiBjE2I
|
||||||
KHEiXxViVtLmRRsJzBPFbbQZmiyrI8IPTAb6wO9y0QteolHUst4PnQ==
|
go/dR2E1LLXsDPtnSuJb2EZYoFvSsjsIQQQDt+YwRv0fplRtssKdxQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ll6hj5ggruetgjwjfnplpn5xtq35uhlcdflksx3xmnjm6s3uad9sz70jkf
|
recipient: age1ll6hj5ggruetgjwjfnplpn5xtq35uhlcdflksx3xmnjm6s3uad9sz70jkf
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTV0lJR1NDZkVmNmNTekJo
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6TjhmOTJ0bUpMQWowb0hB
|
||||||
TjkyakRUbkZTaWpRY05HT1E5eHlIMTJLblhJClp4c3Y0Q3g2VDRWRlFSSHpwc2NJ
|
T3Era1loU1pMdmkxdnAvRkViekpqZWZjaWgwCkFRQXhFUy9PRVBma2JMUDhqY2F1
|
||||||
eGJ1akRwb3dBUzMzdHNObDRMTTBXcm8KLS0tIEp6RnR0bVdzckNhWmlHMmhoY1FD
|
VVFDRFNVbWpNaEczY1JVQUMyck9XdEkKLS0tIGpxc0tGdVFKK3FteVJKM1Fxa2ky
|
||||||
d2NkaDRROFdHbUQ1V0hQM1E3eE5hZEkKFHuCbyc6tFL7H0UxxP8poPTomNRqHsrP
|
a21WLy9qV05hUURCTVBvcVh3cE45Z3cKXCYfXSjhApBoLbHDu2OOd57Y1zN54yy+
|
||||||
b0Dfa26NrmK7tFNG1SHiW02wRZwrfFgHbfsOaJEJYnzaX9uGt0w4yQ==
|
WDQvz8PpMxhc1nU5Kw/cI+WmL1KvN0qQZfOx/7D4W+dy/ZDWX27TpA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age120le4a5l8dh3lyfgvmj3d9ksmej6ajs5mer5y7r0vfg3x9fn69dqf8xgzu
|
recipient: age120le4a5l8dh3lyfgvmj3d9ksmej6ajs5mer5y7r0vfg3x9fn69dqf8xgzu
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUytEOTlCVW1zdGZxVHF1
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZK0FYQnBHMHZ6dlpMYTlC
|
||||||
MmRlbVAxWXcwYWdQNU5JanFXTmVzcVBMY0c4CmhiYkZ6YklwOTE3c0pnU1ovblFv
|
WFdOWDFkRVBuY1pmdTFiUndLV3JXcndZa3pNCnJsd0tHN0FveWV6UUNQSEdpdWw5
|
||||||
NDYrUzRwcDA1cCtpNEUwTDlKQkRnNUEKLS0tIDJFTmM5Y1VOalVueEV1ajdqeE83
|
dWZITkxWelNIRlpKS1pnN0ZmVlQvZjAKLS0tIEUwMXdtNFdkUWdIRjlxc0owdTRr
|
||||||
MW94VHdRSzlQWk1JbWFhcXcwQmtrdlEK1nx9rEHzQLkA5HTfqozP4503fdLfhpS1
|
c1o1TmptWWd1ZGxzcWJJNzJ0K25PTTAKoos5rnkyQBCm+ZuhCCaMJwqJBo1fpnsl
|
||||||
hXF1jyOUMTEpcCnjoVe9ApTuXuXJgpt9ld7XpQq9IzDmN/VPprnkOQ==
|
G74wu5vbTBG4VjVhI5KqyiuiTRU4jPcGxysECqe7AyZUBGp7ndewgw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1jy444f9d9stygj4p3w9kh54cqcfr654tvr75tdvee5cxsgtdtc9q3v60ep
|
recipient: age1jy444f9d9stygj4p3w9kh54cqcfr654tvr75tdvee5cxsgtdtc9q3v60ep
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEdzJ0N0RVWVZrenRhaGJH
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTVkVXaCtGTXVkRXRZMFZ1
|
||||||
YnpmQnhjb3FhRGwvMWRUWVhQbUMvRS9oZ1UwCk9IU1FnLyt4bUFsSW5LUzRONFU5
|
ZUdXM1hRcDhaSURqcGh2eksxTUNCckk5SVJjCnhkSDRKdDFSckxUWXd4SmVxWG5p
|
||||||
SndHQ3dBT3lRWFVxWWVDMFFFT2xGK0UKLS0tIDR6ZHg4WGl2RTA2SDRncnBLdHd0
|
bU5OV0hzaWR6VDFwcDY2WlY4WnN3cFEKLS0tIElOVzRCcXR4U0dhajJySUhaZGps
|
||||||
cFdWZE8zNkZGL2dGVkphUkxzNEMvd2cKsHB45C+Rj/glZAy0nCucuFeYpzDsJsRw
|
MW9rQk1JVDFWRnFxVzhCUkRIS09EamsK1rVidD48PqwlEWQyjF7iQWU7aBdPqQHy
|
||||||
Tua2XQMBF1CJQ1/Hweyg/H+d3v0q2tiT7Sf4d3Hjjn1pMUZHW1ovaw==
|
z5LaSi3LvJX3rNE/+q0E8/gbZyjGpbEn3AUI5mBF64GY3IZkRxZSXQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age120whqj96g26lsgy4udvgsn8dc9lumh8jeu3a564fx79rjr5lxffqmrljuu
|
recipient: age120whqj96g26lsgy4udvgsn8dc9lumh8jeu3a564fx79rjr5lxffqmrljuu
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnSFI1cFVWdklWcCtUbkcw
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvbnJCWU9UMXJXMG92b3hm
|
||||||
S3dFK2k2OFR4K2Z3V3FVVHVlQUV1V2ZtYVcwClBnRDJkeTFRL2JzSFN1eGZmdno3
|
WWNBTFpQamVWQThITmt5QUVwR3h2OHI2SlVRCjcxOEJTVkFjN0NhamFZQ1plK29w
|
||||||
OWZwbFVLRWVEWVh2ektid1B5dk1hSWcKLS0tIG16SW1JbDdHMXVHcTBHOFU2S0J6
|
dk5XYkYxOXQ0YkVzcVc3VnhCQWlsV0UKLS0tIHZ5cWtFZUhDKzZkOE1BK2Y5TStR
|
||||||
aDlUOFRjcC8rcmVSQXZ6WFVxUHdibG8Kc4/bnpWJ0FmmmRSACJ6zXG0FTg4cGb8D
|
SFlDRjE4ZHpiVEJOQk5TUGNEN1B4amMKUCJ8CL8QpmRpFs83HD9TUn7NrPguuP8S
|
||||||
OFLrJo0HVlSZnqs6jX6K6qr+K8KyGMTIJEPdUl6au7tSEFsJjvEylQ==
|
JQH/bzPorXTXJuyOKuKAZq1hK8BmiMUFksaZ03yN6YaFVIOeelEEMg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1xjst4frdh0th6q8m7p7u9g5af7ty5jqeum0p6z8a52a9q7st7ewqw8yl9j
|
recipient: age1xjst4frdh0th6q8m7p7u9g5af7ty5jqeum0p6z8a52a9q7st7ewqw8yl9j
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6UHNRMUd1akx0ampjSE5I
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxWHV0S2dxeHZEMDNRSkhO
|
||||||
VFA5M2d1TytJZ01aV2x1eDdoNENMWUErL1ZvCmwxTFZhQUdIOGxSYzhYQ1R4K1g5
|
ZFJHaiszd0tzYzlzd3gvbXNSQmlMVlJUNGcwCnVrM29MdFZCR1NBYnpkQ1k5VFZQ
|
||||||
RVJFenhVeWRBa0Y3M0FYdkRnMXJYNk0KLS0tIFFhOUt0MEdIVHB0b2FGM3Z6NEhK
|
b3Z2Q3ZGekVQZkZKWGlka3NDOHJ0R1EKLS0tIDlXTmNzUk0wVXo0UWhkd0ZvK3FI
|
||||||
MGFremtoYVJHMzJtcnJTTm5CWk00ZWcK2KlNsdk6D+UC8C4koAjm6t5gIa+t/NnV
|
UzJxU3RkdWs4aTZYVVkrS056bTN1ek0KgKJNz8GvynX5pK33aW9x3v6yr2Ox0LCT
|
||||||
Wnl1/zRxw+fVbtDWfmeoMnmJStukh3Qg+Wm5n6iw8n3ywBYxvQRr+Q==
|
GGrt+ddbKLcwpBpYjfWkFhffO330EKui73S+c/qMf8N9j6wzalOTpQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age10at8862478urh0eeuwh8hzln6ck78jgwtztgxatwqlzwagg77y5snm4xzg
|
recipient: age10at8862478urh0eeuwh8hzln6ck78jgwtztgxatwqlzwagg77y5snm4xzg
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzMTJxYWhTcVBNYlN1R1Rw
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3ZWJlbDl1TEkvWEpBMC9Y
|
||||||
UmY3ZWVmcjFEUmlEcm5zMHI2bEE2dTErM0V3Cm5PVTV0STc0dFgwa3ZHamxSY3N6
|
ejY1MnFMUExBeDhITndxNy9YUi9hU0tXM1JJCjcwNEppcDdxYzlCSVMrMExWa3A4
|
||||||
aStvVExTdGM2dTVjazYwNzVZRGdDdTgKLS0tIGpodmd1ZnczbWlibmdIYjBVcXNN
|
cmFPSjV3LzkyMXZCUDU2QmtHRmpHRmsKLS0tIFFTdXBOaDJJTERseXlGbmdrQzhD
|
||||||
RWJtMVVnQ0pWNjFwblBUVm5wWUJvVFUKr13nSwN1+RrXYhDNDA1MnoVr6ynSKJB7
|
TWtnRFdIRXpsNkY0U1BiczNsdUk1V1kKGpndKmT8kj/oIxQuxQALfzscw+CsVmnj
|
||||||
7yz9O9LwukKwKj7C6dIt7yZSiUAHotm2nJPYZYYmyLCfUodMFZAIVg==
|
cyPC3bF+tG6LcqqoKLjPSJfcIgzhnX7cAr/wwESavemLn8L/zQMe4w==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ezk9x53zt8kcnscdm80jcyf0xq97vndv7jsn3rl8cc0cwm2jmpmq372dzs
|
recipient: age1ezk9x53zt8kcnscdm80jcyf0xq97vndv7jsn3rl8cc0cwm2jmpmq372dzs
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0N3N4N3Y2VDk2MWVRbW9t
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpQVVyY3UrSHFWSmpDdmRE
|
||||||
eDQrVWMyb2NGRVVuay9Fam1HOW9keHE4MXdNCnIrNUFrQ2xMK2c4ZHYyNDVwblpo
|
dlh5akFqVFdDVFNMSHE1eVJnZzR6YzFHSVFRCko4UE9EdXNxZzF2MW5PTTN6dEdU
|
||||||
ZTJMak91NEJwc2RuN1FUV3VLOWdGSG8KLS0tIGI3alVhcmVDeW5LN25yL0dzYldj
|
ZHM1MGowcVB2Y1ZlOTVHdnNtY3diM2cKLS0tIDBYSmh5dVVPaTM3d0ErcC8wMDNB
|
||||||
MSs3WEgySmRKQnBqNEN1eHVJY3FucmMKFstvTOgCy786oP5MuT9xuT1j3Y1hz+N4
|
eUpHWnZlYnJsbHZuS3pwbG15UGtwN2MKVPQA1MpjIfYAsNacoAbpvZNuAIkvx7ER
|
||||||
/R8tfGwjpN62ozYKB0Mj12WayhTSOWnl2FQ8c3LgtOiRhRbmfF/XDw==
|
CvWBKEHUVm6m8905BXzv8MdGTAk0EyCIP3aMmYqTIYfv2k9pP0T08A==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1fxxzpnfse8nd9wz78ht3m0plrmraacf4cpga0pe8fm2tdnqcgy8q7qsyvp
|
recipient: age1fxxzpnfse8nd9wz78ht3m0plrmraacf4cpga0pe8fm2tdnqcgy8q7qsyvp
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0MG56am1GTHFCVmtCOG1B
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0MTQ4MzN0bDJNL1l1bmpX
|
||||||
ZjVtbjE3L3BnTWhjdTc2bkpJRFJKZE1LNUJnClREQTh6WnlEVjVJemVBeHVmVkZ6
|
ekxDUXRNa0JHWWltZFNGTVltTFdSSE82SVZvCmdUWUdja3JIajMzY09IMUE5elox
|
||||||
SGZTeXp5cVUvdk8zcmhnMjhsRUo3SkEKLS0tIDc1dk04cWlmQ0xWUWFpRHFoUy9K
|
MDdEakFJTmtkRWF2R1BGNkQ4U3grNWsKLS0tIE9hZUhkVGI1ZEpzdDhRU21EZm91
|
||||||
RExCTWJxMFpoMGpCV3JuVmg4VDgzb00KQ9CDA5NrQJmY7dKyBXbdsN1jKQ0wttYR
|
VnJNb1kyQ05MM0RJa1lLUEtjWWxkSTAKHVAKcGcWl6LncJALRBU9RKP7ot6C6GSE
|
||||||
HEdf2txIPpczrk2hFwereL0yxvClOjLovvePcn7Z1G5pQiq5Up5/XA==
|
1iZtj1SNX6wzEWrhOEnV37aQ8bKZj6u+Y/q6/vJ4qiBs78y/drdIzA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age190htw7prp4vln076dxjx3gxxaq06h0zl0te7cqgpx79vl3lhkaes8suy05
|
recipient: age190htw7prp4vln076dxjx3gxxaq06h0zl0te7cqgpx79vl3lhkaes8suy05
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFVGhuUFh3UjRWMDBrNHl2
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNUEVWY2NpVEU4OTZPZTZR
|
||||||
Z29qVnhkV2JIZ2J4NEpyVEVrbEpleVRaTEdJClpRaC9DSmltd2FRVVdNLzJXRkxz
|
UzdKcktpUGJOcnJ0aEhkQlhvUWdUYkV2SkdzCm51eGJVeHJMcVRRRld0dFRCYUxr
|
||||||
M29IMkh4aVh6RlgycTZFbERGQnNQVlkKLS0tIG8ycnVoOGR6cmplMkNYOXVrcGtj
|
TTN2WEhOVjRqV0FtQXowZWNTbkJneEUKLS0tIFpUazZpTUNWZUZBSFE0VDZZbkJu
|
||||||
OHJNenBRZ1pFSDEzZDBieHFkRjZZaFkKBCrrAxHld4OYTBUfKMq1E2V3bEaZOgdu
|
SFVlUVhySnNqUENYOG9qUm5ZMDc1ZW8Kv0lY5dhnCEheM0sttfr4p7IL+EVog16T
|
||||||
4I0fGBQ9VTKK6JM4vVzIM9/RHmPqJWkxNSesxxqkOAewex/RR6ndLA==
|
OapUdbuXL2l7t7URzHnvfG/nbOtJIjH8a0XFsWyJChtNXpF2d/vf2g==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ukpqxzl44mnjpy5r96sfuc5sqzm47u4k8ujjh5qdgy6jvl9uqgpspymqfk
|
recipient: age1ukpqxzl44mnjpy5r96sfuc5sqzm47u4k8ujjh5qdgy6jvl9uqgpspymqfk
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAySkhiUEhlSko1SEc5NlNX
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvTzc2aExGTHFrc0ZtL3pq
|
||||||
dXN0OGJxZkZLZ0NPSmN2QTU3SXhVODFVakNzCkVhT25PTDBiaGEzTlV6ekRDZWN4
|
YlZLbzd3MVZHZTB0VUQvSXZ4NGVsZk42c2hBCk1vbTk0Tnl3b01vbVZaMkJ5aldE
|
||||||
Z25WYkd6ZzRYaEdCQzBIZU1WSU13L2cKLS0tIHJ5V0JkYkVSdkMwYVNONzZoT0RV
|
WmZBMGFjb2pjQXpYcnBxWmp0UUsrdXcKLS0tIG16SG9JbFdkbmVidCsxUnpBR3V3
|
||||||
TkdtZW1GR3ppYldJalgvYi82dllDaXMKx2kvTvWlSsVkvAd6rLQC+AClnGSi+nk1
|
RzNOY3hIRWk4UXh6N3NrcjNSU3ZwWTgKaExY4U2s8E6ojljJ+4TU+YJhcLXyuVA1
|
||||||
7toIeJqNsIloj92IWi2cZf57pw0BJX0Aa83FMr+AQAajhydh5gbizQ==
|
ROB70jQCjFvQOeo6thjQohSSUoPKhxSl1/nr4ZiGBO3/VskzihckKg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age15kh7akxlx7zn00tey79rq2g8lgs4j5y77rcnyfxrxap8ckfu0a9sqvtdhh
|
recipient: age15kh7akxlx7zn00tey79rq2g8lgs4j5y77rcnyfxrxap8ckfu0a9sqvtdhh
|
||||||
- enc: |
|
lastmodified: "2026-07-19T02:30:40Z"
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
mac: ENC[AES256_GCM,data:UiL3VMDF6rq4Nr87KspcDx434q3tfNXeb5pwH2O+4ssNQ6xzcYDdzXBnhAY3zLBsqPMKrvHBd4Ot/gEMcq3FMIVe7Q6p9yWKpep66KZ/yWEhAlwIVhD79Oj8VS+1CHKjf25zpRdhZorp04oeFQQd9VfjJB4EE/Q1aVbwTGlpIic=,iv:i/0conaFgFia+wzNTdUL6tlSTw35HTK3Ap1Sr5RGHf8=,tag:ULbz5FllShA/JjlSRdxA0g==,type:str]
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzM3RTZ1VOaXQwTCsrMEV3
|
|
||||||
Zmg2VERzZy95dkM5eFZkbzBWRmtQSDVaT3lvClZzQWJFSkVEeFc2YU05aXVKczdx
|
|
||||||
YzJGeWE4K2pmNytLNmNoeTZwVExydm8KLS0tIEVpYitFU2plblRQTk12MHcrZXNR
|
|
||||||
dGdBUXhXTnRQTXBUMlVkdkFHcFpKZ3cKN2fMs6FsNX4M50GjGBO5ILa+ZW1Xm+hT
|
|
||||||
LsGH6c6JssI4Yf7BDagZsZRi3pCcVZsW8r3PneVz2F0fII3I9ej9yQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1ehkswwz2pqaz4svzh7ela5tdnssl8kn6d4vwwxd6zwg8exfpd43syyrrjp
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3L0hLSUxKRDY5WTRKQ0hY
|
|
||||||
bEJ1WEhCYm8vYjI1ekFyZDU3ZjlySUV3c1RvCkN1ak1YNTEyY0VOeldWTVNpeFJJ
|
|
||||||
UkhDcmZ2a1hHTXZYc3Yxa2JSSzROb2sKLS0tIEpXbTN2T3VuRTM2MGxpRFMzN3Vu
|
|
||||||
SWN0Vzg1RXdxa05nd0lHSmFtVGhjRkkKburqnTF/LOrMqiOomMc8vg7Dr2ucTqFd
|
|
||||||
UL+ZXizcKT+LVWoBZpOBmHZ3/3/6rWHKDTa3SGNVvcvDVMZQ/BJNyA==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1vvev5m3shgckl62awa64rtug3lyact7jgxehkuu3vn3wpzulhans75w65s
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5Y2JQMC9tZnBSazhSQmsw
|
|
||||||
Ym0vTXVCbUt4ZlUrZUQvR1BVc3E4Yi9KWXpZCmRTblBidHo2RHo1UGhqZG1PYjVm
|
|
||||||
M2ZxRWxML25MK201YXhkdVlIMTBTeUEKLS0tIHRud1pZT09kN3h3WnNFRE9BaTZP
|
|
||||||
YWg2Z0F5a0U3TzRiS0JJOVViRGpPelEKbkfSlclZIO7Owdqxg7ETwf7uNkZKdla1
|
|
||||||
JRft9Nitth0HxEVbkIsvvHIzGjuPfhFgJRxs+pYmdBboLopadMjRFQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1eu65wsmez68gegnufl0gqrs0e6w2409mypjlajlq383d7l2e3pjqy5kn6v
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0SUJhTXE3b2RxRmg4WmdQ
|
|
||||||
bW1lcldBd2dRalRZUG1PUVpxTTRoSjlMbWxZCkFqUFhsSzBjekhDWFAwczJlam1H
|
|
||||||
RFJkZHBnOTlYT3JIOXFiS1Y1TTBhNEUKLS0tIGc2VVQwbHhGWnRTcTloZ2lZTEFM
|
|
||||||
NzJGS1h2MUdSRHVoLzlQTHdVU2JMV00KWiwaygiH0aY1i9qF+2+373JXLcTKewkb
|
|
||||||
F2b3/loCqC7T5OaJDDXpLteNLsZznIi7zLCBw/t3E4K++cJJvxS1ng==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age17pwyghxr6lq06fw46gwqzhc9ut4paz28rpwx5pmv3cxwak6rgyjsw7lk3w
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNRU9uRnBTbHNKVkQxOTZv
|
|
||||||
V2l0dEdsaU5WK0JzWHJXSWhrbGFnVUMvWjFzCm5QVFZSaldNUXkwVTA1anprZTIx
|
|
||||||
YzcyVnRRVU8zNjNyb2g4eEFuMGhTbVUKLS0tIDlyczdueTVOL3poZ2RqdGVNcEFy
|
|
||||||
YldOaUNkbnJUU2lSRTZHMzNCdWFHNEEKDm7/U7J+rAwRBV4XSc52jWtpsjabG4ZR
|
|
||||||
QEqqtRZKYPFehKV6jM4vdfqt+sJIcR8QdrZYllryjRsb6Cw/YrDtJw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1jukmg69cqxnjd0lp5f534jhqe65rxew2hufcmyjxa3rkw6ayef6s2ylcmy
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3RWgrbVpEZXRJekVCQnVW
|
|
||||||
d1FEaFVsZTBPOEFGdHpDZ0tHcXdoVG55czJZClpkemkwTFAxeFJWN3NwRzZOTGFz
|
|
||||||
RzhYMjVoQ0ZRUjhGYnRFZ2taQVBqck0KLS0tIDU5bGExSEVsRm9OQXZTY1NsN2U4
|
|
||||||
K2gra3dVZnBTcXZkOXAwbW43RnhBM0EKPZupNGKWC8JvY6zwmPWnvNXGMZQ4VBMK
|
|
||||||
B/A0RoNEny481g4PEnR8RgFY6IWsotT6rveoYDcLgumJnlITUpanZg==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1tzsrtwd3p3lrr9g7nv3z5nvmzsz54t2uc6tfqwutp6usmav83s2sck25cc
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRdUlycEJEMStRYzNCUVNo
|
|
||||||
aFlZeTEycVVlMWhWNDFEZHhhbDBqWDMxK1dJCkZObzJWdW5nUGlNWTFVd0tMNmlm
|
|
||||||
cHZSNlZZN0VRU0xZRk5TT0dFYUFHbkkKLS0tIDdaUlk2eHRCTktPK0lSWGl6MmxK
|
|
||||||
c3B3QTVKSU85a0NDMUdtelRaR0tXdncK1yQTKQixLdMoYHD7EfiLrUbcVkOC9eck
|
|
||||||
/Zm8r+LWS8+lAyvd7eUkrN3AuR/H/9kXXfa888T0z1L/LfeXPUhuJw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1rf4kj99wuq59k7w8ar326djmgmpl9hcwlnuag07f8gauq8c3y5mqne87s4
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmakp4M2hBczlrNkFHaTZW
|
|
||||||
ZlYvbGN4alpGRFQ5Z3NucmxhdHpDWUtYS1Y0CkhUaXpKNVpEQ2RicDdhTTJoY0dI
|
|
||||||
eHg4bHRDbHNUeURmeEppdnhjUVVOa00KLS0tIEJJQ25MVzVubGJOTHZXYldoTG04
|
|
||||||
UThuekxFZlBLeEJFVnIrd203VVBPS0UKQ7BTKth3+AxJEYCAp2CtQdXHWCjdMVFD
|
|
||||||
nbijayg+b2Cv5nACxX7adFcfN80GvmbiVx+PKDnK68213SAHugxi3w==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1d0zhx7u3mfs3nktl67npey87cze4dwsfvfvgaje4rh3gwv500yssckcf6u
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSTDRxc1Y4UTkxc3dxQUN2
|
|
||||||
Zi9icFhRdTVmOHRNZ0QrVU9qMTlQNmt6Y1NjCkZkekxiZWJzdC9XV3ZaWGdDLysw
|
|
||||||
Z0IySzFLZmg2VVJWbmg4ZnJLWW9FeVkKLS0tIHp1QStLTENGcnJrZVVERW5jMytn
|
|
||||||
ODJDdUhoUzJpc2FNTVBMUTZWUWZYMmMKefmM/fewwjRyv7pPmln0mu2XpLyoZDtv
|
|
||||||
qDqS0q0NMdi+ASlKgYl8A2DMO9TBSJmc6SxNMS9LIp0dZYCREJFMRQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1jc6wx33hdhgwhk6nzy5rr8fkgmqxk9um639tk5h632nqfyaw8czskdptj9
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUTjIwa05wTXR1R1FaYVVn
|
|
||||||
R3kxYTlXa1lZR0FTWSsrSE1lN0g5MXAxL21ZCm1Xd0t1UVJXTmR1UVE5Y0plVmtu
|
|
||||||
RCt5RTRBL252aTdxUi93YnNJZ2tWM00KLS0tIEJhK2d2YTJjVHh6dE8ramxtTGpC
|
|
||||||
U0VmdVNRUFRYdjJYZm4vWTBLdjRYVDgK9eJTpg3aGyV+YHSIWPh+0FtmIitchaY6
|
|
||||||
4B2FJZt71VsCwZf4n/GfokVN946MLAEJ1G2TavjLWqWCNhmkM64ngA==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1pgykvq4pmxhhjrqupcp99fyad2uht40pt79dkzg66cfvsjy5apjqls8u68
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3dmlIK3VHZHlZanIyNkpx
|
|
||||||
dUtkWkljSFU0SXE0K3oxZ08vSTBhL3B3cHhBCldGSzhjN2lMRThOOFZDUWtDZjdq
|
|
||||||
aTVIeXN5djB3OEdmTUtFZmpKVU5XNjgKLS0tIGdBcnljeE5iZVJKQkJYdnpyUU1Q
|
|
||||||
Z2FONUlveWhnWGs2VXM1ZlNaWENSS2MKTuPGfA505R4vJ7lAsrkodve2Q6jaVqzU
|
|
||||||
dqixxBAdcFQ89rq0z3He15ucgu/Vm4345LSm8JpcAMqupX0Ikbnsqg==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age19ht95nv8uhz2shjmakeut8mc3l5spvrcxs3thhe85an7u02r6sysyv457n
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5S0o4SlR2aHIrRXlLeHdp
|
|
||||||
MmtEK1BxS2hlVlBVNHYvZ1d0QldDWERiQVVVCk04ZWNjNnZGQkVJL0hscDlGa0cv
|
|
||||||
SlpJNEcwZVhrNk9FcUo3VVQxQSt1Tm8KLS0tIGJuWXpZZFJUaUdtSG54UTFoSzYv
|
|
||||||
MFRxaFhubkJhcTFoV1pzMGc1ZGhEam8KOv07PvhyoG+LIll5pRbyOcMPZLL6GKn/
|
|
||||||
KGnwao8QgHy9FLesA9q9LEYy1IQ8U76l4TtLDKE5+mOqDSBJCFo8cw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1mhaze5tgvc9lwjpml6dnp3xc292337wgm6376yh6tq4fn492ndjq9h23dq
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0eFluenpiUGFDL0o3ZW5P
|
|
||||||
SDdUVGFlSk5kaldzeTd3Y1h0ejAyTCtNMXhJCjdudHBzdnZBRkxuWVc4YkRFaThN
|
|
||||||
dDQ0MThTcENaNEN3YlpqSDNBQkJhQUUKLS0tIFpoNCt5aktyVjFrUmJSQkM3UDBB
|
|
||||||
MkFVQ1VFcjVCbkNWZmdRN085YUo4WkUKJaoop5n/3Rb4myk5cl1kcFwp3RCEHvaW
|
|
||||||
283WmSGgy8G2MzhtAlUkH1vTshz2bJUpjkCtHJ0PxYrzAk/gxBnDaA==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age15me6sx0f8r58xh9v7aqrj6e99n7eu555jkpw422txpkqt4r02v0qpahlyq
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnWnpoRGVUQmNGRWVhRzRq
|
|
||||||
MEpsSjkvZlJsc3VIMkNnTFEvcWlwRG1BdlRzClUydU5rbHJ3TlFvRWhXSjBVUWx2
|
|
||||||
Y0VvQmJSbDNObWY4TkY2QXJyNlgyN3MKLS0tIEdtcjhmSVZ1ZXMzNC95cXFWdjNM
|
|
||||||
RUp0enNxcnpiNzlWQmh5TXRuOFFPSmMKv7hPMN3kww+zyrKLZ+OgF74S2IXfgeG6
|
|
||||||
Z2M6dvEfPKTyCgHoeo58OJKKGFqC7AmnUBWHhReZfWBRwyK4ES1DAg==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1q3hu6eh3mt4saey7dc3yu04s7knnk2ygpm9xt6mg6rqtem6l0uyq4wzul5
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpeEV2eU9BZUZkL2xLaFI0
|
|
||||||
V2E5NlhJeGhoODVjc3RsbU95UnNscnhPbVVJCnUrdmc3UERsWmgwWG16Qm9PMzhU
|
|
||||||
NGpFRVlZREtiWnFEZnpnL3pCT3k3NlkKLS0tIEVvRStnRWFjQnZFUCtYTzBxd01V
|
|
||||||
N3Z5M1FMclduSStKb1EzbEp5b2V6VHcKtsXI/VWWARdH+FDHB+R1YKyJqN4g0ahp
|
|
||||||
AGdj6KLPZZDjbGpGITPy+uA8929//X5ZxKOjpccgCJqXFLUjh577oQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1tczst3x7thwtcz4vce4rg6kmlsszzm6j45kn2nv860z9wa93a92s05ppmc
|
|
||||||
lastmodified: "2026-07-23T21:15:41Z"
|
|
||||||
mac: ENC[AES256_GCM,data:qFhnPra6IE3wyKQ4WKweON0S0YtD5I0adGZVfA0m6BVilN6bX5oC/1j5NK2oHrsz920hSl0SOF8LrpqOrUyGjSRkPsN4kq8qr9bJcrX4URiktP0oRden5LLt6hf+ZRP7WmRXFqixPkPHJnZIoAvkNnTFce7cDq5NEAHkKUEKG7k=,iv:nyblUDGeu3TUfFivYylOn3C/HITj99qiPI2+mh8AGh4=,tag:FrtRzSCylC4wlIoqZdfx7w==,type:str]
|
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
version: 3.13.2
|
version: 3.13.1
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
wifi-password: ENC[AES256_GCM,data:SZQPtU6PYHbf9o83wq3KTupx,iv:FxO68Pn/+N58r/OPLfkAMYPFpP8TYxszMniFd/01E38=,tag:jwxaY6zDEcO5r9OWSfvUyw==,type:str]
|
|
||||||
sops:
|
|
||||||
age:
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvMGJzZjVoandxc1FCaDR6
|
|
||||||
Z0hKTVFYRU9JMzA5djk1V3VBZjBETDJDS2xBCnZaRTkzTGg5OVBwckw0cXA4bzFi
|
|
||||||
OHJpdHFwRFBGbXZCbzUrKytGTGx2S2sKLS0tIGNSdW1rQmdOL2lpek91N051L3gz
|
|
||||||
K0duYnF5RlE3cmNXUVN3V0hLM0lUWlUKRYmIfAAfGCJOFwlimYk6bAvIKLokKufR
|
|
||||||
OYnDcUZuPlj9cx/FJNC+7/K7WWmfrpEEeNOVzv7JdTDLjWBslTCWGQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGNHJPODlpaHpIRFNVUlhF
|
|
||||||
WTJsdjB6Rmd6RlEvdDVXckduUkwxUGhVOFF3CkVJMXYzUVRkVjV0VVkzbGc1UlUr
|
|
||||||
VVVXU0wwYkpzU2pZdmlpaVB3enZ1djAKLS0tIGdkeFVrYkdzM1ZNdElJamhpajFp
|
|
||||||
YVZuVCtqUW95R3VHTlBadVoyZUZvNU0K/AtAp1bZ3QXKcVChHfMZVI9AHzTktXIB
|
|
||||||
JNAPKJAxQycMOYPNFklyslOj+oKmvKm5AdXx4XPYW5qOK4k2HMDIQw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age190htw7prp4vln076dxjx3gxxaq06h0zl0te7cqgpx79vl3lhkaes8suy05
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0a0dPSmdFbFRjQk1OWWNo
|
|
||||||
Q09VSHhmNlUrVjVXQzcrdi9hVklnRjliWG5vCjJ3bVRUNWdPV3d6STEzcjFxZVQr
|
|
||||||
MXZvdEVOVTlkWEVVNFV5UkUzWThSc2MKLS0tIGIwR1dNMnI3dlIxQ1d6NjhxZ3Vk
|
|
||||||
a1N5aHQvUU5PSEgwVlprcUNEeVorVGsKmmdhYnpOqk3x+5P0h4AfGJco1PGLmAtS
|
|
||||||
67hvtBQo1KSTwFyaYQUByfiTwIeMSu50SxvLi56ogT24CNADW6FjUQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1ehkswwz2pqaz4svzh7ela5tdnssl8kn6d4vwwxd6zwg8exfpd43syyrrjp
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTTXYwS0dXUEI2WjRhZHNp
|
|
||||||
Qzd5STdlWUNFTG04UEdySUhSODF2NklvNVhrCkxFd1pVSjRmb0hPaFpyWCtoQi9K
|
|
||||||
eHVBcUU0amt3K1BONnV5Vnc2Z0JoazQKLS0tIGFrL0I5SjJYYThUS0Y0VVlraW9x
|
|
||||||
Z1VLT1hpNWswd3FsWWVGRTkrNkJOZG8KY74xa+6Pfc9lpupTodTBa1hqIrotE2Gu
|
|
||||||
H5+0g5D9838zxf/p7pzxh7rE7eo2gd9JT9bW2I4Pwn257Omblsyv1A==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age17pwyghxr6lq06fw46gwqzhc9ut4paz28rpwx5pmv3cxwak6rgyjsw7lk3w
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpUExNcmw2S3hSKytvMVQr
|
|
||||||
ZGJkVStyVjgvTURPSTRyMXZkdjdpRnhQZFV3CkVtQzBsUjFlRjNsc2dwU0R4UnlL
|
|
||||||
YkhtZWVNSW5zWkhJM0NwOUpkVlJGQVUKLS0tIEJiTWxQT0dPeUI0TTIvRys0Sk9s
|
|
||||||
RDFmcjZ6UGkrbWlFb1h0bmdRUlQwT28KK5vH6Svg8lFUKqXSzweb9405B0Z91TaW
|
|
||||||
65G51im23qSOeD+NLBHP+nqIwYrjDlODrgOeW9Up/9rE5rLgFfjoOw==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1mhaze5tgvc9lwjpml6dnp3xc292337wgm6376yh6tq4fn492ndjq9h23dq
|
|
||||||
lastmodified: "2026-07-22T01:15:21Z"
|
|
||||||
mac: ENC[AES256_GCM,data:dC/oIqMUHkOh3AocOwP7Gc6XGH3L+nTqJfhFNts1DNbRXsopNIxVBtIz2pEhwnWSQrqPisDLmPHFBwRpGVn01u8w8IU1FKbAKC0J2nJXF8ozpInbjzDOmehqPWZG7yaKoq8cwAnp5XOk+IVO4l6tPxLxkExU5fT2ALuMq+sgOko=,iv:jaVyArpf6zMCFa6J9X1aQMGrmFq+W2CPZdWO6vVW68c=,tag:S+qF8/FkgHc4uW0e4ICmSQ==,type:str]
|
|
||||||
unencrypted_suffix: _unencrypted
|
|
||||||
version: 3.13.2
|
|
||||||
+15
-33
@@ -4,49 +4,31 @@ sops:
|
|||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYcFFSVFVxOWp2dCtEVzg4
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPWE1HTUhiSUp5ZEUwWEpI
|
||||||
VFF3Mk94Q0xVSDZNL1lBSXBKNzVXaU05TnlzCnhueTd1ek1BZ1JpZUMwM3ZBUHFh
|
bGpkZlBIMUo5ZlYrQ09SN3Q1a0ZkQ0ZnOEhVCnJPNEZQenVWWGZiODlzQzNEc1Zq
|
||||||
UVFKZjduZUpUejJ0enBXK0pwMjZKSDQKLS0tIHJ1Mi9OQThrWndiWFE3WTRJREJN
|
c3l4OWZJTElJc2Y2UE15OGtEUzhyY1EKLS0tIHNJUStyWnlQWjZBbEZjQ3UwdUpz
|
||||||
Z2E5WTI5dzB3WDFtaFRoUERZa3N6TDQKy2FK57wDNLSPl2/L/FUBUP8rJZuAOqAR
|
ZndoUDR6bisrNGJCUHk3TGI4bTZaMFUK87fFsm9ne9s+PK2pcwtrDjqyGBss2r2E
|
||||||
I0hBe4epAKL28PlJ4M9P77qqg893beZK3UIJQ5O6d5fGPMNTJiIzAg==
|
8lhqoeiKZ2j96z8kP/7ChzovwTCmqdcmAQuyNQD+ZAFijseipSvfbQ==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoaEZ5clpxV0ZQWmhSckxY
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUYi9SRFFGV3Z6cFd2Znk5
|
||||||
a3I5djhISUphMmV1RU9DREJsZFpDdktyaUJzClBQSUNvN054aXp4SzNkMmNqNVhP
|
b2FLbWtzTllJMDBUaGk0NTViOTNBa2hQclVZClZHKzNhbGVjQUJhWkFWdTFBMG5a
|
||||||
QUdnUEppMmdENzUraHAyRVJLWm95V00KLS0tIHZuVXh4SkdhVU05YzV6VlI2c2RS
|
cUFJdUdyVG5HQXJRRnJId3hqRTN2cXMKLS0tIEFMRjh3WE1ON0U2TTNTZ3hxMTR4
|
||||||
SWttRW9nS29MM0lJT2x0QS9rVU4yb2cKYZ1gts1qRFdod8PFWl6Tg0Ry0sXhKMrP
|
ZGRlemlIbDZKeExmVHROc3Eyak5DdzQKaLwIVDi6BN4cxpVxJoqTYvJETPOp4thc
|
||||||
VwttGQj0iDg6UudB+qa2ngFgAc0C++OJebuLMFnK1K+PvUpfw2nRQg==
|
l9uVMvIGuEsEZgDsvShw1dYLljd+uGy/A+dXbcxIUCP/mmPkwmd1Pw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age120le4a5l8dh3lyfgvmj3d9ksmej6ajs5mer5y7r0vfg3x9fn69dqf8xgzu
|
recipient: age120le4a5l8dh3lyfgvmj3d9ksmej6ajs5mer5y7r0vfg3x9fn69dqf8xgzu
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKNXBOeS9od1lBd0VGSDRX
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArOWovSW9DeFpxL0VDUDQ3
|
||||||
RE5BM3g0Zk53ZzBhdW9XUFhscmpHdEV1aG1vCnU0OGZZdENBdHd0Tzd5b05BR3RR
|
SHUwTzJVZUtPV01ZRkdCUXZGL2lTRCtCNFNnCjBSNExqRW5mTEN5SFVucHJHSzZt
|
||||||
ZGNZRlVSVk41YzgyRnlHK1NUaXhqZVUKLS0tIGFKcWF3VGdibldnMndmSis5Skc5
|
cDlNc3BjY3M1c1k1Z2tkVEg4R1pacGsKLS0tIEFWbHNKZW0vbVh1Y2VhQW93OUwx
|
||||||
aEZJVGh1UDFVUldrRndVZURGbVJMUXMKia/z+zwpfcaILCt4fpy7wsR0QkFKf07d
|
MWV0eW9sOXdQd0l2ZjlWOEVVc1dwcTgK2s4p9xoNkawH2OkGsl80bNIo3ad5vn4W
|
||||||
QwkV+gEfM3EydhcA7invDieIKbcOUoKKgOmf2sXMgW07CsjG1QXk6w==
|
Z2w+jwppSoUmbQnD3WFbLmSSxmuobmU8HILwElv6SZu+KE3aspF6XA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1xjst4frdh0th6q8m7p7u9g5af7ty5jqeum0p6z8a52a9q7st7ewqw8yl9j
|
recipient: age1xjst4frdh0th6q8m7p7u9g5af7ty5jqeum0p6z8a52a9q7st7ewqw8yl9j
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpcTJHRy9CZlVWcERMRzlL
|
|
||||||
MVFyVHlRT1hnQTBsSlBYOWtoSzVyejJIM0RzCmFKM3J5cE1BejhHYVl0VzNTSXdj
|
|
||||||
dENsTCtJT0JFT3I0eGJtOFMvdjFmTm8KLS0tIE1zSmhBMjNnelQrdkJ3eVR6SFo1
|
|
||||||
QXJtMVRuS1daOU53dmxGdFIwN0ZOVW8KS0XIDkbp+1W+wUDHkaKXZ84QpcO0zK54
|
|
||||||
FrMzkChowtm/a+RSLVadBGDQv2R4gV89NArP/YAEQa7GK9aHvDlH/g==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1tzsrtwd3p3lrr9g7nv3z5nvmzsz54t2uc6tfqwutp6usmav83s2sck25cc
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2bnJZUFVVRTBrZExoSWJV
|
|
||||||
NDI2WUluaWJHVVdiR1JBUjhJT0owaTNoL1JBCjh2Y3ZvZXYrSE4raGtrcXVZai9L
|
|
||||||
cUFERVRvMEZPaC9oa01NSHR1ejBIdk0KLS0tIG5ZNmZqZWZxKzd4c2pYOEhwa0li
|
|
||||||
NDRQV1hDZ3ZwZksyTUtjcEVGWVpFMTgKmpLn5Ov03MKITQ7QihjK4GsYlHQiAFAc
|
|
||||||
pI7z78qQv34e7DLLJotxbeWYyX/IOe+DvUm9T8NGY4ujI2y3HjXPlQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age15me6sx0f8r58xh9v7aqrj6e99n7eu555jkpw422txpkqt4r02v0qpahlyq
|
|
||||||
lastmodified: "2026-07-19T23:30:21Z"
|
lastmodified: "2026-07-19T23:30:21Z"
|
||||||
mac: ENC[AES256_GCM,data:kLGE2xawQT7mx+sfw68hmGk5nCEGiEjZrqTEl9B1dtQmTrMwmoVr/1RISi4LfJrwxy31mDgff4lcIL4wIJuM373uk3X8j4RNyYQNTfKEkORT6r8NHeepNs267O77pKGd7OmcM4MT/BqOnB8ELS7Wlf2ect7CAlvUUVyc8icxgZE=,iv:EYLDsHYHZ1XOQXafOTqHHWpk/OBNq/R6IJnOBYV33E4=,tag:thxrCPC5oGvDjhK7Dz87YA==,type:str]
|
mac: ENC[AES256_GCM,data:kLGE2xawQT7mx+sfw68hmGk5nCEGiEjZrqTEl9B1dtQmTrMwmoVr/1RISi4LfJrwxy31mDgff4lcIL4wIJuM373uk3X8j4RNyYQNTfKEkORT6r8NHeepNs267O77pKGd7OmcM4MT/BqOnB8ELS7Wlf2ect7CAlvUUVyc8icxgZE=,iv:EYLDsHYHZ1XOQXafOTqHHWpk/OBNq/R6IJnOBYV33E4=,tag:thxrCPC5oGvDjhK7Dz87YA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
|
|||||||
+20
-38
@@ -3,58 +3,40 @@ sops:
|
|||||||
age:
|
age:
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwbWFOSlB0ckJFc2hiQTlh
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaYU9HR0lETDhkYXR5NWlj
|
||||||
eEo1NzZSTWovRXNnTGJDWTVZRUsvbjQ2NGx3CithSDVSbFpCYW9KQjJndzR3aVlL
|
L0diSE5JQU9KWjRoU3hrVjdqZ2tPNUtOdFZvCjdUVGNFbDVYa3pVSC9ZWVNORER4
|
||||||
MHBDRmQxSEpTZ1FsRXFWQlE2aFc1VGsKLS0tIDc5bmI4WWtvcStZVk5WNDhva1dk
|
QWozUlhoSEtjTk9IRlM3VkZoYlc1RTAKLS0tIDlHS01WVWlOMWFEQU5GTVRLZVhV
|
||||||
cEVKM2ZCTGdLM2NEbmRudk1SdDNzMTAKB6QCrxxNzazSbif28j6RNIfdfUTKxklM
|
VTcwekhrRHB3SlVYT2MzOW5GbE52dkkKKCWehPhpdGapdyzpll20NJUcZwvW/7X8
|
||||||
Xip5xoLjTRNOjq561OS34gaMA1GE27YQqsqtgiU15i7SZ7IGefWpZQ==
|
KQ1EqAgI2fewnbwuIDYCleN0b0SLJNUeSV/tFKDDoTMnHWCdeD4ECg==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
recipient: age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2MDVmZGp0bEpwZWRLZFFM
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4YndybFBTQ2p4SGZ4SDJs
|
||||||
ckY1eERsSHFuMnhPK3J0eFRLY3lHYnlEK0NnCmNhQjlnUTNFOVF0UVBLendHYmdx
|
a2p0eFRQOVVWcGd2a25ESW9ESGx1RWw0Zno4CnhpVVh3cGI5UjY0YmFINFFPMTh4
|
||||||
aXVIa2hmNENUNGdZL2FmbTlNekd5NUkKLS0tIDdwbmFGZVFlaG94UEgvOUNxdVlz
|
b1B5SjJ3NTNvUE1QUmJjVFozY1dYS2MKLS0tIC9HZGNpOFZhZGNFZGt5blJuZXVV
|
||||||
VHJZenplYVVPa2NUSUFWeEN5bGQ1MkUKT5CqEiCN61aOpvMlBp0effS3bh345E7u
|
SXpkRzV4d2ppV3ZQZSt1dmxYNGVFMUEKmSe9dkrmkND81Hw2/ATAmFvcmhk1tUC1
|
||||||
wfxrMwMhrEzfnmpkvcafLCn9PMicqLuz48J9ipF5IHLf7MIvbT31ow==
|
LxxBw54IVHUqwYKgRYUYRNu+pykDT5OnFDPiskd49Xso99LY87PyiA==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ll6hj5ggruetgjwjfnplpn5xtq35uhlcdflksx3xmnjm6s3uad9sz70jkf
|
recipient: age1ll6hj5ggruetgjwjfnplpn5xtq35uhlcdflksx3xmnjm6s3uad9sz70jkf
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPS05tWG41Z3BwREhxUVRY
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLd1FSSHVTSGdHWm8wMVQz
|
||||||
ZVBMQjhxRDNwUDBjVE1jNnlVOWZtcE81Smk0CnlHaEl2OTd2UnJhWmlHNmY2V3NQ
|
d0dlOEsxeGwxdHU0eUlFSUxka0ZmcVpnOTM0CndYNUVjVy92QVhNY2orQmpSQjYy
|
||||||
SUJKakZOZkdXeFhnZTF6dmxzTGFzZjAKLS0tIFhwKy84Z0l3cEhSQ0NBbVJIKytM
|
VS9KM0NUTXhuM0lCSDBZMWtISEdtWTgKLS0tIExTL2wvS3FEdVViUmRYZEFsR3R0
|
||||||
QzhxbTY0SkdkOWN5VDU2d0dTMzlGN00KtgobQ2hsilZnvoq/BesaM/RcYbVNHxSh
|
YTJFM200RjF6MjNxOFA4eHRpWVhtRkUKOqBIT445HnPXrrH/qV6FIuAhAuJmSL6V
|
||||||
vEA80nUhyEdRim8WP/yu5KrgHsEVQ9ryclU4B49F1pINdAVaGRo9LA==
|
+PQopM/m3PAnK5m5Mu3cfjYfDiB8+GWTABhljfT+GbcoK7CqWLehrw==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age1ukpqxzl44mnjpy5r96sfuc5sqzm47u4k8ujjh5qdgy6jvl9uqgpspymqfk
|
recipient: age1ukpqxzl44mnjpy5r96sfuc5sqzm47u4k8ujjh5qdgy6jvl9uqgpspymqfk
|
||||||
- enc: |
|
- enc: |
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5NUxUMHdrbzl0OEl2RXoy
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvVXF4a2NmaW15R2VXS3FP
|
||||||
S1ovL05lTXhSNXBXZEtWU2hsdXhBZHlqc21rCnY0a1Z4QXZnTnJyTDR0WVgyYk5m
|
SDVPUGpUWTlIWjl2N2N2SXR1UlRkdllWNVRJClZDbVo4dUhrZytqZkMrYWtpOEZx
|
||||||
cHJJZkhoM2lNaG51TmY3K0hZeXZKM1kKLS0tIGU2MmV1TTFTY3QyQVNEb2ZxYmlU
|
c0dSZGViN04zQ1B1WEZEWm1QM1lsejQKLS0tIE0wa0k5Rm1xZmw3OHFESkVXc25j
|
||||||
c1doTkVLZEhKeDJTWmtLWjIrclRUenMKRem1nKPK/ghuipzOLTf6V3Ke5LK5S6LW
|
NlFpYTJSckQ4MVlZQ01reDlkaWY3TkEKsmQlreRhRAjVZ/q5x52FNATDF2sLhbHo
|
||||||
5ExYf7y++FC90IqkAqeIA67l2KihcPE0DqnBmwrDlNgL5tXyhYV39g==
|
djOZigZx2rs6shqQ6It/XRJ8CiPaXZBPOt529Gwmplu+hWlAU1+l2Q==
|
||||||
-----END AGE ENCRYPTED FILE-----
|
-----END AGE ENCRYPTED FILE-----
|
||||||
recipient: age15kh7akxlx7zn00tey79rq2g8lgs4j5y77rcnyfxrxap8ckfu0a9sqvtdhh
|
recipient: age15kh7akxlx7zn00tey79rq2g8lgs4j5y77rcnyfxrxap8ckfu0a9sqvtdhh
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvZkxwdUhOU3diRVFjSkhH
|
|
||||||
SmFDbjYzblFUZ2NLRmpiVGxuUGVrWC9BWERZCkl6MVJ0M3dvNGU0UmQ5Nm1VWmRm
|
|
||||||
ckVjSENWb05qMWtLQzNJd0tUWmgydWsKLS0tICtydnQyUXluWmxwWVpQWVRqOW9L
|
|
||||||
VjhoelJCZjM4aHh4UG0wcUJMSVFGbncK6B1Rou2axRoRQNXudD6BVq5buz9MJapB
|
|
||||||
vYMEhQPVCU2SinltqIrgd00qjgE7+oPnGr5pTFSQisJLlN3X1LUbhQ==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1rf4kj99wuq59k7w8ar326djmgmpl9hcwlnuag07f8gauq8c3y5mqne87s4
|
|
||||||
- enc: |
|
|
||||||
-----BEGIN AGE ENCRYPTED FILE-----
|
|
||||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvV0E2MnpDZzNkbC9DNGZ4
|
|
||||||
U1JLQ3J3ZHo2cEpraFlSdTA0V01wdlNaMkE0CjQ0SDBXT2tYU2VmZjNja0M0VG5K
|
|
||||||
Z1FkVWhYODVDNkhBR0o1NW43SjZRZW8KLS0tIEtrMFI0YjZIQzd5bHVMSmRSWi83
|
|
||||||
U20zMkg2UWpEclMxalNhMThlWXdhaDQKX6qsiRZqlFVhq5lP5B/XVqStq8Impvx9
|
|
||||||
MTmVRuMzFQ5qTs4BjhIqO25Ez+CI2GAq2K10prdSEin69gSTxntkIg==
|
|
||||||
-----END AGE ENCRYPTED FILE-----
|
|
||||||
recipient: age1jc6wx33hdhgwhk6nzy5rr8fkgmqxk9um639tk5h632nqfyaw8czskdptj9
|
|
||||||
lastmodified: "2026-07-19T02:30:40Z"
|
lastmodified: "2026-07-19T02:30:40Z"
|
||||||
mac: ENC[AES256_GCM,data:rKHZjU/MH08ASTlu32HZO9uWmsBYuMCEC6M8gwVhzuWvmablnP05tS2z13XfaWaCEUXk6kmGJKuU0zu5+IKVZgamCF6DAMtxQb6bVCaLsoAm/GSqWQ5VI9eHqgnSSdN/o3ul/33Rf8iBQo4aw8FFAmDVuNz8bfAn0QefFTj0ByI=,iv:JD2gtqRinOY77etg6PUmZNovkYl1Q3F6ZvRi4x7RznQ=,tag:/5IMpWKRVt+l1luCTQE0BA==,type:str]
|
mac: ENC[AES256_GCM,data:rKHZjU/MH08ASTlu32HZO9uWmsBYuMCEC6M8gwVhzuWvmablnP05tS2z13XfaWaCEUXk6kmGJKuU0zu5+IKVZgamCF6DAMtxQb6bVCaLsoAm/GSqWQ5VI9eHqgnSSdN/o3ul/33Rf8iBQo4aw8FFAmDVuNz8bfAn0QefFTj0ByI=,iv:JD2gtqRinOY77etg6PUmZNovkYl1Q3F6ZvRi4x7RznQ=,tag:/5IMpWKRVt+l1luCTQE0BA==,type:str]
|
||||||
unencrypted_suffix: _unencrypted
|
unencrypted_suffix: _unencrypted
|
||||||
|
|||||||
@@ -45,20 +45,6 @@
|
|||||||
# the installer image's nixos/root users.
|
# the installer image's nixos/root users.
|
||||||
adminSshKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCq/Q5LvIXlZwO2kdeAN5nLGZ59nZB7JHYMEszHxmNtGMzv1lM31jiPNsr0z2EKVZhE7OOfa2IF9rhWYD7JUA9G0yzdZ4WTXFNGVVOJoOVH6vAF3XCxoVilOEwTc7h2Wiy+rzd0B28/3spffzQQWJhY6GRQVa8j+6xAGF60Fcvl1vLosYT9Bn2ZbK4TCWOwAn2jqXIieGpZdn/UNZbGOeKRiCvhktDfMAzuQzN/9jMu/oF4pkPn2X1UrsQdNlvp0Ci8md612MozIpncQJyAF1ADhunr3sMx0isUXiqD29R5DS4TftpekqLNLak+zcxFa8N7DcRNp3DcKfJvyTkwQrR4r+b7lFLYOLHLagSso9CzeW/paAS2q9I5SBm/2DtE1diLLg2jZikYcstsu/G5RgvbzbKqjiaMwTdXC3AMvDxQrs7U5pDRZFzoofG3cpODbTm+uy3m0kP70z0M1K45UbDG0p+itnTu9x40JbQEgefbx38AItNvAIx1A8HO4I1VX28= wayne@stream";
|
adminSshKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCq/Q5LvIXlZwO2kdeAN5nLGZ59nZB7JHYMEszHxmNtGMzv1lM31jiPNsr0z2EKVZhE7OOfa2IF9rhWYD7JUA9G0yzdZ4WTXFNGVVOJoOVH6vAF3XCxoVilOEwTc7h2Wiy+rzd0B28/3spffzQQWJhY6GRQVa8j+6xAGF60Fcvl1vLosYT9Bn2ZbK4TCWOwAn2jqXIieGpZdn/UNZbGOeKRiCvhktDfMAzuQzN/9jMu/oF4pkPn2X1UrsQdNlvp0Ci8md612MozIpncQJyAF1ADhunr3sMx0isUXiqD29R5DS4TftpekqLNLak+zcxFa8N7DcRNp3DcKfJvyTkwQrR4r+b7lFLYOLHLagSso9CzeW/paAS2q9I5SBm/2DtE1diLLg2jZikYcstsu/G5RgvbzbKqjiaMwTdXC3AMvDxQrs7U5pDRZFzoofG3cpODbTm+uy3m0kP70z0M1K45UbDG0p+itnTu9x40JbQEgefbx38AItNvAIx1A8HO4I1VX28= wayne@stream";
|
||||||
|
|
||||||
# Prestaged wifi SSID for the gui host's NetworkManager profile
|
|
||||||
# (modules/networking/wifi.nix). The password is not here -- it's
|
|
||||||
# sops-encrypted in secrets/gui.yaml (wifi-password) instead, since this
|
|
||||||
# file isn't a secret store.
|
|
||||||
wifiSsid = "nbn-fttp-net-5G";
|
|
||||||
|
|
||||||
# Bare-metal gui host's two disks for a ZFS RAID0 (striped) root pool
|
|
||||||
# (modules/disko/baremetal.nix). Only used transiently at disko-format
|
|
||||||
# time (partitioning); the resulting fileSystems/zpool import reference
|
|
||||||
# by-partlabel/by-id paths afterward regardless, same as
|
|
||||||
# modules/disko/proxmox.nix's own plain "/dev/sda".
|
|
||||||
guiRootDisk1 = "/dev/sda";
|
|
||||||
guiRootDisk2 = "/dev/sdb";
|
|
||||||
|
|
||||||
# System
|
# System
|
||||||
timeZone = "Australia/Brisbane";
|
timeZone = "Australia/Brisbane";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user