Deployment-Rules for second server

This commit is contained in:
2026-09-26 11:51:49 +02:00
parent 790e8e1dbc
commit 985c9ce07e
9 changed files with 814 additions and 1 deletions
+104
View File
@@ -0,0 +1,104 @@
# Produktions-Image für endeavour (KeyHelp-Server, siehe docs/implement-endeavour.md).
# Unabhängig von docker/prod.Dockerfile, das der bestehende Server weiter nutzt.
#
# Targets:
# app – PHP-FPM mit Code, vendor/ und gebauten Assets
# web – nginx, liefert public/ aus und reicht PHP an "app" weiter
# ---------------------------------------------------------------------------
# base: PHP 8.5 mit den Extensions, die mareike braucht
# ---------------------------------------------------------------------------
FROM php:8.5-fpm-alpine AS base
ARG UID=2000
ARG GID=2000
RUN apk add --no-cache \
imagemagick \
libpng \
libxml2 \
libzip \
oniguruma \
&& apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
imagemagick-dev \
libpng-dev \
libxml2-dev \
libzip-dev \
oniguruma-dev \
&& pecl install imagick \
&& docker-php-ext-enable imagick \
&& docker-php-ext-install mysqli pdo_mysql mbstring zip exif pcntl gd \
&& apk del .build-deps \
&& rm -rf /tmp/pear
# Nutzer mit der UID/GID des Host-Nutzers, damit das eingebundene storage/ beschreibbar ist.
# Existiert die GID im Image schon, wird die vorhandene Gruppe verwendet.
RUN if getent group "${GID}" > /dev/null; then \
GROUP_NAME="$(getent group "${GID}" | cut -d: -f1)"; \
else \
addgroup -S -g "${GID}" mareike && GROUP_NAME=mareike; \
fi \
&& adduser -S -D -H -u "${UID}" -G "${GROUP_NAME}" mareike
COPY --chmod=0755 docker/php/composer.phar /usr/bin/composer
COPY docker/endeavour/php.ini /usr/local/etc/php/conf.d/zz-mareike.ini
WORKDIR /var/www/html
# ---------------------------------------------------------------------------
# vendor: Composer-Abhängigkeiten (ohne Dev-Pakete)
# ---------------------------------------------------------------------------
FROM base AS vendor
RUN apk add --no-cache git unzip
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist --no-interaction --no-progress
# ---------------------------------------------------------------------------
# assets: Vite-Build (Vite importiert aus app/, daher der volle Quellcode)
# ---------------------------------------------------------------------------
FROM node:24-alpine AS assets
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY . .
RUN npm run build
# ---------------------------------------------------------------------------
# app: PHP-FPM
# ---------------------------------------------------------------------------
FROM base AS app
COPY . .
COPY --from=vendor /var/www/html/vendor ./vendor
COPY --from=assets /app/public/build ./public/build
# Der Code gehört root; beschreibbar sind nur storage/ und bootstrap/cache/.
RUN composer dump-autoload --optimize --no-dev --no-interaction \
&& mkdir -p \
storage/app/private \
storage/app/public \
storage/framework/cache/data \
storage/framework/sessions \
storage/framework/views \
storage/logs \
storage/temp \
&& chown -R mareike: storage bootstrap/cache
USER mareike
EXPOSE 9000
CMD ["php-fpm"]
# ---------------------------------------------------------------------------
# web: nginx vor PHP-FPM
# ---------------------------------------------------------------------------
FROM nginx:stable-alpine AS web
COPY docker/endeavour/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=app /var/www/html/public /var/www/html/public
EXPOSE 80
+36
View File
@@ -0,0 +1,36 @@
# Build-Kontext für docker/endeavour/Dockerfile.
# BuildKit nutzt diese Datei anstelle der .dockerignore im Projektroot, die für den bestehenden Server
# unverändert bleibt. Wichtig: keine .env ins Image – sie wird zur Laufzeit eingebunden.
.git
.ai
.junie
.claude
.idea
.fleet
.vscode
.zed
.env
.env.*
node_modules
vendor
public/build
public/hot
public/storage
storage/*
bootstrap/cache/*.php
tests
.phpunit.cache
.phpunit.result.cache
docker/ssl
docker/logs
docker-compose.yaml
_ide_helper.php
_ide_helper_models.php
gitlog.log
+42
View File
@@ -0,0 +1,42 @@
# Compose-Stack für endeavour (KeyHelp-Server, siehe docs/implement-endeavour.md).
# Wird über /opt/mareike/bin/deploy.sh gesteuert, das die MAREIKE_*-Variablen setzt.
name: mareike
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
app:
build:
context: ../..
dockerfile: docker/endeavour/Dockerfile
target: app
args:
UID: ${MAREIKE_UID:-2000}
GID: ${MAREIKE_GID:-2000}
image: mareike-endeavour-app:latest
restart: unless-stopped
volumes:
- ${MAREIKE_ENV_FILE:-/opt/mareike/shared/.env}:/var/www/html/.env:ro
- ${MAREIKE_STORAGE:-/opt/mareike/storage}:/var/www/html/storage
# Socket der KeyHelp-MariaDB auf dem Host (DB_SOCKET=/run/mysqld/mysqld.sock)
- ${MAREIKE_DB_SOCKET_DIR:-/run/mysqld}:/run/mysqld
logging: *logging
web:
build:
context: ../..
dockerfile: docker/endeavour/Dockerfile
target: web
image: mareike-endeavour-web:latest
restart: unless-stopped
depends_on:
- app
ports:
# Nur lokal erreichbar – nach außen geht es ausschließlich über den KeyHelp-Webserver.
- "127.0.0.1:${MAREIKE_HTTP_PORT:-8080}:80"
logging: *logging
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
#
# Deployment von mareike auf endeavour (KeyHelp-Server), siehe docs/implement-endeavour.md.
#
# Liegt auf dem Server als KOPIE unter /opt/mareike/bin/deploy.sh – nicht direkt aus src/ ausführen,
# weil der git-Checkout sonst das laufende Skript austauscht.
#
# Aufruf (als root):
# deploy.sh [REF] Tag, Branch oder Commit ausrollen (Default: main)
# deploy.sh artisan … php artisan im laufenden app-Container
# deploy.sh compose … docker compose mit der endeavour-Konfiguration (z. B. "compose logs -f")
set -euo pipefail
MAREIKE_BASE="${MAREIKE_BASE:-/opt/mareike}"
MAREIKE_USER="${MAREIKE_USER:-mareike-docker}"
SRC="$MAREIKE_BASE/src"
export MAREIKE_ENV_FILE="$MAREIKE_BASE/shared/.env"
export MAREIKE_STORAGE="$MAREIKE_BASE/storage"
export MAREIKE_HTTP_PORT="${MAREIKE_HTTP_PORT:-8080}"
export MAREIKE_DB_SOCKET_DIR="${MAREIKE_DB_SOCKET_DIR:-/run/mysqld}"
MAREIKE_UID="$(id -u "$MAREIKE_USER")"
MAREIKE_GID="$(id -g "$MAREIKE_USER")"
export MAREIKE_UID MAREIKE_GID
compose() {
docker compose -f "$SRC/docker/endeavour/compose.yaml" "$@"
}
case "${1:-}" in
artisan)
shift
compose exec app php artisan "$@"
exit
;;
compose)
shift
compose "$@"
exit
;;
esac
REF="${1:-main}"
if [[ ! -f "$MAREIKE_ENV_FILE" ]]; then
echo "Fehlt: $MAREIKE_ENV_FILE (Vorlage: docker/endeavour/env.production.example)" >&2
exit 1
fi
echo "==> Quellcode auf '$REF' bringen"
git -C "$SRC" fetch --tags --prune --force origin
if git -C "$SRC" show-ref --verify --quiet "refs/remotes/origin/$REF"; then
git -C "$SRC" checkout --force -B "$REF" "origin/$REF"
else
git -C "$SRC" checkout --force --detach "$REF"
fi
git -C "$SRC" clean -fd
echo "==> storage/ vorbereiten"
for dir in \
"" \
app app/private app/public \
framework framework/cache framework/cache/data framework/sessions framework/views \
logs temp
do
install -d -m 0775 -o "$MAREIKE_USER" -g "$MAREIKE_GID" "$MAREIKE_STORAGE/$dir"
done
echo "==> Images bauen"
compose build --pull
echo "==> Datenbank migrieren"
compose run --rm --no-deps app php artisan migrate --force
echo "==> Container starten"
compose up -d --remove-orphans
compose exec -T app php artisan view:clear
docker image prune -f > /dev/null
echo "==> mareike $(cat "$SRC/version") ($(git -C "$SRC" rev-parse --short HEAD)) läuft."
+65
View File
@@ -0,0 +1,65 @@
# Vorlage für /opt/mareike/shared/.env auf endeavour.
# Nach dem Kopieren alle <...>-Platzhalter ersetzen. Diese Datei kommt NIE ins Image und NIE ins Repo.
# Keine ${...}-Verweise auf andere Variablen verwenden, Werte ausschreiben.
APP_NAME=mareike
APP_ENV=production
APP_DEBUG=false
# Erzeugen mit: echo "base64:$(openssl rand -base64 32)" – danach NIE mehr ändern (verschlüsselte Daten!)
APP_KEY=
# Domain des Dach-Tenants "lv"
APP_URL=https://<lv-subdomain>.meine-anmeldung.digital
# Kein ASSET_URL: Assets kommen von der jeweiligen Tenant-Domain selbst.
APP_LOCALE=de
APP_FALLBACK_LOCALE=de
LOG_CHANNEL=stack
LOG_STACK=daily
LOG_DAILY_DAYS=30
LOG_LEVEL=warning
# KeyHelp-MariaDB auf dem Host, per Unix-Socket (Verzeichnis /run/mysqld ist in den Container gemountet)
DB_CONNECTION=mysql
DB_HOST=localhost
DB_SOCKET=/run/mysqld/mysqld.sock
DB_DATABASE=<datenbankname-aus-keyhelp>
DB_USERNAME=<datenbanknutzer-aus-keyhelp>
DB_PASSWORD=<passwort>
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_SECURE_COOKIE=true
SESSION_DOMAIN=null
CACHE_STORE=database
QUEUE_CONNECTION=sync
FILESYSTEM_DISK=local
# Versand über ein KeyHelp-Postfach (STARTTLS auf 587)
MAIL_MAILER=smtp
MAIL_SCHEME=smtp
MAIL_HOST=<hostname-des-mailservers>
MAIL_PORT=587
MAIL_USERNAME=<noreply@meine-anmeldung.digital>
MAIL_PASSWORD=<passwort>
MAIL_FROM_ADDRESS=<noreply@meine-anmeldung.digital>
MAIL_FROM_NAME=mareike
# Empfänger der Admin-Benachrichtigungen (z. B. neue Registrierungen)
APP_ADMIN_MAIL=<admin@meine-anmeldung.digital>
APP_ADMIN_NAME="<Name>"
# Maximale Größe hochgeladener Belege in MB (php.ini erlaubt bis 64)
MAX_INVOICE_FILE_SIZE=16
# WebDAV-Ablage: unbedingt ein EIGENER Ordner, getrennt vom bestehenden Server
WEBDAV_HOST=<https://cloud.example.org/remote.php/dav/files/<nutzer>/<eigener-ordner>/>
WEBDAV_USER=<nutzer>
WEBDAV_PASS=<app-passwort>
# Prüfdienst für erweiterte Führungszeugnisse
COC_CHECK_URL=<https://efzcheck.example.org/api/v1/check-efz>
# MUSS leer bleiben: "bdp-lv-sachsen" würde beim Seeden die Sachsen-Tenants und deren Personen einspielen.
PROVIDER=
+54
View File
@@ -0,0 +1,54 @@
# nginx im Container "web" auf endeavour.
# TLS terminiert der KeyHelp-Webserver auf dem Host und reicht per Proxy an 127.0.0.1:8080 weiter.
# Echte Client-IP aus X-Forwarded-For übernehmen, aber nur von Docker-internen Adressen
# (der Proxy auf dem Host erreicht den Container über das Bridge-Gateway).
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
server {
listen 80 default_server;
server_name _;
root /var/www/html/public;
index index.php;
server_tokens off;
client_max_body_size 70m;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Vite-Assets sind versioniert (Hash im Dateinamen) und dürfen lange gecacht werden.
location /build/assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Nach außen läuft alles über HTTPS (KeyHelp). So erzeugt Laravel https-URLs,
# ohne dass im App-Code trustProxies gesetzt werden muss.
fastcgi_param HTTPS on;
fastcgi_param REQUEST_SCHEME https;
fastcgi_param SERVER_PORT 443;
fastcgi_read_timeout 300;
}
location ~ /\.(?!well-known) {
deny all;
}
}
+20
View File
@@ -0,0 +1,20 @@
; PHP-Einstellungen für endeavour (Container "app").
; Uploads: Obergrenze für MAX_INVOICE_FILE_SIZE (MB, Default 16) in der .env, POST etwas größer für die
; Formulardaten.
upload_max_filesize = 64M
post_max_size = 70M
memory_limit = 512M
max_execution_time = 120
expose_php = Off
date.timezone = Europe/Berlin
; OPcache ist ab PHP 8.5 fest eingebaut. Zeitstempel werden weiter geprüft, weil Blade kompilierte
; Views in storage/ unter gleichem Dateinamen neu schreibt.
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 2
+410
View File
@@ -0,0 +1,410 @@
# 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`.
```bash
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`):
```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:
```bash
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.
```bash
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):
```bash
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:
```bash
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
```bash
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
```bash
/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:
```bash
/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
```bash
/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.
```bash
/opt/mareike/bin/deploy.sh artisan tinker
```
```php
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:
```bash
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)
```bash
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):
```apache
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):
```nginx
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:
```bash
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
```bash
/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:
```bash
/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:
```bash
/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.
+1 -1
View File
@@ -1 +1 @@
4.9.1
4.9.2