194 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			194 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/env bash
 | |
| # Copyright (C) 2023  Umorpha Systems
 | |
| # SPDX-License-Identifier: AGPL-3.0-or-later
 | |
| 
 | |
| set -euE -o pipefail
 | |
| 
 | |
| 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
 | |
| }				
 | |
| 
 | |
| 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
 | |
| 	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'
 | |
| 	local iso_id
 | |
| 	iso_id=$(bin/vultr-iso-id "$iso_url")
 | |
| 
 | |
| 	# Set up instance
 | |
| 
 | |
| 	local instance_id
 | |
| 	instance_id=$(bin/vultr-api "instances?label=${arg_name}" | jq -r '.instances[]|.id')
 | |
| 
 | |
| 	local user_data
 | |
| 	user_data="#!/bin/bash
 | |
| vultr-api() {
 | |
| 	curl \\
 | |
| 		--no-progress-meter \\
 | |
| 		--fail-with-body \\
 | |
| 		-H 'Authorization: Bearer ${VULTR_API_KEY}' \\
 | |
| 		-H 'Content-Type: application/json' \\
 | |
| 		\"https://api.vultr.com/v2/\$@\"
 | |
| }
 | |
| set -ex
 | |
| 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
 | |
| "
 | |
| 
 | |
| 	if [[ -z "$instance_id" ]]; then
 | |
| 		printf >&2 ':: Creating instance...\n'
 | |
| 		vultr-cli instance create \
 | |
| 			  --host="$arg_name" \
 | |
| 			  --label="$arg_name" \
 | |
| 			  \
 | |
| 			  --plan="$arg_plan" \
 | |
| 			  --region="$arg_region" \
 | |
| 			  --ipv6=true \
 | |
| 			  --auto-backup=true \
 | |
| 			  \
 | |
| 			  --iso="$iso_id" \
 | |
| 			  \
 | |
| 			  --userdata="$user_data"
 | |
| 	else
 | |
| 		printf >&2 ':: Updating instance %s...\n' "$instance_id"
 | |
| 		# Determine what we need to patch.
 | |
| 		instance_data="$(bin/vultr-api instances/$instance_id)"
 | |
| 		declare -A patch
 | |
| 		patch[user_data]="$(base64 --wrap=0 <<<"$user_data")"
 | |
| 		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
 | |
| 			patch[plan]="$arg_plan"
 | |
| 		fi
 | |
| 
 | |
| 		# Format that patch as JSON.
 | |
| 		data='{'
 | |
| 		for k in "${!patch[@]}"; do
 | |
| 			data+="\"$k\":\"${patch[$k]}\","
 | |
| 		done
 | |
| 		data="${data%,}}"
 | |
| 
 | |
| 		# Apply.
 | |
| 
 | |
| 		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
 | |
| 			printf >&2 '    instance iso: state=%q id=%q\n' "$resp_state" "$resp_id"
 | |
| 			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
 | |
| 		printf >&2 '    instance iso: state=%q id=%q\n' "$resp_state" "$resp_id"
 | |
| 
 | |
| 		printf >&2 ' -> Starting instance...\n'
 | |
| 		bin/vultr-api instances/$instance_id/start -XPOST
 | |
| 	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 "$@"
 |