#!/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