Drei Betriebsskripte fuer Linux, im Auslieferungspaket neben den Programmen. setup.sh richtet eine Anlage vollstaendig ein: PostgreSQL auf Wunsch mit (apt/dnf/yum/zypper/pacman), Dienstkonto, Verschluesselungsschluessel, Schema, erster Administrator, gehaertetes Repository, gehaertete systemd-Einheit. Jeder Schritt vermerkt, was er angelegt hat; bricht der Lauf ab, wird genau das zurueckgebaut und nichts sonst. Eine bestehende Installation wird nicht ueberschrieben — dafuer gibt es update.sh, und der Unterschied ist, dass ein Update vorher sichert. update.sh haelt die Reihenfolge ein, um die es geht: sichern, anhalten, tauschen, migrieren, starten, pruefen. Kommt der Dienst danach nicht hoch, holt es die vorige Fassung zurueck. Ohne pg_dump wird gar nicht erst begonnen — ohne Sicherung gibt es nach einer misslungenen Migration keinen Weg zurueck. Repository und Verschluesselungsschluessel bleiben unberuehrt. uninstall.sh entfernt standardmaessig NUR Dienst und Programme. Datenbank, Repository und Konfiguration bleiben liegen; jede dieser Loeschungen verlangt ein woertlich getipptes Bestaetigungswort an einem Terminal. Ein Deinstallationsskript, das nebenbei die Backups mitnimmt, vernichtet genau das, wofuer jemand jahrelang Speicher bezahlt hat. Gegen Debian 12 im Container gefahren — Installation, Anmeldung, Repository eingetragen, Loeschschutz gemessen (advisory auf overlayfs, richtig), echter Sicherungslauf, Update mit unveraendertem Bestand, beide Abbauarten. Drei Fehler dabei gefunden und behoben, alle derselben Art: - "tr </dev/urandom | head -c 32" und "psql | grep -q": Der frueh geschlossene Pipe schickt dem Schreiber SIGPIPE, und mit "set -o pipefail" bricht das Skript mitten in der Einrichtung ab, ohne erkennbaren Grund. - update.sh las die laufende Fassung mit "grep -o" aus der Antwort von /health/ready. Die enthaelt gar kein Versionsfeld; grep endet mit 1, und das Skript nahm ein GELUNGENES Update wieder zurueck — wegen einer Zeile, die nur der Ausgabe dient. Dazu: SYNCOVA_ADMIN_PASSWORD stand in der Doku und existiert nicht — das Passwort kommt ueber die Standardeingabe. Und "syncova-repo break-lock" fehlte zwar nicht mehr, aber der Test auf die Uebereinstimmung der drei Skripte ist neu: Drei Skripte, die sich ueber den Installationsort uneinig sind, ergeben eine Anlage, die sich nicht mehr entfernen laesst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
6.8 KiB
Bash
Executable File
177 lines
6.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Baut das Auslieferungspaket der Version V1 (Phase 23).
|
|
#
|
|
# Das Paket enthält alles, was §25 des Umsetzungsplans verlangt: Server,
|
|
# Oberfläche, beide Agenten, Repository-Werkzeuge, Migrationen und die
|
|
# Dokumentation. Es ist bewusst **ein** Verzeichnisbaum je Zielplattform und
|
|
# kein Installationsprogramm: Ein Betreiber soll sehen können, was er auspackt.
|
|
#
|
|
# Aufruf: make release (oder scripts/build-release.sh)
|
|
|
|
set -euo pipefail
|
|
|
|
# releaseVersion ist die Version des Pakets.
|
|
#
|
|
# Ohne Git-Tag der Kurz-Hash — nie eine erfundene Nummer: Ein Paket, dessen
|
|
# Version man nicht auf einen Stand zurückführen kann, ist bei einer
|
|
# Fehlersuche wertlos.
|
|
releaseVersion="${BUILD_VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo "0.1.0-dev")}"
|
|
|
|
# distributionRoot ist das Ausgabeverzeichnis.
|
|
distributionRoot="dist"
|
|
|
|
# targetPlatforms sind die ausgelieferten Zielplattformen.
|
|
#
|
|
# Das Format ist "betriebssystem/architektur/dateiendung". macOS ist bewusst
|
|
# nicht dabei: Dort wurde entwickelt, aber der Plan nennt es nicht als Ziel, und
|
|
# eine Plattform auszuliefern, für die es kein Betriebskonzept gibt, weckt
|
|
# Erwartungen, die niemand einlöst.
|
|
targetPlatforms=(
|
|
"linux/amd64/"
|
|
"linux/arm64/"
|
|
"windows/amd64/.exe"
|
|
)
|
|
|
|
# serverCommands sind die Programme der Control Plane.
|
|
serverCommands=(
|
|
"syncova-api:apps/api/cmd/syncova-api"
|
|
"syncova-migrate:apps/api/cmd/syncova-migrate"
|
|
"syncova-admin:apps/api/cmd/syncova-admin"
|
|
"syncova-repo:apps/api/cmd/syncova-repo"
|
|
"syncova-dr:apps/api/cmd/syncova-dr"
|
|
"syncova-proxmox:apps/api/cmd/syncova-proxmox"
|
|
"syncova-bench:apps/api/cmd/syncova-bench"
|
|
)
|
|
|
|
# agentCommand ist das Programm des Agenten.
|
|
agentCommand="syncova-agent:apps/agent/cmd/syncova-agent"
|
|
|
|
# windowsServerCommands sind die Programme, die es auch für Windows gibt.
|
|
#
|
|
# Der vollständige Server läuft unter Linux; für Windows wird der Agent
|
|
# ausgeliefert und dazu die Werkzeuge, die ein Betreiber dort tatsächlich
|
|
# braucht. Alles andere zu übersetzen, nur weil es übersetzt, ergäbe Programme
|
|
# ohne Betriebskonzept.
|
|
windowsServerCommands=(
|
|
"syncova-repo:apps/api/cmd/syncova-repo"
|
|
)
|
|
|
|
echo "Syncova V1 — Auslieferungspaket ${releaseVersion}"
|
|
echo
|
|
|
|
rm -rf "${distributionRoot}"
|
|
mkdir -p "${distributionRoot}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Oberfläche
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Zuerst, weil sie am längsten braucht und weil ein Fehler hier den ganzen Lauf
|
|
# ungültig macht: Ein Paket ohne Oberfläche ist kein vollständiges Paket.
|
|
|
|
echo "==> Weboberfläche"
|
|
|
|
if [[ ! -d apps/web/node_modules ]]; then
|
|
echo " Abhängigkeiten fehlen — 'make web-install' zuerst ausführen." >&2
|
|
exit 1
|
|
fi
|
|
|
|
(cd apps/web && npm run build >/dev/null)
|
|
|
|
if [[ ! -f apps/web/dist/index.html ]]; then
|
|
echo " Der Bau der Oberfläche hat kein index.html erzeugt." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Programme je Zielplattform
|
|
# ---------------------------------------------------------------------------
|
|
|
|
for platformEntry in "${targetPlatforms[@]}"; do
|
|
IFS='/' read -r targetOS targetArch binaryExtension <<< "${platformEntry}"
|
|
|
|
packageName="syncova-${releaseVersion}-${targetOS}-${targetArch}"
|
|
packageRoot="${distributionRoot}/${packageName}"
|
|
|
|
echo "==> ${targetOS}/${targetArch}"
|
|
|
|
mkdir -p "${packageRoot}/bin" "${packageRoot}/migrations" "${packageRoot}/docs" \
|
|
"${packageRoot}/deployment"
|
|
|
|
# Welche Programme in dieses Paket gehören.
|
|
commandList=("${agentCommand}")
|
|
|
|
if [[ "${targetOS}" == "windows" ]]; then
|
|
commandList+=("${windowsServerCommands[@]}")
|
|
else
|
|
commandList+=("${serverCommands[@]}")
|
|
fi
|
|
|
|
for commandEntry in "${commandList[@]}"; do
|
|
commandName="${commandEntry%%:*}"
|
|
commandPath="${commandEntry#*:}"
|
|
|
|
# CGO aus: Ein statisch gebundenes Programm läuft auf jeder
|
|
# Distribution, unabhängig von deren libc-Version. Ohne diesen Schalter
|
|
# scheitert der Start auf einem älteren System mit einer Meldung über
|
|
# GLIBC, die niemand einem Backupprogramm zuordnet.
|
|
CGO_ENABLED=0 GOOS="${targetOS}" GOARCH="${targetArch}" \
|
|
go build -trimpath -ldflags "-s -w -X main.buildVersion=${releaseVersion}" \
|
|
-o "${packageRoot}/bin/${commandName}${binaryExtension}" "./${commandPath}"
|
|
|
|
echo " ${commandName}${binaryExtension}"
|
|
done
|
|
|
|
# Die Oberfläche gehört nur zum Serverpaket.
|
|
if [[ "${targetOS}" != "windows" ]]; then
|
|
mkdir -p "${packageRoot}/web"
|
|
cp -R apps/web/dist/. "${packageRoot}/web/"
|
|
fi
|
|
|
|
# Migrationen als Dateien **zusätzlich** zur Einbettung: Sie stecken bereits
|
|
# im Binary, aber ein Betreiber, der eine Abfrage nachvollziehen will, soll
|
|
# sie lesen können, ohne den Quellbestand zu holen.
|
|
cp migrations/*.sql "${packageRoot}/migrations/"
|
|
cp migrations/checksums.txt "${packageRoot}/migrations/"
|
|
|
|
cp -R docs/. "${packageRoot}/docs/"
|
|
cp README.md CHANGELOG.md "${packageRoot}/"
|
|
|
|
# Die Betriebsskripte gehoeren neben die Programme, nicht in einen
|
|
# Unterordner: Wer ein Paket auspackt, soll setup.sh sehen, ohne zu suchen.
|
|
# Fuer Windows entfallen sie — dort gibt es weder systemd noch apt.
|
|
if [[ "${targetOS}" != "windows" ]]; then
|
|
install -m 0755 scripts/setup.sh scripts/update.sh scripts/uninstall.sh "${packageRoot}/"
|
|
fi
|
|
cp deployment/docker-compose.yml "${packageRoot}/deployment/" 2>/dev/null || true
|
|
cp deployment/syncova-agent.service "${packageRoot}/deployment/" 2>/dev/null || true
|
|
|
|
# Prüfsummen je Paket. Sie liegen **im** Paket, damit sich der Inhalt auch
|
|
# dann prüfen lässt, wenn nur er übertragen wurde.
|
|
(cd "${packageRoot}" && find . -type f ! -name SHA256SUMS -exec shasum -a 256 {} \; \
|
|
| sed 's|\./||' | sort -k2 > SHA256SUMS)
|
|
|
|
# Archiv: tar für Unix, zip für Windows — das jeweils übliche Format. Ein
|
|
# tar.gz auf einem Windows-Server auszupacken verlangt Zusatzwerkzeug.
|
|
if [[ "${targetOS}" == "windows" ]]; then
|
|
(cd "${distributionRoot}" && zip -qr "${packageName}.zip" "${packageName}")
|
|
else
|
|
(cd "${distributionRoot}" && tar -czf "${packageName}.tar.gz" "${packageName}")
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Prüfsummen über die Archive
|
|
# ---------------------------------------------------------------------------
|
|
|
|
(cd "${distributionRoot}" && shasum -a 256 ./*.tar.gz ./*.zip 2>/dev/null \
|
|
| sed 's|\./||' | sort -k2 > SHA256SUMS)
|
|
|
|
echo
|
|
echo "Auslieferungspakete:"
|
|
(cd "${distributionRoot}" && ls -lh ./*.tar.gz ./*.zip 2>/dev/null \
|
|
| awk '{printf " %-52s %s\n", $9, $5}')
|
|
echo
|
|
echo "Prüfsummen: ${distributionRoot}/SHA256SUMS"
|