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