216 lines
8.3 KiB
Bash
216 lines
8.3 KiB
Bash
#!/bin/sh
|
|
# shellcheck shell=sh
|
|
#
|
|
# Treiber für den Vorlagenbau, wenn er aus der CluPilot-Pipeline kommt.
|
|
#
|
|
# `App\Provisioning\Steps\Host\BuildVmTemplate` lädt diese Datei zusammen mit
|
|
# `template.sh` und `assets/docker-compose.yml` auf den Host und startet sie
|
|
# abgekoppelt. Die eigentliche Arbeit macht `template.sh` — hier steht nur, in
|
|
# welcher Reihenfolge, und wie der Fortschritt zurückgemeldet wird.
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Warum das Skript hochgeladen und nicht in PHP nachgebaut wird
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# `template.sh` prüft drei Fallen, die je einen bezahlten Auftrag gekostet
|
|
# haben. Eine zweite Fassung davon in PHP wären zwei Installationen, die bei
|
|
# jeder Proxmox-Version nachgezogen werden müssten — und die zweite fiele erst
|
|
# auf, wenn jemand sie benutzt. Es gibt eine Fassung, und sie liegt eine Datei
|
|
# weiter.
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Rückmeldung
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Im Arbeitsverzeichnis:
|
|
#
|
|
# state running | ok | failed
|
|
# pid die PID dieses Skripts — und, weil es per `setsid` gestartet
|
|
# wird, zugleich die seiner PROZESSGRUPPE. Das ist der Unterschied
|
|
# zwischen „Aufgeben" und „Aufgeben, während der Bau weiterläuft":
|
|
# das Skript wartet die meiste Zeit auf ein virt-customize, und nur
|
|
# über die Gruppe ist das mit zu beenden.
|
|
# phase die laufende Phase, für die Fortschrittszeile in der Konsole
|
|
# note der Grund im Fehlerfall
|
|
# build.log alles
|
|
#
|
|
# `state` allein ist keine Aussage über den Lauf: stirbt das Skript, bleibt dort
|
|
# für immer `running` stehen, weil niemand mehr da ist, der es ändert. Deshalb
|
|
# fragt der Schritt zusätzlich `kill -0` gegen `pid` — „läuft noch" heißt, der
|
|
# Prozess lebt.
|
|
|
|
set -u
|
|
|
|
CLUPILOT_WORK_DIR="${CLUPILOT_WORK_DIR:-/var/lib/clupilot/template-build}"
|
|
|
|
# Als ALLERERSTES, vor jedem Einlesen und jeder Prüfung: der Startbefehl wartet
|
|
# darauf, dass hier etwas steht, und bis dahin gilt der Bau als noch nicht
|
|
# angelaufen. `$$` und nicht `$!` auf der anderen Seite, weil `setsid`
|
|
# dazwischen liegt — und weil `setsid` daraus eine eigene Sitzung macht, ist
|
|
# diese Zahl zugleich die Prozessgruppe, an die ein Abbruch geschickt wird.
|
|
echo $$ > "${CLUPILOT_WORK_DIR}/pid"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Die vier Helfer, die template.sh erwartet
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Eigene, knappe Fassung statt `clupilot-bootstrap.sh` anzufassen: das ist der
|
|
# stillgelegte Weg, und diese vier sind curl-Hüllen ohne
|
|
# Entwurfsentscheidung. Die 252 Zeilen mit den Fallen bleiben die eine Fassung,
|
|
# und nur darum ging es.
|
|
|
|
log() {
|
|
printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$1"
|
|
}
|
|
|
|
die() {
|
|
log "$1"
|
|
exit 1
|
|
}
|
|
|
|
http_get() {
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsSL --retry 3 --retry-delay 2 "$1"
|
|
else
|
|
wget -qO- "$1"
|
|
fi
|
|
}
|
|
|
|
http_download() {
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsSL --retry 3 --retry-delay 2 -o "$2" "$1"
|
|
else
|
|
wget -qO "$2" "$1"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Rückmeldung
|
|
# ---------------------------------------------------------------------------
|
|
|
|
phase() {
|
|
printf '%s' "$1" > "${CLUPILOT_WORK_DIR}/phase"
|
|
log "--- $1"
|
|
}
|
|
|
|
fail() {
|
|
printf '%s' "$1" > "${CLUPILOT_WORK_DIR}/note"
|
|
printf 'failed' > "${CLUPILOT_WORK_DIR}/state"
|
|
log "ABBRUCH: $1"
|
|
exit 1
|
|
}
|
|
|
|
# Ein Abbruch, den keine Zeile hier abgefangen hat (kein Speicher, ein Signal),
|
|
# darf nicht als `running` liegen bleiben und den Schritt gegen eine Leiche
|
|
# pollen lassen. Der Schritt erkennt das zwar auch an `kill -0`, aber ein Grund
|
|
# ist besser als eine Vermutung.
|
|
on_exit() {
|
|
_code=$?
|
|
if [ "$_code" -ne 0 ] && [ "$(cat "${CLUPILOT_WORK_DIR}/state" 2>/dev/null)" = 'running' ]; then
|
|
printf 'Der Bau brach unerwartet ab (Rückgabewert %s); siehe build.log' "$_code" \
|
|
> "${CLUPILOT_WORK_DIR}/note"
|
|
printf 'failed' > "${CLUPILOT_WORK_DIR}/state"
|
|
fi
|
|
}
|
|
trap on_exit EXIT
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ablauf
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cd "$CLUPILOT_WORK_DIR" || die "Arbeitsverzeichnis ${CLUPILOT_WORK_DIR} fehlt"
|
|
|
|
# CLUPILOT_TEMPLATE_VMIDS und alles andere Veränderliche schreibt der Schritt.
|
|
# shellcheck source=/dev/null
|
|
. "${CLUPILOT_WORK_DIR}/env"
|
|
# shellcheck source=lib/template.sh
|
|
. "${CLUPILOT_WORK_DIR}/template.sh"
|
|
|
|
CLUPILOT_TEMPLATE_VMIDS="${CLUPILOT_TEMPLATE_VMIDS:-9000}"
|
|
|
|
log "Vorlagenbau für VMID(s): ${CLUPILOT_TEMPLATE_VMIDS}"
|
|
|
|
# Der billigste Test zuerst. Ein df vorab ist billiger als ein Abbruch nach
|
|
# zwanzig Minuten mitten im virt-customize.
|
|
phase 'Platz prüfen'
|
|
check_build_space "$CLUPILOT_WORK_DIR" \
|
|
|| fail "Zu wenig Platz unter ${CLUPILOT_WORK_DIR} für Abbild, Arbeitskopie und importierte Platte (${CLUPILOT_TEMPLATE_MIN_FREE_MB} MB nötig)"
|
|
|
|
phase 'libguestfs-tools einbauen'
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
apt-get install -y libguestfs-tools >> "${CLUPILOT_WORK_DIR}/apt.log" 2>&1 \
|
|
|| fail 'libguestfs-tools ließ sich nicht installieren — ohne virt-customize entsteht keine Vorlage'
|
|
|
|
phase 'Ablage für Platten sicherstellen'
|
|
ensure_image_storage \
|
|
|| fail 'Keine Ablage auf diesem Host nimmt Platten auf, und die vorhandene ließ sich nicht erweitern'
|
|
|
|
_storage="$(detect_vm_storage)"
|
|
[ -n "$_storage" ] || fail 'kein Proxmox-Speicher gefunden, der Platten aufnimmt'
|
|
log "Ablage: ${_storage}"
|
|
|
|
# Verzeichnis-Ablagen liegen auf einem Dateisystem, das `df` beantworten kann;
|
|
# Block-Ablagen haben keinen Pfad und werden übersprungen.
|
|
_storage_path="$(storage_table | awk -v n="$_storage" '$1 == n { print $4 }')"
|
|
check_build_space "$_storage_path" \
|
|
|| fail "Zu wenig Platz auf der Ablage ${_storage} (${_storage_path}) für die importierte Platte"
|
|
|
|
_image="${CLUPILOT_WORK_DIR}/${CLUPILOT_CLOUD_IMAGE}"
|
|
if [ ! -f "$_image" ]; then
|
|
phase 'Cloud-Abbild laden'
|
|
fetch_cloud_image "$_image" \
|
|
|| fail 'Debian-Cloud-Abbild nicht ladbar oder Prüfsumme falsch'
|
|
else
|
|
log 'Cloud-Abbild liegt bereits geprüft vor'
|
|
fi
|
|
|
|
# Falle 2, VOR dem Umbauen: taugt der Aufbau überhaupt?
|
|
phase 'Aufbau des Abbilds prüfen'
|
|
verify_image_layout "$_image" \
|
|
|| fail 'Der Aufbau des Cloud-Abbilds erfüllt Falle 2 nicht (LVM oder Wurzel nicht als letzte Partition)'
|
|
|
|
# Umgebaut wird eine KOPIE. Das geprüfte Abbild bleibt unangetastet liegen, sonst
|
|
# erbt ein Wiederanlauf ein halb umgebautes Abbild und hält es für fertig
|
|
# geladen — `[ ! -f "$_image" ]` oben kann den Unterschied nicht sehen.
|
|
phase 'Arbeitskopie ziehen'
|
|
_build="${CLUPILOT_WORK_DIR}/build.qcow2"
|
|
rm -f "$_build"
|
|
cp "$_image" "$_build" || fail 'Arbeitskopie des Abbilds ließ sich nicht anlegen'
|
|
|
|
_compose="${CLUPILOT_WORK_DIR}/docker-compose.yml"
|
|
[ -f "$_compose" ] || fail "docker-compose.yml fehlt unter ${CLUPILOT_WORK_DIR}"
|
|
|
|
phase 'Abbild umbauen (Docker, Compose, Gastagent)'
|
|
customise_cloud_image "$_build" "$_compose" \
|
|
|| fail "Abbild ließ sich nicht umbauen; siehe ${CLUPILOT_WORK_DIR}/virt-customize.log"
|
|
|
|
# Fallen 1, 3 und 4, NACH dem Umbauen: virt-customize meldet auch dann Erfolg,
|
|
# wenn apt ein Paket stillschweigend nicht installiert hat.
|
|
phase 'Abbildinhalt prüfen'
|
|
verify_image_contents "$_build" \
|
|
|| fail 'Das fertige Abbild erfüllt Falle 1, 3 oder 4 nicht — siehe build.log'
|
|
|
|
for _vmid in $CLUPILOT_TEMPLATE_VMIDS; do
|
|
phase "Vorlage ${_vmid} anlegen"
|
|
|
|
# create_proxmox_template liest die VMID aus der globalen Variablen.
|
|
CLUPILOT_TEMPLATE_VMID="$_vmid"
|
|
|
|
create_proxmox_template "$_build" "$_storage" \
|
|
|| fail "Vorlage ${_vmid} ließ sich nicht anlegen; siehe ${CLUPILOT_WORK_DIR}/qm.log"
|
|
|
|
# Das Merkmal, nicht die Existenz. Eine VM, die zufällig so heißt, ist keine
|
|
# Vorlage.
|
|
template_is_really_a_template \
|
|
|| fail "VMID ${_vmid} existiert, meldet aber nicht template: 1"
|
|
|
|
log "Vorlage ${_vmid} auf ${_storage} fertig, alle vier Fallen geprüft"
|
|
done
|
|
|
|
rm -f "$_build"
|
|
|
|
phase 'fertig'
|
|
printf 'ok' > "${CLUPILOT_WORK_DIR}/state"
|
|
log 'Vorlagenbau abgeschlossen'
|
|
exit 0
|