umorpha-boxes/bin/vultr-install

197 lines
6.4 KiB
Plaintext
Raw Normal View History

2023-11-03 23:15:57 +00:00
#!/usr/bin/env bash
# Copyright (C) 2023 Umorpha Systems
# SPDX-License-Identifier: AGPL-3.0-or-later
2023-11-04 04:16:34 +00:00
set -euE -o pipefail
2023-11-03 23:15:57 +00:00
usage() {
cat <<EOF
Usage: ${0##*/} --name=HOSTNAME --plan=PLAN_ID --region=REGION_ID
or: ${0##*/} --help
Create/wipe a Vultr instance and install an image onto it.
For selecting the appropriate plan ID, determine which type you want
from the following table, then list the plans of that type with
\`vultr-cli plans list --type=TYPE\`
| type | WebUI label | ID format |
|-------+---------------------------------------------+---------------------------------------------|
| vc2 | Cloud Compute : Regular Performance | vc2-${nproc}c-${ram}gb[-sc1] |
| vhf | Cloud Compute : High Frequency | vhf-${nproc}c-${ram}gb[-sc1] |
| vhp | Cloud Compute : High Performance | vhp-${nproc}c-${ram}gb-{amd,intel}[-sc1] |
| voc-g | Optimized Cloud Compute : General Purpose | voc-g-${nproc}c-${ram}gb-${disk}s-amd[-sc1] |
| voc-c | Optimized Cloud Compute : CPU Optimized | voc-c-${nproc}c-${ram}gb-${disk}s-amd[-sc1] |
| voc-m | Optimized Cloud Compute : Memory Optimized | voc-m-${nproc}c-${ram}gb-${disk}s-amd[-sc1] |
| voc-s | Optimized Cloud Compute : Storage Optimized | voc-s-${nproc}c-${ram}gb-${disk}s-amd[-sc1] |
| vcg | Cloud GPU | vcg-${gpu}-{nproc}c-${ram}g-${vram}vram |
(The "-sc1" prefix for the "sao" region (São Paulo, Brazil), which have
separate plan types because it's more expensive there.)
For selecting the appropriate region ID, here lis a list of the
regions in the US:
| ID | Location | HDD | NVMe |
|-----+----------------+-----+------|
| atl | Atlanta | yes | |
| dfw | Dallas | yes | |
| ewr | New Jersey | yes | yes |
| hnl | Honolulu | yes | |
| lax | Los Angeles | yes | yes |
| mia | Miami | yes | |
| ord | Chicago | yes | |
| sea | Seattle | yes | |
| sjc | Silicon Valley | yes | |
EOF
}
readonly os_iso=159 # From `vultr-cli os list`
run() {
local arg_name arg_plan arg_region
arg_name=$1
arg_plan=$2
arg_region=$3
export VULTR_API_KEY=$(cat secrets/vultr-api-key.txt)
# Upload ISO
2023-11-08 22:02:54 +00:00
printf >&2 ':: Uploading bootstrap ISO to Vultr S3...\n'
local iso_file iso_label iso_url
iso_file="images/${arg_name%%.*}.bootstrap.iso"
iso_label=$(xorriso -indev "$iso_file" |& sed -En "s/^Volume id *: *'(.*)'$/\1/p")
iso_url=$(bin/vultr-upload "$iso_file" "s3://umorpha-images/${iso_label}.iso")
# Set ISO as an ISO
printf >&2 ':: Converting from Vultr S3 to Vultr ISO...\n'
2023-11-03 23:15:57 +00:00
local iso_id
2023-11-08 22:02:54 +00:00
iso_id=$(bin/vultr-iso-id "$iso_url")
2023-11-03 23:15:57 +00:00
# Set up instance
local instance_id
instance_id=$(bin/vultr-api "instances?label=${arg_name}" | jq -r '.instances[]|.id')
2023-11-10 18:09:51 +00:00
local user_data
user_data="#!/bin/sh
vultr-api() {
curl \\
--no-progress-meter \\
--fail-with-body \\
-H 'Authorizarion: Bearer ${VULTR_API_KEY}' \\
-H 'Content-Type: application/json' \\
\"https://api.vultr.com/v2/\$@\"
}
set -x
instance_id=\$(curl --fail-with-body --no-progress-meter http://169.254.169.254/v1/instance-v2-id)
vultr-api instances/\${instance_id} -XPATCH --data '{\"user_data\":\"\"}'
vultr-api instances/\${instance_id}/iso/detach -XPOST
"
2023-11-10 06:39:29 +00:00
2023-11-03 23:15:57 +00:00
if [[ -z "$instance_id" ]]; then
2023-11-04 02:28:14 +00:00
printf >&2 ':: Creating instance...\n'
2023-11-03 23:15:57 +00:00
vultr-cli instance create \
--host="$arg_name" \
--label="$arg_name" \
\
--plan="$arg_plan" \
--region="$arg_region" \
--ipv6=true \
--auto-backup=true \
\
--os="$os_iso" \
--iso="$iso_id" \
\
2023-11-04 02:28:14 +00:00
--userdata="$user_data"
2023-11-03 23:15:57 +00:00
else
2023-11-04 02:28:14 +00:00
printf >&2 ':: Updating instance %s...\n' "$instance_id"
2023-11-03 23:15:57 +00:00
# Determine what we need to patch.
instance_data="$(bin/vultr-api instances/$instance_id)"
declare -A patch
2023-11-04 02:28:14 +00:00
patch[user_data]="$(base64 --wrap=0 <<<"$user_data")"
2023-11-03 23:15:57 +00:00
if [[ "$(jq -r .instance.region <<<"$instance_data")" != "$arg_region" ]]; then
patch[region]="$arg_region"
fi
if [[ "$(jq -r .instance.plan <<<"$instance_data")" != "$arg_plan" ]]; then
2023-11-04 04:16:34 +00:00
patch[plan]="$arg_plan"
2023-11-03 23:15:57 +00:00
fi
# Format that patch as JSON.
data='{'
for k in "${!patch[@]}"; do
data+="\"$k\":\"${patch[$k]}\","
done
data="${data%,}}"
# Apply.
2023-11-04 02:28:14 +00:00
printf >&2 ' -> Halting instance...\n'
bin/vultr-api instances/$instance_id/halt -XPOST
printf >&2 ' -> Updating instance...\n'
bin/vultr-api instances/$instance_id -XPATCH --data "$data" | jq
printf >&2 ' -> Attaching ISO...\n'
resp=$(bin/vultr-api instances/$instance_id/iso/attach -XPOST --data "{\"iso_id\":\"${iso_id}\"}")
resp_state=$(jq -r .iso_status.state <<<"$resp")
resp_id=$(jq -r .iso_status.iso_id <<<"$resp")
while ! [[ "$resp_state" == isomounted && "$resp_id" == "$iso_id" ]]; do
2023-11-04 04:16:34 +00:00
printf >&2 ' instance iso: state=%q id=%q\n' "$resp_state" "$resp_id"
2023-11-04 02:28:14 +00:00
sleep 1
resp=$(bin/vultr-api instances/$instance_id/iso)
resp_state=$(jq -r .iso_status.state <<<"$resp")
resp_id=$(jq -r .iso_status.iso_id <<<"$resp")
done
2023-11-04 04:16:34 +00:00
printf >&2 ' instance iso: state=%q id=%q\n' "$resp_state" "$resp_id"
2023-11-04 02:28:14 +00:00
printf >&2 ' -> Starting instance...\n'
bin/vultr-api instances/$instance_id/start -XPOST
2023-11-03 23:15:57 +00:00
fi
}
main() {
local arg_name=
local arg_plan=
local arg_region=
local arg_mode=run
local args
if ! args="$(getopt -n "${0##*/}" -o '' -l 'help,name:,plan:,region:' -- "$@")"; then
arg_mode=error
else
eval "set -- $args"
while true; do
case "$1" in
--help) shift; arg_mode=help;;
--name) shift; arg_name=$1; shift;;
--plan) shift; arg_plan=$1; shift;;
--region) shift; arg_region=$1; shift;;
--) shift; break;;
esac
done
if [[ $@ -gt 0 ]]; then
printf >&2 '%s: error: unexpected positional arguments: %s' "${*@Q}"
arg_mode=error
fi
if [[ -z "$arg_name" ]]; then
printf >&2 '%s: error: --name=HOSTNAME is required\n' "${0##*/}"
arg_mode=error
fi
if [[ -z "$arg_plan" ]]; then
printf >&2 '%s: error: --plan=PLAN_ID is required\n' "${0##*/}"
arg_mode=error
fi
if [[ -z "$arg_region" ]]; then
printf >&2 '%s: error: --region=REGION_ID is required\n' "${0##*/}"
arg_mode=error
fi
case "$arg_mode" in
error) printf >&2 "Try '%q --help' for more information\n" "${0##*/}"; exit 2;;
help) show_help;;
run) run "$arg_name" "$arg_plan" "$arg_region";;
esac
fi
}
main "$@"