honestly its been months, i found unstaged changes and forgot

This commit is contained in:
boris committed 2026-10-23 18:34:59 +01:00
1 parent b5d1ffda95
commit ef8d5645e3
55 files changed
+1554 -180

No files matched your search

+367
View File
@@ -0,0 +1,367 @@
#!/usr/bin/env bash
set -euo pipefail
HOST_VARS_DIR=host_vars
TEMPLATE_VMID=9000
TEMPLATE_STORAGE=local-lvm
TEMPLATE_BRIDGE=vmbr0
PBS_STORAGE=
DRY_RUN=0
ASSUME_YES=0
INTERACTIVE=1
usage() {
cat <<'USAGE'
Initialise Proxmox VMs from host_vars/*.yml.
Run this on a Proxmox node as root from the repository root.
Each host var file must set proxmox_vmid to be managed by this script.
Files without proxmox_vmid are skipped.
Supported host vars:
proxmox_vmid: 101
proxmox_name: cloud
proxmox_template_vmid: 9000
proxmox_storage: local-lvm
proxmox_bridge: vmbr0
proxmox_memory: 4096
proxmox_cores: 2
proxmox_cpu: host
proxmox_disk_size: 64G
proxmox_backup_enabled: true
proxmox_ipconfig0: ip=dhcp
proxmox_onboot: true
proxmox_start: false
Options:
--host-vars-dir PATH Host vars directory. Default: host_vars
--template-vmid ID Default template VMID. Default: 9000
--template-storage NAME Storage used if template must be created. Default: local-lvm
--template-bridge NAME Bridge used if template must be created. Default: vmbr0
--pbs-storage NAME PBS storage/datastore ID. Passed to template script and used as default backup target note.
--dry-run Print commands without executing them.
--yes Accept defaults for prompts.
--non-interactive Do not prompt.
-h, --help Show this help.
USAGE
}
log() {
printf '%s\n' "$*"
}
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
run() {
if [ "$DRY_RUN" -eq 1 ]; then
printf '+'
printf ' %q' "$@"
printf '\n'
return 0
fi
"$@"
}
need_command() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
prompt_value() {
local label=$1
local default_value=$2
local value
if [ "$ASSUME_YES" -eq 1 ] || [ "$INTERACTIVE" -eq 0 ] || [ ! -t 0 ]; then
printf '%s\n' "$default_value"
return 0
fi
printf '%s [%s]: ' "$label" "$default_value" >&2
read -r value
if [ -z "$value" ]; then
printf '%s\n' "$default_value"
else
printf '%s\n' "$value"
fi
}
prompt_optional_value() {
local label=$1
local value
if [ "$ASSUME_YES" -eq 1 ] || [ "$INTERACTIVE" -eq 0 ] || [ ! -t 0 ]; then
printf '\n'
return 0
fi
printf '%s [leave empty]: ' "$label" >&2
read -r value
printf '%s\n' "$value"
}
prompt_confirm() {
local label=$1
local value
if [ "$ASSUME_YES" -eq 1 ] || [ "$INTERACTIVE" -eq 0 ] || [ ! -t 0 ]; then
return 0
fi
printf '%s [Y/n]: ' "$label" >&2
read -r value
case "$value" in
""|y|Y|yes|YES)
return 0
;;
*)
return 1
;;
esac
}
yaml_scalar() {
local file=$1
local key=$2
awk -v key="$key" '
$0 ~ "^[[:space:]]*" key ":" {
sub("^[[:space:]]*" key ":[[:space:]]*", "", $0)
sub("[[:space:]]+#.*$", "", $0)
gsub(/^["'\'']|["'\'']$/, "", $0)
print $0
exit
}
' "$file"
}
bool_true() {
case "${1:-}" in
true|True|TRUE|yes|Yes|YES|1|on|On|ON)
return 0
;;
*)
return 1
;;
esac
}
parse_args() {
while [ "$#" -gt 0 ]; do
case "$1" in
--host-vars-dir)
HOST_VARS_DIR=${2:?missing value for --host-vars-dir}
shift 2
;;
--template-vmid)
TEMPLATE_VMID=${2:?missing value for --template-vmid}
shift 2
;;
--template-storage)
TEMPLATE_STORAGE=${2:?missing value for --template-storage}
shift 2
;;
--template-bridge)
TEMPLATE_BRIDGE=${2:?missing value for --template-bridge}
shift 2
;;
--pbs-storage)
PBS_STORAGE=${2:?missing value for --pbs-storage}
shift 2
;;
--dry-run)
DRY_RUN=1
shift
;;
--yes)
ASSUME_YES=1
shift
;;
--non-interactive)
INTERACTIVE=0
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
}
collect_config() {
TEMPLATE_VMID=$(prompt_value "Default template VMID" "$TEMPLATE_VMID")
TEMPLATE_STORAGE=$(prompt_value "Default template/storage name" "$TEMPLATE_STORAGE")
TEMPLATE_BRIDGE=$(prompt_value "Default bridge" "$TEMPLATE_BRIDGE")
if [ -z "$PBS_STORAGE" ]; then
PBS_STORAGE=$(prompt_optional_value "PBS storage/datastore ID")
fi
}
template_exists() {
if [ "$DRY_RUN" -eq 1 ]; then
return 1
fi
qm status "$TEMPLATE_VMID" >/dev/null 2>&1
}
vm_exists() {
local vmid=$1
if [ "$DRY_RUN" -eq 1 ]; then
return 1
fi
qm status "$vmid" >/dev/null 2>&1
}
ensure_template() {
if template_exists; then
log "Template VMID $TEMPLATE_VMID already exists."
return 0
fi
log "Template VMID $TEMPLATE_VMID not found; creating it first."
local args=(
scripts/create-proxmox-template.sh
--vmid "$TEMPLATE_VMID"
--storage "$TEMPLATE_STORAGE"
--bridge "$TEMPLATE_BRIDGE"
--yes
--non-interactive
)
if [ -n "$PBS_STORAGE" ]; then
args+=(--pbs-storage "$PBS_STORAGE")
fi
if [ "$DRY_RUN" -eq 1 ]; then
args+=(--dry-run)
fi
run "${args[@]}"
}
disk_volume() {
local vmid=$1
local storage=$2
if [ "$DRY_RUN" -eq 1 ]; then
printf '%s:vm-%s-disk-0\n' "$storage" "$vmid"
return 0
fi
qm config "$vmid" | awk -F': ' '/^scsi0:/{split($2, parts, ","); print parts[1]; exit}'
}
init_vm_from_host_vars() {
local file=$1
local stem
stem=$(basename "$file" .yml)
local vmid
vmid=$(yaml_scalar "$file" proxmox_vmid)
if [ -z "$vmid" ]; then
log "Skipping $file: proxmox_vmid not set"
return 0
fi
local name template_vmid storage bridge memory cores cpu disk_size backup_enabled ipconfig0 onboot start_vm
name=$(yaml_scalar "$file" proxmox_name)
[ -n "$name" ] || name=$(yaml_scalar "$file" nbops_hostname)
[ -n "$name" ] || name=$stem
template_vmid=$(yaml_scalar "$file" proxmox_template_vmid)
[ -n "$template_vmid" ] || template_vmid=$TEMPLATE_VMID
storage=$(yaml_scalar "$file" proxmox_storage)
[ -n "$storage" ] || storage=$TEMPLATE_STORAGE
bridge=$(yaml_scalar "$file" proxmox_bridge)
[ -n "$bridge" ] || bridge=$TEMPLATE_BRIDGE
memory=$(yaml_scalar "$file" proxmox_memory)
[ -n "$memory" ] || memory=2048
cores=$(yaml_scalar "$file" proxmox_cores)
[ -n "$cores" ] || cores=2
cpu=$(yaml_scalar "$file" proxmox_cpu)
[ -n "$cpu" ] || cpu=host
disk_size=$(yaml_scalar "$file" proxmox_disk_size)
[ -n "$disk_size" ] || disk_size=32G
backup_enabled=$(yaml_scalar "$file" proxmox_backup_enabled)
[ -n "$backup_enabled" ] || backup_enabled=false
ipconfig0=$(yaml_scalar "$file" proxmox_ipconfig0)
[ -n "$ipconfig0" ] || ipconfig0=ip=dhcp
onboot=$(yaml_scalar "$file" proxmox_onboot)
[ -n "$onboot" ] || onboot=true
start_vm=$(yaml_scalar "$file" proxmox_start)
[ -n "$start_vm" ] || start_vm=false
if vm_exists "$vmid"; then
log "Skipping $name ($vmid): VM already exists"
return 0
fi
log "Initialising $name ($vmid) from $file"
run qm clone "$template_vmid" "$vmid" --name "$name" --full true --storage "$storage"
run qm set "$vmid" --memory "$memory" --cores "$cores" --cpu "$cpu"
run qm set "$vmid" --net0 "virtio,bridge=$bridge"
run qm set "$vmid" --ipconfig0 "$ipconfig0"
if bool_true "$onboot"; then
run qm set "$vmid" --onboot 1
else
run qm set "$vmid" --onboot 0
fi
local volume backup_flag
volume=$(disk_volume "$vmid" "$storage")
backup_flag=0
if bool_true "$backup_enabled"; then
backup_flag=1
fi
run qm set "$vmid" --scsi0 "$volume,discard=on,ssd=1,backup=$backup_flag"
if [ -n "$disk_size" ]; then
run qm resize "$vmid" scsi0 "$disk_size"
fi
if bool_true "$start_vm"; then
run qm start "$vmid"
fi
}
main() {
parse_args "$@"
collect_config
if [ "$DRY_RUN" -eq 0 ]; then
need_command qm
need_command awk
[ "$(id -u)" -eq 0 ] || die "run this script as root on a Proxmox node"
fi
[ -d "$HOST_VARS_DIR" ] || die "host vars directory not found: $HOST_VARS_DIR"
cat <<SUMMARY
VM initialisation:
Host vars: $HOST_VARS_DIR/*.yml
Template VMID: $TEMPLATE_VMID
Default storage: $TEMPLATE_STORAGE
Default bridge: $TEMPLATE_BRIDGE
PBS storage: ${PBS_STORAGE:-not set}
Mode: $([ "$DRY_RUN" -eq 1 ] && printf 'dry-run' || printf 'apply')
SUMMARY
if ! prompt_confirm "Initialise VMs from host vars?"; then
die "cancelled"
fi
ensure_template
local found=0
for file in "$HOST_VARS_DIR"/*.yml; do
[ -e "$file" ] || continue
found=1
init_vm_from_host_vars "$file"
done
[ "$found" -eq 1 ] || die "no .yml files found in $HOST_VARS_DIR"
}
main "$@"