From 7e165d37b62d04433e2869652c7fbc643e67a850 Mon Sep 17 00:00:00 2001 From: patrick Date: Thu, 5 Nov 2026 09:40:35 +0100 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20deploy.sh=20+=20lokale=20Host-K?= =?UTF-8?q?onfig,=20server-update/setup=5Fserver=20=C3=BCbernehmen=20Unit?= =?UTF-8?q?=20und=20sudoers=20aus=20dem=20Repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- .gitignore | 3 +++ deploy.local.env.example | 5 ++++ deploy.sh | 50 +++++++++++++++++++++++++++++------- push-frontend.sh | 9 ++++--- remote.sh | 6 +++-- scripts/check_migrations.sh | 10 +++++--- server-update.sh | 21 +++++++++++++++ setup_server.sh | 51 +++++++++++++++++++++++++++++++------ 8 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 deploy.local.env.example diff --git a/.gitignore b/.gitignore index 6874917..024925d 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ resume~ Thumbs.db CLAUDE.md .claude/ + +# Lokale Deploy-Konfiguration (Hosts) +deploy.local.env diff --git a/deploy.local.env.example b/deploy.local.env.example new file mode 100644 index 0000000..64c3a39 --- /dev/null +++ b/deploy.local.env.example @@ -0,0 +1,5 @@ +# Kopie nach deploy.local.env (gitignored) und anpassen – wird von deploy.sh, +# push-frontend.sh, remote.sh und scripts/check_migrations.sh gelesen. +TM_DEV_HOST=dev.example.com +TM_PROD_HOST=prod.example.com +TM_SSH_USER=root diff --git a/deploy.sh b/deploy.sh index 498a09a..08ac1f4 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,14 +1,46 @@ #!/usr/bin/env bash # ============================================================================= -# STILLGELEGT – nicht mehr verwenden. +# deploy.sh – LOKAL ausführen: Server aus dem Repo aktualisieren # -# Dieses Skript rsyncte das lokale Arbeitsverzeichnis in den git-Baum des -# Servers und verschmutzte ihn dadurch (spätere 'git pull' schlugen fehl). +# Usage: +# ./deploy.sh dev|prod [--frontend] [--no-migrate] # -# Neuer, entkoppelter Ablauf (jeder Server macht sich selbst): -# 1. Lokal: git add -A && git commit && git push origin main -# 2. Server: ssh root@ 'cd /opt/timemaster && ./server-update.sh' -# 3. Frontend (falls geändert, lokal): ./push-frontend.sh <137|164> +# Ablauf: prüft, dass alles gepusht ist → ssh './server-update.sh' +# (Server zieht aus dem Git-Remote, migriert, übernimmt Unit/sudoers aus dem Repo, +# startet neu) → optional Frontend bauen + hochladen (push-frontend.sh). +# Hosts stehen in deploy.local.env (gitignored, Vorlage: deploy.local.env.example). # ============================================================================= -echo "deploy.sh ist stillgelegt. Nutze: git push → server-update.sh (auf dem Server) → push-frontend.sh (lokal)." >&2 -exit 1 +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +[[ -f "$SCRIPT_DIR/deploy.local.env" ]] && source "$SCRIPT_DIR/deploy.local.env" + +die() { echo "[FEHLER] $*" >&2; exit 1; } + +TARGET="${1:-}"; shift || true +case "$TARGET" in + dev) HOST="${TM_DEV_HOST:-}" ;; + prod) HOST="${TM_PROD_HOST:-}" ;; + *) die "Usage: ./deploy.sh dev|prod [--frontend] [--no-migrate]" ;; +esac +[[ -n "$HOST" ]] || die "Host für '$TARGET' nicht gesetzt (deploy.local.env)." + +FRONTEND=false; UPDATE_ARGS=() +for a in "$@"; do + case "$a" in + --frontend) FRONTEND=true ;; + --no-migrate) UPDATE_ARGS+=("$a") ;; + *) die "Unbekannte Option: $a" ;; + esac +done + +cd "$SCRIPT_DIR" +git fetch -q origin main +[[ -z "$(git status --porcelain --untracked-files=no)" ]] || echo "[!] Uncommittete Änderungen – der Server bekommt nur gepushte Commits." +[[ "$(git rev-parse HEAD)" == "$(git rev-parse origin/main)" ]] || die "Lokaler HEAD ist nicht gepusht (git push origin main)." + +echo "==> Update $TARGET (${TM_SSH_USER:-root}@$HOST)" +ssh "${TM_SSH_USER:-root}@$HOST" "cd /opt/timemaster && ./server-update.sh ${UPDATE_ARGS[*]:-}" + +if $FRONTEND; then + "$SCRIPT_DIR/push-frontend.sh" "$TARGET" +fi diff --git a/push-frontend.sh b/push-frontend.sh index ea42112..3f89493 100755 --- a/push-frontend.sh +++ b/push-frontend.sh @@ -7,8 +7,8 @@ # (server-update.sh kann das Frontend also nicht selbst bauen). # # Usage: -# ./push-frontend.sh 137 # = dev → root@$TM_DEV_HOST (Default 192.168.1.137) -# ./push-frontend.sh 164 # = prod → root@$TM_PROD_HOST (Default 192.168.1.164) +# ./push-frontend.sh 137 # = dev → root@$TM_DEV_HOST (aus deploy.local.env) +# ./push-frontend.sh 164 # = prod → root@$TM_PROD_HOST (aus deploy.local.env) # ./push-frontend.sh dev --no-build # bestehendes dist/ nur syncen # Überschreiben: TM_DEV_HOST, TM_PROD_HOST, TM_SSH_USER (Default root) # ============================================================================= @@ -22,8 +22,9 @@ GREEN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m' log() { echo -e "${GREEN}==>${NC} $*"; } die() { echo -e "${RED}[FEHLER]${NC} $*" >&2; exit 1; } -DEV_HOST="${TM_DEV_HOST:-192.168.1.137}" -PROD_HOST="${TM_PROD_HOST:-192.168.1.164}" +[[ -f "$SCRIPT_DIR/deploy.local.env" ]] && source "$SCRIPT_DIR/deploy.local.env" +DEV_HOST="${TM_DEV_HOST:-}" +PROD_HOST="${TM_PROD_HOST:-}" SSH_USER="${TM_SSH_USER:-root}" TARGET="${1:-}" case "$TARGET" in diff --git a/remote.sh b/remote.sh index 77bd9b5..459765b 100755 --- a/remote.sh +++ b/remote.sh @@ -12,8 +12,10 @@ # ./remote.sh env → .env auf Server bearbeiten # ============================================================ -# Ziel: TM_DEV_HOST (Default 192.168.1.137); TM_TARGET=prod nutzt TM_PROD_HOST (Default 192.168.1.164) -if [[ "${TM_TARGET:-dev}" == "prod" ]]; then HOST="${TM_PROD_HOST:-192.168.1.164}"; else HOST="${TM_DEV_HOST:-192.168.1.137}"; fi +# Hosts aus deploy.local.env; TM_TARGET=prod nutzt TM_PROD_HOST +[[ -f "$(dirname "$0")/deploy.local.env" ]] && source "$(dirname "$0")/deploy.local.env" +if [[ "${TM_TARGET:-dev}" == "prod" ]]; then HOST="${TM_PROD_HOST:-}"; else HOST="${TM_DEV_HOST:-}"; fi +[[ -n "$HOST" ]] || { echo "Host nicht gesetzt (deploy.local.env)." >&2; exit 1; } SERVER="${TM_SSH_USER:-root}@$HOST" REMOTE="/opt/timemaster" diff --git a/scripts/check_migrations.sh b/scripts/check_migrations.sh index 7dc0f54..17b48b3 100755 --- a/scripts/check_migrations.sh +++ b/scripts/check_migrations.sh @@ -9,8 +9,8 @@ # ./scripts/check_migrations.sh [137|164|both] # # Argumente: -# 137 Nur Dev-Server prüfen (Standard; $TM_DEV_HOST, Default 192.168.1.137) -# 164 Nur Prod-Server prüfen ($TM_PROD_HOST, Default 192.168.1.164) +# 137 Nur Dev-Server prüfen (Standard; $TM_DEV_HOST) +# 164 Nur Prod-Server prüfen ($TM_PROD_HOST) # both Beide Server prüfen # # Exit-Codes: @@ -27,9 +27,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" BACKEND_DIR="$PROJECT_DIR/backend" +[[ -f "$(dirname "$0")/../deploy.local.env" ]] && source "$(dirname "$0")/../deploy.local.env" SSH_USER="${TM_SSH_USER:-root}" -SERVER_137="$SSH_USER@${TM_DEV_HOST:-192.168.1.137}" -SERVER_164="$SSH_USER@${TM_PROD_HOST:-192.168.1.164}" +[[ -n "${TM_DEV_HOST:-}" && -n "${TM_PROD_HOST:-}" ]] || { echo "TM_DEV_HOST/TM_PROD_HOST nicht gesetzt (deploy.local.env)." >&2; exit 1; } +SERVER_137="$SSH_USER@$TM_DEV_HOST" +SERVER_164="$SSH_USER@$TM_PROD_HOST" REMOTE_PATH="/opt/timemaster" REMOTE_VENV="$REMOTE_PATH/backend/venv" diff --git a/server-update.sh b/server-update.sh index 334c292..867df70 100755 --- a/server-update.sh +++ b/server-update.sh @@ -10,6 +10,7 @@ # # Frontend: wird NICHT hier gebaut (kein Node auf den Servern, dist ist # gitignored). Frontend liefert der Dev-Rechner separat per push-frontend.sh. +# Lokal aufrufbar über: ./deploy.sh [--frontend] # # Usage (auf dem Server): # cd /opt/timemaster && ./server-update.sh @@ -56,6 +57,26 @@ else warn "Migration übersprungen (--no-migrate)" fi +# ── 2b. Systemkonfiguration aus dem Repo (idempotent) ───────────────────────── +id timemaster >/dev/null 2>&1 || { log "Systemuser timemaster anlegen"; useradd --system --no-create-home --shell /usr/sbin/nologin timemaster; } +[[ -f backend/.env ]] && { chown root:timemaster backend/.env; chmod 640 backend/.env; } +UNIT_CHANGED=false +if ! cmp -s "$REMOTE/timemaster.service" /etc/systemd/system/timemaster.service; then + log "systemd-Unit aus Repo übernehmen" + install -m 644 "$REMOTE/timemaster.service" /etc/systemd/system/timemaster.service + UNIT_CHANGED=true +fi +if command -v visudo >/dev/null && ! cmp -s "$REMOTE/timemaster.sudoers" /etc/sudoers.d/timemaster 2>/dev/null; then + log "sudo-Regel aus Repo übernehmen" + install -d -m 755 /etc/sudoers.d + install -m 440 "$REMOTE/timemaster.sudoers" /etc/sudoers.d/timemaster.new + visudo -cf /etc/sudoers.d/timemaster.new >/dev/null && mv /etc/sudoers.d/timemaster.new /etc/sudoers.d/timemaster \ + || { rm -f /etc/sudoers.d/timemaster.new; warn "sudoers ungültig – nicht übernommen"; } +elif ! command -v visudo >/dev/null; then + warn "sudo nicht installiert – TLS-Erneuerung aus der App nicht möglich (apt install sudo)" +fi +$UNIT_CHANGED && systemctl daemon-reload + # ── 3. Service neustarten ───────────────────────────────────────────────────── log "systemctl restart $SERVICE" systemctl restart "$SERVICE" diff --git a/setup_server.sh b/setup_server.sh index 4ee794b..4c88f6d 100644 --- a/setup_server.sh +++ b/setup_server.sh @@ -1,10 +1,18 @@ #!/bin/bash # ============================================================ # TimeMaster – Nativer Server-Setup (Debian 13) -# Führe dieses Script als root oder mit sudo aus +# Voraussetzung: Repo liegt unter /opt/timemaster +# git clone /opt/timemaster +# Führe dieses Script als root aus: bash /opt/timemaster/setup_server.sh +# Erzeugt DB-Passwort, SECRET_KEY(_DATA) und backend/.env automatisch (nur wenn +# .env noch fehlt), richtet Systemuser, systemd-Unit und sudo-Regel aus dem Repo ein. # ============================================================ set -e +[[ $EUID -eq 0 ]] || { echo "Bitte als root ausführen." >&2; exit 1; } +cd /opt/timemaster 2>/dev/null || { echo "/opt/timemaster fehlt – Repo dorthin klonen." >&2; exit 1; } +ENV_FILE=/opt/timemaster/backend/.env + echo "==> [1/7] System-Pakete aktualisieren" apt-get update && apt-get upgrade -y @@ -14,17 +22,23 @@ apt-get install -y \ postgresql postgresql-contrib \ redis-server \ nginx \ - git curl build-essential libpq-dev + git curl build-essential libpq-dev openssl sudo \ + libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b echo "==> [3/7] PostgreSQL einrichten" systemctl enable postgresql && systemctl start postgresql # Datenbank + User anlegen (Produktiv-DB + Test-DB) -sudo -u postgres psql < [4/7] Redis einrichten" systemctl enable redis-server && systemctl start redis-server +echo "==> [4b] Systemuser, .env, systemd-Unit und sudo-Regel aus dem Repo" +id timemaster >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin timemaster +if [[ ! -f "$ENV_FILE" ]]; then + IP="$(hostname -I | awk '{print $1}')" + umask 077 + cat > "$ENV_FILE" < [5/7] Python venv + Abhängigkeiten" cd /opt/timemaster/backend python3 -m venv venv @@ -158,15 +194,14 @@ echo "" echo "==> [Optional] Tests ausführen" echo " python -m pytest tests/ -q" echo "" +systemctl restart timemaster && sleep 3 && systemctl is-active timemaster echo "✓ Setup abgeschlossen!" -echo " Backend: sudo systemctl start timemaster" +echo " Backend: läuft (systemctl status timemaster)" echo " Frontend: http://$(hostname -I | awk '{print $1}')/" echo " API-Docs: http://$(hostname -I | awk '{print $1}')/docs (und /redoc)" echo "" echo "" -echo "Frontend deployen (nach lokalem Build):" -echo " npm run build (im frontend/ Verzeichnis)" -echo " rsync -avz dist/ root@SERVER:/opt/timemaster/frontend/dist/" +echo "Frontend deployen (lokal, vom Dev-Rechner): ./deploy.sh --frontend" echo "" echo "Hinweise:" echo " - passlib wurde durch direktes bcrypt ersetzt (kompatibel mit bcrypt >= 4.0)"