Rueckweg bei jedem Fehlschlag, install.sh oben im Paket, eigener Port je Instanz
This commit is contained in:
1 parent
337a52cc38
commit
258e90bf4f
4 files changed
+465
-39
No files matched your search
+165
-20
@@ -28,6 +28,9 @@ set -euo pipefail
|
||||
|
||||
APP=""
|
||||
INSTANCE=""
|
||||
#: Verzeichnis der Umgebungsdateien. Ueberschreibbar, damit sich die
|
||||
#: Portsuche in einem Test wirklich ausfuehren laesst.
|
||||
ETC_TESM="${TESM_ETC_DIR:-/etc/tesm}"
|
||||
PORT=""
|
||||
HTTP_PORT=""
|
||||
SKIP_PACKAGES=0
|
||||
@@ -46,6 +49,62 @@ warn() { printf '%s !!%s %s\n' "$COLOR_WARN" "$COLOR_OFF" "$*"; }
|
||||
fail() { printf '%s !!%s %s\n' "$COLOR_ERR" "$COLOR_OFF" "$*" >&2; exit 1; }
|
||||
info() { printf ' %s\n' "$*"; }
|
||||
|
||||
#: Merker fuer die EXIT-Falle. Zurueckgerollt wird nur, was auch gesichert ist.
|
||||
SICHERUNG_STEHT=0
|
||||
INSTALL_FERTIG=0
|
||||
ZURUECKGEROLLT=0
|
||||
|
||||
zurueckrollen_install() {
|
||||
[[ $SICHERUNG_STEHT -eq 1 && $ZURUECKGEROLLT -eq 0 ]] || return 0
|
||||
# Kommt nur zum Zug, wenn die Sicherung steht -- dann sind diese Variablen
|
||||
# gesetzt. Der Vollstaendigkeit halber trotzdem gepruefte Namen.
|
||||
[[ -n "${BACKUP_DIR:-}" && -d "$BACKUP_DIR" ]] || { warn "Keine Sicherung gefunden -- nichts wiederhergestellt."; return 0; }
|
||||
ZURUECKGEROLLT=1
|
||||
warn "Alten Stand wiederherstellen."
|
||||
systemctl stop "$UNIT" 2>/dev/null || true
|
||||
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
||||
rm -rf "$FAILED_DIR"
|
||||
mv "$ROOT" "$FAILED_DIR" 2>/dev/null || true
|
||||
cp -a "$BACKUP_DIR" "$ROOT"
|
||||
systemctl start "$UNIT" 2>/dev/null || true
|
||||
[[ "$APP" == "tesm" ]] && systemctl start "$MONITOR_UNIT" 2>/dev/null || true
|
||||
warn "Wiederhergestellt aus $BACKUP_DIR"
|
||||
warn "Fehlgeschlagener Stand liegt unter $FAILED_DIR"
|
||||
}
|
||||
|
||||
# Bei **jedem** Fehlschlag, nicht nur bei einem, an den ich gedacht habe:
|
||||
# Systempakete, venv, sudoers, nginx, ein Ctrl-C. Vorher blieb der Dienst
|
||||
# gestoppt und das Verzeichnis teils ersetzt zurueck.
|
||||
bei_fehler() {
|
||||
local code=$?
|
||||
[[ $INSTALL_FERTIG -eq 1 || "$code" == "0" ]] && return "$code"
|
||||
if [[ $SICHERUNG_STEHT -eq 1 ]]; then
|
||||
zurueckrollen_install
|
||||
else
|
||||
# Neuinstallation: es gibt keinen alten Stand. Nichts loeschen -- im
|
||||
# Zielverzeichnis kann eine Fassung 1.x liegen, deren Sicherung ein
|
||||
# anderes Skript haelt (upgrade.sh).
|
||||
# Vorsichtige Ersetzung: bricht es schon beim Auswerten der Argumente ab,
|
||||
# gibt es $ROOT noch nicht -- und "set -u" wuerde die Falle selbst zum
|
||||
# Fehler machen.
|
||||
if [[ ${IS_UPDATE:-0} -eq 1 ]]; then
|
||||
# Abbruch vor dem Sicherungsschritt: die Installation ist unangetastet.
|
||||
warn "Abgebrochen (Code $code), bevor etwas geaendert wurde."
|
||||
warn "Die bestehende Installation ist unberuehrt -- nichts zurueckzurollen."
|
||||
else
|
||||
warn "Abgebrochen (Code $code). Es gab keinen vorherigen Stand dieser Fassung,"
|
||||
warn "also wurde nichts wiederhergestellt und nichts geloescht."
|
||||
fi
|
||||
# Nur sagen, was es wirklich gibt: bei einem Abbruch an der Rueckfrage
|
||||
# existiert das Verzeichnis noch nicht, und die Zeile log dann.
|
||||
if [[ -n "${ROOT:-}" && -d "${ROOT:-}" ]]; then
|
||||
warn "Angelegt wurde bis hierhin: $ROOT (Daten unter data/ unberuehrt)."
|
||||
fi
|
||||
fi
|
||||
return "$code"
|
||||
}
|
||||
trap bei_fehler EXIT
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Verwendung: install.sh --app <tesm|tesm-license> [Optionen]
|
||||
@@ -112,13 +171,24 @@ MONITOR_UNIT="$NAME-monitor.service"
|
||||
NGINX_SITE="/etc/nginx/sites-available/$NAME"
|
||||
ENV_PREFIX="TESM_"
|
||||
[[ "$APP" == "tesm-license" ]] && ENV_PREFIX="TESM_LICENSE_"
|
||||
|
||||
# Ausgabe des Lizenzservers: die Master-Ausgabe bringt das Teilpaket
|
||||
# tesm_license/master mit -- den einzigen Weg, auf dem eine Lizenzdatei die
|
||||
# Rolle "master" bekommt. Das Release fuer Sub-Lizenzserver hat es nicht.
|
||||
# Die Ausgabe haengt an der Anwesenheit des Verzeichnisses, nicht an einer
|
||||
# Einstellung: eine Einstellung liesse sich vor Ort umstellen.
|
||||
MASTER_TEILPAKET="apps/tesm-license/src/tesm_license/master"
|
||||
EDITION="standard"
|
||||
if [[ "$APP" == "tesm-license" ]]; then
|
||||
if [[ -d "$REPO_DIR/$MASTER_TEILPAKET" ]]; then EDITION="master"; else EDITION="sub"; fi
|
||||
fi
|
||||
# Bei einer Aktualisierung gilt zuerst, was schon eingerichtet ist. Sonst
|
||||
# verschiebt ein Update ohne Portangaben die Anwendung: eine benannte Instanz
|
||||
# faellt auf 8080/8443 zurueck, auch wenn sie seit der Installation auf Port 80
|
||||
# lief -- und ein Reverse Proxy davor zeigt dann ins Leere. Dieselbe Regel wie
|
||||
# beim Secure-Flag weiter unten: eine Aktualisierung nimmt nichts weg, was
|
||||
# nicht ausdruecklich neu gesetzt wird.
|
||||
ENV_FILE="/etc/tesm/$NAME.env"
|
||||
ENV_FILE="$ETC_TESM/$NAME.env"
|
||||
if [[ -z "$PORT" && -f "$ENV_FILE" ]]; then
|
||||
PORT="$(sed -n "s/^${ENV_PREFIX}BIND=127\.0\.0\.1:\([0-9]\{1,5\}\)$/\1/p" "$ENV_FILE" | head -1)"
|
||||
fi
|
||||
@@ -127,10 +197,30 @@ if [[ -f "$NGINX_SITE" ]]; then
|
||||
[[ -z "$HTTPS_PORT" ]] && HTTPS_PORT="$(sed -n 's/^[[:space:]]*listen[[:space:]]\([0-9]\{1,5\}\)[[:space:]]\+ssl.*/\1/p' "$NGINX_SITE" | head -1)"
|
||||
fi
|
||||
|
||||
port_belegt() {
|
||||
local p="$1"
|
||||
# Schon von einer anderen Installation vergeben? Die Umgebungsdateien sind
|
||||
# die Wahrheit -- ein Dienst kann gerade gestoppt sein und den Port trotzdem
|
||||
# beanspruchen.
|
||||
grep -qs "BIND=127\.0\.0\.1:$p\$" "$ETC_TESM"/*.env 2>/dev/null &&
|
||||
return 0
|
||||
# Oder gerade in Benutzung, von was auch immer.
|
||||
command -v ss >/dev/null 2>&1 && [[ -n "$(ss -Hltn "sport = :$p" 2>/dev/null)" ]] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ -z "$PORT" ]]; then
|
||||
PORT=5000
|
||||
[[ "$APP" == "tesm-license" ]] && PORT=5001
|
||||
[[ -n "$INSTANCE" ]] && PORT=$((PORT + 100))
|
||||
BASISPORT=5000
|
||||
[[ "$APP" == "tesm-license" ]] && BASISPORT=5001
|
||||
if [[ -n "$INSTANCE" ]]; then
|
||||
# Frueher bekam **jede** Instanz Basis+100. Die zweite startete nie
|
||||
# ("Address already in use") -- und die Gesundheitspruefung merkte es
|
||||
# nicht, weil die erste auf demselben Port antwortete.
|
||||
PORT=$((BASISPORT + 100))
|
||||
while port_belegt "$PORT"; do PORT=$((PORT + 1)); done
|
||||
else
|
||||
PORT=$BASISPORT
|
||||
fi
|
||||
fi
|
||||
if [[ -z "$HTTP_PORT" ]]; then
|
||||
HTTP_PORT=80
|
||||
@@ -212,12 +302,24 @@ fi
|
||||
# Der Vorgaenger lief als root. Hier laeuft der Dienst unprivilegiert; Rootrechte
|
||||
# gibt es ausschliesslich ueber den Helfer.
|
||||
|
||||
# Ein Sub-Paket auf eine Master-Installation waere ein stiller Rueckbau: der
|
||||
# Abgleich unten laeuft mit --delete und wuerde das Teilpaket entfernen. Danach
|
||||
# gaebe es auf dem Master keinen Weg mehr, eine Master-Lizenz auszustellen --
|
||||
# und niemand haette es gesagt.
|
||||
if [[ "$EDITION" == "sub" && -d "$ROOT/$MASTER_TEILPAKET" ]]; then
|
||||
fail "Hier laeuft die Master-Ausgabe, dieses Paket ist die Ausgabe fuer Sub-Lizenzserver.
|
||||
Eine Aktualisierung damit wuerde die Master-Ausgabe entfernen.
|
||||
Passendes Paket: Repository tesm-license-master (privat).
|
||||
Bewusster Rueckbau: $ROOT/$MASTER_TEILPAKET von Hand entfernen, dann erneut."
|
||||
fi
|
||||
|
||||
# -- Backup ------------------------------------------------------------------
|
||||
if [[ $IS_UPDATE -eq 1 ]]; then
|
||||
step "Sicherung vor der Aktualisierung"
|
||||
systemctl stop "$UNIT" 2>/dev/null || true
|
||||
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
||||
cp -a "$ROOT" "$BACKUP_DIR"
|
||||
SICHERUNG_STEHT=1
|
||||
info "Sicherung: $BACKUP_DIR"
|
||||
fi
|
||||
|
||||
@@ -347,8 +449,8 @@ step "Umgebung"
|
||||
# EnvironmentFile, die Kommandozeilenwerkzeuge ueber ihren Wrapper. Ohne das
|
||||
# wuesste "tesm-admin" nicht, wo die Instanz liegt, und wuerde sich eine leere
|
||||
# Datenbank im Arbeitsverzeichnis anlegen.
|
||||
install -d -o root -g root -m 0755 /etc/tesm
|
||||
ENV_FILE="/etc/tesm/$NAME.env"
|
||||
install -d -o root -g root -m 0755 "$ETC_TESM"
|
||||
ENV_FILE="$ETC_TESM/$NAME.env"
|
||||
TMP_ENV="$(mktemp)"
|
||||
{
|
||||
echo "# Von install.sh erzeugt fuer $NAME -- nicht von Hand bearbeiten."
|
||||
@@ -491,7 +593,11 @@ fi
|
||||
step "Gesundheitspruefung"
|
||||
HEALTHY=0
|
||||
for attempt in $(seq 1 20); do
|
||||
if curl -fsS --max-time 3 "http://127.0.0.1:$PORT/gesundheit" >/dev/null 2>&1; then
|
||||
# Beides: der Dienst muss laufen **und** antworten. Nur die Antwort zu
|
||||
# pruefen ging schon einmal schief -- auf einem gemeinsamen Port antwortete
|
||||
# eine andere Instanz, und ein toter Dienst galt als gesund.
|
||||
if systemctl is-active --quiet "$UNIT" &&
|
||||
curl -fsS --max-time 3 "http://127.0.0.1:$PORT/gesundheit" >/dev/null 2>&1; then
|
||||
HEALTHY=1
|
||||
info "Antwort nach ${attempt}x"
|
||||
break
|
||||
@@ -502,13 +608,9 @@ done
|
||||
if [[ $HEALTHY -eq 0 ]]; then
|
||||
warn "Die Anwendung antwortet nicht."
|
||||
if [[ $IS_UPDATE -eq 1 ]]; then
|
||||
warn "Rollback wird ausgefuehrt."
|
||||
systemctl stop "$UNIT" 2>/dev/null || true
|
||||
[[ "$APP" == "tesm" ]] && systemctl stop "$MONITOR_UNIT" 2>/dev/null || true
|
||||
mv "$ROOT" "$FAILED_DIR"
|
||||
cp -a "$BACKUP_DIR" "$ROOT"
|
||||
systemctl start "$UNIT" || true
|
||||
[[ "$APP" == "tesm" ]] && systemctl start "$MONITOR_UNIT" || true
|
||||
# Dieselbe Funktion wie die EXIT-Falle -- zwei Wege, die dasselbe tun
|
||||
# sollen, laufen sonst irgendwann auseinander.
|
||||
zurueckrollen_install
|
||||
fail "Aktualisierung fehlgeschlagen. Alter Stand wiederhergestellt.
|
||||
Fehlgeschlagene Version : $FAILED_DIR
|
||||
Sicherung : $BACKUP_DIR
|
||||
@@ -568,12 +670,45 @@ if [[ $HTTPS -eq 1 ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# -- Lizenzserver einrichten -------------------------------------------------
|
||||
# Bei einer Neuinstallation: Signaturschluessel anlegen (braucht jeder
|
||||
# Lizenzserver, um Kundenlizenzen zu signieren) und in der Master-Ausgabe
|
||||
# gleich die eigene Master-Lizenz. Bei einer Aktualisierung nichts davon --
|
||||
# ein bestehender Schluessel darf nie angetastet werden.
|
||||
if [[ "$APP" == "tesm-license" && $IS_UPDATE -eq 0 ]]; then
|
||||
# Das Datenverzeichnis steht in der Umgebungsdatei -- es muss nicht
|
||||
# $ROOT/data sein, wenn jemand es dort umgestellt hat.
|
||||
DATEN_DIR="$(sed -n "s/^${ENV_PREFIX}DATA_DIR=//p" "$ENV_FILE" | tail -1)"
|
||||
DATEN_DIR="${DATEN_DIR:-$ROOT/data}"
|
||||
step "Signaturschluessel"
|
||||
if sudo -u "$SERVICE_USER" "/usr/local/bin/$NAME-admin" init-key; then
|
||||
info "Bitte die Datei $DATEN_DIR/master_signing_key.json SOFORT sichern."
|
||||
else
|
||||
fail "Der Signaturschluessel liess sich nicht erzeugen. Ohne ihn kann dieser Server
|
||||
keine Lizenz ausstellen. Protokoll: journalctl -u $UNIT -n 50"
|
||||
fi
|
||||
|
||||
if [[ "$EDITION" == "master" ]]; then
|
||||
step "Eigene Master-Lizenz"
|
||||
if [[ -s "$DATEN_DIR/license.json" ]]; then
|
||||
info "Es liegt schon eine Lizenzdatei -- unangetastet."
|
||||
elif sudo -u "$SERVICE_USER" "/usr/local/bin/$NAME-admin" bootstrap; then
|
||||
systemctl restart "$UNIT"
|
||||
info "Dienst neu gestartet, damit die Lizenz gelesen wird."
|
||||
else
|
||||
fail "Die Master-Lizenz liess sich nicht ausstellen.
|
||||
Nachtraeglich: sudo -u $SERVICE_USER $NAME-admin bootstrap"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $IS_UPDATE -eq 1 ]]; then
|
||||
rm -rf "$BACKUP_DIR"
|
||||
info "Sicherung entfernt (Aktualisierung erfolgreich)"
|
||||
fi
|
||||
|
||||
# -- Abschluss ---------------------------------------------------------------
|
||||
INSTALL_FERTIG=1
|
||||
step "Fertig"
|
||||
ADMIN_CMD="tesm-admin"
|
||||
[[ "$APP" == "tesm-license" ]] && ADMIN_CMD="tesm-license-admin"
|
||||
@@ -589,14 +724,24 @@ cat <<SUMMARY
|
||||
Naechste Schritte:
|
||||
SUMMARY
|
||||
|
||||
if [[ "$APP" == "tesm-license" ]]; then
|
||||
if [[ "$APP" == "tesm-license" && "$EDITION" == "master" ]]; then
|
||||
cat <<SUMMARY
|
||||
1. Signaturschluessel erzeugen und sofort sichern:
|
||||
sudo -u $SERVICE_USER $NAME-admin init-key
|
||||
2. Eigene Master-Lizenz ausstellen:
|
||||
sudo -u $SERVICE_USER $NAME-admin bootstrap
|
||||
3. Administrator anlegen:
|
||||
1. Administrator anlegen:
|
||||
sudo -u $SERVICE_USER $NAME-admin create-admin
|
||||
2. Signaturschluessel sichern -- ohne ihn ist keine ausgestellte Lizenz
|
||||
mehr pruefbar:
|
||||
$ROOT/data/master_signing_key.json
|
||||
SUMMARY
|
||||
elif [[ "$APP" == "tesm-license" ]]; then
|
||||
cat <<SUMMARY
|
||||
1. Administrator anlegen:
|
||||
sudo -u $SERVICE_USER $NAME-admin create-admin
|
||||
2. Beim Master-Lizenzserver eine Lizenz fuer diesen Server anfordern und
|
||||
unter "Eigene Lizenz" einspielen. Diese Ausgabe ist ein Sub-Lizenzserver
|
||||
und stellt keine eigene Master-Lizenz aus.
|
||||
3. Signaturschluessel sichern -- mit ihm signiert dieser Server seine
|
||||
Kundenlizenzen:
|
||||
$ROOT/data/master_signing_key.json
|
||||
SUMMARY
|
||||
else
|
||||
cat <<SUMMARY
|
||||
|
||||
Reference in new issue
Block a user