2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00
2026-06-13 18:53:52 +02:00

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

S
Description
No description provided
Readme 471 KiB
Languages
Shell 100%