#!/usr/bin/env sh
set -euC

################################################################################
## Constants

readonly apiurl=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$RANDOM
mkdir $tmpdir

################################################################################
## Parse arguments

username=
password=
sockets=1
cores=1
memory=2048
vmids=

help () {
  cat <<EOF
Usage: $0 [OPTION...] [ID...]

Authentication options:
  --username STR    Username, with provider (eg. niols@pve)
  --password STR    Password

  If not provided via the command line, username and password will be looked for
  in a '.proxmox' file in the current working directory, the username on the
  first line, and the password on the second.

Other options:
  --sockets INT     Number of sockets (default: $sockets)
  --cores INT       Number of cores (default: $cores)
  --memory INT      Memory (default: $memory)

Others:
  -h|-?|--help      Show this help and exit
EOF
}

die () { printf '\033[31m'; printf "$@"; printf '\033[0m\n'; exit 2; }
die_with_help () { printf '\033[31m'; printf "$@"; printf '\033[0m\n'; help; exit 2; }

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 ;;

    -h|-\?|--help) help; exit 0 ;;

    -*) die_with_help 'Unknown argument: `%s`.' "$argument" ;;

    *) vmids="$vmids $argument" ;;
  esac
done

if [ -z "$username" ] || [ -z "$password" ]; then
  if [ -f .proxmox ]; then
    { read username; read 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 $apiurl/nodes
#   node=$(from_response .data[].node | sort -R | head -n 1)
#   printf ' done. Picked `%s`.\n' "$node"
# fi
# readonly node

################################################################################
## Getting started

printf 'Authenticating...'
response=$(
    http \
        --verify no \
        POST $apiurl/access/ticket \
        "username=$username" \
        "password=$password"
    )
readonly ticket=$(echo "$response" | jq -r .data.ticket)
readonly csrfToken=$(echo "$response" | jq -r .data.CSRFPreventionToken)
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
  http \
    --form \
    --verify no \
    --ignore-stdin \
    "$@" \
    "Cookie:PVEAuthCookie=$ticket" \
    "CSRFPreventionToken:$csrfToken"
  release_lock proxmox
}

## 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 $apiurl/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 $apiurl/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... unsupported for now. (FIXME)\n' $1
}

################################################################################
## Create VM

create_vm () {
  printf 'Creating VM %d...\n' $1

  proxmox_sync POST $apiurl/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 $apiurl/nodes/$node/qemu/$1/status/start

  while :; do
    response=$(proxmox GET $apiurl/nodes/$node/qemu/$1/status/current)
    status=$(echo "$response" | jq -r .data.status)
    case $status in
      running) sleep 1 ;;
      stopped) break ;;
      *) printf ' unexpected status: `%s`\n' "$status"; exit 2 ;;
    esac
  done

  printf 'done installing VM %d.\n' $1
)

################################################################################
## Start VM

start_vm () {
  printf 'Starting VM %d...\n' $1

  proxmox_sync POST $apiurl/nodes/$node/qemu/$1/config \
    ide2=='none,media=cdrom' \
    net0=='virtio,bridge=vnet1305'

  proxmox_sync POST $apiurl/nodes/$node/qemu/$1/status/start

  printf 'done starting VM %d.\n' $1
}

################################################################################
## Main loop

printf 'Provisioning VMs%s with:\n' "$vmids"
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 vmid in $vmids; do
  provision_vm $vmid &
done
wait

printf 'done provisioning VMs%s.\n' "$vmids"

################################################################################
## Cleanup

rm -Rf $tmpdir