Files
mareike/docs/implement-endeavour.md
T

16 KiB
Raw Blame History

mareike auf endeavour ausrollen (KeyHelp-Server mit Docker)

Diese Anleitung beschreibt, wie mareike zusätzlich zum bestehenden Server auf endeavour läuft. Endeavour ist ein Server, der mit KeyHelp verwaltet wird. Die Parent-Domain ist meine-anmeldung.digital.

KeyHelp selbst nutzt kein Docker. mareike läuft deshalb in zwei Containern neben KeyHelp. KeyHelp übernimmt weiter, was es gut kann: Domains, Let's-Encrypt-Zertifikate, MariaDB, Postfächer und Backups.

Der bestehende Server bleibt unberührt. Für endeavour gibt es eigene Dateien unter docker/endeavour/. docker-compose.prod, docker/prod.Dockerfile, docker/run-mareike.sh, docker/nginx/default.conf und .dockerignore werden nicht verändert und von endeavour nicht benutzt.


1. Überblick

Browser ──HTTPS──▶ KeyHelp-Webserver (Apache/nginx, Let's Encrypt, eine Domain je Tenant)
                        │  Proxy, Host-Header bleibt erhalten
                        ▼
                 127.0.0.1:8080 ──▶ Container "web" (nginx, liefert public/ aus)
                                          │ FastCGI
                                          ▼
                                    Container "app" (PHP 8.5-FPM, mareike)
                                          │ Unix-Socket /run/mysqld/mysqld.sock
                                          ▼
                                    KeyHelp-MariaDB (Host)
  • Tenant-Erkennung: IdentifyTenant sucht den Tenant über tenants.url = Host der Anfrage. Deshalb muss der Proxy den Host-Header unverändert weitergeben, und jede Tenant-Domain braucht exakt diesen Eintrag in tenants.url.
  • HTTPS: TLS endet bei KeyHelp. Die nginx im Container meldet PHP trotzdem HTTPS=on, damit Laravel https://-Links erzeugt. Eine Code-Änderung ist dafür nicht nötig.
  • Port 8080 ist nur an 127.0.0.1 gebunden und von außen nicht erreichbar.

Dateien im Repo

Datei Zweck
docker/endeavour/Dockerfile Multi-Stage-Build. Target app enthält PHP-FPM, Code, vendor/ und Assets, Target web enthält nginx und public/.
docker/endeavour/Dockerfile.dockerignore Eigener Build-Kontext. Unter anderem kommt keine .env ins Image.
docker/endeavour/compose.yaml Stack mit den Services app und web.
docker/endeavour/nginx.conf nginx im Container, inkl. HTTPS on und echter Client-IP.
docker/endeavour/php.ini Upload-Größen, Speicher, OPcache.
docker/endeavour/env.production.example Vorlage für die .env auf dem Server.
docker/endeavour/deploy.sh Deploy-Skript: pullen, bauen, migrieren, starten.

Verzeichnisse auf endeavour

/opt/mareike/
├── bin/deploy.sh      Kopie von docker/endeavour/deploy.sh
├── src/               Git-Checkout (wird bei jedem Deploy aktualisiert)
├── shared/.env        Konfiguration mit Zugangsdaten (nur root lesbar)
└── storage/           Laravel-storage/ (Logs, Sessions, erzeugte PDFs/ZIPs) – persistent

2. Docker installieren

Als root auf endeavour. Das Beispiel gilt für Debian, bei Ubuntu steht in den Pfaden ubuntu statt debian.

apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Log-Rotation für alle Container (/etc/docker/daemon.json):

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "5" }
}

Docker soll erst nach MariaDB starten, weil der Container das Socket-Verzeichnis /run/mysqld einbindet:

mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/after-mariadb.conf <<'EOF'
[Unit]
After=mariadb.service
Wants=mariadb.service
EOF
systemctl daemon-reload
systemctl restart docker
docker run --rm hello-world

KeyHelp-Firewall: Docker legt eigene iptables-Regeln an. Lädt KeyHelp seine Firewall neu, können diese Regeln verloren gehen. Die Container sind dann nicht mehr erreichbar (502 im Browser). Abhilfe ist systemctl restart docker. Weil mareike nur an 127.0.0.1 lauscht, öffnet Docker nach außen keine Ports.


3. Systemnutzer, Verzeichnisse, Repository

Die Container laufen mit der UID eines eigenen Host-Nutzers, damit storage/ sauber beschreibbar ist. Der Name weicht bewusst von den KeyHelp-Konten ab, weil KeyHelp für seine Konten selbst Systemnutzer anlegt.

getent passwd 2000 || useradd --system --uid 2000 --user-group --no-create-home --shell /usr/sbin/nologin mareike-docker
# Ist UID 2000 belegt, eine andere freie UID wählen. deploy.sh liest sie automatisch aus.

install -d -m 0755 /opt/mareike /opt/mareike/bin
install -d -m 0700 /opt/mareike/shared
install -d -m 0775 -o mareike-docker -g mareike-docker /opt/mareike/storage

Deploy-Key für das Repository anlegen (nur Lesezugriff):

ssh-keygen -t ed25519 -f /root/.ssh/mareike_deploy -N "" -C "endeavour-deploy"
cat /root/.ssh/mareike_deploy.pub     # in Gitea beim Repo th.guenther/mareike als Deploy-Key (read-only) eintragen

cat >> /root/.ssh/config <<'EOF'
Host git.zahlenlabyrinth.de
    Port 9922
    User git
    IdentityFile /root/.ssh/mareike_deploy
    IdentitiesOnly yes
EOF

git clone ssh://git@git.zahlenlabyrinth.de:9922/th.guenther/mareike.git /opt/mareike/src
install -m 0750 /opt/mareike/src/docker/endeavour/deploy.sh /opt/mareike/bin/deploy.sh

Ändert sich docker/endeavour/deploy.sh im Repo, danach den install-Befehl erneut ausführen.


4. Datenbank in KeyHelp anlegen

  1. Im KeyHelp-Panel ein Konto für mareike anlegen oder ein vorhandenes wählen, z. B. anmeldung. Unter diesem Konto liegen später auch die Domains.

  2. Unter Datenbanken eine neue MariaDB-Datenbank mit Benutzer anlegen. Der Benutzer ist @localhost. Den Remote-Zugriff nicht aktivieren, denn der Container verbindet sich über den Unix-Socket und gilt damit als localhost.

  3. Den Socket-Pfad prüfen:

    mysql -e "SELECT @@socket;"     # erwartet: /run/mysqld/mysqld.sock
    

    Liegt der Socket woanders, in /opt/mareike/bin/deploy.sh die Variable MAREIKE_DB_SOCKET_DIR auf das Verzeichnis setzen und DB_SOCKET in der .env anpassen.

Weil die Datenbank von KeyHelp verwaltet wird, ist sie automatisch im KeyHelp-Backup enthalten.


5. .env anlegen

install -m 0600 /opt/mareike/src/docker/endeavour/env.production.example /opt/mareike/shared/.env
echo "base64:$(openssl rand -base64 32)"      # Ergebnis als APP_KEY eintragen
nano /opt/mareike/shared/.env

Die wichtigsten Punkte:

  • APP_KEY wird einmal erzeugt und dann nie wieder geändert, weil Laravel damit verschlüsselte Daten sonst nicht mehr lesen kann.
  • APP_URL ist die Domain des Dach-Tenants lv (siehe Abschnitt 6).
  • DB_* sind die Daten aus Schritt 4. DB_HOST=localhost und DB_SOCKET bleiben wie in der Vorlage.
  • MAIL_*: Am einfachsten ist ein Postfach in KeyHelp, etwa noreply@meine-anmeldung.digital. Als Host den Mail-Hostnamen des Servers eintragen, mit Port 587.
  • WEBDAV_*: eigener Zielordner, damit sich endeavour und der bestehende Server nicht gegenseitig Dateien überschreiben.
  • PROVIDER muss leer bleiben. Mit bdp-lv-sachsen würde db:seed die Sachsen-Tenants samt Personendaten einspielen.
  • ASSET_URL nicht setzen, dann kommen die Assets von der jeweiligen Tenant-Domain.
  • In der Datei keine ${VAR}-Verweise verwenden, sondern alle Werte ausschreiben.

6. Erstinstallation

6.1 Erstes Deployment

/opt/mareike/bin/deploy.sh main        # oder einen Release-Tag, z. B. 4.9.1

Das Skript holt den Code, baut beide Images, legt die Tabellen per migrate an und startet die Container. Der erste Build dauert ein paar Minuten.

Kurztest:

/opt/mareike/bin/deploy.sh compose ps
curl -sI http://127.0.0.1:8080/ | head -1      # 404 ist hier richtig: es gibt noch keinen Tenant

6.2 Stammdaten einspielen

/opt/mareike/bin/deploy.sh artisan db:seed --force

Mit leerem PROVIDER läuft nur der ProductionDataSeeder. Er legt Rollen, Status, Zahlungsarten, Kostenstellentypen und die Befreiungsgründe an. Den Befehl nur einmal ausführen, ein zweiter Lauf würde Duplikate anlegen.

6.3 Dach-Tenant lv und Cron-Aufgaben anlegen

Der Slug lv ist im Code fest als Dach-Ebene verdrahtet (LvOnlyMiddleware, Registrierung, Tenant-Verwaltung). endeavour braucht deshalb genau einen Tenant mit diesem Slug. Seine url ist die Domain, die in Abschnitt 7 in KeyHelp angelegt wird.

/opt/mareike/bin/deploy.sh artisan tinker
App\Models\Tenant::create([
    'slug'                  => 'lv',
    'name'                  => '<Name des Verbands>',
    'url'                   => '<lv-subdomain>.meine-anmeldung.digital',
    'email'                 => '<kontakt@…>',
    'email_finance'         => '<finanzen@…>',
    'account_name'          => '<Kontoinhaber>',
    'account_iban'          => '<IBAN>',
    'account_bic'           => '<BIC>',
    'city'                  => '<Ort>',
    'postcode'              => '<PLZ>',
    'is_active_local_group' => true,
    'has_active_instance'   => true,
]);

use App\Enumerations\CronTaskType;
App\Models\CronTask::create(['name' => 'UploadInvoices', 'execution_type' => CronTaskType::CRON_TASK_TYPE_REALTIME]);
App\Models\CronTask::create(['name' => 'CloseCostUnit',  'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:05']);
App\Models\CronTask::create(['name' => 'CloseEvent',     'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '00:10']);
App\Models\CronTask::create(['name' => 'NotifyTeam',     'execution_type' => CronTaskType::CRON_TASK_TYPE_DAILY, 'schedule_time' => '20:00']);

Die Cron-Aufgaben entsprechen denen des bestehenden Servers (BdpLvSachsenDataSeeder::installCronTasks()). Sie gelten global und werden für jeden aktiven Tenant ausgeführt.

6.4 Erste Administratorin / ersten Administrator

  1. Auf https://<lv-subdomain>.meine-anmeldung.digital ganz normal registrieren. Voraussetzung: Abschnitt 7 ist erledigt und der Mailversand funktioniert.

  2. Das Konto zur Administration hochstufen:

    mysql <datenbankname> -e "UPDATE users SET user_role_main='ROLE_ADMINISTRATOR', user_role_local_group='ROLE_GROUP_LEADER', active=1 WHERE email='<adresse>';"
    

7. Tenant-Domain in KeyHelp einrichten (je Tenant)

Diesen Ablauf für den Dach-Tenant lv und später für jede weitere Gruppe wiederholen.

7.1 Einmalig: Proxy-Module aktivieren (Apache)

a2enmod proxy proxy_http headers
systemctl reload apache2

7.2 Domain anlegen

  1. KeyHelp → Domains → neue Domain für das mareike-Konto, z. B. wilde-moehre.meine-anmeldung.digital. Den DNS-Eintrag (A/AAAA) auf endeavour setzen, falls KeyHelp den DNS nicht selbst verwaltet.

  2. SSL/TLS: Let's Encrypt aktivieren und HTTPS erzwingen einschalten.

  3. PHP wird für diese Domain nicht gebraucht.

  4. In den Domain-Einstellungen im Feld für individuelle Webserver-Direktiven (nur als Admin sichtbar, die Bezeichnung variiert je nach KeyHelp-Version) Folgendes eintragen:

    Apache (KeyHelp-Standard):

    ProxyPreserveHost On
    ProxyRequests Off
    RequestHeader set X-Forwarded-Proto "https"
    ProxyPass        /.well-known/acme-challenge/ !
    ProxyPass        / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
    

    nginx (falls KeyHelp auf nginx läuft):

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        client_max_body_size 70m;
    }
    

    Die Ausnahme für /.well-known/acme-challenge/ sorgt dafür, dass KeyHelp die Zertifikate weiter selbst verlängern kann.

  5. Speichern und die Domain aufrufen. Solange der Tenant noch nicht existiert, zeigt mareike 404 „Tenant not found“. Das ist ein gutes Zeichen, denn der Proxy funktioniert.

7.3 Tenant in mareike anlegen

Im Dach-Tenant lv unter der Tenant-Verwaltung den neuen Tenant anlegen. Als URL exakt die Domain ohne https:// und ohne Schrägstrich eintragen, z. B. wilde-moehre.meine-anmeldung.digital.

Für den Tenant lv selbst ist das bereits in Schritt 6.3 passiert.


8. Cron einrichten

mareike hat keinen Laravel-Scheduler. Die Aufgaben laufen über die URL /execute-crons, die für alle aktiven Tenants arbeitet. Sie muss einmal pro Minute aufgerufen werden, dabei genügt der Host-Header eines beliebigen aktiven Tenants:

cat > /etc/cron.d/mareike <<'EOF'
* * * * * root curl -fsS -o /dev/null -H "Host: <lv-subdomain>.meine-anmeldung.digital" http://127.0.0.1:8080/execute-crons
EOF

Kontrolle: /opt/mareike/storage/logs/ enthält die Task-Logs.


9. Updates ausrollen

/opt/mareike/bin/deploy.sh 4.9.2       # Release-Tag
/opt/mareike/bin/deploy.sh main        # oder Branch

Das Skript läuft in dieser Reihenfolge:

  1. git fetch und Checkout des Standes.
  2. storage/-Ordner prüfen.
  3. Images neu bauen, dabei werden Basis-Images mit Sicherheitsupdates frisch gezogen.
  4. migrate --force ausführen.
  5. Container neu starten.
  6. Kompilierte Views leeren.
  7. Alte Images aufräumen.

Nützliche Kurzbefehle:

/opt/mareike/bin/deploy.sh compose ps
/opt/mareike/bin/deploy.sh compose logs -f web
/opt/mareike/bin/deploy.sh artisan migrate:status
/opt/mareike/bin/deploy.sh artisan tinker

Rollback: Den vorherigen Tag ausrollen (deploy.sh 4.9.1). Migrationen werden dabei nicht zurückgedreht. Enthält ein Release Datenbankänderungen, deshalb vorher ein DB-Backup ziehen.


10. Backup

Was Wie
Datenbank Über das KeyHelp-Backup, weil die DB von KeyHelp verwaltet wird.
/opt/mareike/storage Nicht im KeyHelp-Backup. Zusätzlich sichern, z. B. per borg/restic oder rsync auf das Backup-Ziel.
/opt/mareike/shared/.env Ebenfalls zusätzlich sichern, besonders wegen des APP_KEY.
Code und Images Nicht nötig, lassen sich jederzeit aus dem Repo neu bauen.

11. Fehlersuche

Symptom Ursache / Lösung
404 „Tenant not found“ Der Host passt nicht zu tenants.url (Tippfehler, https:// oder / im Eintrag), der Tenant ist nicht has_active_instance, oder der Proxy reicht den Host nicht durch (ProxyPreserveHost On fehlt).
502/503 vom KeyHelp-Webserver Container laufen nicht (deploy.sh compose ps) oder die Firewall wurde neu geladen, dann hilft systemctl restart docker.
Seite lädt ohne CSS, Mixed Content ASSET_URL ist gesetzt oder die nginx-Config im Container wurde nicht übernommen (fastcgi_param HTTPS on).
SQLSTATE[HY000] [2002] Socket nicht erreichbar. SELECT @@socket prüfen, MAREIKE_DB_SOCKET_DIR bzw. DB_SOCKET anpassen, nach einem MariaDB-Neustart den Stack mit deploy.sh compose restart app neu starten.
Permission denied in storage/ Besitzer von /opt/mareike/storage muss mareike-docker sein, der nächste deploy.sh-Lauf korrigiert das.
Upload bricht ab Grenzen prüfen: MAX_INVOICE_FILE_SIZE in der .env, 64 MB in php.ini, 70 MB in nginx.conf.
Mails kommen nicht an MAIL_* prüfen, Log in storage/logs/. Absenderadresse muss zum Postfach passen (SPF/DKIM der Domain in KeyHelp).

Logs:

/opt/mareike/bin/deploy.sh compose logs --tail=200 app web
ls -lt /opt/mareike/storage/logs/

12. Abgrenzung zum bestehenden Server

bestehender Server endeavour
Dockerfile docker/prod.Dockerfile docker/endeavour/Dockerfile
Compose docker-compose.prod docker/endeavour/compose.yaml
nginx docker/nginx/default.conf docker/endeavour/nginx.conf
Build-Kontext .dockerignore docker/endeavour/Dockerfile.dockerignore
Datenbank eigene KeyHelp-MariaDB auf endeavour
Tenants *.mareike.sachsen.pfadfinden.de *.meine-anmeldung.digital

Beide Server nutzen denselben Code-Stand aus demselben Repository, haben aber getrennte Datenbanken, .env, WebDAV-Ordner und Tenants. Änderungen an docker/endeavour/ wirken nur auf endeavour.