#!/bin/sh
# edge-reimage.sh — перезалить boot-диск edge-VM на новый golden-образ НА НОДЕ, НЕ трогая
# VM-определение (edge_id, MAC/IP, guest-метаданные, правила сохраняются).
#
# Один проход на каждый эдж: запомнить состояние → стоп → подмена диска (с откатом) →
# восстановить состояние. Запускать НА НОДЕ-владельце пула (шелл нод — csh, слать через sh):
#     scp scripts/edge-reimage.sh root@<node>:/tmp/
#     ssh root@<node> 'env TARGET_IMG=vStack-edge-13.0-v8 DRY_RUN=0 sh /tmp/edge-reimage.sh'
#
# Отбор: без аргументов — все эджи на ноде, чей origin boot-диска НЕ TARGET_IMG (надёжно,
# идемпотентно). С аргументами — ровно эти vm_id (origin-фильтр игнорится; для канарейки).
#
# Почему identity выживает: UUID и /usr/local/etc/vstack-edge.config живут в guest-метаданных
# (operator-script, every-boot хук); на свежем диске их нет → operator-script пересоздаёт их с
# правильным edge_id. Правила НЕ удаляются → диспетчер зальёт их на bootstrap.
#
# Состояние берётся из `vstack-vm vm list` STATUS (Started/Suspended/Offline; snapshot.bvmss
# есть и у Started — НЕ признак suspend). Восстановление (RESTORE=1): Started → `vstack-vm vm
# start --id`; Suspended/Offline/неизвестно → оставить stopped (resume на новый диск бессмыслен).
# start --id поднимает только зарегистрированные в контроллере; незарегистрированные
# («VM does not exist») поднимать через диспетчер (edge-start/vms-start).
#
# ВНИМАНИЕ: пароль edge_ctrl зашит в operator-script (guest) при создании эджа. Если он неверный
# (баг деплоя AMQP_EDGE_CTRL_PWD), после перезаливки эдж не сделает bootstrap. Правка guest этим
# скриптом НЕ выполняется — чинить пароль отдельно (локальный /pools/<pool>/.vm_V4/<pad>/guest.json
# или vm.machine.guest в БД) ДО запуска.
#
# ENV:
#   TARGET_IMG  имя нового golden-образа. По умолч. vStack-edge-13.0-v8
#   DRY_RUN     1 (по умолч.) — только показать; 0 — реально менять
#   FORCE       0 (по умолч.) — уже-целевые пропускать; 1 — перезалить даже если origin==TARGET
#   RESTORE     1 (по умолч.) — вернуть прежнее состояние; 0 — оставить stopped
set -u
TARGET_IMG="${TARGET_IMG:-vStack-edge-13.0-v8}"
DRY_RUN="${DRY_RUN:-1}"
FORCE="${FORCE:-0}"
RESTORE="${RESTORE:-1}"
TS=$(date +%Y%m%d%H%M%S)

LIST=$(/usr/local/sbin/vstack-vm vm list 2>/dev/null)
state_of() { echo "$LIST" | awk -v id="$1" '$1==id{print $3; exit}'; }

# целевые vm_id: явные аргументы или дискавери по origin
if [ "$#" -gt 0 ]; then
    targets="$*"
else
    targets=$(zfs list -H -o name,origin -t volume 2>/dev/null \
        | awk '$1 ~ /\/\.vm_V4\/[0-9]+\/disk_000000000$/ && $2 ~ /\.vm_images\/vStack-edge-/' \
        | while read ds origin; do
            case "$origin" in */"$TARGET_IMG"@*) continue ;; esac
            expr "$(echo "$ds" | sed -E 's#.*/\.vm_V4/0*([0-9]+)/.*#\1#')" + 0
          done)
fi
[ -z "$targets" ] && { echo "нет эджей для перезаливки (origin != $TARGET_IMG)" >&2; exit 0; }

for vmid in $targets; do
    pad=$(printf '%09d' "$vmid")
    boot=$(zfs list -H -o name -t volume 2>/dev/null | grep -E "/\.vm_V4/$pad/disk_000000000$" | head -1)
    [ -z "$boot" ] && { echo "vm_id=$vmid: boot dataset not found on this node" >&2; continue; }
    pool=${boot%%/*}
    origin=$(zfs get -H -o value origin "$boot")
    case "$origin" in
        */"$TARGET_IMG"@*)
            if [ "$FORCE" != 1 ]; then
                echo "vm_id=$vmid: already on $TARGET_IMG, skip (FORCE=1 чтобы перезалить)" >&2; continue
            fi
            echo "vm_id=$vmid: already on $TARGET_IMG, FORCE — перезаливаю" >&2 ;;
    esac
    snap=$(zfs list -H -t snapshot -o name "$pool/.vm_images/$TARGET_IMG" 2>/dev/null | grep '@\.clone\.source_' | head -1)
    [ -z "$snap" ] && { echo "vm_id=$vmid: target snapshot ($pool/.vm_images/$TARGET_IMG@.clone.source_*) not found" >&2; continue; }

    st=$(state_of "$vmid"); [ -z "$st" ] && st=UNKNOWN
    volsize=$(zfs get -Hp -o value volsize "$boot")
    bak="$boot.pre-$TS"
    echo "vm_id=$vmid: state=$st  $origin -> $snap  (boot=$boot volsize=$volsize)" >&2

    if [ "$DRY_RUN" = 1 ]; then
        [ "$st" = Started ] || [ "$st" = Suspended ] && echo "  [dry-run] vstack-vm vm stop --id $vmid" >&2
        echo "  [dry-run] zfs rename $boot $bak" >&2
        echo "  [dry-run] zfs clone $snap $boot" >&2
        echo "  [dry-run] zfs set volsize=$volsize $boot ; zfs set volmode=dev $boot" >&2
        if [ "$RESTORE" = 1 ] && [ "$st" = Started ]; then
            echo "  [dry-run] restore: vstack-vm vm start --id $vmid" >&2
        else
            echo "  [dry-run] restore: оставляю stopped (state=$st)" >&2
        fi
        continue
    fi

    # стоп (если запущен/приостановлен)
    case "$st" in
        Started|Suspended)
            /usr/local/sbin/vstack-vm vm stop --id "$vmid" 2>&1 | sed 's/^/  stop: /' >&2 ;;
    esac
    if ps axww -o command | grep -q "[v]mc-bhyve: $vmid "; then
        echo "  STILL RUNNING после stop — пропускаю (не трогаю диск)" >&2; continue
    fi

    # подмена диска с откатом
    zfs rename "$boot" "$bak" || { echo "  rename failed, abort this vm" >&2; continue; }
    if zfs clone "$snap" "$boot"; then
        zfs set volsize="$volsize" "$boot"
        zfs set volmode=dev "$boot"
        echo "  reimaged OK (rollback backup: $bak)" >&2
    else
        echo "  clone failed — rolling back" >&2
        zfs rename "$bak" "$boot"
        continue
    fi

    # восстановление состояния
    if [ "$RESTORE" = 1 ] && [ "$st" = Started ]; then
        /usr/local/sbin/vstack-vm vm start --id "$vmid" 2>&1 | sed 's/^/  start: /' >&2
    else
        echo "  restore: оставляю stopped (state=$st)" >&2
    fi
done
