#!/usr/bin/env bash set -euC ################################################################################ ## Constants readonly api_url=https://192.168.51.81:8006/api2/json ## 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-provision-$RANDOM mkdir $tmpdir ################################################################################ ## Parse arguments username= password= sockets=1 cores=1 memory=2048 vm_ids= debug=false help () { cat <&2 '\033[37m'; printf >&2 "$@"; printf >&2 '\033[0m\n'; fi } while [ $# -gt 0 ]; do argument=$1 shift case $argument in --username) readonly username="$1"; shift ;; --password) readonly password="$1"; shift ;; --sockets) sockets=$1; shift ;; --cores) cores=$1; shift ;; --memory) memory=$1; shift ;; --debug) debug=true ;; -h|-\?|--help) help; exit 0 ;; -*) die_with_help "Unknown argument: '%s'." "$argument" ;; *) vm_ids="$vm_ids $argument" ;; esac done if [ -z "$username" ] || [ -z "$password" ]; then if [ -f .proxmox ]; then { read -r username; read -r password; } < .proxmox else die_with_help "Required: '--username' and '--password'.\n" fi fi readonly sockets readonly cores readonly memory ## FIXME: When we figure out how to use other nodes than node051. # if [ -z "$node" ]; then # printf 'Picking random node...' # proxmox GET "$api_url/nodes" # node=$(from_response .data[].node | sort -R | head -n 1) # printf " done. Picked '%s'.\n" "$node" # fi # readonly node readonly debug ################################################################################ ## Getting started printf 'Authenticating...' response=$( http \ --verify no \ POST "$api_url/access/ticket" \ "username=$username" \ "password=$password" ) ticket=$(echo "$response" | jq -r .data.ticket) readonly ticket csrf_token=$(echo "$response" | jq -r .data.CSRFPreventionToken) readonly csrf_token printf ' done.\n' acquire_lock () { until mkdir "$tmpdir/lock-$1" 2>/dev/null; do sleep 1; done } release_lock () { rmdir "$tmpdir/lock-$1" } proxmox () { acquire_lock proxmox debug 'request %s' "$*" response=$( http \ --form \ --verify no \ --ignore-stdin \ "$@" \ "Cookie:PVEAuthCookie=$ticket" \ "CSRFPreventionToken:$csrf_token" ) debug 'response to request %s:\n %s' "$*" "$response" release_lock proxmox echo "$response" } ## Synchronous variant for when the `proxmox` function would just respond an ## UPID in the `data` JSON field. proxmox_sync () ( 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 ;; *) die "unexpected status: '%s'" "$status" ;; esac done ) ################################################################################ ## Build ISO build_iso () { acquire_lock build printf 'Building ISO for VM %d...\n' "$1" nix build \ ".#isoInstallers.provisioning.fedi$1" \ --log-format raw --quiet \ --out-link "$tmpdir/installer-fedi$1" ln -sf "$tmpdir/installer-fedi$1/iso/installer.iso" "$tmpdir/installer-fedi$1.iso" printf 'done building ISO for VM %d.\n' "$1" release_lock build } ################################################################################ ## Upload ISO upload_iso () { acquire_lock upload printf 'Uploading ISO for VM %d...\n' "$1" proxmox_sync POST "$api_url/nodes/$node/storage/local/upload" \ "filename@$tmpdir/installer-fedi$1.iso" \ content==iso printf 'done uploading ISO for VM %d.\n' "$1" release_lock upload } ################################################################################ ## Remove ISO remove_iso () { printf 'Removing ISO for VM %d...\n' "$1" proxmox_sync DELETE "$api_url/nodes/$node/storage/local/content/local:iso/installer-fedi$1.iso" printf 'done removing ISO for VM %d.\n' "$1" } ################################################################################ ## Create VM create_vm () { printf 'Creating VM %d...\n' "$1" proxmox_sync POST "$api_url/nodes/$node/qemu" \ \ vmid=="$1" \ name=="fedi$1" \ pool==Fediversity \ \ ide2=="local:iso/installer-fedi$1.iso,media=cdrom" \ ostype==l26 \ \ bios==ovmf \ efidisk0=='linstor_storage:1,efitype=4m' \ agent==1 \ \ scsihw==virtio-scsi-single \ scsi0=='linstor_storage:32,discard=on,ssd=on,iothread=on' \ \ sockets=="$sockets" \ cores=="$cores" \ cpu==x86-64-v2-AES \ numa==1 \ \ memory=="$memory" \ \ net0=='virtio,bridge=vnet1306' printf 'done creating VM %d.\n' "$1" } ################################################################################ ## Install VM install_vm () ( printf 'Installing VM %d...\n' "$1" proxmox_sync POST "$api_url/nodes/$node/qemu/$1/status/start" while :; do response=$(proxmox GET "$api_url/nodes/$node/qemu/$1/status/current") status=$(echo "$response" | jq -r .data.status) case $status in running) sleep 1 ;; stopped) break ;; *) die " unexpected status: '%s'\n" "$status" ;; esac done printf 'done installing VM %d.\n' "$1" ) ################################################################################ ## Start VM start_vm () { printf 'Starting VM %d...\n' "$1" proxmox_sync POST "$api_url/nodes/$node/qemu/$1/config" \ ide2=='none,media=cdrom' \ net0=='virtio,bridge=vnet1305' proxmox_sync POST "$api_url/nodes/$node/qemu/$1/status/start" printf 'done starting VM %d.\n' "$1" } ################################################################################ ## Main loop printf 'Provisioning VMs%s with:\n' "$vm_ids" printf ' sockets: %d\n' "$sockets" printf ' cores: %d\n' "$cores" printf ' memory: %d\n' "$memory" provision_vm () { build_iso "$1" upload_iso "$1" create_vm "$1" install_vm "$1" start_vm "$1" remove_iso "$1" } for vm_id in $vm_ids; do provision_vm "$vm_id" & done wait printf 'done provisioning VMs%s.\n' "$vm_ids" ################################################################################ ## Cleanup rm -Rf $tmpdir