Files
psono/psono-migration.sh
T
2026-06-13 18:53:52 +02:00

584 lines
25 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================
# Psono Passwortmanager — Migrations-Script
# Von: altem Host (z.B. Synology NAS) → neuer Debian 12/13
# ============================================================
set -euo pipefail
# ── Farben & Symbole ─────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
OK=" ${GREEN}[ OK ]${RESET}"
ERR=" ${RED}[ ERROR ]${RESET}"
WARN="${YELLOW}[ WARNING ]${RESET}"
INFO="${CYAN}[ INFO ]${RESET}"
# ── Hilfsfunktionen ──────────────────────────────────────────
print_header() {
echo ""
echo -e "${BOLD}${BLUE}╔══════════════════════════════════════════════════════════╗${RESET}"
echo -e "${BOLD}${BLUE}$1${RESET}"
echo -e "${BOLD}${BLUE}╚══════════════════════════════════════════════════════════╝${RESET}"
echo ""
}
print_phase() {
echo ""
echo -e "${BOLD}${CYAN}┌──────────────────────────────────────────────────────────┐${RESET}"
echo -e "${BOLD}${CYAN}│ Phase $1: $2${RESET}"
echo -e "${BOLD}${CYAN}└──────────────────────────────────────────────────────────┘${RESET}"
echo ""
}
print_step() {
echo -e "${DIM}──────────────────────────────────────────────────────────${RESET}"
echo -e "${BOLD} Schritt $1${RESET}"
echo ""
}
# Befehl ausführen mit OK/ERROR-Ausgabe
run() {
local desc="$1"; shift
echo -ne " ${DIM}${RESET} ${desc} ... "
if output=$("$@" 2>&1); then
echo -e "$OK"
return 0
else
echo -e "$ERR"
echo -e " ${RED}Ausgabe: ${output}${RESET}"
return 1
fi
}
# Befehl ausführen, Fehler nur als Warning
run_warn() {
local desc="$1"; shift
echo -ne " ${DIM}${RESET} ${desc} ... "
if output=$("$@" 2>&1); then
echo -e "$OK"
else
echo -e "$WARN"
echo -e " ${YELLOW}Ausgabe: ${output}${RESET}"
fi
}
# Befehl mit sichtbarer Ausgabe (z.B. für pg_restore)
run_visible() {
local desc="$1"; shift
echo -e " ${DIM}${RESET} ${desc}"
echo -e "${DIM} ────────────────────────────────────────${RESET}"
if "$@" 2>&1 | sed 's/^/ /'; then
echo -e "${DIM} ────────────────────────────────────────${RESET}"
echo -e "$OK ${desc}"
return 0
else
echo -e "${DIM} ────────────────────────────────────────${RESET}"
echo -e "$ERR ${desc}"
return 1
fi
}
# Fehlerbehandlung: fragen ob weiter oder abbrechen
handle_error() {
local msg="${1:-Ein Fehler ist aufgetreten.}"
echo ""
echo -e "$ERR ${RED}${BOLD}${msg}${RESET}"
echo ""
echo -e "${YELLOW} Wie möchtest du fortfahren?${RESET}"
echo -e " ${BOLD}[C]${RESET} Fortsetzen (auf eigene Gefahr)"
echo -e " ${BOLD}[Q]${RESET} Script abbrechen"
echo ""
read -rp " Eingabe [C/Q]: " choice
case "${choice^^}" in
C) echo -e "$WARN Fortgesetzt trotz Fehler." ;;
*) echo -e "$ERR Script abgebrochen."; exit 1 ;;
esac
}
# Nach jeder Phase pausieren
pause_phase() {
echo ""
echo -e "${DIM}──────────────────────────────────────────────────────────${RESET}"
echo -e "${GREEN} Phase $1 abgeschlossen.${RESET}"
echo -e "${DIM}──────────────────────────────────────────────────────────${RESET}"
echo ""
echo -e " ${BOLD}[ENTER]${RESET} Weiter mit Phase $2"
echo -e " ${BOLD}[Ctrl+C]${RESET} Script abbrechen"
echo ""
read -rp " → "
}
# Passwort einlesen (ohne Echo)
read_password() {
local prompt="$1"
local var_name="$2"
local pw1 pw2
while true; do
read -rsp " ${prompt}: " pw1; echo ""
read -rsp " Wiederholen: " pw2; echo ""
if [[ "$pw1" == "$pw2" ]]; then
if [[ ${#pw1} -lt 12 ]]; then
echo -e "$WARN Passwort zu kurz (min. 12 Zeichen). Bitte nochmal."
else
eval "$var_name='$pw1'"
break
fi
else
echo -e "$ERR Passwörter stimmen nicht überein. Bitte nochmal."
fi
done
}
# ── Trap für Ctrl+C ──────────────────────────────────────────
trap 'echo -e "\n$ERR Script durch Benutzer abgebrochen (Ctrl+C)."; exit 130' INT
# ════════════════════════════════════════════════════════════
# START
# ════════════════════════════════════════════════════════════
clear
print_header "Psono Passwortmanager — Migration"
echo -e " Dieses Script migriert Psono von einem alten Host"
echo -e " auf diesen Debian 12/13 Server."
echo ""
echo -e " ${YELLOW}Voraussetzungen:${RESET}"
echo -e " • Docker installiert"
echo -e " • PostgreSQL 17 installiert"
echo -e " • Nginx installiert"
echo -e " • Dehydrated mit gültigem Zertifikat für deine Domain"
echo -e " • Datenbank-Backup (.tar) lokal vorhanden"
echo -e " • settings.yaml vom alten psono-combo Container gesichert"
echo ""
echo -e "${DIM}──────────────────────────────────────────────────────────${RESET}"
# ── Parameter einlesen ───────────────────────────────────────
print_header "Parameter eingeben"
echo -e "${BOLD} 1/6 Domain${RESET}"
read -rp " Psono-Domain (z.B. psono.example.com): " PSONO_DOMAIN
echo ""
echo -e "${BOLD} 2/6 Server-IP${RESET}"
read -rp " Öffentliche IP dieses Servers: " SERVER_IP
echo ""
echo -e "${BOLD} 3/6 Datenbank-Passwort${RESET}"
echo -e "${DIM} Passwort für den PostgreSQL-Benutzer 'psono'${RESET}"
read_password "Passwort" DB_PASSWORD
echo ""
echo -e "${BOLD} 4/6 Pfad zum Datenbank-Backup${RESET}"
read -rp " Vollständiger Pfad zur .tar-Datei: " BACKUP_PATH
while [[ ! -f "$BACKUP_PATH" ]]; do
echo -e "$ERR Datei nicht gefunden: $BACKUP_PATH"
read -rp " Pfad zur .tar-Datei: " BACKUP_PATH
done
echo ""
echo -e "${BOLD} 5/6 Pfad zur settings.yaml${RESET}"
read -rp " Vollständiger Pfad zur settings.yaml: " SETTINGS_PATH
while [[ ! -f "$SETTINGS_PATH" ]]; do
echo -e "$ERR Datei nicht gefunden: $SETTINGS_PATH"
read -rp " Pfad zur settings.yaml: " SETTINGS_PATH
done
echo ""
echo -e "${BOLD} 6/6 E-Mail-Adresse${RESET}"
read -rp " E-Mail für Psono-Benachrichtigungen: " PSONO_EMAIL
echo ""
# ── Zusammenfassung ──────────────────────────────────────────
print_header "Zusammenfassung"
echo -e " ${BOLD}Domain:${RESET} ${CYAN}${PSONO_DOMAIN}${RESET}"
echo -e " ${BOLD}Server-IP:${RESET} ${CYAN}${SERVER_IP}${RESET}"
echo -e " ${BOLD}DB-Passwort:${RESET} ${CYAN}(gesetzt, ${#DB_PASSWORD} Zeichen)${RESET}"
echo -e " ${BOLD}Backup-Datei:${RESET} ${CYAN}${BACKUP_PATH}${RESET}"
echo -e " ${BOLD}settings.yaml:${RESET} ${CYAN}${SETTINGS_PATH}${RESET}"
echo -e " ${BOLD}E-Mail:${RESET} ${CYAN}${PSONO_EMAIL}${RESET}"
echo ""
echo -e " ${BOLD}Zertifikat erwartet unter:${RESET}"
echo -e " ${DIM}/var/lib/dehydrated/certs/${PSONO_DOMAIN}/fullchain.pem${RESET}"
echo ""
echo -e "${DIM}──────────────────────────────────────────────────────────${RESET}"
echo ""
echo -e " ${YELLOW}${BOLD}Bitte prüfe alle Angaben sorgfältig.${RESET}"
echo -e " Gib ${BOLD}YES${RESET} (Großbuchstaben) ein um das Script zu starten."
echo -e " Alles andere bricht ab."
echo ""
read -rp " Bestätigung: " CONFIRM
if [[ "$CONFIRM" != "YES" ]]; then
echo -e "$ERR Abgebrochen. Keine Änderungen vorgenommen."
exit 0
fi
# ════════════════════════════════════════════════════════════
# PHASE 1 — PostgreSQL vorbereiten
# ════════════════════════════════════════════════════════════
print_phase "1" "PostgreSQL vorbereiten"
print_step "1.1 — Datenbank anlegen"
if run "Datenbank 'psono' anlegen" sudo -u postgres psql -c "CREATE DATABASE psono;"; then
: # ok
else
handle_error "Datenbank konnte nicht angelegt werden."
fi
print_step "1.2 — Benutzer anlegen"
if run "Benutzer 'psono' anlegen" sudo -u postgres psql -c "CREATE USER psono WITH PASSWORD '${DB_PASSWORD}';"; then
: # ok
else
handle_error "Benutzer konnte nicht angelegt werden."
fi
print_step "1.3 — Rechte vergeben"
run "GRANT auf Datenbank" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE psono TO psono;" \
|| handle_error "GRANT fehlgeschlagen."
run "GRANT auf Schema" sudo -u postgres psql -d psono -c "GRANT ALL ON SCHEMA public TO psono;" \
|| handle_error "GRANT auf Schema fehlgeschlagen."
print_step "1.4 — Erweiterungen installieren"
run "Extension ltree" sudo -u postgres psql -d psono -c "CREATE EXTENSION IF NOT EXISTS ltree;" \
|| handle_error "Extension ltree konnte nicht installiert werden."
run "Extension pgcrypto" sudo -u postgres psql -d psono -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;" \
|| handle_error "Extension pgcrypto konnte nicht installiert werden."
print_step "1.5 — pg_hba.conf anpassen"
HBA_FILE=$(sudo -u postgres psql -tAc "SHOW hba_file;")
echo -e " ${INFO} pg_hba.conf: ${HBA_FILE}"
run "Eintrag für lokalen psono-Benutzer" sudo sed -i \
'/^local all all/i local all psono md5' \
"$HBA_FILE" || handle_error "pg_hba.conf konnte nicht angepasst werden."
echo "host psono psono 172.17.0.0/16 md5" | sudo tee -a "$HBA_FILE" > /dev/null \
&& echo -e "$OK Docker-Netzwerk 172.17.0.0/16 eingetragen" \
|| handle_error "pg_hba.conf Eintrag für 172.17.0.0/16 fehlgeschlagen."
echo "host psono psono 172.18.0.0/16 md5" | sudo tee -a "$HBA_FILE" > /dev/null \
&& echo -e "$OK Docker-Netzwerk 172.18.0.0/16 eingetragen" \
|| handle_error "pg_hba.conf Eintrag für 172.18.0.0/16 fehlgeschlagen."
print_step "1.6 — listen_addresses anpassen"
run "listen_addresses setzen" sudo -u postgres psql -c \
"ALTER SYSTEM SET listen_addresses = 'localhost,172.17.0.1,172.18.0.1';" \
|| handle_error "listen_addresses konnte nicht gesetzt werden."
run "PostgreSQL neu starten" sudo systemctl restart postgresql \
|| handle_error "PostgreSQL-Neustart fehlgeschlagen."
print_step "1.7 — Prüfung"
if run "Verbindung als psono-Benutzer testen" sudo -u postgres psql -U psono -d psono \
-c "SELECT count(*) FROM pg_extension WHERE extname IN ('ltree','pgcrypto');" 2>/dev/null; then
echo -e " ${INFO} Erweiterungen vorhanden"
else
handle_error "Verbindungstest fehlgeschlagen — Erweiterungen oder Benutzer nicht korrekt."
fi
LISTEN=$(sudo -u postgres psql -tAc "SHOW listen_addresses;" 2>/dev/null | tr -d ' ')
echo -e " ${INFO} listen_addresses = ${CYAN}${LISTEN}${RESET}"
pause_phase "1" "2"
# ════════════════════════════════════════════════════════════
# PHASE 2 — Backup einspielen
# ════════════════════════════════════════════════════════════
print_phase "2" "Backup einspielen"
print_step "2.1 — Backup entpacken"
DUMP_DIR="/root/psono-backup/dump"
run "Verzeichnis anlegen" mkdir -p "$DUMP_DIR" \
|| handle_error "Verzeichnis $DUMP_DIR konnte nicht angelegt werden."
run "Backup entpacken" tar -xf "$BACKUP_PATH" -C "$DUMP_DIR" \
|| handle_error "Backup konnte nicht entpackt werden."
echo -e " ${INFO} Inhalt des Dumps:"
ls "$DUMP_DIR" | head -5 | sed 's/^/ /'
echo -e " ${DIM}... ($(ls "$DUMP_DIR" | wc -l) Dateien gesamt)${RESET}"
print_step "2.2 — Backup einspielen"
echo -e " ${INFO} Das kann einige Sekunden dauern ..."
echo ""
PGPASSWORD="$DB_PASSWORD" pg_restore \
-U psono \
-d psono \
-Fd \
--no-owner \
--no-privileges \
"$DUMP_DIR" 2>&1 | while IFS= read -r line; do
if echo "$line" | grep -qi "error:"; then
echo -e " $ERR ${line}"
elif echo "$line" | grep -qi "warning:"; then
echo -e " $WARN ${DIM}${line}${RESET}"
else
echo -e " ${DIM} ${line}${RESET}"
fi
done
PG_EXIT=${PIPESTATUS[0]}
if [[ $PG_EXIT -eq 0 ]]; then
echo -e "$OK pg_restore abgeschlossen"
else
echo -e "$WARN pg_restore mit Warnungen/Fehlern beendet (Exit: $PG_EXIT)"
handle_error "pg_restore meldete Fehler. Prüfe die Ausgabe oben."
fi
print_step "2.3 — Prüfung"
USER_COUNT=$(PGPASSWORD="$DB_PASSWORD" psql -U psono -d psono -tAc \
"SELECT count(*) FROM restapi_user;" 2>/dev/null || echo "0")
echo -e " ${INFO} Importierte Psono-Benutzer: ${CYAN}${USER_COUNT}${RESET}"
if [[ "$USER_COUNT" -gt 0 ]]; then
echo -e "$OK Daten erfolgreich importiert"
else
handle_error "Keine Benutzer gefunden — Import möglicherweise fehlgeschlagen."
fi
pause_phase "2" "3"
# ════════════════════════════════════════════════════════════
# PHASE 3 — Psono-Konfiguration wiederherstellen
# ════════════════════════════════════════════════════════════
print_phase "3" "Psono-Konfiguration wiederherstellen"
print_step "3.1 — Verzeichnisse anlegen"
run "Verzeichnis /opt/docker/psono" mkdir -p /opt/docker/psono \
|| handle_error "Verzeichnis konnte nicht angelegt werden."
run "Verzeichnis /opt/docker/psono-client" mkdir -p /opt/docker/psono-client \
|| handle_error "Verzeichnis konnte nicht angelegt werden."
print_step "3.2 — settings.yaml kopieren"
run "settings.yaml kopieren" cp "$SETTINGS_PATH" /opt/docker/psono/settings.yaml \
|| handle_error "settings.yaml konnte nicht kopiert werden."
print_step "3.3 — settings.yaml anpassen"
run "HOST auf host.docker.internal setzen" sed -i \
"s/'HOST': 'psono-database'/'HOST': 'host.docker.internal'/" \
/opt/docker/psono/settings.yaml || handle_error "HOST konnte nicht ersetzt werden."
run "DB-Passwort setzen" sed -i \
"s/'PASSWORD': '.*'/'PASSWORD': '${DB_PASSWORD}'/" \
/opt/docker/psono/settings.yaml || handle_error "Passwort konnte nicht gesetzt werden."
# Cache deaktivieren (nur wenn vorhanden)
if grep -q "^CACHE_ENABLE: TRUE" /opt/docker/psono/settings.yaml 2>/dev/null; then
run "CACHE_ENABLE deaktivieren" sed -i \
"s/^CACHE_ENABLE: TRUE/CACHE_ENABLE: FALSE/" /opt/docker/psono/settings.yaml \
|| handle_error "CACHE_ENABLE konnte nicht deaktiviert werden."
run "CACHE_REDIS deaktivieren" sed -i \
"s/^CACHE_REDIS: TRUE/CACHE_REDIS: FALSE/" /opt/docker/psono/settings.yaml \
|| true
run "CACHE_REDIS_LOCATION auskommentieren" sed -i \
"s/^CACHE_REDIS_LOCATION:/#CACHE_REDIS_LOCATION:/" /opt/docker/psono/settings.yaml \
|| true
else
echo -e " ${INFO} Cache war bereits deaktiviert — keine Änderung nötig"
fi
print_step "3.4 — config.json erstellen"
cat > /opt/docker/psono-client/config.json << EOF
{
"backend_servers": [{
"title": "Psono Server",
"url": "https://${PSONO_DOMAIN}/server"
}],
"base_url": "https://${PSONO_DOMAIN}/",
"allow_custom_server": true,
"allow_registration": true,
"allow_lost_password": true,
"disable_download_bar": false,
"remember_me_default": false,
"trust_device_default": false,
"authentication_methods": ["AUTHKEY"]
}
EOF
echo -e "$OK config.json erstellt"
print_step "3.5 — Prüfung"
echo -e " ${INFO} Aktuelle Datenbankverbindung in settings.yaml:"
grep -E "'HOST'|'PASSWORD'|'NAME'" /opt/docker/psono/settings.yaml 2>/dev/null | sed 's/^/ /'
CACHE_STATE=$(grep "^CACHE_ENABLE" /opt/docker/psono/settings.yaml 2>/dev/null || echo "nicht gefunden")
echo -e " ${INFO} Cache: ${CYAN}${CACHE_STATE}${RESET}"
pause_phase "3" "4"
# ════════════════════════════════════════════════════════════
# PHASE 4 — Docker Container starten
# ════════════════════════════════════════════════════════════
print_phase "4" "Psono-Container starten"
print_step "4.1 — docker-compose.yml erstellen"
cat > /opt/docker/psono/docker-compose.yml << 'EOF'
services:
psono-combo:
image: psono/psono-combo:latest
restart: unless-stopped
ports:
- "127.0.0.1:10200:80"
volumes:
- /opt/docker/psono/settings.yaml:/root/.psono_server/settings.yaml:ro
- /opt/docker/psono-client/config.json:/usr/share/nginx/html/config.json:ro
extra_hosts:
- "host.docker.internal:host-gateway"
EOF
echo -e "$OK docker-compose.yml erstellt"
print_step "4.2 — Container starten"
run "Docker Image pullen" docker compose -f /opt/docker/psono/docker-compose.yml pull \
|| handle_error "Docker Image konnte nicht geladen werden."
run "Container starten" docker compose -f /opt/docker/psono/docker-compose.yml up -d \
|| handle_error "Container konnte nicht gestartet werden."
print_step "4.3 — Warten und Logs prüfen"
echo -e " ${INFO} Warte 10 Sekunden auf Container-Start ..."
sleep 10
echo -e " ${INFO} Letzte Log-Einträge:"
docker compose -f /opt/docker/psono/docker-compose.yml logs psono-combo 2>&1 | tail -8 | sed 's/^/ /'
print_step "4.4 — Prüfung"
if run "HTTP-Test auf Port 10200" curl -sf http://localhost:10200/server/info/ > /dev/null; then
PSONO_VERSION=$(curl -s http://localhost:10200/server/info/ | grep -o '"version": "[^"]*"' | head -1)
echo -e " ${INFO} ${CYAN}${PSONO_VERSION}${RESET}"
else
handle_error "Psono antwortet nicht auf Port 10200. Logs prüfen."
fi
pause_phase "4" "5"
# ════════════════════════════════════════════════════════════
# PHASE 5 — Nginx einrichten
# ════════════════════════════════════════════════════════════
print_phase "5" "Nginx Reverse Proxy einrichten"
print_step "5.1 — Zertifikat prüfen"
CERT_PATH="/var/lib/dehydrated/certs/${PSONO_DOMAIN}"
if [[ -f "${CERT_PATH}/fullchain.pem" && -f "${CERT_PATH}/privkey.pem" ]]; then
echo -e "$OK Zertifikat gefunden: ${CERT_PATH}"
EXPIRY=$(openssl x509 -enddate -noout -in "${CERT_PATH}/fullchain.pem" 2>/dev/null | cut -d= -f2)
echo -e " ${INFO} Gültig bis: ${CYAN}${EXPIRY}${RESET}"
else
handle_error "Zertifikat nicht gefunden unter ${CERT_PATH}. Dehydrated zuerst ausführen."
fi
print_step "5.2 — Nginx-Konfiguration erstellen"
NGINX_CONF="/etc/nginx/sites-available/${PSONO_DOMAIN}.conf"
cat > "$NGINX_CONF" << EOF
server {
listen 80;
listen [::]:80;
server_name ${PSONO_DOMAIN};
server_tokens off;
return 301 https://\$host\$request_uri;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name ${PSONO_DOMAIN};
include snippets/letsencrypt-acme-challenge.conf;
ssl_certificate /var/lib/dehydrated/certs/${PSONO_DOMAIN}/fullchain.pem;
ssl_certificate_key /var/lib/dehydrated/certs/${PSONO_DOMAIN}/privkey.pem;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_session_cache shared:MozSSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
server_tokens off;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains;" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
client_max_body_size 256m;
location / {
proxy_pass http://127.0.0.1:10200;
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;
}
}
EOF
echo -e "$OK Nginx-Konfiguration erstellt: $NGINX_CONF"
print_step "5.3 — Konfiguration aktivieren"
if [[ ! -L "/etc/nginx/sites-enabled/${PSONO_DOMAIN}.conf" ]]; then
run "Symlink erstellen" ln -s "$NGINX_CONF" "/etc/nginx/sites-enabled/${PSONO_DOMAIN}.conf" \
|| handle_error "Symlink konnte nicht erstellt werden."
else
echo -e " ${INFO} Symlink bereits vorhanden"
fi
run "Nginx-Konfiguration testen" nginx -t \
|| handle_error "Nginx-Konfiguration fehlerhaft — bitte manuell prüfen."
run "Nginx neu laden" systemctl reload nginx \
|| handle_error "Nginx konnte nicht neu geladen werden."
print_step "5.4 — Prüfung"
if run "HTTPS-Test" curl -sf "https://${PSONO_DOMAIN}/server/info/" > /dev/null; then
echo -e " ${INFO} ${GREEN}Psono ist über HTTPS erreichbar!${RESET}"
else
echo -e "$WARN HTTPS-Test fehlgeschlagen — DNS noch nicht umgestellt?"
echo -e " ${INFO} Test mit direkter IP-Auflösung:"
if curl -sf --resolve "${PSONO_DOMAIN}:443:127.0.0.1" \
"https://${PSONO_DOMAIN}/server/info/" > /dev/null 2>&1; then
echo -e "$OK Lokal erreichbar — DNS-Umstellung ausstehend"
else
handle_error "Psono nicht erreichbar. Nginx-Logs prüfen."
fi
fi
pause_phase "5" "6"
# ════════════════════════════════════════════════════════════
# PHASE 6 — Abschluss & Backups
# ════════════════════════════════════════════════════════════
print_phase "6" "Abschluss & Backups einrichten"
print_step "6.1 — Backup-Verzeichnis anlegen"
run "Verzeichnis /opt/backups/psono anlegen" mkdir -p /opt/backups/psono \
|| handle_error "Backup-Verzeichnis konnte nicht angelegt werden."
print_step "6.2 — Cronjobs einrichten"
CRON_BACKUP="0 2 * * * PGPASSWORD='${DB_PASSWORD}' pg_dump -U psono -Ft psono > /opt/backups/psono/psono-\$(date +\\%Y-\\%m-\\%d).tar"
CRON_CLEANUP="0 3 * * * find /opt/backups/psono -name '*.tar' -mtime +30 -delete"
(crontab -l 2>/dev/null | grep -v "psono"; echo "$CRON_BACKUP"; echo "$CRON_CLEANUP") | crontab - \
&& echo -e "$OK Cronjobs eingerichtet" \
|| echo -e "$WARN Cronjobs konnten nicht eingerichtet werden — bitte manuell eintragen"
echo -e " ${INFO} Backup täglich um 02:00, Aufbewahrung 30 Tage"
# ── Abschlussbericht ─────────────────────────────────────────
echo ""
echo -e "${BOLD}${GREEN}╔══════════════════════════════════════════════════════════╗${RESET}"
echo -e "${BOLD}${GREEN}║ Migration erfolgreich abgeschlossen! ║${RESET}"
echo -e "${BOLD}${GREEN}╚══════════════════════════════════════════════════════════╝${RESET}"
echo ""
echo -e " ${BOLD}Psono läuft unter:${RESET} ${CYAN}https://${PSONO_DOMAIN}${RESET}"
echo -e " ${BOLD}Admin-Panel:${RESET} ${CYAN}https://${PSONO_DOMAIN}/portal/login${RESET}"
echo -e " ${BOLD}Backups:${RESET} ${CYAN}/opt/backups/psono/${RESET}"
echo ""
echo -e " ${YELLOW}Nächste Schritte:${RESET}"
echo -e " • Login mit bekanntem Benutzer testen"
echo -e " • Passwörter in Tresoren prüfen"
echo -e " • Alten psono-combo Container auf der NAS stoppen"
echo -e " • Nach 12 Wochen stabilem Betrieb: alte Container löschen"
echo ""