From 3f5d85c7ee1d30fa8778ba51b0a9e4637b000095 Mon Sep 17 00:00:00 2001 From: alientim Date: Sun, 23 Aug 2026 10:15:02 +0200 Subject: [PATCH] Lizenzserver-Deployment: install-license.sh/update-license.sh + systemd/nginx-Vorlagen Fork von install.sh/update.sh/tesm.service/nginx-Site (TESM selbst), beschraenkt auf /srv/tesm-license -- deployt ausschliesslich den in Phase 2 gebauten Master-Lizenzserver, laesst eine evtl. auf demselben Host vorhandene TESM-Installation komplett unberuehrt (eigener Pfad-/Env-Var- Namespace, siehe app.py). Gleiches Backup+Health-Check+Rueckroll- Sicherheitsnetz wie bei TESMs eigenem Installer. Teil von Phase 3 (Infrastruktur), siehe Plan toasty-twirling-hickey.md. Co-Authored-By: Claude Sonnet 5 --- etc/nginx/sites-available/tesm-license | 18 +++ etc/systemd/system/tesm-license.service | 27 +++++ install.sh | 155 ++++++++++++++++++++++++ update.sh | 73 +++++++++++ 4 files changed, 273 insertions(+) create mode 100644 etc/nginx/sites-available/tesm-license create mode 100644 etc/systemd/system/tesm-license.service create mode 100644 install.sh create mode 100644 update.sh diff --git a/etc/nginx/sites-available/tesm-license b/etc/nginx/sites-available/tesm-license new file mode 100644 index 0000000..c35b53b --- /dev/null +++ b/etc/nginx/sites-available/tesm-license @@ -0,0 +1,18 @@ +server { + listen 80; + server_name _; + + location / { + client_max_body_size 4m; + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /static/ { + alias /srv/tesm-license/static/; + expires 7d; + } +} diff --git a/etc/systemd/system/tesm-license.service b/etc/systemd/system/tesm-license.service new file mode 100644 index 0000000..56dc1a9 --- /dev/null +++ b/etc/systemd/system/tesm-license.service @@ -0,0 +1,27 @@ +[Unit] +Description=TESM Lizenzserver (Master) +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/srv/tesm-license +# Analog zu TESM_WEB_PROCESS bei TESM selbst -- markiert diesen Prozess als +# den echten Web-Dienst (startet den Lizenz-Heartbeat- und den Auditlog- +# Archivierungs-Thread genau einmal). Anders als bei TESM importiert hier +# kein Hilfsskript app.py periodisch als Modul (kein Geräte-Polling auf +# dem Lizenzserver) -- das Flag bleibt trotzdem gesetzt, für den Fall +# künftiger Hilfsskripte und zur Konsistenz mit dem TESM-Muster. +Environment=TESM_LICENSE_WEB_PROCESS=1 +# Kein WebSocket-Terminal wie bei TESM -- gthread/--timeout 0 daher nicht +# nötig, ein paar Threads reichen für die parallele Aktivierungs-/ +# Heartbeat-API mehrerer Kunden. +# "--bind 127.0.0.1:5000" wie bei TESM: nur über den nginx-Reverse-Proxy +# von außen erreichbar. +ExecStart=/srv/tesm-license/venv/bin/gunicorn --workers 1 --threads 8 \ + --bind 127.0.0.1:5000 app:app +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..c9445be --- /dev/null +++ b/install.sh @@ -0,0 +1,155 @@ +#!/bin/bash +# ============================================================================ +# TESM-Lizenzserver — Installer/Updater +# Fork von install.sh (TESM selbst), auf /srv/tesm-license statt /srv/tesm +# beschränkt -- deployt NUR den Master-Lizenzserver-Teil dieses Repos +# (srv/tesm-license/), lässt srv/tesm/ komplett unangetastet. Für dasselbe +# Sicherheitsnetz (Backup + Health-Check + automatischer Rückroll) wie bei +# TESM, siehe dortigen Kommentarkopf für die ausführliche Begründung -- +# hier nur die Kurzfassung je Schritt. +# +# Auszuführen als root, nachdem dieses Repo/Release-Paket z.B. nach +# /root/tesm entpackt wurde: +# sudo ./install-license.sh +# ============================================================================ +set -e + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +print_status() { echo -e "${GREEN}✔${NC} ${1} completed."; } +step() { echo -e "${RED}→${NC} ${1}..." | tee -a /var/log/tesm-license-install.log; } + +install_if_changed() { + local src="$1" dst="$2" mode="${3:-644}" + if [ -f "$dst" ] && cmp -s "$src" "$dst"; then + return 1 + fi + sudo install -m "$mode" "$src" "$dst" + return 0 +} + +clear 2>/dev/null || true + +# ---- Bestehende Installation erkennen ---- +EXISTING_INSTALL=0 +[ -f /srv/tesm-license/sqlite.db ] && EXISTING_INSTALL=1 + +NEEDS_MIGRATION_GUARD=0 +BACKUP_DIR="" + +if [ "$EXISTING_INSTALL" -eq 1 ]; then + OLD_SCHEMA_VERSION="" + [ -f /srv/tesm-license/SCHEMA_VERSION ] && OLD_SCHEMA_VERSION="$(tr -d '[:space:]' < /srv/tesm-license/SCHEMA_VERSION)" + NEW_SCHEMA_VERSION="" + [ -f "$REPO_DIR/srv/tesm-license/SCHEMA_VERSION" ] && NEW_SCHEMA_VERSION="$(tr -d '[:space:]' < "$REPO_DIR/srv/tesm-license/SCHEMA_VERSION")" + + step "Stopping tesm-license.service for update" + systemctl stop tesm-license.service 2>/dev/null || true + print_status "Service stopped" + + if [ -n "$OLD_SCHEMA_VERSION" ] && [ -n "$NEW_SCHEMA_VERSION" ] && [ "$OLD_SCHEMA_VERSION" == "$NEW_SCHEMA_VERSION" ]; then + echo -e "${GREEN}Bestehende Installation erkannt, Datenbank-Schema unverändert (Version ${OLD_SCHEMA_VERSION}):${NC} In-Place-Update." + else + echo -e "${YELLOW}Bestehende Installation erkannt, Schema-Version geändert oder unbekannt:${NC} Update mit automatischer Migration." + fi + NEEDS_MIGRATION_GUARD=1 + BACKUP_DIR="/srv/tesm-license-backup-pre-update-$(date +%Y%m%d-%H%M%S)" + step "Backing up current installation to $BACKUP_DIR before update" + cp -a /srv/tesm-license "$BACKUP_DIR" + print_status "Backup created" +fi + +# ---- Pakete ---- +step "Installing system packages" +sudo apt-get update >>/var/log/tesm-license-install.log 2>&1 && print_status "apt update" +sudo apt-get install -y python3 python3-venv python3-pip nginx sqlite3 rsync logrotate certbot >>/var/log/tesm-license-install.log 2>&1 && print_status "Packages installed" + +# ---- Log-Verzeichnis ---- +sudo mkdir -p /var/log/tesm-license +sudo chmod 755 /var/log/tesm-license + +# ---- App-Verzeichnis ---- +step "Deploying application to /srv/tesm-license" +sudo mkdir -p /srv/tesm-license +sudo rsync -a --delete --exclude 'venv' --exclude 'sqlite.db' --exclude 'fernet.key' --exclude 'secret.key' \ + --exclude 'license.json' --exclude 'master_signing_key.json' \ + "$REPO_DIR/srv/tesm-license/" /srv/tesm-license/ >>/var/log/tesm-license-install.log 2>&1 +print_status "Application files copied" + +# ---- Python venv ---- +step "Setting up Python virtual environment" +cd /srv/tesm-license +sudo python3 -m venv venv +sudo ./venv/bin/python3 -m pip install --upgrade pip >>/var/log/tesm-license-install.log 2>&1 +sudo ./venv/bin/python3 -m pip install -r requirements.txt >>/var/log/tesm-license-install.log 2>&1 +print_status "Virtual environment ready" + +# ---- Datenbank ---- +if [ ! -f /srv/tesm-license/sqlite.db ]; then + step "Initializing database" + sudo ./venv/bin/python3 create_db.py >>/var/log/tesm-license-install.log 2>&1 + print_status "Database initialized" + echo -e "${RED}→${NC} Kein Admin-Benutzer vorhanden. Bitte danach ausführen:" + echo " sudo /srv/tesm-license/venv/bin/python3 /srv/tesm-license/create_admin.py" + echo -e "${RED}→${NC} Und die eigene Bootstrap-Lizenz erzeugen:" + echo " sudo /srv/tesm-license/venv/bin/python3 /srv/tesm-license/create_master_license.py" +fi + +# ---- systemd ---- +step "Copying systemd unit" +UNITS_CHANGED=0 +install_if_changed "$REPO_DIR/etc/systemd/system/tesm-license.service" /etc/systemd/system/tesm-license.service 644 && UNITS_CHANGED=1 +print_status "Unit copied" + +# ---- nginx ---- +step "Configuring nginx reverse proxy" +if [ ! -f /etc/nginx/sites-available/tesm-license ]; then + sudo cp "$REPO_DIR/etc/nginx/sites-available/tesm-license" /etc/nginx/sites-available/tesm-license +fi +sudo ln -sf /etc/nginx/sites-available/tesm-license /etc/nginx/sites-enabled/tesm-license +sudo rm -f /etc/nginx/sites-enabled/default +sudo nginx -t >>/var/log/tesm-license-install.log 2>&1 && sudo systemctl reload nginx +print_status "nginx configured" + +# ---- Dienst aktivieren ---- +step "Enabling service" +if [ "$UNITS_CHANGED" -eq 1 ]; then + sudo systemctl daemon-reload +fi +sudo systemctl enable --now tesm-license.service +print_status "Service enabled and started" + +# ---- Sicherheitsnetz prüfen ---- +if [ "$NEEDS_MIGRATION_GUARD" -eq 1 ]; then + step "Verifying migrated installation" + HEALTHY=0 + for i in $(seq 1 15); do + sleep 2 + if curl -fsS -o /dev/null http://127.0.0.1:5000/login 2>/dev/null; then + HEALTHY=1 + break + fi + done + if [ "$HEALTHY" -eq 1 ]; then + print_status "Migration verified healthy" + rm -rf "$BACKUP_DIR" + echo -e "${GREEN}✔${NC} Update erfolgreich -- Backup wieder entfernt." + else + echo -e "${RED}✖ Update fehlgeschlagen -- Dienst antwortet nicht gesund. Rolle zurück...${NC}" + sudo systemctl stop tesm-license.service 2>/dev/null || true + FAILED_DIR="/srv/tesm-license-failed-update-$(date +%Y%m%d-%H%M%S)" + sudo mv /srv/tesm-license "$FAILED_DIR" + sudo cp -a "$BACKUP_DIR" /srv/tesm-license + sudo systemctl start tesm-license.service + echo -e "${YELLOW}Zurückgerollt auf den Stand vor dem Update.${NC}" + echo "Backup bleibt erhalten unter: ${BACKUP_DIR}" + echo "Die fehlgeschlagene Installation liegt zur Fehlersuche unter: ${FAILED_DIR}" + echo "Bitte /var/log/tesm-license/app.log dort prüfen, bevor erneut aktualisiert wird." + fi +fi + +echo -e "${GREEN}✔${NC} Installation abgeschlossen. Web-App erreichbar auf Port 80 (nginx) bzw. 5000 (Flask direkt)." diff --git a/update.sh b/update.sh new file mode 100644 index 0000000..90bbd89 --- /dev/null +++ b/update.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# ============================================================================ +# TESM-Lizenzserver — Update/Reinstall (Bootstrapper) +# Fork von update.sh (TESM selbst) -- lädt dasselbe gepackte Gitea-Release +# herunter (das Repo enthält beide Apps: srv/tesm/ UND srv/tesm-license/) +# und übergibt an install-license.sh aus dem Paket, statt install.sh. +# +# Auszuführen als root auf dem Zielsystem: +# sudo ./update-license.sh +# Optional übersteuerbar (z.B. für eine bestimmte Version statt "latest"): +# TESM_RELEASE_TAG=v1.0.0 sudo -E ./update-license.sh +# ============================================================================ +set -e + +GITEA_BASE="${TESM_GITEA_BASE:-https://gitea.int.eertmoed.net}" +REPO_OWNER="${TESM_REPO_OWNER:-alientim}" +REPO_NAME="${TESM_REPO_NAME:-tesm}" +RELEASE_TAG="${TESM_RELEASE_TAG:-latest}" +GITEA_USER="${TESM_GITEA_USER:-}" +GITEA_TOKEN="${TESM_GITEA_TOKEN:-}" +PACKAGE_NAME="tesm-${RELEASE_TAG}.tar.gz" +DOWNLOAD_URL="${GITEA_BASE}/${REPO_OWNER}/${REPO_NAME}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" + +WORK_DIR="/tmp/tesm-license-update-$(date +%s)" +PACKAGE_FILE="$WORK_DIR/${PACKAGE_NAME}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +if [ "$(id -u)" -ne 0 ]; then + echo "Bitte als root ausführen (sudo ./update-license.sh)." >&2 + exit 1 +fi + +echo -e "${YELLOW}============================================================================${NC}" +echo -e "${YELLOW} TESM-Lizenzserver Update/Reinstall — Release \"${RELEASE_TAG}\"${NC}" +echo -e "${YELLOW}============================================================================${NC}" +echo +echo "Lädt das Release-Paket herunter (enthält TESM und den Lizenzserver" +echo "zusammen) und übergibt Installation/Update an install-license.sh" +echo "daraus -- deployt ausschließlich srv/tesm-license/, lässt eine" +echo "eventuell auf demselben Host vorhandene TESM-Installation unberührt." +echo + +read -r -p "Fortfahren? [y/N] " confirm +if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + echo "Abgebrochen — nichts wurde verändert." + exit 0 +fi + +echo -e "${RED}→${NC} Lade Paket von ${DOWNLOAD_URL}..." +mkdir -p "$WORK_DIR" +CURL_AUTH=() +if [ -n "$GITEA_USER" ] && [ -n "$GITEA_TOKEN" ]; then + CURL_AUTH=(-u "${GITEA_USER}:${GITEA_TOKEN}") +fi +curl -fsSL "${CURL_AUTH[@]}" -o "$PACKAGE_FILE" "$DOWNLOAD_URL" +echo -e "${GREEN}✔${NC} Paket heruntergeladen ($(du -h "$PACKAGE_FILE" | cut -f1))." + +echo -e "${RED}→${NC} Entpacke Paket..." +mkdir -p "$WORK_DIR/pkg" +tar xzf "$PACKAGE_FILE" -C "$WORK_DIR/pkg" --strip-components=1 +echo -e "${GREEN}✔${NC} Paket entpackt." + +echo -e "${RED}→${NC} Starte install-license.sh aus dem Paket..." +bash "$WORK_DIR/pkg/install-license.sh" + +echo +echo -e "${GREEN}============================================================================${NC}" +echo -e "${GREEN} TESM-Lizenzserver Update/Reinstall (Release \"${RELEASE_TAG}\") abgeschlossen.${NC}" +echo -e "${GREEN}============================================================================${NC}"