initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
|
||||
# - common
|
||||
*.log
|
||||
*.swp
|
||||
*.tar.gz
|
||||
log*
|
||||
+542
@@ -0,0 +1,542 @@
|
||||
# --------------------
|
||||
# Install psono password manager
|
||||
# --------------------
|
||||
|
||||
# see also:
|
||||
# https://doc.psono.com/admin/overview/summary.html
|
||||
|
||||
|
||||
# System Requirements
|
||||
#
|
||||
# A production grade setup of Psono contains
|
||||
#
|
||||
# 1 VM (or server) for the database
|
||||
# 1 VM (or server) for the server and client module and admin portal
|
||||
# 1 VM (or server) for the fileserver module (if you plan to use it)
|
||||
#
|
||||
# Install guide for the fileserver:
|
||||
# https://doc.psono.com/admin/installation-optional/install-fileserver.html
|
||||
|
||||
|
||||
# 0. Installation Preparation
|
||||
# ===========================
|
||||
|
||||
# Software Requirements:
|
||||
#
|
||||
# Docker
|
||||
# Postgres 14 (but preferable latest)
|
||||
#
|
||||
|
||||
# PostgreSQL
|
||||
#
|
||||
apt install postgresql postgresql-client
|
||||
|
||||
|
||||
# Docker
|
||||
#
|
||||
cd /usr/local/src/
|
||||
git clone https://git.oopen.de/install/docker
|
||||
/usr/local/src/docker/install-docker.sh
|
||||
|
||||
|
||||
# In case of migration from an existing system, you need
|
||||
# - 'settings.yaml' from the # existing psono-combo Container.
|
||||
# - Database dump from the existing system
|
||||
|
||||
|
||||
# **********
|
||||
# Phase 1 . PostgreSQL vorbereiten
|
||||
# **********
|
||||
|
||||
# Schritt 1.1 - Als postgres-Benutzer anmelden
|
||||
#
|
||||
sudo -i -u postgres
|
||||
|
||||
|
||||
# Schritt 1.2 - PostgreSQL-Konsole öffnen
|
||||
#
|
||||
psql
|
||||
|
||||
|
||||
# Schritt 1.3 — Datenbank, Benutzer und Rechte anlegen
|
||||
#
|
||||
# database name......: psono
|
||||
# database user......: psono
|
||||
# database password..: 9Sec-H2.PEPmo.vi
|
||||
#
|
||||
# Befehle nacheinander in der psql-Konsole ausführen:
|
||||
#
|
||||
CREATE DATABASE psono;
|
||||
CREATE USER psono WITH PASSWORD '9Sec-H2.PEPmo.vi';
|
||||
GRANT ALL PRIVILEGES ON DATABASE psono TO psono;
|
||||
|
||||
# in die neie datenbank wechseln
|
||||
\c psono
|
||||
GRANT ALL ON SCHEMA public TO psono;
|
||||
|
||||
|
||||
# Schritt 1.4 - Erweiterungen installieren
|
||||
#
|
||||
# noch immer in der psql-Konsole
|
||||
#
|
||||
# Befehle nacheinander in der psql-Konsole ausführen:
|
||||
#
|
||||
CREATE EXTENSION IF NOT EXISTS ltree;
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
|
||||
# Schritt 1.5 - psql beenden und zurück zu root
|
||||
#
|
||||
|
||||
# in der psql Konsole:
|
||||
\q
|
||||
|
||||
# zurück zu root
|
||||
exit
|
||||
|
||||
|
||||
# **********
|
||||
# Phase 2 . Datenbank prüfen
|
||||
# **********
|
||||
|
||||
# Wir müssen die pg_hba.conf anpassen. Zuerst den Pfad herausfinden:
|
||||
#
|
||||
sudo -u postgres psql -c "SHOW hba_file;"
|
||||
|
||||
# Ausgabe war:
|
||||
#
|
||||
# hba_file
|
||||
# -------------------------------------
|
||||
# /etc/postgresql/17/main/pg_hba.conf
|
||||
# (1 row)
|
||||
|
||||
# Aktuelle Konfiguration ansehen
|
||||
#
|
||||
cat /etc/postgresql/17/main/pg_hba.conf | grep -v "^#" | grep -v "^$"
|
||||
|
||||
# Ausgabe war:
|
||||
#
|
||||
# local all postgres peer
|
||||
# local all all peer
|
||||
# host all all 127.0.0.1/32 scram-sha-256
|
||||
# host all all ::1/128 scram-sha-256
|
||||
# local replication all peer
|
||||
# host replication all 127.0.0.1/32 scram-sha-256
|
||||
# host replication all ::1/128 scram-sha-256
|
||||
|
||||
|
||||
# Wir müssen eine Zeile hinzufügen, die dem psono-Benutzer Passwort-Authentifizierung über den
|
||||
# lokalen Socket erlaubt. Das geht mit diesem Befehl:
|
||||
#
|
||||
sudo sed -i '/^local all all/i local all psono md5' /etc/postgresql/17/main/pg_hba.conf
|
||||
|
||||
# Konfiguration nochmal ansehen?
|
||||
#
|
||||
cat /etc/postgresql/17/main/pg_hba.conf | grep -v "^#" | grep -v "^$"
|
||||
|
||||
# Ausgabe war:
|
||||
# local all postgres peer
|
||||
# local all psono md5
|
||||
# local all all peer
|
||||
# host all all 127.0.0.1/32 scram-sha-256
|
||||
# host all all ::1/128 scram-sha-256
|
||||
# local replication all peer
|
||||
# host replication all 127.0.0.1/32 scram-sha-256
|
||||
# host replication all ::1/128 scram-sha-256
|
||||
#
|
||||
# -> das sieht jetzt gut aus
|
||||
|
||||
|
||||
# PostgreSQL neu laden
|
||||
#
|
||||
sudo systemctl reload postgresql
|
||||
|
||||
# Jetzt die zuvor erstellte Datenbbank prpfen. Derr Schalter '-W' erzwingt die Passwortabfrage:
|
||||
#
|
||||
sudo -u postgres psql -U psono -d psono -W -c '\dx'
|
||||
|
||||
# Ausgabe war:
|
||||
#
|
||||
# Password:
|
||||
# List of installed extensions
|
||||
# Name | Version | Schema | Description
|
||||
# ----------+---------+------------+-------------------------------------------------
|
||||
# ltree | 1.3 | public | data type for hierarchical tree-like structures
|
||||
# pgcrypto | 1.3 | public | cryptographic functions
|
||||
# plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
|
||||
# (3 rows)
|
||||
|
||||
|
||||
# PostgreSQL lauscht standardmäßig nur auf localhost und nicht auf der Docker-Bridge-IP 172.17.0.1.
|
||||
# Wir müssen PostgreSQL anweisen, auch auf dieser Schnittstelle zu lauschen.
|
||||
#
|
||||
# Wir setzen 'listen_addresses' auf ''localhost,172.17.0.1,172.18.0.1, damit PostgreSQL auch auf der
|
||||
# Docker-Bridge-IP antwortet:
|
||||
#
|
||||
# - localhost: für lokale Verbindungen (z.B. psql direkt auf dem Host)
|
||||
# - 172.17.0.1: Docker default bridge
|
||||
# - 172.18.0.1: Docker Compose Netzwerk (wo der Psono-Container läuft)
|
||||
#
|
||||
#
|
||||
|
||||
sudo -u postgres psql -c "ALTER SYSTEM SET listen_addresses = 'localhost,172.17.0.1,172.18.0.1';"
|
||||
systemctl restart postgresql
|
||||
sudo -u postgres psql -c "SHOW listen_addresses;"
|
||||
|
||||
# Ausgabe sollte jetzt sein:
|
||||
#
|
||||
# listen_addresses
|
||||
# ------------------
|
||||
# *
|
||||
# (1 row)
|
||||
|
||||
|
||||
# Im Falle einer Migration jetzt den datenbank dump der alten installation einspielen:
|
||||
#
|
||||
|
||||
|
||||
# Schritt 2.2 - Backup entpacken
|
||||
# ==============================
|
||||
|
||||
mkdir -p /root/psono-backup/dump
|
||||
tar -xf /tmp/2026-06-13_00-00.tar -C /root/psono-backup/dump
|
||||
|
||||
# das verzeichnis dunp sollte in etwa so aussehen:
|
||||
#
|
||||
# ls /root/psono-backup/dump
|
||||
#
|
||||
# 3845.dat 3855.dat 3864.dat 3870.dat 3875.dat 3880.dat 3885.dat 3890.dat 3895.dat 3900.dat restore.sql
|
||||
# 3847.dat 3857.dat 3866.dat 3871.dat 3876.dat 3881.dat 3886.dat 3891.dat 3896.dat 3901.dat toc.dat
|
||||
# 3849.dat 3859.dat 3867.dat 3872.dat 3877.dat 3882.dat 3887.dat 3892.dat 3897.dat 3902.dat
|
||||
# 3851.dat 3861.dat 3868.dat 3873.dat 3878.dat 3883.dat 3888.dat 3893.dat 3898.dat 3903.dat
|
||||
# 3852.dat 3863.dat 3869.dat 3874.dat 3879.dat 3884.dat 3889.dat 3894.dat 3899.dat 3904.dat
|
||||
#
|
||||
# toc.dat, alle .dat-Dateien und sogar eine restore.sql
|
||||
|
||||
|
||||
# Schritt 2.3 - Backup einspielen
|
||||
# ===============================
|
||||
|
||||
# Passwort wird abgefragt:
|
||||
pg_restore \
|
||||
-U psono \
|
||||
-d psono \
|
||||
-Fd \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
/root/psono-backup/dump
|
||||
|
||||
# Ausgabe:
|
||||
#
|
||||
# Password:
|
||||
# pg_restore: error: could not execute query: ERROR: must be owner of extension ltree
|
||||
# Command was: COMMENT ON EXTENSION ltree IS 'data type for hierarchical tree-like structures';
|
||||
#
|
||||
#
|
||||
# pg_restore: error: could not execute query: ERROR: must be owner of extension pgcrypto
|
||||
# Command was: COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
|
||||
#
|
||||
#
|
||||
# pg_restore: warning: errors ignored on restore: 2
|
||||
#
|
||||
# WICHTIG:
|
||||
# Das sind keine echten Fehler - das sind nur Warnungen! Der psono-Benutzer darf keine Kommentare
|
||||
# auf Extensions schreiben, die als Superuser installiert wurden. Das ist vollkommen harmlos und
|
||||
# hat keinen Einfluss auf die Daten.
|
||||
#
|
||||
# Entscheidend ist die letzte Zeile:
|
||||
#
|
||||
# errors ignored on restore: 2
|
||||
#
|
||||
# genau diese 2 Warnungen haben wir gesehen, nichts weiter.
|
||||
|
||||
|
||||
# Jetzt prüfen wir ob die Daten wirklich da sind:
|
||||
#
|
||||
psql -U psono -d psono -W -c 'SELECT count(*) FROM restapi_user;'
|
||||
|
||||
# Ausgabe etwa:
|
||||
#
|
||||
# Password:
|
||||
# count
|
||||
# -------
|
||||
# 16
|
||||
# (1 row)
|
||||
|
||||
|
||||
# **********
|
||||
# Phase 3 - Psono-Konfiguration wiederherstellen
|
||||
# **********
|
||||
|
||||
# Schritt 3.1 - Verzeichnisse anlegen
|
||||
# ===================================
|
||||
|
||||
mkdir -p /opt/docker/psono
|
||||
mkdir -p /opt/docker/psono-client
|
||||
|
||||
|
||||
# Schritt 3.2 - settings.yaml auf den Host übertragen
|
||||
# ===================================================
|
||||
#
|
||||
scp settings.yaml root@psono-ndm.oopen.de:/opt/docker/psono/settings.yaml
|
||||
|
||||
# die relevanten teile der datei ansehen:
|
||||
#
|
||||
grep -E "ALLOWED_HOSTS|WEB_CLIENT_URL|HOST|PORT|NAME|USER|PASSWORD" /opt/docker/psono/settings.yaml | grep -v "^#"
|
||||
|
||||
# mit der Komanndoausgabe:
|
||||
#
|
||||
# WEB_CLIENT_URL: 'https://psono.neuemedienmacher.de'
|
||||
# ALLOWED_HOSTS: ['*']
|
||||
# HOST_URL: 'https://psono.neuemedienmacher.de/server'
|
||||
# EMAIL_HOST: 'smtp.gmail.com'
|
||||
# EMAIL_HOST_USER: 'sysadmin@neuemedienmacher.de'
|
||||
# EMAIL_HOST_PASSWORD: 'RQ5ZDdcNVQkD'
|
||||
# EMAIL_PORT: 587
|
||||
# ALLOW_USER_SEARCH_BY_USERNAME_PARTIAL: True
|
||||
# ALLOW_USER_SEARCH_BY_EMAIL: True
|
||||
# 'NAME': 'psono'
|
||||
# 'USER': 'psono'
|
||||
# 'PASSWORD': 'password'
|
||||
# #'HOST': '172.21.0.6'
|
||||
# 'HOST': 'psono-database'
|
||||
# 'PORT': '5432'
|
||||
# ALLOW_LOST_PASSWORD: True
|
||||
|
||||
# Die URL-Einstellungen passen bereits für den neuen Host ('psono.neuemedienmacher.de').
|
||||
#
|
||||
# Aber die Datenbankverbindung muss geändert werden - aktuell zeigt HOST auf psono-database (den alten
|
||||
# Docker-Container-Namen auf der NAS). Auf dem neuen Host läuft PostgreSQL direkt, nicht in Docker.
|
||||
#
|
||||
# wir müssen insgesamt 2 Zeilen ändern_ HOST und PASSWORD der Datenbankverbindung:
|
||||
#
|
||||
sed -i "s/'HOST': 'psono-database'/'HOST': 'host.docker.internal'/" /opt/docker/psono/settings.yaml
|
||||
sed -i "s/'PASSWORD': 'password'/'PASSWORD': '9Sec-H2.PEPmo.vi'/" /opt/docker/psono/settings.yaml
|
||||
|
||||
# zur Kontrolle:
|
||||
#
|
||||
grep -E "HOST|PASSWORD" /opt/docker/psono/settings.yaml | grep -v "^#" | grep -v EMAIL
|
||||
|
||||
# mit der Ausgabe:
|
||||
#
|
||||
# ALLOWED_HOSTS: ['*']
|
||||
# HOST_URL: 'https://psono.neuemedienmacher.de/server'
|
||||
# 'PASSWORD': '9Sec-H2.PEPmo.vi'
|
||||
# #'HOST': '172.21.0.6'
|
||||
# 'HOST': 'host.docker.internal'
|
||||
# ALLOW_LOST_PASSWORD: True
|
||||
#
|
||||
# So soll es sein:
|
||||
#
|
||||
# - HOST zeigt jetzt auf host.docker.internal
|
||||
# - Passwort ist korrekt gesetzt.
|
||||
|
||||
|
||||
# Disable redis caching (psono-valkey) falls das im alten System aktiviert war
|
||||
#
|
||||
sed -i "s/^CACHE_ENABLE: TRUE/CACHE_ENABLE: FALSE/" /opt/docker/psono/settings.yaml
|
||||
sed -i "s/^CACHE_REDIS: TRUE/CACHE_REDIS: FALSE/" /opt/docker/psono/settings.yaml
|
||||
sed -i "s/^CACHE_REDIS_LOCATION:/#CACHE_REDIS_LOCATION:/" /opt/docker/psono/settings.yaml
|
||||
|
||||
# Kontrolle:
|
||||
#
|
||||
grep -i "cache_enable\|cache_redis" /opt/docker/psono/settings.yaml
|
||||
|
||||
# Ausgabe könnte etwa so aussehen:
|
||||
#
|
||||
# CACHE_ENABLE: FALSE
|
||||
# CACHE_REDIS: FALSE
|
||||
# #CACHE_REDIS_LOCATION: 'redis://@psono-valkey:6379/13'
|
||||
|
||||
|
||||
# Schritt 3.3 - config.json für den Web-Client anlegen
|
||||
# ====================================================
|
||||
|
||||
cat > /opt/docker/psono-client/config.json << 'EOF'
|
||||
{
|
||||
"backend_servers": [{
|
||||
"title": "Psono Server",
|
||||
"url": "https://psono.neuemedienmacher.de/server"
|
||||
}],
|
||||
"base_url": "https://psono.neuemedienmacher.de/",
|
||||
"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
|
||||
|
||||
|
||||
# Prüfen
|
||||
#
|
||||
cat /opt/docker/psono-client/config.json
|
||||
|
||||
# Ausgabe sollte so aussehen
|
||||
#
|
||||
# {
|
||||
# "backend_servers": [{
|
||||
# "title": "Psono Server",
|
||||
# "url": "https://psono.neuemedienmacher.de/server"
|
||||
# }],
|
||||
# "base_url": "https://psono.neuemedienmacher.de/",
|
||||
# "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"]
|
||||
# }
|
||||
|
||||
|
||||
# **********
|
||||
# Phase 4 - Psono-Container starten
|
||||
# **********
|
||||
|
||||
# Schritt 4.1 - Docker Compose Datei 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
|
||||
|
||||
|
||||
# Schritt 4.2 - PostgreSQL für Docker-Verbindung öffnen
|
||||
# =====================================================
|
||||
|
||||
|
||||
# Der Psono-Container wird sich über host.docker.internal mit PostgreSQL verbinden.
|
||||
# Dafür muss pg_hba.conf noch eine Zeile für de Docker-Netzwerke bekommen:
|
||||
#
|
||||
if ! grep -qE "host\s+psono\s+psono\s+172.17.0.0/16" /etc/postgresql/17/main/pg_hba.conf ; then
|
||||
echo "host psono psono 172.17.0.0/16 md5" >> /etc/postgresql/17/main/pg_hba.conf
|
||||
fi
|
||||
if ! grep -qE "host\s+psono\s+psono\s+172.18.0.0/16" /etc/postgresql/17/main/pg_hba.conf ; then
|
||||
echo "host psono psono 172.18.0.0/16 md5" >> /etc/postgresql/17/main/pg_hba.conf
|
||||
fi
|
||||
|
||||
# PostgreSQL neu laden:
|
||||
#
|
||||
systemctl reload postgresql
|
||||
|
||||
|
||||
# Schritt 4.3 - Container starten
|
||||
# ===============================
|
||||
|
||||
cd /opt/docker/psono
|
||||
docker compose up -d
|
||||
|
||||
# Ausgabe:
|
||||
#
|
||||
# [+] up 16/16
|
||||
# ✔ Image psono/psono-combo:latest Pulled 11.0s
|
||||
# ✔ Network psono_default Created 0.0s
|
||||
# ✔ Container psono-psono-combo-1 Started
|
||||
|
||||
|
||||
|
||||
# **********
|
||||
# Phase 5 - Nginx Reverse Proxy einrichten
|
||||
# **********
|
||||
|
||||
|
||||
# Schritt 5.1 - Nginx installieren
|
||||
#
|
||||
cd /usr/local/src/nginx
|
||||
/usr/local/src/nginx/install_nginx.sh
|
||||
|
||||
# TLS Zertifikate installieren - via dehydrated script
|
||||
#
|
||||
# a) dehydrated installieren
|
||||
#
|
||||
/usr/local/src/dehydrated-cron/install_dehydrated.sh
|
||||
|
||||
# domain.txt erstellen
|
||||
if ! grep -q 'psono.neuemedienmacher.de' /var/lib/dehydrated/domains.txt ; then
|
||||
cat >> /var/lib/dehydrated/domains.txt << 'EOF'
|
||||
psono.neuemedienmacher.de
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Zerifikate erstellen
|
||||
#
|
||||
/var/lib/dehydrated/cron/dehydrated_cron.sh
|
||||
|
||||
|
||||
# nginx Konfiguration erstellen:
|
||||
#
|
||||
cat > /etc/nginx/sites-available/psono.neuemedienmacher.de.conf << 'EOF'
|
||||
server {
|
||||
listen 80 ;
|
||||
listen [::]:80 ;
|
||||
|
||||
server_name psono.neuemedienmacher.de;
|
||||
|
||||
# Prevent nginx HTTP Server Detection
|
||||
server_tokens off;
|
||||
|
||||
# Enforce HTTPS
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
server_name psono.neuemedienmacher.de;
|
||||
|
||||
include snippets/letsencrypt-acme-challenge.conf;
|
||||
|
||||
ssl_certificate /var/lib/dehydrated/certs/psono.neuemedienmacher.de/fullchain.pem;
|
||||
ssl_certificate_key /var/lib/dehydrated/certs/psono.neuemedienmacher.de/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;
|
||||
|
||||
# HSTS - ohne preload, da interner Dienst
|
||||
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains;" always;
|
||||
|
||||
# Kein Clickjacking
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
|
||||
# Kein MIME-Type Sniffing
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
# Referrer nur innerhalb gleicher Domain
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
|
||||
# Unnötige Browser-Features deaktivieren
|
||||
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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+583
@@ -0,0 +1,583 @@
|
||||
#!/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 1–2 Wochen stabilem Betrieb: alte Container löschen"
|
||||
echo ""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
+542
@@ -0,0 +1,542 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# Psono Passwortmanager — Neuinstallations-Script
|
||||
# Debian 12/13 · PostgreSQL 17 · Docker · Nginx
|
||||
# ============================================================
|
||||
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 ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 " → "
|
||||
}
|
||||
|
||||
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 'echo -e "\n$ERR Script durch Benutzer abgebrochen (Ctrl+C)."; exit 130' INT
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# START
|
||||
# ════════════════════════════════════════════════════════════
|
||||
clear
|
||||
print_header "Psono Passwortmanager — Neuinstallation"
|
||||
echo -e " Dieses Script installiert Psono Community Edition"
|
||||
echo -e " auf diesem Debian 12/13 Server."
|
||||
echo ""
|
||||
echo -e " ${YELLOW}Voraussetzungen:${RESET}"
|
||||
echo -e " • Docker installiert (download.docker.com)"
|
||||
echo -e " • PostgreSQL 17 installiert"
|
||||
echo -e " • Nginx installiert"
|
||||
echo -e " • Dehydrated mit gültigem Zertifikat für deine Domain"
|
||||
echo -e " • DNS-Eintrag für deine Domain zeigt auf diesen Server"
|
||||
echo -e " • SMTP-Zugangsdaten für den E-Mail-Versand"
|
||||
echo ""
|
||||
echo -e "${DIM}──────────────────────────────────────────────────────────${RESET}"
|
||||
|
||||
# ── Parameter einlesen ───────────────────────────────────────
|
||||
print_header "Parameter eingeben"
|
||||
|
||||
echo -e "${BOLD} 1/7 Domain${RESET}"
|
||||
read -rp " Psono-Domain (z.B. psono.example.com): " PSONO_DOMAIN
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD} 2/7 Server-IP${RESET}"
|
||||
read -rp " Öffentliche IP dieses Servers: " SERVER_IP
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD} 3/7 Datenbank-Passwort${RESET}"
|
||||
echo -e "${DIM} Passwort für den PostgreSQL-Benutzer 'psono'${RESET}"
|
||||
read_password "Passwort" DB_PASSWORD
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD} 4/7 E-Mail-Absender${RESET}"
|
||||
read -rp " E-Mail-Adresse für Psono (z.B. psono@example.com): " PSONO_EMAIL
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD} 5/7 SMTP-Server${RESET}"
|
||||
read -rp " SMTP-Host (z.B. smtp.example.com): " SMTP_HOST
|
||||
read -rp " SMTP-Port [587]: " SMTP_PORT
|
||||
SMTP_PORT="${SMTP_PORT:-587}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD} 6/7 SMTP-Zugangsdaten${RESET}"
|
||||
read -rp " SMTP-Benutzername: " SMTP_USER
|
||||
read_password "SMTP-Passwort" SMTP_PASSWORD
|
||||
echo ""
|
||||
|
||||
echo -e "${BOLD} 7/7 Registrierung${RESET}"
|
||||
echo -e "${DIM} Sollen sich neue Benutzer selbst registrieren können?${RESET}"
|
||||
read -rp " Registrierung erlauben? [j/N]: " REG_CHOICE
|
||||
case "${REG_CHOICE,,}" in
|
||||
j|ja|y|yes) ALLOW_REGISTRATION="True" ;;
|
||||
*) ALLOW_REGISTRATION="False" ;;
|
||||
esac
|
||||
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}E-Mail:${RESET} ${CYAN}${PSONO_EMAIL}${RESET}"
|
||||
echo -e " ${BOLD}SMTP-Host:${RESET} ${CYAN}${SMTP_HOST}:${SMTP_PORT}${RESET}"
|
||||
echo -e " ${BOLD}SMTP-Benutzer:${RESET} ${CYAN}${SMTP_USER}${RESET}"
|
||||
echo -e " ${BOLD}SMTP-Passwort:${RESET} ${CYAN}(gesetzt, ${#SMTP_PASSWORD} Zeichen)${RESET}"
|
||||
echo -e " ${BOLD}Registrierung:${RESET} ${CYAN}${ALLOW_REGISTRATION}${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"
|
||||
run "Datenbank 'psono' anlegen" sudo -u postgres psql -c "CREATE DATABASE psono;" \
|
||||
|| handle_error "Datenbank konnte nicht angelegt werden."
|
||||
|
||||
print_step "1.2 — Benutzer anlegen"
|
||||
run "Benutzer 'psono' anlegen" sudo -u postgres psql \
|
||||
-c "CREATE USER psono WITH PASSWORD '${DB_PASSWORD}';" \
|
||||
|| handle_error "Benutzer konnte nicht angelegt werden."
|
||||
|
||||
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" \
|
||||
bash -c "PGPASSWORD='${DB_PASSWORD}' psql -U psono -d psono -c 'SELECT 1;' > /dev/null"; then
|
||||
echo -e " ${INFO} Verbindung erfolgreich"
|
||||
else
|
||||
handle_error "Verbindungstest fehlgeschlagen."
|
||||
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 — Psono-Konfiguration erstellen
|
||||
# ════════════════════════════════════════════════════════════
|
||||
print_phase "2" "Psono-Konfiguration erstellen"
|
||||
|
||||
print_step "2.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 "2.2 — Kryptographische Schlüssel generieren"
|
||||
echo -e " ${INFO} Generiere Schlüssel (Docker-Image wird ggf. heruntergeladen) ..."
|
||||
echo ""
|
||||
|
||||
KEYS_RAW=$(docker run --rm psono/psono-combo:latest \
|
||||
python3 ./psono/manage.py generateserverkeys 2>/dev/null)
|
||||
|
||||
if [[ -z "$KEYS_RAW" ]]; then
|
||||
handle_error "Schlüssel konnten nicht generiert werden."
|
||||
fi
|
||||
|
||||
SECRET_KEY=$(echo "$KEYS_RAW" | grep "^SECRET_KEY:" | head -1 | sed "s/SECRET_KEY: '//;s/'$//")
|
||||
ACT_SECRET=$(echo "$KEYS_RAW" | grep "^ACTIVATION_LINK_SECRET:" | head -1 | sed "s/ACTIVATION_LINK_SECRET: '//;s/'$//")
|
||||
DB_SECRET=$(echo "$KEYS_RAW" | grep "^DB_SECRET:" | head -1 | sed "s/DB_SECRET: '//;s/'$//")
|
||||
EMAIL_SALT=$(echo "$KEYS_RAW" | grep "^EMAIL_SECRET_SALT:" | head -1 | sed "s/EMAIL_SECRET_SALT: '//;s/'$//")
|
||||
PRIVATE_KEY=$(echo "$KEYS_RAW" | grep "^PRIVATE_KEY:" | head -1 | sed "s/PRIVATE_KEY: '//;s/'$//")
|
||||
PUBLIC_KEY=$(echo "$KEYS_RAW" | grep "^PUBLIC_KEY:" | head -1 | sed "s/PUBLIC_KEY: '//;s/'$//")
|
||||
|
||||
echo -e "$OK Schlüssel generiert"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${BOLD}WICHTIG: Diese Schlüssel jetzt sichern!${RESET}"
|
||||
echo -e " ${DIM}Ohne diese Schlüssel sind alle Passwörter verloren.${RESET}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}PUBLIC_KEY:${RESET} ${CYAN}${PUBLIC_KEY}${RESET}"
|
||||
echo -e " ${BOLD}PRIVATE_KEY:${RESET} ${CYAN}${PRIVATE_KEY:0:20}...${RESET} ${DIM}(vollständig in settings.yaml)${RESET}"
|
||||
echo ""
|
||||
|
||||
print_step "2.3 — settings.yaml erstellen"
|
||||
cat > /opt/docker/psono/settings.yaml << EOF
|
||||
SECRET_KEY: '${SECRET_KEY}'
|
||||
ACTIVATION_LINK_SECRET: '${ACT_SECRET}'
|
||||
DB_SECRET: '${DB_SECRET}'
|
||||
EMAIL_SECRET_SALT: '${EMAIL_SALT}'
|
||||
PRIVATE_KEY: '${PRIVATE_KEY}'
|
||||
PUBLIC_KEY: '${PUBLIC_KEY}'
|
||||
|
||||
DEBUG: False
|
||||
|
||||
ALLOWED_HOSTS: ['${PSONO_DOMAIN}']
|
||||
HOST_URL: 'https://${PSONO_DOMAIN}/server'
|
||||
WEB_CLIENT_URL: 'https://${PSONO_DOMAIN}'
|
||||
|
||||
DATABASES:
|
||||
default:
|
||||
ENGINE: 'django.db.backends.postgresql_psycopg2'
|
||||
NAME: 'psono'
|
||||
USER: 'psono'
|
||||
PASSWORD: '${DB_PASSWORD}'
|
||||
HOST: 'host.docker.internal'
|
||||
PORT: '5432'
|
||||
|
||||
CACHE_ENABLE: FALSE
|
||||
|
||||
EMAIL_FROM: 'Psono <${PSONO_EMAIL}>'
|
||||
EMAIL_HOST: '${SMTP_HOST}'
|
||||
EMAIL_HOST_USER: '${SMTP_USER}'
|
||||
EMAIL_HOST_PASSWORD: '${SMTP_PASSWORD}'
|
||||
EMAIL_PORT: ${SMTP_PORT}
|
||||
EMAIL_USE_TLS: True
|
||||
|
||||
ALLOW_REGISTRATION: ${ALLOW_REGISTRATION}
|
||||
ALLOW_LOST_PASSWORD: True
|
||||
EOF
|
||||
echo -e "$OK settings.yaml erstellt: /opt/docker/psono/settings.yaml"
|
||||
|
||||
print_step "2.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": ${ALLOW_REGISTRATION,,},
|
||||
"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: /opt/docker/psono-client/config.json"
|
||||
|
||||
pause_phase "2" "3"
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# PHASE 3 — Docker Container starten
|
||||
# ════════════════════════════════════════════════════════════
|
||||
print_phase "3" "Psono-Container starten"
|
||||
|
||||
print_step "3.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 "3.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 "3.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 "3.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 "3" "4"
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# PHASE 4 — Nginx einrichten
|
||||
# ════════════════════════════════════════════════════════════
|
||||
print_phase "4" "Nginx Reverse Proxy einrichten"
|
||||
|
||||
print_step "4.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 "4.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 "4.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 "4.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 propagiert?"
|
||||
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-Propagierung abwarten"
|
||||
else
|
||||
handle_error "Psono nicht erreichbar. Nginx-Logs prüfen."
|
||||
fi
|
||||
fi
|
||||
|
||||
pause_phase "4" "5"
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# PHASE 5 — Ersteinrichtung & Backups
|
||||
# ════════════════════════════════════════════════════════════
|
||||
print_phase "5" "Ersteinrichtung & Backups einrichten"
|
||||
|
||||
print_step "5.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 "5.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}║ Installation 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 -e " ${BOLD}settings.yaml:${RESET} ${CYAN}/opt/docker/psono/settings.yaml${RESET}"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}Nächste Schritte:${RESET}"
|
||||
echo -e " 1. https://${PSONO_DOMAIN} öffnen und ersten Benutzer registrieren"
|
||||
echo -e " 2. Admin-Rechte vergeben:"
|
||||
echo -e " ${DIM}docker exec -ti psono-psono-combo-1 \\"
|
||||
echo -e " python3 ./psono/manage.py promoteuser BENUTZER@${PSONO_DOMAIN} superuser${RESET}"
|
||||
echo -e " 3. settings.yaml zusätzlich sichern:"
|
||||
echo -e " ${DIM}cp /opt/docker/psono/settings.yaml /opt/backups/psono/settings.yaml.bak${RESET}"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user