diff --git a/docker/endeavour/Dockerfile b/docker/endeavour/Dockerfile new file mode 100644 index 0000000..683459f --- /dev/null +++ b/docker/endeavour/Dockerfile @@ -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 diff --git a/docker/endeavour/Dockerfile.dockerignore b/docker/endeavour/Dockerfile.dockerignore new file mode 100644 index 0000000..6dbfb4d --- /dev/null +++ b/docker/endeavour/Dockerfile.dockerignore @@ -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 diff --git a/docker/endeavour/compose.yaml b/docker/endeavour/compose.yaml new file mode 100644 index 0000000..ef5a473 --- /dev/null +++ b/docker/endeavour/compose.yaml @@ -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 diff --git a/docker/endeavour/deploy.sh b/docker/endeavour/deploy.sh new file mode 100755 index 0000000..0d67235 --- /dev/null +++ b/docker/endeavour/deploy.sh @@ -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." diff --git a/docker/endeavour/env.production.example b/docker/endeavour/env.production.example new file mode 100644 index 0000000..83a7c2f --- /dev/null +++ b/docker/endeavour/env.production.example @@ -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://.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= +DB_USERNAME= +DB_PASSWORD= + +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= +MAIL_PORT=587 +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_FROM_ADDRESS= +MAIL_FROM_NAME=mareike + +# Empfänger der Admin-Benachrichtigungen (z. B. neue Registrierungen) +APP_ADMIN_MAIL= +APP_ADMIN_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=//> +WEBDAV_USER= +WEBDAV_PASS= + +# Prüfdienst für erweiterte Führungszeugnisse +COC_CHECK_URL= + +# MUSS leer bleiben: "bdp-lv-sachsen" würde beim Seeden die Sachsen-Tenants und deren Personen einspielen. +PROVIDER= diff --git a/docker/endeavour/nginx.conf b/docker/endeavour/nginx.conf new file mode 100644 index 0000000..83895aa --- /dev/null +++ b/docker/endeavour/nginx.conf @@ -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; + } +} diff --git a/docker/endeavour/php.ini b/docker/endeavour/php.ini new file mode 100644 index 0000000..fb8553f --- /dev/null +++ b/docker/endeavour/php.ini @@ -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 diff --git a/docs/implement-endeavour.md b/docs/implement-endeavour.md new file mode 100644 index 0000000..9ab0d3a --- /dev/null +++ b/docs/implement-endeavour.md @@ -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' => '', + 'url' => '.meine-anmeldung.digital', + 'email' => '', + 'email_finance' => '', + 'account_name' => '', + 'account_iban' => '', + 'account_bic' => '', + 'city' => '', + 'postcode' => '', + '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://.meine-anmeldung.digital` ganz normal registrieren. Voraussetzung: Abschnitt 7 ist + erledigt und der Mailversand funktioniert. +2. Das Konto zur Administration hochstufen: + + ```bash + mysql -e "UPDATE users SET user_role_main='ROLE_ADMINISTRATOR', user_role_local_group='ROLE_GROUP_LEADER', active=1 WHERE email='';" + ``` + +--- + +## 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: .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. diff --git a/version b/version index 5b341fd..dad10c7 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.9.1 +4.9.2