**Sicherungsart.** Bisher entschied der Executor allein: Liegt ein Elternbackup
vor, wird inkrementell gesichert. Jetzt waehlbar je Auftrag —
- `incremental` (Standard, bisheriges Verhalten),
- `always_full`, oder
- inkrementell **mit einem festen Volltag** ("immer freitags").
Migration 000014 mit drei CHECKs. Der dritte lehnt "immer voll" zusammen mit
einem Wochentag ab: Dann ist ohnehin jeder Lauf voll, und die Regel gehoert in
die Datenbank, weil im Code jede Stelle sie einhalten muesste — eine vergisst
es. Real geprueft: der Widerspruch wird abgewiesen.
Der Wochentag wird in der **Zeitzone des Zeitplans** bestimmt. Rechnete der
Server in UTC, bekaeme ein Betreiber in Berlin seine Vollsicherung am
Donnerstagabend und wunderte sich, warum sie freitags fehlt. Vier Tests, der
entscheidende durch Mutation als fangend bestaetigt.
Zur Einordnung, weil es leicht verwechselt wird: Der Platzbedarf steigt bei
"immer voll" **nicht** nennenswert — unveraenderte Bloecke werden dedupliziert
und liegen weiterhin nur einmal im Repository. Was steigt, ist die Laufzeit.
Steht so in der Maske.
**Aufnahme-Token zeigte "undefined".** Das Feld heisst `token`, nicht
`enrollment_token` — Letzteres ist der Name im *Anfrage*koerper der
Registrierung. Der dritte Formfehler dieser Art; alle konsumierten Endpunkte
sind jetzt gegen den laufenden Dienst abgeglichen.
**Der Aufnahmedialog** hat jetzt eine vollstaendige Anleitung fuer Linux und
Windows mit fertig ausgefuellten Befehlen — Serveradresse und Token eingesetzt,
je Schritt einzeln kopierbar. Eine Anleitung mit Platzhaltern fuehrt
zuverlaessig dazu, dass jemand `<token>` woertlich einsetzt und dann eine
Fehlermeldung sucht, die nichts mit seinem Problem zu tun hat. Dazu die beiden
Stolperstellen: `--state` will eine Datei, und der Agent braucht Schreibzugriff
aufs Repository. Beim Windows-Weg steht dabei, dass der Dienst nie auf echter
Hardware lief.
**update.sh ruestet die Wiederherstellungsflaeche nach** — anlegen und in
ReadWritePaths eintragen. Ein Schritt, den man von Hand ausfuehren muss, wird
uebersehen und faellt erst im Ernstfall auf.
84 Tests im Frontend, alle Go-Tests gruen, shellcheck sauber.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
524 lines
20 KiB
Bash
Executable File
524 lines
20 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Aktualisiert eine vorhandene Syncova-Installation.
|
|
#
|
|
# Die Reihenfolge ist nicht verhandelbar und der Grund fuer dieses Skript:
|
|
#
|
|
# sichern -> anhalten -> Programme tauschen -> migrieren -> starten -> pruefen
|
|
#
|
|
# Laeuft der Dienst noch, waehrend sich das Schema aendert, arbeitet er auf
|
|
# einem Stand, den er nicht kennt. Und ohne die Sicherung davor gibt es keinen
|
|
# Weg zurueck: Ein Rollback nimmt genau eine Migration zuruck, und Daten in
|
|
# Tabellen, die es vorher nicht gab, sind danach weg.
|
|
#
|
|
# Scheitert ein Schritt, wird die vorige Fassung zurueckgeholt.
|
|
#
|
|
# ./update.sh prueft, fragt, aktualisiert
|
|
# ./update.sh --pruefen sieht nur nach und aendert nichts
|
|
# ./update.sh --ja ohne Rueckfrage
|
|
#
|
|
# Das Repository wird nicht angefasst. Der Verschluesselungsschluessel auch
|
|
# nicht — er bleibt, wie er ist, sonst waeren alle Geheimnisse unlesbar.
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Feste Orte — gleichlautend in setup.sh und uninstall.sh
|
|
# ---------------------------------------------------------------------------
|
|
|
|
readonly installationRoot="/opt/syncova"
|
|
readonly configurationDirectory="/etc/syncova"
|
|
readonly configurationFile="${configurationDirectory}/syncova.env"
|
|
readonly serviceName="syncova-api"
|
|
readonly serviceAccount="syncova"
|
|
readonly backupDirectory="/var/backups/syncova"
|
|
|
|
# restoreDirectory ist die Flaeche, auf die zurueckgeschrieben werden darf.
|
|
#
|
|
# setup.sh legt sie an; bei einer Anlage aus einer aelteren Fassung fehlt sie.
|
|
# Ohne sie scheitert jede Wiederherstellung an ProtectSystem=strict — und der
|
|
# Betreiber sucht den Fehler bei den Rechten des Zielverzeichnisses.
|
|
readonly restoreDirectory="/srv/syncova-restore"
|
|
|
|
# previousReleaseRoot nimmt die abgeloeste Fassung auf.
|
|
#
|
|
# Sie bleibt liegen, bis die neue nachweislich laeuft. Ein Update, das die alte
|
|
# Fassung sofort loescht, hat keinen Rueckweg.
|
|
readonly previousReleaseRoot="${installationRoot}.vorherige"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ausgabe
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [[ -t 1 ]]; then
|
|
readonly colorReset=$'\033[0m'
|
|
readonly colorBold=$'\033[1m'
|
|
readonly colorRed=$'\033[31m'
|
|
readonly colorGreen=$'\033[32m'
|
|
readonly colorYellow=$'\033[33m'
|
|
else
|
|
readonly colorReset='' colorBold='' colorRed='' colorGreen='' colorYellow=''
|
|
fi
|
|
|
|
writeStep() { printf '%s==>%s %s\n' "${colorBold}" "${colorReset}" "$1"; }
|
|
writeDetail() { printf ' %s\n' "$1"; }
|
|
writeSuccess() { printf ' %s✓%s %s\n' "${colorGreen}" "${colorReset}" "$1"; }
|
|
writeWarning() { printf ' %s!%s %s\n' "${colorYellow}" "${colorReset}" "$1" >&2; }
|
|
|
|
abortWithMessage() {
|
|
printf '\n%sAbbruch:%s %s\n' "${colorRed}${colorBold}" "${colorReset}" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
|
|
# serviceIsReachable prueft, ob eine Adresse mit HTTP 200 antwortet.
|
|
#
|
|
# Drei Wege, weil keiner ueberall vorhanden ist. Genau daran ist die Einrichtung
|
|
# in Issue #2 gescheitert: Der Dienst lief einwandfrei — Protokoll und
|
|
# Zeitstempel belegen es —, aber auf dem System war kein curl installiert. Die
|
|
# Bereitschaftspruefung hielt das fuer einen gescheiterten Start und baute eine
|
|
# funktionierende Anlage wieder zurueck.
|
|
#
|
|
# Ein Einrichtungsskript darf nicht voraussetzen, was es nicht selbst mitbringt.
|
|
serviceIsReachable() {
|
|
local requestURL="$1"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
curl -fsS --max-time 5 "${requestURL}" >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
# wget endet bei 4xx und 5xx mit einem Fehler — dasselbe Verhalten wie
|
|
# "curl -f".
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -q -O /dev/null --timeout=5 "${requestURL}" >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
bashHTTPStatusIsOK "${requestURL}"
|
|
}
|
|
|
|
# bashHTTPStatusIsOK spricht HTTP ohne jedes Zusatzwerkzeug.
|
|
#
|
|
# Der letzte Ausweg: /dev/tcp gehoert zur Bash selbst. Damit laesst sich die
|
|
# Bereitschaft auch auf einem System pruefen, auf dem weder curl noch wget
|
|
# liegen — und das ist bei einem schlanken Serverabbild der Normalfall, nicht
|
|
# die Ausnahme.
|
|
bashHTTPStatusIsOK() {
|
|
local strippedURL="${1#http://}"
|
|
local hostAndPort="${strippedURL%%/*}"
|
|
local requestPath="/${strippedURL#*/}"
|
|
|
|
# Ohne Pfadanteil liefert die Ersetzung oben die Adresse zurueck.
|
|
[[ "${requestPath}" == "/${hostAndPort}" ]] && requestPath="/"
|
|
|
|
local targetHost="${hostAndPort%%:*}"
|
|
local targetPort="${hostAndPort##*:}"
|
|
[[ "${targetPort}" == "${targetHost}" ]] && targetPort=80
|
|
|
|
exec 3<>"/dev/tcp/${targetHost}/${targetPort}" 2>/dev/null || return 1
|
|
|
|
printf 'GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n' \
|
|
"${requestPath}" "${hostAndPort}" >&3
|
|
|
|
local statusLine=""
|
|
read -r -t 5 statusLine <&3
|
|
|
|
exec 3<&- 3>&- 2>/dev/null
|
|
|
|
[[ "${statusLine}" == *" 200 "* ]]
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Schalter
|
|
# ---------------------------------------------------------------------------
|
|
|
|
checkOnly="nein"
|
|
assumeYes="nein"
|
|
packageDirectory=""
|
|
|
|
showUsage() {
|
|
cat <<'USAGE'
|
|
Verwendung:
|
|
update.sh [--paket <verzeichnis>] [--pruefen] [--ja]
|
|
|
|
Schalter:
|
|
--paket <verzeichnis> Entpacktes neues Paket (Vorgabe: Verzeichnis des Skripts)
|
|
--pruefen Nur nachsehen: installierte und neue Fassung vergleichen
|
|
--ja Ohne Rueckfrage aktualisieren
|
|
--hilfe Diese Uebersicht
|
|
|
|
Vor dem Tausch werden Datenbank und Konfiguration gesichert nach:
|
|
/var/backups/syncova
|
|
|
|
Nicht angetastet werden das Repository und der Verschluesselungsschluessel.
|
|
USAGE
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--paket) packageDirectory="${2:-}"; shift 2 ;;
|
|
--pruefen) checkOnly="ja"; shift ;;
|
|
--ja) assumeYes="ja"; shift ;;
|
|
--hilfe|-h|--help) showUsage; exit 0 ;;
|
|
*) showUsage >&2; abortWithMessage "unbekannter Schalter: $1" ;;
|
|
esac
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Was ist installiert?
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '\n%sSyncova Backups — Aktualisierung%s\n\n' "${colorBold}" "${colorReset}"
|
|
|
|
writeStep "Vorhandene Installation"
|
|
|
|
if [[ "$(id -u)" -ne 0 ]]; then
|
|
abortWithMessage "Dieses Skript braucht root-Rechte (sudo ./update.sh)."
|
|
fi
|
|
|
|
if [[ ! -x "${installationRoot}/bin/syncova-api" ]]; then
|
|
abortWithMessage "Unter ${installationRoot} ist keine Installation zu finden.
|
|
Zum Einrichten: ./setup.sh"
|
|
fi
|
|
|
|
if [[ ! -f "${configurationFile}" ]]; then
|
|
abortWithMessage "Die Konfiguration ${configurationFile} fehlt.
|
|
Ohne sie laesst sich weder sichern noch migrieren."
|
|
fi
|
|
|
|
installedVersion="$("${installationRoot}/bin/syncova-api" --version 2>/dev/null | awk '{print $2}')"
|
|
[[ -n "${installedVersion}" ]] || installedVersion="unbekannt"
|
|
|
|
writeSuccess "Installiert: ${installedVersion}"
|
|
|
|
# Der Schemastand gehoert zur Bestandsaufnahme: Steht er auf "unsauber", ist
|
|
# eine fruehere Migration abgebrochen, und ein Update darauf waere fahrlaessig.
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "${configurationFile}"
|
|
set +a
|
|
|
|
schemaState="$("${installationRoot}/bin/syncova-migrate" status 2>&1 | tail -2 | tr '\n' ' ')"
|
|
writeDetail "Schema: ${schemaState}"
|
|
|
|
if printf '%s' "${schemaState}" | grep -qi "unsauber\|dirty"; then
|
|
printf '\n'
|
|
writeWarning "Der Schemastand ist als unsauber vermerkt."
|
|
writeDetail "Eine fruehere Migration ist abgebrochen. Bringen Sie das Schema"
|
|
writeDetail "zuerst in Ordnung ('syncova-migrate force <n>' nach Ruecksprache),"
|
|
writeDetail "bevor Sie aktualisieren."
|
|
abortWithMessage "Ein Update auf einem unsauberen Schema wird nicht durchgefuehrt."
|
|
fi
|
|
|
|
serviceWasRunning="nein"
|
|
if systemctl is-active --quiet "${serviceName}" 2>/dev/null; then
|
|
serviceWasRunning="ja"
|
|
writeSuccess "Dienst laeuft"
|
|
else
|
|
writeDetail "Der Dienst laeuft derzeit nicht."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Was ist neu?
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '\n'
|
|
writeStep "Neues Paket"
|
|
|
|
if [[ -z "${packageDirectory}" ]]; then
|
|
packageDirectory="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
fi
|
|
|
|
if [[ ! -x "${packageDirectory}/bin/syncova-api" ]]; then
|
|
abortWithMessage "Unter ${packageDirectory} liegt kein Auslieferungspaket.
|
|
Geben Sie es mit --paket <verzeichnis> an."
|
|
fi
|
|
|
|
packageVersion="$("${packageDirectory}/bin/syncova-api" --version 2>/dev/null | awk '{print $2}')"
|
|
writeSuccess "Neu: ${packageVersion}"
|
|
|
|
if [[ -f "${packageDirectory}/SHA256SUMS" ]] && command -v sha256sum >/dev/null 2>&1; then
|
|
if ( cd "${packageDirectory}" && sha256sum --quiet -c SHA256SUMS >/dev/null 2>&1 ); then
|
|
writeSuccess "Pruefsummen stimmen"
|
|
else
|
|
abortWithMessage "Die Pruefsummen des neuen Pakets stimmen nicht.
|
|
Ein unvollstaendig uebertragenes Paket wird nicht eingespielt."
|
|
fi
|
|
fi
|
|
|
|
if [[ "${installedVersion}" == "${packageVersion}" ]]; then
|
|
printf '\n'
|
|
writeDetail "Installiert und neu sind dieselbe Fassung (${packageVersion})."
|
|
|
|
if [[ "${checkOnly}" == "ja" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${assumeYes}" != "ja" ]]; then
|
|
read -r -p " Trotzdem neu einspielen? [j/N]: " reinstallAnswer
|
|
[[ "${reinstallAnswer}" =~ ^[jJyY] ]] || { printf '\n Nichts zu tun.\n\n'; exit 0; }
|
|
else
|
|
printf '\n Nichts zu tun.\n\n'
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
if [[ "${checkOnly}" == "ja" ]]; then
|
|
printf '\n'
|
|
printf ' %s%s%s → %s%s%s\n\n' "${colorBold}" "${installedVersion}" "${colorReset}" \
|
|
"${colorBold}${colorGreen}" "${packageVersion}" "${colorReset}"
|
|
printf ' Zum Aktualisieren: %s\n\n' "$0"
|
|
exit 0
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Rueckfrage
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [[ "${assumeYes}" != "ja" ]]; then
|
|
printf '\n'
|
|
printf ' %s%s%s → %s%s%s\n\n' "${colorBold}" "${installedVersion}" "${colorReset}" \
|
|
"${colorBold}${colorGreen}" "${packageVersion}" "${colorReset}"
|
|
writeDetail "Vor dem Tausch werden Datenbank und Konfiguration gesichert."
|
|
writeDetail "Repository und Verschluesselungsschluessel bleiben unberuehrt."
|
|
writeDetail "Der Dienst ist waehrend des Updates kurz nicht erreichbar."
|
|
printf '\n'
|
|
|
|
read -r -p " Jetzt aktualisieren? [j/N]: " updateAnswer
|
|
[[ "${updateAnswer}" =~ ^[jJyY] ]] || { printf '\n Abgebrochen. Es wurde nichts geaendert.\n\n'; exit 0; }
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Sichern
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '\n'
|
|
writeStep "Sicherung vor dem Update"
|
|
|
|
install -d -m 0700 "${backupDirectory}"
|
|
|
|
backupTimestamp="$(date -u '+%Y%m%d-%H%M%S')"
|
|
backupPrefix="${backupDirectory}/${backupTimestamp}-vor-${packageVersion}"
|
|
|
|
# Die Konfiguration zuerst: Sie ist klein und enthaelt den Schluessel.
|
|
install -m 0600 "${configurationFile}" "${backupPrefix}.env"
|
|
writeSuccess "Konfiguration: ${backupPrefix}.env"
|
|
|
|
# Die Datenbank. Ohne pg_dump gibt es keinen Weg zurueck — deshalb bricht das
|
|
# Skript hier ab, statt "auf gut Glueck" weiterzumachen.
|
|
if ! command -v pg_dump >/dev/null 2>&1; then
|
|
printf '\n'
|
|
writeWarning "pg_dump ist nicht vorhanden."
|
|
writeDetail "Ohne Datenbanksicherung gibt es nach einer misslungenen Migration"
|
|
writeDetail "keinen Weg zurueck: Ein Rollback nimmt genau eine Migration"
|
|
writeDetail "zurueck, und Daten in neuen Tabellen sind danach weg."
|
|
printf '\n'
|
|
writeDetail "Installieren Sie postgresql-client, oder sichern Sie von Hand und"
|
|
writeDetail "starten Sie danach mit --ja erneut."
|
|
abortWithMessage "Ohne Datenbanksicherung wird nicht aktualisiert."
|
|
fi
|
|
|
|
databaseDumpFile="${backupPrefix}.dump"
|
|
|
|
if ! PGPASSWORD="${SYNCOVA_DB_PASSWORD:-}" pg_dump \
|
|
--host "${SYNCOVA_DB_HOST:-127.0.0.1}" \
|
|
--port "${SYNCOVA_DB_PORT:-5432}" \
|
|
--username "${SYNCOVA_DB_USER:-syncova}" \
|
|
--dbname "${SYNCOVA_DB_NAME:-syncova}" \
|
|
--format=custom --file "${databaseDumpFile}" 2>/tmp/syncova-pgdump.log; then
|
|
printf '\n'
|
|
writeWarning "Die Datenbanksicherung schlug fehl:"
|
|
head -5 /tmp/syncova-pgdump.log | sed 's/^/ /' >&2
|
|
rm -f /tmp/syncova-pgdump.log
|
|
abortWithMessage "Ohne Sicherung wird nicht aktualisiert."
|
|
fi
|
|
|
|
rm -f /tmp/syncova-pgdump.log
|
|
chmod 0600 "${databaseDumpFile}"
|
|
|
|
dumpSize="$(du -h "${databaseDumpFile}" | awk '{print $1}')"
|
|
writeSuccess "Datenbank: ${databaseDumpFile} (${dumpSize})"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Tauschen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# updateFailed holt die vorige Fassung zurueck.
|
|
#
|
|
# Aufgerufen aus dem trap. Die Reihenfolge ist die Umkehrung des Updates: erst
|
|
# den Dienst anhalten, dann die Programme zuruecktauschen, dann starten.
|
|
updateFailed() {
|
|
local exitCode=$?
|
|
[[ ${exitCode} -eq 0 ]] && exit 0
|
|
|
|
printf '\n%sDas Update wird zurueckgenommen.%s\n' "${colorYellow}${colorBold}" "${colorReset}" >&2
|
|
|
|
systemctl stop "${serviceName}" 2>/dev/null || true
|
|
|
|
if [[ -d "${previousReleaseRoot}" ]]; then
|
|
rm -rf "${installationRoot}"
|
|
mv "${previousReleaseRoot}" "${installationRoot}"
|
|
writeDetail "Die vorige Fassung ${installedVersion} liegt wieder an ihrem Platz."
|
|
fi
|
|
|
|
if [[ "${serviceWasRunning}" == "ja" ]]; then
|
|
systemctl start "${serviceName}" 2>/dev/null || \
|
|
writeWarning "Der Dienst liess sich nicht wieder starten: systemctl status ${serviceName}"
|
|
fi
|
|
|
|
printf '\n' >&2
|
|
writeDetail "Die Datenbank wurde NICHT zurueckgespielt — das geht nur von Hand:"
|
|
writeDetail ""
|
|
writeDetail " systemctl stop ${serviceName}"
|
|
writeDetail " pg_restore --clean --if-exists -d ${SYNCOVA_DB_NAME:-syncova} ${databaseDumpFile}"
|
|
writeDetail " systemctl start ${serviceName}"
|
|
writeDetail ""
|
|
writeDetail "Notwendig ist das nur, wenn die Migration bereits gelaufen war."
|
|
writeDetail "Der Schemastand sagt es: ${installationRoot}/bin/syncova-migrate status"
|
|
|
|
exit "${exitCode}"
|
|
}
|
|
|
|
trap updateFailed EXIT
|
|
|
|
printf '\n'
|
|
writeStep "Programme tauschen"
|
|
|
|
if [[ "${serviceWasRunning}" == "ja" ]]; then
|
|
systemctl stop "${serviceName}"
|
|
writeSuccess "Dienst angehalten"
|
|
fi
|
|
|
|
rm -rf "${previousReleaseRoot}"
|
|
mv "${installationRoot}" "${previousReleaseRoot}"
|
|
|
|
install -d -m 0755 "${installationRoot}"
|
|
cp -R "${packageDirectory}/bin" "${installationRoot}/"
|
|
[[ -d "${packageDirectory}/web" ]] && cp -R "${packageDirectory}/web" "${installationRoot}/"
|
|
[[ -d "${packageDirectory}/migrations" ]] && cp -R "${packageDirectory}/migrations" "${installationRoot}/"
|
|
[[ -d "${packageDirectory}/docs" ]] && cp -R "${packageDirectory}/docs" "${installationRoot}/"
|
|
[[ -d "${packageDirectory}/deployment" ]] && cp -R "${packageDirectory}/deployment" "${installationRoot}/"
|
|
chmod -R 0755 "${installationRoot}/bin"
|
|
|
|
[[ -f "${packageDirectory}/diagnose.sh" ]] && \
|
|
install -m 0755 "${packageDirectory}/diagnose.sh" "${installationRoot}/diagnose.sh"
|
|
|
|
printf '%s\n' "${packageVersion}" > "${installationRoot}/VERSION"
|
|
|
|
writeSuccess "Programme ausgetauscht"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. Migrieren
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '\n'
|
|
writeStep "Datenbankschema"
|
|
|
|
# Mit dem NEUEN Programm und bei angehaltenem Dienst. Beides gehoert zusammen:
|
|
# Die neue Fassung bringt ihre Migrationen mit, und ein laufender Dienst
|
|
# arbeitete waehrenddessen auf einem Stand, den er nicht kennt.
|
|
migrationOutput="$("${installationRoot}/bin/syncova-migrate" up 2>&1 | tail -1)"
|
|
writeDetail "${migrationOutput}"
|
|
|
|
if printf '%s' "${migrationOutput}" | grep -qi "error\|fehler"; then
|
|
abortWithMessage "Die Migration schlug fehl."
|
|
fi
|
|
|
|
writeSuccess "Schema aktuell"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6b. Wiederherstellungsflaeche nachruesten
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Sie kam erst mit rc8 dazu. Eine Anlage aus einer aelteren Fassung hat sie
|
|
# nicht, und ohne sie schlaegt jede Wiederherstellung fehl. Das Nachruesten
|
|
# gehoert hierher und nicht in eine Anleitung: Ein Schritt, den man von Hand
|
|
# ausfuehren muss, wird uebersehen — und faellt erst im Ernstfall auf.
|
|
|
|
printf '\n'
|
|
writeStep "Wiederherstellungsflaeche"
|
|
|
|
if [[ -d "${restoreDirectory}" ]]; then
|
|
writeDetail "Vorhanden: ${restoreDirectory}"
|
|
else
|
|
install -d -m 0750 -o "${serviceAccount}" -g "${serviceAccount}" "${restoreDirectory}"
|
|
writeSuccess "Angelegt: ${restoreDirectory}"
|
|
fi
|
|
|
|
# Der Eintrag in der Einheit ist der eigentliche Punkt. Die Rechte des
|
|
# Verzeichnisses nuetzen nichts, solange ProtectSystem=strict den Pfad nicht
|
|
# freigibt.
|
|
unitFile="/etc/systemd/system/${serviceName}.service"
|
|
|
|
if [[ -f "${unitFile}" ]] && ! grep -q "${restoreDirectory}" "${unitFile}"; then
|
|
if grep -q "^ReadWritePaths=" "${unitFile}"; then
|
|
sed -i "s|^ReadWritePaths=.*|& ${restoreDirectory}|" "${unitFile}"
|
|
writeSuccess "In ReadWritePaths eingetragen"
|
|
else
|
|
writeWarning "Die Einheit hat kein ReadWritePaths — bitte von Hand pruefen."
|
|
fi
|
|
else
|
|
writeDetail "ReadWritePaths ist bereits eingetragen."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. Starten und pruefen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '\n'
|
|
writeStep "Dienst"
|
|
|
|
systemctl daemon-reload
|
|
systemctl start "${serviceName}"
|
|
|
|
healthAddress="${SYNCOVA_HTTP_LISTEN_ADDRESS:-127.0.0.1:8080}"
|
|
serviceIsReady="nein"
|
|
|
|
for _attempt in $(seq 1 20); do
|
|
if serviceIsReachable "http://${healthAddress}/health/ready"; then
|
|
serviceIsReady="ja"
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
if [[ "${serviceIsReady}" != "ja" ]]; then
|
|
printf '\n'
|
|
writeWarning "Der Dienst meldet sich nach dem Update nicht als betriebsbereit."
|
|
journalctl -u "${serviceName}" -n 15 --no-pager 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
abortWithMessage "Das Update wird zurueckgenommen."
|
|
fi
|
|
|
|
# Die laufende Fassung ist eine Beigabe, kein Pruefkriterium — sie wird aus dem
|
|
# Programm gelesen, nicht aus der Gesundheitsantwort.
|
|
#
|
|
# Der erste Anlauf holte sie mit "grep -o" aus der Antwort von /health/ready.
|
|
# Die enthaelt aber gar kein Versionsfeld; grep endet dann mit 1, und mit
|
|
# "set -o pipefail" nahm das Skript ein **gelungenes** Update wieder zurueck.
|
|
# Ein Rollback wegen einer Zeile, die nur der Ausgabe dient.
|
|
runningVersion="$("${installationRoot}/bin/syncova-api" --version 2>/dev/null | awk '{print $2}' || true)"
|
|
|
|
writeSuccess "Dienst laeuft und ist betriebsbereit${runningVersion:+ (${runningVersion})}"
|
|
|
|
# Ab hier ist das Update gelungen.
|
|
trap - EXIT
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Abschluss
|
|
# ---------------------------------------------------------------------------
|
|
|
|
printf '\n%sDie Aktualisierung ist abgeschlossen.%s\n\n' "${colorGreen}${colorBold}" "${colorReset}"
|
|
|
|
printf ' %s → %s\n\n' "${installedVersion}" "${packageVersion}"
|
|
printf ' Sicherung %s.dump\n' "${backupPrefix}"
|
|
printf ' Vorige Fassung %s\n' "${previousReleaseRoot}"
|
|
printf '\n'
|
|
|
|
writeDetail "Die vorige Fassung bleibt liegen, bis Sie sie entfernen:"
|
|
writeDetail " rm -rf ${previousReleaseRoot}"
|
|
printf '\n'
|
|
writeDetail "Sehen Sie vorher nach, ob die neue Fassung im Betrieb traegt —"
|
|
writeDetail "ein Update, das man am selben Tag entsorgt, hat keinen Rueckweg."
|
|
printf '\n'
|