2026-01-09 17:12:49 +01:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
set -Eeuo pipefail
|
|
|
|
|
|
2026-01-09 20:09:29 +01:00
|
|
|
# ---------------------------
|
2026-01-11 11:50:35 +01:00
|
|
|
# Logging / Errors / Traps
|
2026-01-09 20:09:29 +01:00
|
|
|
# ---------------------------
|
2026-01-09 17:12:49 +01:00
|
|
|
_ts() { date '+%F %T'; }
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# log to stderr (wichtig: damit Funktions-Rückgaben via stdout sauber bleiben)
|
2026-01-09 18:53:26 +01:00
|
|
|
log() { echo "[$(_ts)] $*" >&2; }
|
2026-01-09 17:12:49 +01:00
|
|
|
info() { log "INFO: $*"; }
|
|
|
|
|
warn() { log "WARN: $*"; }
|
|
|
|
|
die() { log "ERROR: $*"; exit 1; }
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
on_error() {
|
|
|
|
|
local lineno="$1"
|
|
|
|
|
local cmd="$2"
|
|
|
|
|
local code="$3"
|
|
|
|
|
die "Failed at line ${lineno}: ${cmd} (exit=${code})"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setup_traps() {
|
|
|
|
|
trap 'on_error "$LINENO" "$BASH_COMMAND" "$?"' ERR
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 17:12:49 +01:00
|
|
|
need_cmd() {
|
2026-01-09 18:53:26 +01:00
|
|
|
local c
|
2026-01-09 17:12:49 +01:00
|
|
|
for c in "$@"; do
|
|
|
|
|
command -v "$c" >/dev/null 2>&1 || die "Missing command: $c"
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# ---------------------------
|
|
|
|
|
# Safe secret generators (NO tr|head pipelines)
|
|
|
|
|
# ---------------------------
|
|
|
|
|
# Hex secret, length = bytes*2 chars
|
|
|
|
|
gen_hex() {
|
|
|
|
|
local bytes="${1:-32}"
|
|
|
|
|
if command -v openssl >/dev/null 2>&1; then
|
|
|
|
|
openssl rand -hex "$bytes"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
# fallback python
|
|
|
|
|
python3 - <<PY
|
|
|
|
|
import secrets
|
|
|
|
|
print(secrets.token_hex($bytes))
|
|
|
|
|
PY
|
2026-01-09 18:53:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# URL-safe token with approx length
|
|
|
|
|
gen_urlsafe() {
|
|
|
|
|
local nbytes="${1:-32}"
|
|
|
|
|
python3 - <<PY
|
|
|
|
|
import secrets
|
|
|
|
|
print(secrets.token_urlsafe($nbytes))
|
|
|
|
|
PY
|
2026-01-09 18:53:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 20:09:29 +01:00
|
|
|
# ---------------------------
|
2026-01-09 17:12:49 +01:00
|
|
|
# Proxmox helpers
|
2026-01-09 20:09:29 +01:00
|
|
|
# ---------------------------
|
2026-01-09 18:53:26 +01:00
|
|
|
pve_storage_exists() {
|
|
|
|
|
local st="$1"
|
|
|
|
|
pvesm status --storage "$st" >/dev/null 2>&1
|
|
|
|
|
}
|
2026-01-09 17:12:49 +01:00
|
|
|
|
|
|
|
|
pve_bridge_exists() {
|
2026-01-09 18:53:26 +01:00
|
|
|
local br="$1"
|
|
|
|
|
[[ -d "/sys/class/net/${br}/bridge" ]]
|
2026-01-09 17:12:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pve_template_ensure_debian12() {
|
2026-01-11 11:50:35 +01:00
|
|
|
# Gibt NUR den template-string auf stdout zurück
|
|
|
|
|
# Logs gehen auf stderr (info/warn), damit TEMPLATE nicht "vermüllt".
|
|
|
|
|
local desired="debian-12-standard_12.12-1_amd64.tar.zst"
|
|
|
|
|
local store="$1"
|
2026-01-09 20:09:29 +01:00
|
|
|
|
|
|
|
|
need_cmd pveam awk grep
|
2026-01-09 17:12:49 +01:00
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# prüfen, ob storage für templates geeignet ist
|
2026-01-09 17:12:49 +01:00
|
|
|
if ! pveam list "$store" >/dev/null 2>&1; then
|
2026-01-09 20:09:29 +01:00
|
|
|
warn "pveam storage '${store}' not available for templates; falling back to 'local'"
|
2026-01-09 17:12:49 +01:00
|
|
|
store="local"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-01-09 18:53:26 +01:00
|
|
|
pveam update >/dev/null 2>&1 || true
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# vorhanden?
|
|
|
|
|
if ! pveam list "$store" | awk '{print $2}' | grep -qx "$desired"; then
|
|
|
|
|
# check available list
|
|
|
|
|
if pveam available | awk '{print $2}' | grep -qx "$desired"; then
|
|
|
|
|
info "Downloading CT template to ${store}: ${desired}"
|
|
|
|
|
pveam download "$store" "$desired" >/dev/null
|
|
|
|
|
else
|
|
|
|
|
die "Template not available via pveam: ${desired}"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
# optional: trotzdem loggen
|
|
|
|
|
:
|
2026-01-09 17:12:49 +01:00
|
|
|
fi
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
echo "${store}:vztmpl/${desired}"
|
2026-01-09 17:12:49 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 18:53:26 +01:00
|
|
|
pve_build_net0() {
|
2026-01-11 11:50:35 +01:00
|
|
|
local bridge="$1"
|
|
|
|
|
local ipcfg="$2"
|
|
|
|
|
local vlan="${3:-}"
|
|
|
|
|
|
|
|
|
|
local base="name=eth0,bridge=${bridge},ip=${ipcfg}"
|
|
|
|
|
if [[ -n "${vlan}" && "${vlan}" != "0" ]]; then
|
|
|
|
|
base="${base},tag=${vlan}"
|
2026-01-09 18:53:26 +01:00
|
|
|
fi
|
2026-01-11 11:50:35 +01:00
|
|
|
echo "$base"
|
2026-01-09 18:53:26 +01:00
|
|
|
}
|
2026-01-09 17:12:49 +01:00
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# CTID nach "Unixzeit - 1e9" (Kundensicher bis 2038) + lokale Kollisionsprüfung
|
|
|
|
|
pve_select_customer_ctid() {
|
|
|
|
|
need_cmd date pct awk sort tail
|
|
|
|
|
|
|
|
|
|
local base
|
|
|
|
|
base="$(($(date +%s) - 1000000000))"
|
|
|
|
|
|
|
|
|
|
# Falls lokal belegt, hochzählen bis frei
|
|
|
|
|
local ctid="$base"
|
|
|
|
|
while pct status "$ctid" >/dev/null 2>&1; do
|
|
|
|
|
ctid="$((ctid + 1))"
|
|
|
|
|
done
|
|
|
|
|
echo "$ctid"
|
2026-01-09 18:53:26 +01:00
|
|
|
}
|
2026-01-09 17:12:49 +01:00
|
|
|
|
2026-01-09 20:09:29 +01:00
|
|
|
pct_wait_for_ip() {
|
2026-01-09 18:53:26 +01:00
|
|
|
local ctid="$1"
|
2026-01-11 11:50:35 +01:00
|
|
|
local tries="${2:-60}"
|
|
|
|
|
local i=0
|
2026-01-09 18:53:26 +01:00
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
need_cmd pct awk grep
|
|
|
|
|
|
|
|
|
|
while (( i < tries )); do
|
|
|
|
|
# pct exec ip -4 addr show dev eth0 → robust bei DHCP
|
|
|
|
|
local ip=""
|
|
|
|
|
ip="$(pct exec "$ctid" -- bash -lc "ip -4 -o addr show dev eth0 | awk '{print \$4}' | cut -d/ -f1 | head -n1" 2>/dev/null || true)"
|
2026-01-09 18:53:26 +01:00
|
|
|
if [[ -n "$ip" ]]; then
|
|
|
|
|
echo "$ip"
|
|
|
|
|
return 0
|
2026-01-09 17:12:49 +01:00
|
|
|
fi
|
2026-01-09 20:09:29 +01:00
|
|
|
sleep 1
|
2026-01-11 11:50:35 +01:00
|
|
|
((i++))
|
2026-01-09 17:12:49 +01:00
|
|
|
done
|
2026-01-09 18:53:26 +01:00
|
|
|
return 1
|
2026-01-09 17:12:49 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
pct_exec() {
|
|
|
|
|
local ctid="$1"; shift
|
|
|
|
|
pct exec "$ctid" -- bash -lc "$*"
|
2026-01-09 18:53:26 +01:00
|
|
|
}
|
2026-01-09 17:12:49 +01:00
|
|
|
|
2026-01-11 11:50:35 +01:00
|
|
|
# Locale fix (Debian 12)
|
|
|
|
|
pct_fix_locales() {
|
|
|
|
|
local ctid="$1"
|
|
|
|
|
local locale="${2:-de_DE.UTF-8}"
|
|
|
|
|
pct_exec "$ctid" "export DEBIAN_FRONTEND=noninteractive; apt-get update -y"
|
|
|
|
|
pct_exec "$ctid" "export DEBIAN_FRONTEND=noninteractive; apt-get install -y locales"
|
|
|
|
|
pct_exec "$ctid" "sed -i 's/^# *${locale} UTF-8/${locale} UTF-8/' /etc/locale.gen || true"
|
|
|
|
|
pct_exec "$ctid" "locale-gen ${locale} >/dev/null"
|
|
|
|
|
pct_exec "$ctid" "update-locale LANG=${locale} LC_ALL=${locale}"
|
2026-01-09 20:09:29 +01:00
|
|
|
}
|
|
|
|
|
|