Fediversity/deployment/proxmox/provision.sh

341 lines
8.4 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
2024-11-07 16:18:25 +01:00
set -euC
2024-11-14 13:12:06 +01:00
################################################################################
## 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-provision-$RANDOM
2024-11-14 13:12:06 +01:00
mkdir $tmpdir
2024-11-07 16:18:25 +01:00
################################################################################
## Parse arguments
api_url=
2024-11-07 16:18:25 +01:00
username=
password=
sockets=1
cores=1
memory=2048
vm_ids=
2024-11-07 16:18:25 +01:00
2025-02-19 18:17:59 +01:00
debug=false
2024-11-07 16:18:25 +01:00
help () {
cat <<EOF
Usage: $0 [OPTION...] ID [ID...]
2024-11-07 16:18:25 +01:00
2025-02-21 11:08:06 +01:00
ID can be either:
- of the form INT:STR, in which case the integer will be taken as the Proxmox
id for the machine, and the string as its name in Proxmox and the name of
the NixOS configuration to install,
- an integer, in which case it will be taken as the Proxmox id for the
machine, and the name will be the id prefixed by 'fedi'.
Options:
--api-url STR Base URL of the Proxmox API (required)
--username STR Username, with provider (eg. niols@pve; required)
--password STR Password (required)
2024-11-07 16:18:25 +01:00
--cores INT Number of cores (default: $cores)
--memory INT Memory (default: $memory)
--sockets INT Number of sockets (default: $sockets)
2024-11-07 16:18:25 +01:00
--debug Run this script in debug mode (default: $debug)
2024-11-07 16:18:25 +01:00
-h|-?|--help Show this help and exit
Options can also be provided by adding assignments to a '.proxmox' file in the
current working directory. For instance, it could contain:
api_url=https://192.168.51.81:8006/api2/json
cores=7
username=mireille@pve
debug=true
Command line options take precedence over options found in the '.proxmox' file.
2024-11-07 16:18:25 +01:00
EOF
}
# shellcheck disable=SC2059
2024-11-14 13:55:26 +01:00
die () { printf '\033[31m'; printf "$@"; printf '\033[0m\n'; exit 2; }
# shellcheck disable=SC2059
die_with_help () { printf '\033[31m'; printf "$@"; printf '\033[0m\n\n'; help; exit 2; }
2024-11-07 16:18:25 +01:00
2025-02-19 18:17:59 +01:00
# shellcheck disable=SC2059
debug () { if $debug; then printf >&2 '\033[37m'; printf >&2 "$@"; printf >&2 '\033[0m\n'; fi }
if [ -f .proxmox ]; then
. "$PWD"/.proxmox
fi
2024-11-07 16:18:25 +01:00
while [ $# -gt 0 ]; do
argument=$1
shift
case $argument in
--api-url|--api_url) readonly api_url="$1"; shift ;;
--username) readonly username="$1"; shift ;;
--password) readonly password="$1"; shift ;;
2024-11-07 16:18:25 +01:00
--sockets) sockets=$1; shift ;;
--cores) cores=$1; shift ;;
--memory) memory=$1; shift ;;
2025-02-19 18:17:59 +01:00
--debug) debug=true ;;
2024-11-07 16:18:25 +01:00
-h|-\?|--help) help; exit 0 ;;
2024-11-14 13:12:06 +01:00
-*) die_with_help "Unknown argument: '%s'." "$argument" ;;
2024-11-14 13:12:06 +01:00
*) vm_ids="$vm_ids $argument" ;;
2024-11-07 16:18:25 +01:00
esac
done
if [ -z "$vm_ids" ]; then
die_with_help "Required: at least one VM id."
fi
if [ -z "$api_url" ] || [ -z "$username" ] || [ -z "$password" ]; then
die_with_help "Required: '--api-url', '--username' and '--password'."
2024-11-07 16:18:25 +01:00
fi
readonly sockets
readonly cores
readonly memory
2024-11-14 13:12:06 +01:00
## FIXME: When we figure out how to use other nodes than node051.
# if [ -z "$node" ]; then
# printf 'Picking random node...'
2025-02-19 17:55:31 +01:00
# proxmox GET "$api_url/nodes"
2024-11-14 13:12:06 +01:00
# node=$(from_response .data[].node | sort -R | head -n 1)
# printf " done. Picked '%s'.\n" "$node"
2024-11-14 13:12:06 +01:00
# fi
# readonly node
2024-11-07 16:18:25 +01:00
2025-02-19 18:17:59 +01:00
readonly debug
2024-11-07 16:18:25 +01:00
################################################################################
## Getting started
printf 'Authenticating...'
response=$(
http \
--verify no \
2025-02-19 17:55:31 +01:00
POST "$api_url/access/ticket" \
2024-11-07 16:18:25 +01:00
"username=$username" \
"password=$password"
)
ticket=$(echo "$response" | jq -r .data.ticket)
readonly ticket
csrf_token=$(echo "$response" | jq -r .data.CSRFPreventionToken)
readonly csrf_token
2024-11-07 16:18:25 +01:00
printf ' done.\n'
2024-11-14 13:55:26 +01:00
acquire_lock () {
until mkdir "$tmpdir/lock-$1" 2>/dev/null; do sleep 1; done
2024-11-14 13:55:26 +01:00
}
release_lock () {
rmdir "$tmpdir/lock-$1"
2024-11-14 13:55:26 +01:00
}
2024-11-14 13:12:06 +01:00
proxmox () {
2024-11-14 13:55:26 +01:00
acquire_lock proxmox
2025-02-19 18:17:59 +01:00
debug 'request %s' "$*"
response=$(
http \
--form \
--verify no \
--ignore-stdin \
"$@" \
"Cookie:PVEAuthCookie=$ticket" \
"CSRFPreventionToken:$csrf_token"
)
debug 'response to request %s:\n %s' "$*" "$response"
2024-11-14 13:55:26 +01:00
release_lock proxmox
2025-02-19 18:17:59 +01:00
echo "$response"
2024-11-07 16:18:25 +01:00
}
2024-11-14 13:12:06 +01:00
## Synchronous variant for when the `proxmox` function would just respond an
## UPID in the `data` JSON field.
2024-11-14 13:55:26 +01:00
proxmox_sync () (
2024-11-14 13:12:06 +01:00
response=$(proxmox "$@")
upid=$(echo "$response" | jq -r .data)
2024-11-14 13:55:26 +01:00
2024-11-07 16:18:25 +01:00
while :; do
response=$(proxmox GET "$api_url/nodes/$node/tasks/$upid/status")
2024-11-14 13:12:06 +01:00
status=$(echo "$response" | jq -r .data.status)
2024-11-14 13:55:26 +01:00
2024-11-07 16:18:25 +01:00
case $status in
2024-11-14 13:12:06 +01:00
running) sleep 1 ;;
2024-11-07 16:18:25 +01:00
stopped) break ;;
*) die "unexpected status: '%s'" "$status" ;;
2024-11-07 16:18:25 +01:00
esac
done
2024-11-14 13:55:26 +01:00
)
2024-11-07 16:18:25 +01:00
2024-11-14 13:12:06 +01:00
################################################################################
## Build ISO
build_iso () {
2024-11-14 13:55:26 +01:00
acquire_lock build
2025-02-21 11:08:06 +01:00
printf 'Building ISO for VM %s...\n' "$2"
2024-11-14 13:12:06 +01:00
nix build \
--impure --expr "
let flake = builtins.getFlake (builtins.toString ./.); in
flake.lib.makeInstallerIso {
2025-02-21 11:08:06 +01:00
nixosConfiguration = flake.nixosConfigurations.$2;
nixpkgs = flake.inputs.nixpkgs;
hostKeys = {
ed25519 = {
2025-02-21 11:08:06 +01:00
private = ./deployment/hostKeys/$2/ssh_host_ed25519_key;
public = ./deployment/hostKeys/$2/ssh_host_ed25519_key.pub;
};
};
}
" \
2024-11-14 13:12:06 +01:00
--log-format raw --quiet \
2025-02-21 11:08:06 +01:00
--out-link "$tmpdir/installer-$2"
2024-11-14 13:12:06 +01:00
2025-02-21 11:08:06 +01:00
ln -sf "$tmpdir/installer-$2/iso/installer.iso" "$tmpdir/installer-$2.iso"
2024-11-14 13:12:06 +01:00
2025-02-21 11:08:06 +01:00
printf 'done building ISO for VM %s.\n' "$2"
2024-11-14 13:55:26 +01:00
release_lock build
2024-11-14 13:12:06 +01:00
}
2024-11-07 16:18:25 +01:00
################################################################################
## Upload ISO
2024-11-14 13:12:06 +01:00
upload_iso () {
2024-11-14 13:55:26 +01:00
acquire_lock upload
2025-02-21 11:08:06 +01:00
printf 'Uploading ISO for VM %s...\n' "$2"
2024-11-14 13:12:06 +01:00
2025-02-19 17:55:31 +01:00
proxmox_sync POST "$api_url/nodes/$node/storage/local/upload" \
2025-02-21 11:08:06 +01:00
"filename@$tmpdir/installer-$2.iso" \
2024-11-14 13:12:06 +01:00
content==iso
2025-02-21 11:08:06 +01:00
printf 'done uploading ISO for VM %s.\n' "$2"
2024-11-14 13:55:26 +01:00
release_lock upload
2024-11-14 13:12:06 +01:00
}
################################################################################
## Remove ISO
remove_iso () {
2025-02-21 11:08:06 +01:00
printf 'Removing ISO for VM %s...\n' "$2"
2025-01-29 15:14:10 +01:00
2025-02-21 11:08:06 +01:00
proxmox_sync DELETE "$api_url/nodes/$node/storage/local/content/local:iso/installer-$2.iso"
2025-01-29 15:14:10 +01:00
2025-02-21 11:08:06 +01:00
printf 'done removing ISO for VM %s.\n' "$2"
2024-11-14 13:12:06 +01:00
}
2024-11-07 16:18:25 +01:00
################################################################################
## Create VM
2024-11-14 13:12:06 +01:00
create_vm () {
2025-02-21 11:08:06 +01:00
printf 'Creating VM %s with id %d...\n' "$2" "$1"
2024-11-14 13:12:06 +01:00
proxmox_sync POST "$api_url/nodes/$node/qemu" \
2024-11-14 13:12:06 +01:00
\
vmid=="$1" \
2025-02-21 11:08:06 +01:00
name=="$2" \
2024-11-14 13:12:06 +01:00
pool==Fediversity \
\
2025-02-21 11:08:06 +01:00
ide2=="local:iso/installer-$2.iso,media=cdrom" \
2024-11-14 13:12:06 +01:00
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" \
2024-11-14 13:12:06 +01:00
cpu==x86-64-v2-AES \
numa==1 \
\
memory=="$memory" \
2024-11-14 13:12:06 +01:00
\
net0=='virtio,bridge=vnet1306'
2025-02-21 11:08:06 +01:00
printf 'done creating VM %s.\n' "$2"
2024-11-14 13:12:06 +01:00
}
2024-11-07 16:18:25 +01:00
################################################################################
## Install VM
2024-11-14 13:55:26 +01:00
install_vm () (
2025-02-21 11:08:06 +01:00
printf 'Installing VM %s...\n' "$2"
2024-11-07 16:18:25 +01:00
proxmox_sync POST "$api_url/nodes/$node/qemu/$1/status/start"
2024-11-07 16:18:25 +01:00
2024-11-14 13:12:06 +01:00
while :; do
response=$(proxmox GET "$api_url/nodes/$node/qemu/$1/status/current")
2024-11-14 13:12:06 +01:00
status=$(echo "$response" | jq -r .data.status)
case $status in
running) sleep 1 ;;
stopped) break ;;
2025-02-19 18:09:25 +01:00
*) die " unexpected status: '%s'\n" "$status" ;;
2024-11-14 13:12:06 +01:00
esac
done
2024-11-07 16:18:25 +01:00
2025-02-21 11:08:06 +01:00
printf 'done installing VM %s.\n' "$2"
2024-11-14 13:55:26 +01:00
)
2024-11-07 16:18:25 +01:00
################################################################################
## Start VM
2024-11-14 13:12:06 +01:00
start_vm () {
2025-02-21 11:08:06 +01:00
printf 'Starting VM %s...\n' "$2"
2024-11-07 16:18:25 +01:00
proxmox_sync POST "$api_url/nodes/$node/qemu/$1/config" \
2024-11-14 13:12:06 +01:00
ide2=='none,media=cdrom' \
net0=='virtio,bridge=vnet1305'
2024-11-07 16:18:25 +01:00
proxmox_sync POST "$api_url/nodes/$node/qemu/$1/status/start"
2024-11-07 16:18:25 +01:00
2025-02-21 11:08:06 +01:00
printf 'done starting VM %s.\n' "$2"
2024-11-14 13:12:06 +01:00
}
################################################################################
## 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"
2024-11-14 13:12:06 +01:00
provision_vm () {
2025-02-21 11:08:06 +01:00
build_iso "$@"
upload_iso "$@"
create_vm "$@"
install_vm "$@"
start_vm "$@"
remove_iso "$@"
2024-11-14 13:12:06 +01:00
}
for vm_id in $vm_ids; do
2025-02-21 11:08:06 +01:00
vm_name=${vm_id#*:}
vm_id=${vm_id%:*}
if [ "$vm_id" = "$vm_name" ]; then
vm_name=$(printf 'fedi%03d' "$vm_id")
fi
provision_vm "$vm_id" "$vm_name" &
2024-11-14 13:12:06 +01:00
done
2024-11-14 13:55:26 +01:00
wait
2024-11-14 13:12:06 +01:00
printf 'done provisioning VMs%s.\n' "$vm_ids"
2024-11-14 13:12:06 +01:00
################################################################################
## Cleanup
rm -Rf $tmpdir