# -------------------- # 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