#!/usr/bin/env bash # ============================================================================= # Installation und Aktualisierung von TESM und TESM-Lizenzserver. # # Aufruf (aus dem entpackten Release-Verzeichnis): # sudo bash deploy/install.sh --app tesm # sudo bash deploy/install.sh --app tesm-license # sudo bash deploy/install.sh --app tesm --instance test --port 5100 # # Die im Vorgaengerprojekt real aufgetretenen Fallen sind hier von vornherein # vermieden -- jeweils mit Kommentar an der Stelle, wo es zaehlt: # # * Die nginx-Site-Datei wird bei jedem Lauf **inhaltlich verglichen** und bei # Abweichung erneuert. Der Vorgaenger schrieb sie nur, wenn sie fehlte -- ein # einmal falscher alias-Pfad blieb dadurch ueber Jahre stehen und lieferte # alle statischen Dateien als 404. # * pip laeuft immer ueber "python3 -m pip", nie ueber den venv-Shim: dessen # Shebang baeckt einen absoluten Pfad ein, der bei einer Verzeichnis- # umbenennung bricht (live auf POETEST reproduziert). # * Vor jedem Update ein vollstaendiges Backup -- unabhaengig davon, ob sich # das Schema geaendert hat. Ein reiner Code-Fehler soll durch dasselbe Netz # laufen. # * Nach dem Update ein Health-Check mit automatischem Rollback. # * Instanzdaten (Datenbank, Schluessel, Lizenz) sind vom Abgleich # ausgeschlossen und werden nie ueberschrieben. # ============================================================================= set -euo pipefail APP="" INSTANCE="" PORT="" HTTP_PORT="" SKIP_PACKAGES=0 ASSUME_YES=0 HTTPS=0 HTTPS_PORT="" DOMAIN="" ACME_EMAIL="" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" COLOR_OK=$'\033[32m'; COLOR_WARN=$'\033[33m'; COLOR_ERR=$'\033[31m'; COLOR_OFF=$'\033[0m' step() { printf '\n%s==>%s %s\n' "$COLOR_OK" "$COLOR_OFF" "$*"; } 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' "$*"; } usage() { cat <<'USAGE' Verwendung: install.sh --app [Optionen] --app Welche Anwendung installiert wird (Pflicht) --instance Zusaetzliche Instanz neben der Hauptinstallation. Erzeugt /srv/-, -.service usw. Damit laesst sich eine neue Version testen, ohne die laufende anzufassen. --port Port des Anwendungsservers (Standard: 5000 bzw. 5001) --http-port Port, auf dem nginx diese Instanz anbietet (Standard 80). Fuer eine Testinstanz neben einer laufenden Produktion: auf Port 80 kann nur eine Site die Vorgabe sein. --https HTTPS einrichten: Port 443 mit TLS, Port 80 leitet dorthin um (ausser dem ACME-Pfad), HSTS aktiv, Sitzungscookie mit Secure-Flag. Ohne --domain wird ein selbstsigniertes Zertifikat erzeugt (geschlossene Netze); mit --domain wird zusaetzlich versucht, ein Let's-Encrypt-Zertifikat zu holen. --https-port Wie --https, aber auf abweichendem Port (Testinstanz). --domain Oeffentlicher Name dieser Installation. --acme-email Kontaktadresse fuer Let's Encrypt. --skip-packages Keine Systempakete installieren --yes Rueckfragen ueberspringen USAGE } while [[ $# -gt 0 ]]; do case "$1" in --app) APP="${2:?}"; shift 2 ;; --instance) INSTANCE="${2:?}"; shift 2 ;; --port) PORT="${2:?}"; shift 2 ;; --http-port) HTTP_PORT="${2:?}"; shift 2 ;; --https) HTTPS=1; shift ;; --https-port) HTTPS_PORT="${2:?}"; HTTPS=1; shift 2 ;; --domain) DOMAIN="${2:?}"; shift 2 ;; --acme-email) ACME_EMAIL="${2:?}"; shift 2 ;; --skip-packages) SKIP_PACKAGES=1; shift ;; --yes|-y) ASSUME_YES=1; shift ;; -h|--help) usage; exit 0 ;; *) fail "unbekannte Option: $1" ;; esac done [[ -n "$APP" ]] || { usage; fail "--app fehlt"; } [[ "$APP" == "tesm" || "$APP" == "tesm-license" ]] || fail "--app muss tesm oder tesm-license sein" # Seit die beiden Anwendungen in getrennten Repositorien liegen, bringt ein # Auscheckout nur die eigene mit. Ohne diese Pruefung waere die Meldung ein # Folgefehler tief im Installationslauf statt eines klaren Satzes hier. [[ -d "$REPO_DIR/apps/$APP" ]] || fail "In diesem Verzeichnis liegt '$APP' nicht ($REPO_DIR/apps/$APP fehlt). Vorhanden: $( ls -1 "$REPO_DIR/apps" 2>/dev/null | tr ' ' ' ' || echo 'nichts')" [[ "$(id -u)" -eq 0 ]] || fail "bitte mit sudo ausfuehren" [[ -z "$INSTANCE" || "$INSTANCE" =~ ^[a-z0-9-]{1,16}$ ]] || fail "ungueltiger Instanzname" # -- Namensraum -------------------------------------------------------------- NAME="$APP" [[ -n "$INSTANCE" ]] && NAME="$APP-$INSTANCE" ROOT="/srv/$NAME" LOG_DIR="/var/log/$NAME" SERVICE_USER="$APP" UNIT="$NAME.service" MONITOR_UNIT="$NAME-monitor.service" NGINX_SITE="/etc/nginx/sites-available/$NAME" ENV_PREFIX="TESM_" [[ "$APP" == "tesm-license" ]] && ENV_PREFIX="TESM_LICENSE_" if [[ -z "$PORT" ]]; then PORT=5000 [[ "$APP" == "tesm-license" ]] && PORT=5001 [[ -n "$INSTANCE" ]] && PORT=$((PORT + 100)) fi if [[ -z "$HTTP_PORT" ]]; then HTTP_PORT=80 [[ -n "$INSTANCE" ]] && HTTP_PORT=8080 fi if [[ -z "$HTTPS_PORT" ]]; then HTTPS_PORT=443 [[ -n "$INSTANCE" ]] && HTTPS_PORT=8443 fi # Das Secure-Flag am Sitzungscookie darf erst gesetzt werden, wenn es # tatsaechlich HTTPS gibt -- sonst kommt das Cookie nie beim Browser an und die # Anmeldung schlaegt ohne erkennbaren Grund fehl. # Bei einer Aktualisierung zaehlt, was schon gilt: wer HTTPS eingerichtet hat, # darf es durch ein Update ohne --https nicht verlieren. Ein zurueckgesetztes # Secure-Flag wuerde die Anmeldung stillschweigend unmoeglich machen -- der # Browser sendet das Cookie dann weiter nur ueber HTTPS, die Anwendung setzt es # aber ohne das Flag neu, und beide reden aneinander vorbei. COOKIE_SECURE=0 if [[ -f "/etc/tesm/$NAME.env" ]] && grep -q "^${ENV_PREFIX}COOKIE_SECURE=1" "/etc/tesm/$NAME.env"; then COOKIE_SECURE=1 fi [[ $HTTPS -eq 1 ]] && COOKIE_SECURE=1 [[ -z "$DOMAIN" || "$DOMAIN" =~ ^[A-Za-z0-9]([A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$ ]] || fail "ungueltige Domain: $DOMAIN" TIMESTAMP="$(date +%Y%m%d-%H%M%S)" BACKUP_DIR="/srv/$NAME-backup-$TIMESTAMP" FAILED_DIR="/srv/$NAME-failed-$TIMESTAMP" # Einziges Signal fuer "schon installiert": die Datenbank ist da. IS_UPDATE=0 [[ -f "$ROOT/data/app.db" ]] && IS_UPDATE=1 step "TESM-Installation" info "Anwendung : $APP${INSTANCE:+ (Instanz $INSTANCE)}" info "Ziel : $ROOT" info "Dienst : $UNIT auf 127.0.0.1:$PORT" if [[ $HTTPS -eq 1 ]]; then info "nginx : Port $HTTPS_PORT (HTTPS), Port $HTTP_PORT leitet um" elif [[ "$COOKIE_SECURE" == "1" ]]; then info "nginx : HTTPS ist bereits eingerichtet und bleibt unveraendert" if [[ -n "$DOMAIN" ]]; then info "Zertifikat : Lets Encrypt fuer $DOMAIN, sonst selbstsigniert" else info "Zertifikat : selbstsigniert (keine Domain angegeben)" fi else info "nginx : Port $HTTP_PORT (nur HTTP)" fi info "Modus : $([[ $IS_UPDATE -eq 1 ]] && echo Aktualisierung || echo Neuinstallation)" if [[ $ASSUME_YES -eq 0 ]]; then read -r -p "Fortfahren? [j/N] " answer [[ "$answer" =~ ^[jJyY]$ ]] || fail "abgebrochen" fi # -- Systempakete ------------------------------------------------------------ if [[ $SKIP_PACKAGES -eq 0 ]]; then step "Systempakete" PACKAGES=(python3 python3-venv python3-pip nginx sqlite3 rsync logrotate ca-certificates curl) if [[ "$APP" == "tesm" ]]; then PACKAGES+=(iputils-ping openssh-client cifs-utils) fi export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq "${PACKAGES[@]}" info "${#PACKAGES[@]} Pakete geprueft" else info "Systempakete uebersprungen" fi # -- Dienstbenutzer ---------------------------------------------------------- step "Dienstbenutzer" if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then useradd --system --home-dir "$ROOT" --shell /usr/sbin/nologin "$SERVICE_USER" info "Benutzer $SERVICE_USER angelegt" else info "Benutzer $SERVICE_USER existiert" fi # Der Vorgaenger lief als root. Hier laeuft der Dienst unprivilegiert; Rootrechte # gibt es ausschliesslich ueber den Helfer. # -- 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" info "Sicherung: $BACKUP_DIR" fi # -- Dateien abgleichen ------------------------------------------------------ step "Anwendungsdateien" mkdir -p "$ROOT" rsync -a --delete \ --exclude 'venv' \ --exclude 'data' \ --exclude '*.pyc' \ --exclude '__pycache__' \ --exclude '.git' \ --exclude 'tests' \ "$REPO_DIR/" "$ROOT/" mkdir -p "$ROOT/data" "$LOG_DIR" info "Dateien abgeglichen (Instanzdaten unter data/ bleiben unangetastet)" # -- venv -------------------------------------------------------------------- step "Python-Umgebung" if [[ ! -x "$ROOT/venv/bin/python3" ]]; then python3 -m venv "$ROOT/venv" info "venv erzeugt" fi # Immer ueber "python3 -m pip" -- der pip-Shim im venv baeckt einen absoluten # Pfad in seinen Shebang, der nach einer Umbenennung des Verzeichnisses bricht. "$ROOT/venv/bin/python3" -m pip install --quiet --upgrade pip setuptools wheel "$ROOT/venv/bin/python3" -m pip install --quiet \ "$ROOT/packages/tesm-licensing" \ "$ROOT/packages/tesm-core" \ "$ROOT/apps/$APP" \ gunicorn if [[ "$APP" == "tesm" ]]; then "$ROOT/venv/bin/python3" -m pip install --quiet flask-sock fi info "Abhaengigkeiten installiert" # -- Statische Dateien fuer nginx -------------------------------------------- step "Statische Dateien" rm -rf "$ROOT/static" "$ROOT/static-core" CORE_STATIC="$("$ROOT/venv/bin/python3" -c \ 'import tesm_core, pathlib; print(pathlib.Path(tesm_core.__file__).parent / "static" / "tesm_core")')" APP_MODULE="$APP" [[ "$APP" == "tesm-license" ]] && APP_MODULE="tesm_license" APP_STATIC="$("$ROOT/venv/bin/python3" -c \ "import $APP_MODULE, pathlib; print(pathlib.Path($APP_MODULE.__file__).parent / 'static')")" cp -a "$CORE_STATIC" "$ROOT/static-core" [[ -d "$APP_STATIC" ]] && cp -a "$APP_STATIC" "$ROOT/static" || mkdir -p "$ROOT/static" info "nginx liefert /core-assets/ und /assets/ direkt aus" # -- Privilegierter Helfer --------------------------------------------------- step "Privilegierter Helfer und sudoers" install -d -o root -g root -m 0755 /usr/local/lib/tesm install_if_changed() { local src="$1" dst="$2" mode="$3" owner="${4:-root:root}" if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then return 1 fi install -o "${owner%%:*}" -g "${owner##*:}" -m "$mode" "$src" "$dst" return 0 } install_if_changed "$ROOT/deploy/tesm-helper" /usr/local/lib/tesm/tesm-helper 0755 && \ info "Helfer aktualisiert" || info "Helfer unveraendert" # visudo -c prueft die Datei, bevor sie aktiv wird -- eine kaputte sudoers-Datei # waere sonst ein Totalausfall der Administration. TMP_SUDO="$(mktemp)" cp "$ROOT/deploy/sudoers/$APP" "$TMP_SUDO" visudo -c -f "$TMP_SUDO" >/dev/null || fail "sudoers-Datei ist fehlerhaft" install -o root -g root -m 0440 "$TMP_SUDO" "/etc/sudoers.d/$APP" rm -f "$TMP_SUDO" info "sudoers: $SERVICE_USER darf ausschliesslich tesm-helper als root aufrufen" # -- Rechte ------------------------------------------------------------------ step "Dateirechte" chown -R "$SERVICE_USER:$SERVICE_USER" "$ROOT/data" "$LOG_DIR" chown -R root:root "$ROOT/packages" "$ROOT/apps" "$ROOT/deploy" 2>/dev/null || true chmod 0750 "$ROOT/data" chmod 0755 "$LOG_DIR" find "$ROOT/data" -maxdepth 1 -name '*.key*' -o -name 'data.keys' -o -name '*signing_key*' \ | xargs -r chmod 0600 info "Datenverzeichnis 0750, Schluesseldateien 0600" # -- Umgebungsdatei ---------------------------------------------------------- step "Umgebung" # Eine Quelle fuer beide Seiten: die systemd-Unit liest sie ueber # 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" TMP_ENV="$(mktemp)" { echo "# Von install.sh erzeugt fuer $NAME -- nicht von Hand bearbeiten." echo "${ENV_PREFIX}BASE_DIR=$ROOT" echo "${ENV_PREFIX}DATA_DIR=$ROOT/data" echo "${ENV_PREFIX}LOG_DIR=$LOG_DIR" # Name dieser Installation auf dem Host. Alles ausserhalb von /srv/ # -- nginx-Site, netplan-Datei, logrotate-Regel, Zertifikatsverzeichnis, # Sitzungscookie -- laeuft darueber. Ohne das wuerde eine Testinstanz beim # Speichern der Webserver-Einstellungen die Site der Produktion # ueberschreiben und beide teilten sich dasselbe Cookie. echo "${ENV_PREFIX}SITE_KEY=$NAME" # Genau die Adresse, auf die gunicorn hoert. nginx nimmt sie von hier als # proxy_pass-Ziel -- so koennen Dienst und Reverse Proxy nicht auseinander # laufen. Vorher stand die 5000 fest im Code: eine Instanz auf 5100 erzeugte # damit eine Site, die auf die andere Installation zeigte. echo "${ENV_PREFIX}BIND=127.0.0.1:$PORT" echo "${ENV_PREFIX}COOKIE_SECURE=$COOKIE_SECURE" echo "${ENV_PREFIX}TRUSTED_PROXY_HOPS=1" echo "PYTHONUNBUFFERED=1" } > "$TMP_ENV" install_if_changed "$TMP_ENV" "$ENV_FILE" 0640 "root:$SERVICE_USER" >/dev/null && info "Umgebungsdatei $ENV_FILE geschrieben" || info "Umgebungsdatei unveraendert" rm -f "$TMP_ENV" if [[ "$COOKIE_SECURE" == "1" ]]; then info "COOKIE_SECURE=1 -- die Anmeldung funktioniert damit nur ueber HTTPS." else info "Hinweis: COOKIE_SECURE=0 bis HTTPS eingerichtet ist. Danach: install.sh --https" info " erneut aufrufen oder in $ENV_FILE auf 1 setzen und Dienst neu starten." fi # Wrapper, damit die Werkzeuge dieselbe Umgebung sehen wie der Dienst. ADMIN_BIN="tesm-admin" [[ "$APP" == "tesm-license" ]] && ADMIN_BIN="tesm-license-admin" TMP_WRAPPER="$(mktemp)" cat > "$TMP_WRAPPER" </dev/null && info "Werkzeug: $NAME-admin" || true rm -f "$TMP_WRAPPER" # -- systemd ----------------------------------------------------------------- step "systemd" UNITS_CHANGED=0 render_unit() { local src="$1" dst="$2" local tmp; tmp="$(mktemp)" sed -e "s#/srv/$APP#$ROOT#g" \ -e "s#/var/log/$APP#$LOG_DIR#g" \ -e "s#127.0.0.1:5000#127.0.0.1:$PORT#" \ -e "s#127.0.0.1:5001#127.0.0.1:$PORT#" \ -e "/^WorkingDirectory=/a EnvironmentFile=$ENV_FILE" \ "$src" > "$tmp" install_if_changed "$tmp" "$dst" 0644 && UNITS_CHANGED=1 rm -f "$tmp" } render_unit "$ROOT/deploy/systemd/$APP.service" "/etc/systemd/system/$UNIT" if [[ "$APP" == "tesm" ]]; then render_unit "$ROOT/deploy/systemd/tesm-monitor.service" "/etc/systemd/system/$MONITOR_UNIT" fi [[ $UNITS_CHANGED -eq 1 ]] && systemctl daemon-reload && info "Units neu geladen" \ || info "Units unveraendert" # -- logrotate --------------------------------------------------------------- TMP_ROTATE="$(mktemp)" sed -e "s#/var/log/$APP/#$LOG_DIR/#" "$ROOT/deploy/logrotate/$APP" > "$TMP_ROTATE" install_if_changed "$TMP_ROTATE" "/etc/logrotate.d/$NAME" 0644 >/dev/null && info "logrotate aktualisiert" rm -f "$TMP_ROTATE" # -- nginx ------------------------------------------------------------------- step "nginx" TMP_SITE="$(mktemp)" sed -e "s#/srv/$APP/#$ROOT/#g" \ -e "s#127.0.0.1:5000#127.0.0.1:$PORT#g" \ -e "s#127.0.0.1:5001#127.0.0.1:$PORT#g" \ -e "s#listen 80;#listen $HTTP_PORT;#" \ -e "s#listen \[::\]:80;#listen [::]:$HTTP_PORT;#" \ -e "s#/var/www/$APP-acme#/var/www/$NAME-acme#g" \ "$ROOT/deploy/nginx/$APP.conf" > "$TMP_SITE" # Auf Port 80 kann nur eine Site die Vorgabe sein. Eine Instanz neben der # Produktion bekommt darum ueber --http-port einen eigenen Port -- sonst # verdeckt sie die laufende Installation (nginx meldet dann # "conflicting server name" und ignoriert eine der beiden). if [[ -n "$INSTANCE" && "$HTTP_PORT" == "80" ]]; then warn "Instanz $INSTANCE auf Port 80: sie verdeckt moeglicherweise die Hauptinstallation." fi # Inhaltsvergleich statt reiner Existenzpruefung -- genau der Punkt, an dem der # Vorgaenger einen falschen alias-Pfad jahrelang mitschleppte. Die Anwendung # schreibt diese Datei spaeter ohnehin selbst neu (Verwaltung -> Webserver); # hier geht es nur um einen funktionierenden Erststart. if [[ -f "$NGINX_SITE" ]] && ! cmp -s "$TMP_SITE" "$NGINX_SITE"; then if grep -qE "alias\s+$ROOT/(static|static-core)/;" "$NGINX_SITE"; then info "Vorhandene Site-Konfiguration behalten (alias-Pfade stimmen)" else warn "Vorhandene Site-Konfiguration zeigt auf falsche Pfade -- wird erneuert" cp "$NGINX_SITE" "$NGINX_SITE.bak-$TIMESTAMP" install -m 0644 "$TMP_SITE" "$NGINX_SITE" fi elif [[ ! -f "$NGINX_SITE" ]]; then install -m 0644 "$TMP_SITE" "$NGINX_SITE" info "Site-Konfiguration angelegt" fi TMP_SITE_KEEP="$TMP_SITE" ln -sfn "$NGINX_SITE" "/etc/nginx/sites-enabled/$NAME" rm -f /etc/nginx/sites-enabled/default mkdir -p "/var/www/$NAME-acme" chown -R www-data:www-data "/var/www/$NAME-acme" # "alias-Pfade stimmen" heisst noch nicht "nginx akzeptiert die Datei". Eine # von Hand nachgebesserte Site kann syntaktisch kaputt sein -- und dann # scheitert nicht nur diese Installation, sondern jeder Reload des Webservers, # auch fuer alle anderen Sites. Darum: pruefen, und im Zweifel die frisch # erzeugte Vorlage nehmen. if ! nginx -t >/dev/null 2>&1; then warn "nginx lehnt die vorhandene Site-Konfiguration ab -- sie wird durch die Vorlage ersetzt." [[ -f "$NGINX_SITE" ]] && cp "$NGINX_SITE" "$NGINX_SITE.bak-$TIMESTAMP" install -m 0644 "$TMP_SITE_KEEP" "$NGINX_SITE" nginx -t >/dev/null 2>&1 || fail "nginx lehnt auch die Vorlage ab -- bitte 'nginx -t' pruefen. Sicherung: $NGINX_SITE.bak-$TIMESTAMP" fi rm -f "$TMP_SITE_KEEP" systemctl reload nginx info "nginx neu geladen" # -- Datenbank und Start ----------------------------------------------------- step "Anwendung starten" # Das Schema entsteht beim ersten Start durch die Migrationen -- es gibt kein # separates Init-Skript mehr. systemctl enable --now "$UNIT" >/dev/null if [[ "$APP" == "tesm" ]]; then systemctl enable --now "$MONITOR_UNIT" >/dev/null fi # -- Health-Check und Rollback ---------------------------------------------- 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 HEALTHY=1 info "Antwort nach ${attempt}x" break fi sleep 2 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 fail "Aktualisierung fehlgeschlagen. Alter Stand wiederhergestellt. Fehlgeschlagene Version : $FAILED_DIR Sicherung : $BACKUP_DIR Protokoll : journalctl -u $UNIT -n 100" fi fail "Neuinstallation fehlgeschlagen. Protokoll: journalctl -u $UNIT -n 100" fi URL_HINT="http://:$HTTP_PORT/" [[ "$HTTP_PORT" == "80" ]] && URL_HINT="http:///" # HTTPS war schon eingerichtet und wird durch dieses Update nicht angefasst. [[ $HTTPS -eq 0 && "$COOKIE_SECURE" == "1" ]] && URL_HINT="https:/// (unveraendert)" # -- HTTPS ------------------------------------------------------------------- # Bewusst erst hier: die Anwendung muss laufen, denn sie erzeugt die # TLS-Konfiguration selbst (tesm-admin web-setup). Das Installationsskript # baut sich keine zweite, konkurrierende Vorlage -- genau daran ist der # Vorgaenger gescheitert. if [[ $HTTPS -eq 1 ]]; then step "HTTPS einrichten" WEB_ARGS=(web-setup --https --apply --self-signed --http-port "$HTTP_PORT" --https-port "$HTTPS_PORT") if [[ -n "$DOMAIN" ]]; then # HSTS nur mit einem Zertifikat, dem Browser auch trauen. Bei einem # selbstsignierten waere es eine Falle: der Browser merkt sich fuer Monate, # dass er die Seite nur ueber HTTPS aufrufen darf -- und zeigt bei jedem # Aufruf eine Warnung, die sich nicht mehr wegklicken laesst. WEB_ARGS+=(--domain "$DOMAIN") if [[ -n "$ACME_EMAIL" ]]; then # Let's Encrypt verlangt ohnehin eine Kontaktadresse. Sie ist damit das # eindeutige Signal "hol ein oeffentliches Zertifikat" -- ohne sie bleibt # es bei einem selbstsignierten, auch wenn eine Domain gesetzt ist # (interne Namen wie lizenz.firma.local kann keine CA bestaetigen). WEB_ARGS+=(--acme --acme-email "$ACME_EMAIL" --hsts) info "Versuche Lets Encrypt fuer $DOMAIN; bei Misserfolg selbstsigniert." else info "Domain $DOMAIN, aber keine --acme-email: selbstsigniert, ohne HSTS." fi else info "Ohne Domain: selbstsigniertes Zertifikat, ohne HSTS." fi if sudo -u "$SERVICE_USER" "/usr/local/bin/$NAME-admin" "${WEB_ARGS[@]}"; then info "HTTPS aktiv." URL_HINT="https:///" [[ "$HTTPS_PORT" == "443" ]] || URL_HINT="https://:$HTTPS_PORT/" else warn "HTTPS-Einrichtung fehlgeschlagen -- die Installation laeuft weiter ueber HTTP." warn "Nachtraeglich: sudo -u $SERVICE_USER $NAME-admin web-setup --https --self-signed --hsts --apply" # Ein Secure-Cookie ohne HTTPS macht die Anmeldung unmoeglich. Lieber # zurueckdrehen als eine Installation hinterlassen, an der niemand mehr # ankommt. if grep -q "^${ENV_PREFIX}COOKIE_SECURE=1" "$ENV_FILE"; then sed -i "s/^${ENV_PREFIX}COOKIE_SECURE=1/${ENV_PREFIX}COOKIE_SECURE=0/" "$ENV_FILE" systemctl restart "$UNIT" warn "COOKIE_SECURE auf 0 zurueckgesetzt, damit die Anmeldung weiter funktioniert." fi fi fi if [[ $IS_UPDATE -eq 1 ]]; then rm -rf "$BACKUP_DIR" info "Sicherung entfernt (Aktualisierung erfolgreich)" fi # -- Abschluss --------------------------------------------------------------- step "Fertig" ADMIN_CMD="tesm-admin" [[ "$APP" == "tesm-license" ]] && ADMIN_CMD="tesm-license-admin" cat < Lizenz SUMMARY fi cat <<'SUMMARY' Danach in der Anwendung unter "Verwaltung -> Webserver und TLS" die Domain eintragen, ein Zertifikat anfordern und die Konfiguration uebernehmen -- damit laeuft der Zugriff ueber HTTPS auf Port 443 mit Umleitung von 80. SUMMARY