#!/usr/bin/env bash set -euC ################################################################################ ## Constants ## FIXME: There seems to be a problem with file upload where the task is ## registered to `node051` no matter what node we are actually uploading to? For ## now, let us just use `node051` everywhere. readonly node=node051 readonly tmpdir=/tmp/proxmox-remove-$RANDOM mkdir $tmpdir ################################################################################ ## Parse arguments api_url= username= password= vm_ids_or_names= help () { cat </dev/null; do sleep 1; done } release_lock () { rmdir "$tmpdir/lock-$1" } proxmox () { acquire_lock proxmox http \ --verify no \ --form \ "$@" \ "Cookie:PVEAuthCookie=$ticket" \ "CSRFPreventionToken:$csrf_token" release_lock proxmox } ## Way to inject different behaviour on unexpected status. # shellcheck disable=SC2317 default_proxmox_sync_unexpected_status_handler () { die "unexpected status: '%s'" "$1" } proxmox_sync_unexpected_status_handler=default_proxmox_sync_unexpected_status_handler ## Synchronous variant for when the `proxmox` function would just respond an ## UPID in the `data` JSON field. proxmox_sync () { local response upid status response=$(proxmox "$@") upid=$(echo "$response" | jq -r .data) while :; do response=$(proxmox GET "$api_url/nodes/$node/tasks/$upid/status") status=$(echo "$response" | jq -r .data.status) case $status in running) sleep 1 ;; stopped) break ;; *) "$proxmox_sync_unexpected_status_handler" "$status" ;; esac done } ################################################################################ ## Grab VM options ## ## Takes the name of the VM, grabs `.#vmOptions.` and gets the id from it. is_integer () { [ "$1" -eq "$1" ] 2>/dev/null } grab_vm_options () { local options if is_integer "$1"; then vm_id=$1 vm_name="#$1" else vm_name=$1 printf 'Grabing VM options for VM %s...\n' "$vm_name" options=$( nix eval \ --impure --raw --expr " builtins.toJSON (builtins.getFlake (builtins.toString ./.)).vmOptions.$vm_name " \ --log-format raw --quiet ) vm_id=$(echo "$options" | jq -r .vmId) printf 'done grabing VM options for VM %s. Got id: %d.\n' \ "$vm_name" "$vm_id" fi } ################################################################################ ## Stop VM stop_vm () { printf 'Stopping VM %s...\n' "$vm_name" proxmox_sync POST "$api_url/nodes/$node/qemu/$vm_id/status/stop" \ 'overrule-shutdown'==1 printf 'done stopping VM %s.\n' "$vm_name" } ################################################################################ ## Delete VM # shellcheck disable=SC2317 proxmox_sync_unexpected_status_handler_ignore_null () { case $1 in null) printf "Attempted to delete VM %s, but got 'null' status. Maybe the VM already does not exist?\n" \ "$vm_name" exit 0 ;; *) default_proxmox_sync_unexpected_status_handler "$1" ;; esac } delete_vm () { printf 'Deleting VM %s...\n' "$vm_name" proxmox_sync_unexpected_status_handler=proxmox_sync_unexpected_status_handler_ignore_null proxmox_sync DELETE "$api_url/nodes/$node/qemu/$vm_id" \ 'destroy-unreferenced-disks'==1 \ 'purge'==1 proxmox_sync_unexpected_status_handler=default_proxmox_sync_unexpected_status_handler printf 'done deleting VM %s.\n' "$vm_name" } ################################################################################ ## Main loop printf 'Removing VMs%s...\n' "$vm_ids_or_names" remove_vm () ( grab_vm_options "$1" stop_vm delete_vm ) for vm_id_or_name in $vm_ids_or_names; do remove_vm "$vm_id_or_name" & done nb_errors=0 while :; do wait -n && : case $? in 0) ;; 127) break ;; *) nb_errors=$((nb_errors + 1)) ;; esac done if [ "$nb_errors" != 0 ]; then die 'encountered %d errors while removing VMs%s.' "$nb_errors" "$vm_ids_or_names" fi printf 'done removing VMs%s.\n' "$vm_ids_or_names" ################################################################################ ## Cleanup rm -Rf $tmpdir exit 0