From 30a7220b2c3a29079371d931437ab114c4f57eda Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Tue, 13 Oct 2026 13:25:21 -0700 Subject: [PATCH] ops(esh-nas): infra-ops identity bootstrapped in CT 103 via pct from esh-pve-nas Prime's ruling 2026-10-13. esh-nas (10.0.50.50) had no infra-ops user, so key login was refused. The same steps as bootstrap-infra-ops-user.yaml (uid/gid 850, fleet key, NOPASSWD sudoers with log_output and the BULK_DATA carve-out, visudo-validated) ran inside the container from its host. Verified with a direct ssh infra-ops@10.0.50.50 and sudo -n whoami = root. --- playbooks/bootstrap-infra-ops-user.yaml | 3 ++- playbooks/esh-nas-infra-ops.yaml | 23 ++++++++++++++++ .../esh-nas-infra-ops/bootstrap-in-ct.sh | 27 +++++++++++++++++++ servers/esh-pve-nas/README.md | 2 +- 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 playbooks/esh-nas-infra-ops.yaml create mode 100644 scripts/oneoff/esh-nas-infra-ops/bootstrap-in-ct.sh diff --git a/playbooks/bootstrap-infra-ops-user.yaml b/playbooks/bootstrap-infra-ops-user.yaml index 7077653..e7c0d81 100644 --- a/playbooks/bootstrap-infra-ops-user.yaml +++ b/playbooks/bootstrap-infra-ops-user.yaml @@ -25,7 +25,8 @@ # esh-pve-nas) DO get infra-ops — done that day. # PVE ships without sudo: `apt-get install sudo` first. # Also granted by operator request: esh-docker-vm, -# esh-vm-db (2026-08-24) and vm-esh-nas (2026-09-27). +# esh-vm-db (2026-08-24), vm-esh-nas (2026-09-27) and esh-nas +# (CT 103, 2026-10-13, via pct from its host: playbooks/esh-nas-infra-ops.yaml). # - nh3-nas (Synology DSM) (no standard useradd / sudoers.d) # # Idempotent: re-running reconciles the key + sudoers without error. diff --git a/playbooks/esh-nas-infra-ops.yaml b/playbooks/esh-nas-infra-ops.yaml new file mode 100644 index 0000000..40494b5 --- /dev/null +++ b/playbooks/esh-nas-infra-ops.yaml @@ -0,0 +1,23 @@ +# Bootstrap the infra-ops identity INSIDE esh-nas (CT 103 on esh-pve-nas, 10.0.50.50), Prime 2026-10-13 ("set it up"). +# infra-ops could not log into the container (no user), so this runs on the HOST and reaches in with pct. +# scripts/elway infra-ops@10.0.50.55 --playbook playbooks/esh-nas-infra-ops.yaml +# The in-container steps are scripts/oneoff/esh-nas-infra-ops/bootstrap-in-ct.sh (= playbooks/bootstrap-infra-ops-user.yaml). +vars: + ctid: "103" +steps: + - name: Stage the bootstrap script on the host + upload: + src: scripts/oneoff/esh-nas-infra-ops/bootstrap-in-ct.sh + dest: /tmp/esh-nas-bootstrap-infra-ops.sh + mode: "0700" + - name: Push it into CT {{ ctid }} and run it as root there + sudo: true + shell: pct push {{ ctid }} /tmp/esh-nas-bootstrap-infra-ops.sh /root/bootstrap-infra-ops.sh --perms 0700 && pct exec {{ ctid }} -- bash /root/bootstrap-infra-ops.sh + - name: Remove the staged copies + sudo: true + shell: pct exec {{ ctid }} -- rm -f /root/bootstrap-infra-ops.sh && rm -f /tmp/esh-nas-bootstrap-infra-ops.sh +verify: + - name: The user and its sudoers file exist in CT {{ ctid }} + sudo: true + shell: pct exec {{ ctid }} -- sh -c 'id infra-ops && visudo -cf /etc/sudoers.d/infra-ops && grep -q NOPASSWD:ALL /etc/sudoers.d/infra-ops' + changed_when: "false" diff --git a/scripts/oneoff/esh-nas-infra-ops/bootstrap-in-ct.sh b/scripts/oneoff/esh-nas-infra-ops/bootstrap-in-ct.sh new file mode 100644 index 0000000..5f0cac5 --- /dev/null +++ b/scripts/oneoff/esh-nas-infra-ops/bootstrap-in-ct.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Run INSIDE esh-nas (CT 103 on esh-pve-nas) as root, via `pct exec 103`. The same steps as +# playbooks/bootstrap-infra-ops-user.yaml, for a container infra-ops cannot log into yet (Prime 2026-10-13: "set it up"). +set -euo pipefail +U=infra-ops; ID=850 +KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN+1HBwfXrkfTYWdcnWCjLJ6VLAGC87gxH5h5vKaaA3c infra-ops@pfi-fleet' +if ! id "$U" >/dev/null 2>&1; then + if ! getent passwd "$ID" >/dev/null && ! getent group "$ID" >/dev/null; then + groupadd -g "$ID" "$U"; useradd -m -s /bin/bash -u "$ID" -g "$ID" "$U" + else + echo "uid/gid $ID taken; letting the OS allocate"; useradd -m -s /bin/bash "$U" + fi + echo "created $U" +fi +chmod 0700 /home/$U +getent group docker >/dev/null && usermod -aG docker "$U" || echo "no docker group, skipped" +install -d -m 700 -o "$U" -g "$U" /home/$U/.ssh +printf '%s\n' "$KEY" > /home/$U/.ssh/authorized_keys +chown "$U:$U" /home/$U/.ssh/authorized_keys; chmod 600 /home/$U/.ssh/authorized_keys +f=/etc/sudoers.d/$U +printf 'Defaults:%s log_output\n%s ALL=(ALL) NOPASSWD:ALL\n' "$U" "$U" > "$f.tmp" +printf '%s\n' '# Bulk data movers: keep the audit line, do not record the bytes (eshpfi playbooks/sudoers-infra-ops-bulk-no-log-output.yaml)' \ + 'Cmnd_Alias BULK_DATA = /usr/bin/rsync, /usr/bin/tar, /usr/bin/cat, /usr/bin/dd, /usr/bin/zstd, /usr/bin/zcat, /usr/bin/gzip, /usr/bin/pv, /usr/bin/docker save *, /usr/bin/docker export *, /usr/bin/docker cp *' \ + 'Defaults!BULK_DATA !log_output' >> "$f.tmp" +chmod 440 "$f.tmp" +if visudo -cf "$f.tmp"; then mv "$f.tmp" "$f"; else rm -f "$f.tmp"; echo "VISUDO VALIDATION FAILED"; exit 1; fi +id "$U"; stat -c '%a %U %n' /home/$U /home/$U/.ssh /home/$U/.ssh/authorized_keys "$f" diff --git a/servers/esh-pve-nas/README.md b/servers/esh-pve-nas/README.md index 94e85d9..7840e4a 100644 --- a/servers/esh-pve-nas/README.md +++ b/servers/esh-pve-nas/README.md @@ -60,7 +60,7 @@ Media services on this box sit at `10.0.50.56` (Plex) and `10.0.50.57` (Jellyfin rebooted this node without accounting for it. Before any planned reboot of either node, either accept the read-only window on the survivor or set `pvecm expected 1` on it for the duration. Discovered 2026-08-18. -- ⚠ **CT 103 `esh-nas` (10.0.50.50) runs on THIS host and serves `hard` NFS.** +- ⚠ **CT 103 `esh-nas` (10.0.50.50) runs on THIS host and serves `hard` NFS.** Since 2026-10-13 it has the `infra-ops` identity (uid 850, NOPASSWD sudo, Prime's ruling): `ssh infra-ops@10.0.50.50` works directly. Before that, the only way in was `pct exec 103` from this host. Never reboot this host casually — quiesce the clients first. Measured from the server on 2026-08-18 (`ss -tn '( sport = :2049 )'` inside CT 103), there are **five** clients, not the two long documented here: