From 75d09d57e3ecc77a38bc284078c987900e762bdf Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Mon, 20 Jul 2026 13:22:08 +0000 Subject: [PATCH] Fix --allow-duplicate-host creating real duplicates in create-proxmox-resource.sh --allow-duplicate-host previously just skipped the existing-resource check entirely, so re-running e.g. --type lxc --host docker while an lxc-docker container already existed created a second container sharing the same hostname/identity instead of replacing it -- both then fight over DNS/DHCP for that hostname, and it's easy to end up testing the stale one without realizing. Now splits matches into "exact" (same --type as the one being created, e.g. another lxc-docker) and "cross-type" (a different platform sharing this host identity, e.g. a proxmox-docker VM alongside an lxc-docker container -- a deliberate, valid coexistence this script has never managed and still won't). Only an exact match is destroyed and replaced, after typing the hostname back to confirm; a cross-type match is always left untouched. Without --allow-duplicate-host, both cases still refuse to run exactly as before. Verified live against pve.sweet.home: correctly split VMID 103 (a stopped proxmox-docker VM, cross-type -- left untouched) from VMID 105 (the running lxc-docker container, exact-type -- flagged for destroy+replace), and confirmed the destroy prompt safely aborts on a non-matching confirmation, leaving both resources untouched. --- scripts/create-proxmox-resource.sh | 109 ++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/scripts/create-proxmox-resource.sh b/scripts/create-proxmox-resource.sh index 2f7a696..090ca0a 100755 --- a/scripts/create-proxmox-resource.sh +++ b/scripts/create-proxmox-resource.sh @@ -13,12 +13,21 @@ # refuses to run if the target VMID already exists on the node, or if # a VM/CT identified as --host already exists under any other VMID # (checked live against the node; --allow-duplicate-host overrides). +# - --allow-duplicate-host distinguishes an exact match (same --type +# *and* --host, e.g. re-running --type lxc --host docker while an +# lxc-docker container already exists -- almost always a redeploy of +# the same target to pick up a rebuilt image) from a cross-type match +# (a different platform sharing the same host identity, e.g. a +# proxmox-docker VM coexisting with lxc-docker). Only the exact match +# is destroyed and replaced, after typing the hostname back to +# confirm (outside --dry-run) -- a cross-type match is always left +# untouched, matching-or-not. # - --modify only ever touches a resource you name explicitly via # --vmid, shows exactly what will change first, and (outside # --dry-run) always requires typing that VMID back to confirm before # anything is sent to the node. There is no bulk/implicit modify. -# - Neither mode can start/stop/delete a resource. Not implemented on -# purpose -- ask before adding it. +# - Outside of --allow-duplicate-host's exact-match replace above, +# neither mode can start/stop/delete a resource. # # See --help for the full option list. set -euo pipefail @@ -64,7 +73,13 @@ Create mode (default): already exists on the node (checked live via qm/pct, not any file in this repo) -- otherwise refused, since it'd share that - host's hostName/hostId. + host's hostName/hostId. An existing resource + of this *same* --type (e.g. re-running --type + lxc --host docker over an existing lxc-docker) + is destroyed and replaced, after confirming -- + a different --type sharing the same --host + (e.g. a proxmox-docker VM) is always left + untouched. Modify mode (reconfigure an EXISTING resource -- requires --modify): --modify Switch to modify mode. @@ -304,12 +319,13 @@ fi # this script itself always uses unless --name is overridden) -- a guest # manually renamed on the node afterwards wouldn't match, but nothing here # creates guests that way. -if [[ "$allow_duplicate_host" -eq 1 ]]; then - echo - echo "--allow-duplicate-host: skipping the check for an existing '${host}' on ${node}." -elif [[ "$dry_run" -eq 1 ]]; then +if [[ "$dry_run" -eq 1 ]]; then echo echo "[dry-run] would check ${node} for an existing VM/CT identified as '${host}'" + if [[ "$allow_duplicate_host" -eq 1 ]]; then + echo "[dry-run] --allow-duplicate-host: an existing ${type} named '${host}' would be" \ + "destroyed and replaced; a different-type match would be left untouched" + fi else echo echo "==> Checking ${node} for an existing VM/CT identified as '${host}'..." @@ -334,17 +350,90 @@ REMOTE_SCRIPT echo "check entirely)." >&2 exit 1 fi + + # Split into "exact" (same resource kind as --type -- i.e. literally this + # same host+platform combo already exists, almost always a redeploy of + # the same target to test a rebuilt image) vs "cross-type" (a different + # platform sharing this host identity, e.g. a stopped proxmox-docker VM + # coexisting with an lxc-docker container -- a deliberate, valid setup + # this script has never managed and still won't). Read via a herestring + # (not a pipe) so the appends below survive outside the loop. + this_kind="$type" + exact_matches="" + cross_matches="" if [[ -n "$existing" ]]; then - echo "ERROR: '${host}' already exists on ${node}:" >&2 - echo "$existing" | while read -r kind id n; do + while read -r kind id n; do + [[ -z "$kind" ]] && continue + if [[ "$kind" == "$this_kind" ]]; then + exact_matches+="${kind} ${id} ${n}"$'\n' + else + cross_matches+="${kind} ${id} ${n}"$'\n' + fi + done <<<"$existing" + fi + + if [[ -n "$exact_matches" && "$allow_duplicate_host" -ne 1 ]]; then + echo "ERROR: '${host}' already exists on ${node} as this same resource type:" >&2 + echo "$exact_matches" | while read -r kind id n; do + [[ -z "$kind" ]] && continue + echo " - ${kind} VMID ${id} (${n})" >&2 + done + echo "Refusing to create a second ${this_kind} sharing this identity. Pass" >&2 + echo "--allow-duplicate-host to destroy it and create a fresh one in its place" >&2 + echo "(after confirming), or use --modify to reconfigure the existing one instead." >&2 + exit 1 + fi + + if [[ -n "$cross_matches" && "$allow_duplicate_host" -ne 1 ]]; then + echo "ERROR: '${host}' already exists on ${node} as a different resource type:" >&2 + echo "$cross_matches" | while read -r kind id n; do + [[ -z "$kind" ]] && continue echo " - ${kind} VMID ${id} (${n})" >&2 done echo "Refusing to create a second resource sharing this identity. Pass" >&2 echo "--allow-duplicate-host to create one anyway (it gets its own distinct" >&2 - echo "sops key and VMID -- the existing resource(s) above are left untouched)," >&2 + echo "sops key and VMID -- the existing resource above is left untouched)," >&2 echo "or use --modify to reconfigure the existing one instead." >&2 exit 1 fi + + if [[ -n "$cross_matches" ]]; then + echo "--allow-duplicate-host: '${host}' also exists on ${node} as a different resource" \ + "type -- leaving it untouched:" + echo "$cross_matches" | while read -r kind id n; do + [[ -z "$kind" ]] && continue + echo " - ${kind} VMID ${id} (${n})" + done + fi + + if [[ -n "$exact_matches" ]]; then + echo "--allow-duplicate-host: '${host}' already exists on ${node} as this same resource" \ + "type -- it will be destroyed and replaced:" + echo "$exact_matches" | while read -r kind id n; do + [[ -z "$kind" ]] && continue + echo " - ${kind} VMID ${id} (${n})" + done + echo + read -rp "Type the hostname (${host}) to confirm destroying the above and replacing it: " confirm + if [[ "$confirm" != "$host" ]]; then + echo "Cancelled -- input didn't match ${host}." >&2 + exit 1 + fi + echo "$exact_matches" | while read -r kind id n; do + [[ -z "$kind" ]] && continue + echo "==> Destroying ${kind} VMID ${id} (${n})..." + if [[ "$kind" == "vm" ]]; then + # qm destroy has no --force to stop-then-destroy in one call (pct's + # does) -- stop explicitly first if it's running. + if ssh "$ssh_target" "qm status ${id}" 2>/dev/null | grep -q running; then + ssh "$ssh_target" "qm stop ${id}" + fi + ssh "$ssh_target" "qm destroy ${id} --purge 1" + else + ssh "$ssh_target" "pct destroy ${id} --force 1 --purge 1" + fi + done + fi fi echo "Target: ${flake_target} (host=${host}, type=${type}) -> Proxmox resource '${name}'"