Files
AirCardIOS/deploy/README.md
T
ClaudeandClaude Sonnet 5.5 cd55d4c891 fix(api): Log-Flut begrenzen und Appwrite-Fehler strenger behandeln
- Kein Zugriffsprotokoll je Anfrage mehr (abgewiesene Anfragen an das
  unauthentifizierte /api/melden loggten sonst unbegrenzt); nur noch
  Ereignis- und 5xx-Zeilen, höchstens 120 pro Minute mit Sammelzeile
- Compose: logging json-file, max-size 5m, max-file 2
- appwrite.js: 404 nur mit Typ row_not_found/storage_file_not_found als
  "fehlt", listRows ohne rows-Array wirft, redirect: error
- Appwrite-Fehler: 502 bei 0/429/5xx, sonst 500 "Interner Fehler."
- Veröffentlichen legt beide Dateien auf die sichtbaren Rechte fest
  (erst nach dem Anlegen der Zeile, Rollback bei Fehler)
- setFilePermissions mit Promise.allSettled, gemeinsamer safeToken-Helfer
- Tests: 85 (Log-Flut, Log-Budget, 404-Typen, Auth-Header, Konto-Antworten)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 14:24:05 +00:00

9.6 KiB

Deployment aircard-ios.org

Die Seite wird auf dem Webklar-Server aus dem Gitea-Repo knso/AirCardIOS gebaut und von nginx ausgeliefert. Traefik stellt TLS und Weiterleitungen. Dieser Ordner enthält alles, was dafür im Repo liegt; die Dateien auf dem Server sind Kopien davon.

Bestandteile

Datei im Repo Ziel auf dem Server (/home/webklar/apps/) Aufgabe
deploy/deploy.sh aircard-ios.org/deploy.sh (ausführbar) klont/aktualisiert das Repo, baut web/ und kopiert dist/ nach out/
deploy/webhook-server.js aircard-ios.org/webhook-server.js nimmt den Gitea-Webhook an, startet deploy.sh nacheinander, pollt alle 15 Minuten
deploy/nginx/aircard-ios.org.conf aircard-ios.org/nginx/aircard-ios.org.conf nginx-Konfiguration der Seite
deploy/traefik/aircard.yml traefik/dynamic/aircard.yml Route /v1 mit Header X-Appwrite-Project: 6ac2130a00289343d6d5 an Appwrite
deploy/api/appwrite.js, app.js, server.js aircard-ios.org/api/ (nur diese drei Dateien) API-Dienst aircard-api der Community-Galerie, siehe api/README.md
deploy/compose-snippet.yml docker-compose.yml (nach dem eship-Block) Dienste aircard-build, aircard und aircard-api

Weitere Ordner unter aircard-ios.org/ (nicht im Repo): build/ (Klon des Repos, Arbeitsordner des Build-Containers), out/ (gebaute Seite, von nginx ausgeliefert), dev/ (Arbeitskopie für die Entwicklung, nicht gemountet).

Einrichten (einmalig)

APP=/home/webklar/apps
SRC=$APP/aircard-ios.org/dev/deploy

mkdir -p $APP/aircard-ios.org/build $APP/aircard-ios.org/out $APP/aircard-ios.org/nginx $APP/aircard-ios.org/api
cp $SRC/deploy.sh $APP/aircard-ios.org/deploy.sh && chmod +x $APP/aircard-ios.org/deploy.sh
cp $SRC/webhook-server.js $APP/aircard-ios.org/webhook-server.js
cp $SRC/nginx/aircard-ios.org.conf $APP/aircard-ios.org/nginx/aircard-ios.org.conf
cp $SRC/api/appwrite.js $SRC/api/app.js $SRC/api/server.js $APP/aircard-ios.org/api/

# Compose: Backup, dann den Inhalt von compose-snippet.yml unter `services:` direkt nach dem eship-Block einfügen
cp $APP/docker-compose.yml $APP/docker-compose.yml.bak-2026-10-04-aircard
cd $APP && docker compose config -q; echo $?    # 0; einzige erlaubte Ausgabe: Warnung "attribute `version` is obsolete"

# Traefik-Route atomar ablegen (Datei-Provider lädt sie ohne Neustart)
cp $SRC/traefik/aircard.yml $APP/traefik/dynamic/aircard.yml.tmp && mv $APP/traefik/dynamic/aircard.yml.tmp $APP/traefik/dynamic/aircard.yml

Die .env neben der docker-compose.yml muss GITEA_DEPLOY_TOKEN (Lesezugriff auf knso/AirCardIOS) und GITEA_DEPLOY_WEBHOOK_SECRET enthalten, für den API-Dienst außerdem AIRCARD_APPWRITE_PROJECT_ID, AIRCARD_API_KEY und AIRCARD_API_SALT. Diese Werte gehören nie in Logs, Chats oder Commits. Das Salz entsteht, ohne dass der Wert auf dem Bildschirm erscheint:

printf 'AIRCARD_API_SALT=%s\n' "$(openssl rand -hex 32)" >> /home/webklar/apps/.env

Den Key AIRCARD_API_KEY legt Kenso nach docs/appwrite-console.md (Abschnitt 7.3) an und trägt ihn mit dem verdeckten read -s-Befehl ein.

Starten

cd /home/webklar/apps && docker compose up -d aircard-build aircard aircard-api

Nur diese drei Dienste starten, nie ein nacktes docker compose up -d (das würde nicht laufende Dienste wie eship-build wieder anlegen). Der Build-Container installiert beim Start git, startet den Webhook-Server und baut sofort einmal (initial). Den Fortschritt zeigt docker logs -f webklar-aircard-build; am Ende steht Deployment abgeschlossen (oder eine Zeile mit ERROR:).

Ohne AIRCARD_WEBHOOK_TOKEN (also ohne GITEA_DEPLOY_WEBHOOK_SECRET in der .env) beendet sich der Webhook-Server mit Exit-Code 1, statt Anfragen ungeprüft durchzulassen. Ebenso beendet sich aircard-api mit Exit-Code 1, wenn AIRCARD_API_KEY, AIRCARD_API_SALT oder AIRCARD_APPWRITE_PROJECT_ID fehlen (die Meldung nennt nur den Namen der Variable).

API-Dienst aircard-api

Kleiner Node-Dienst ohne Abhängigkeiten (deploy/api/), Container webklar-aircard-api. Er führt alle Schreibzugriffe der Community-Galerie mit dem Appwrite-Server-Key aus (Veröffentlichen, Zurückziehen, Melden mit Auto-Ausblenden ab 3 Meldern, Admin-Sichtbarkeit). Endpunkte, Statuscodes und Betrieb stehen in api/README.md.

  • Route: Traefik-Label am Dienst, Router aircard-gallery-api (Host(`aircard-ios.org`) && PathPrefix(`/api`), Priorität 200, Port 8088). Der Name ist absichtlich nicht aircard-api: so heißt schon die Appwrite-Route (/v1) in traefik/dynamic/aircard.yml.
  • Prüfen nach dem Start: curl -s https://aircard-ios.org/api/gesundheit liefert {"ok":true}; docker logs webklar-aircard-api zeigt listening on :8088.
  • Ändern: Dateien mit cp nach aircard-ios.org/api/ ersetzen, dann cd /home/webklar/apps && docker compose restart aircard-api.
  • Log-Grenze: Der Docker-Daemon des Servers hat keine eigenen Log-Optionen, deshalb steht im Service logging: json-file mit max-size: 5m und max-file: 2 (höchstens 10 MB). Der Dienst selbst schreibt kein Zugriffsprotokoll und höchstens 120 Zeilen pro Minute.
  • Kein Port nach außen: der Dienst vertraut dem Header X-Real-Ip (setzt Traefik) für Rate-Limit und Melder-Hash, deshalb nie ports: ergänzen.

Manuell deployen

docker exec webklar-aircard-build sh /deploy.sh

Ohne DEPLOY_TRIGGER baut das Skript immer neu, auch wenn sich der Commit nicht geändert hat.

Es läuft immer nur ein Deployment: Ist schon eines aktiv (Sperrverzeichnis build/.git/aircard-deploy.lock), schreibt das Skript „Deployment läuft bereits“ und endet mit Exit-Code 0. Eine verwaiste Sperre (Prozess weg oder älter als 60 Minuten) übernimmt der nächste Lauf selbst. npm ci und der Build brechen nach 15 Minuten ab (timeout 900).

Gitea-Webhook

Repo knso/AirCardIOS → Einstellungen → Webhooks → Gitea:

Feld Wert
Ziel-URL https://aircard-ios.org/webhook/deploy
HTTP-Methode POST
Content-Type application/json
Secret Wert von GITEA_DEPLOY_WEBHOOK_SECRET aus /home/webklar/apps/.env (volle Länge, nur dort nachsehen)
Auslöser nur Push-Ereignisse
Branch-Filter main
Aktiv ja

„Test delivery“ muss 202 ergeben. 401 heißt: Secret stimmt nicht. 200 mit {"ignored":true} heißt: Push auf einen anderen Branch als main.

Verhalten

  • Webhook (POST /webhook/deploy): prüft die Signatur im Header x-gitea-signature (HMAC-SHA256 über den Rohbody). 401 bei falscher oder fehlender Signatur, 413 bei Body über 1 MB, 200 bei Push auf einen anderen Branch, 202 bei Push auf main; der Build läuft danach im Hintergrund. Alles andere antwortet 404.
  • Warteschlange: Builds laufen nacheinander, höchstens einer läuft und einer wartet. Weitere Pushes während ein Lauf wartet, werden mit 202 beantwortet und fügen keinen Lauf hinzu (der wartende Lauf holt ohnehin den neuesten Stand).
  • Trigger: webhook und manuelle Aufrufe bauen immer. initial (Container-Start) und poll (alle 15 Minuten) bauen nur, wenn es einen neuen Commit gibt oder out/index.html fehlt.
  • Gemerkter Commit: build/.git/aircard-last-deployed-commit enthält den zuletzt versuchten Commit, geschrieben vor dem Build. Ein kaputter Build wird deshalb nicht alle 15 Minuten wiederholt; die alte Seite bleibt online, bis ein neuer Commit oder ein Webhook-/manueller Lauf kommt.
  • Fehler: Ein fehlgeschlagener Lauf wird mit [webhook] … deployment failed protokolliert und beendet den Server nicht. Fehlerzeilen des Skripts beginnen mit ERROR:.
  • Build-Variablen: VITE_APPWRITE_ENDPOINT und VITE_APPWRITE_PROJECT_ID kommen aus der Container-Umgebung (compose); AIRCARD_APPWRITE_PROJECT_ID wird aus /home/webklar/apps/.env gelesen. Vite liest sie direkt.
  • .env-Variablen: AIRCARD_APPWRITE_PROJECT_ID (Projekt-ID 6ac2130a00289343d6d5) und AIRCARD_APPWRITE_API_KEY (für das Setup-Skript) müssen in /home/webklar/apps/.env eingetragen sein; für aircard-api zusätzlich AIRCARD_API_KEY (Key aircard-api) und AIRCARD_API_SALT.

Änderungen später

  • deploy.sh ändern: Datei auf dem Server mit cp überschreiben (nicht mv, sonst zeigt der Datei-Mount auf die alte Datei). Gilt beim nächsten Lauf.
  • webhook-server.js ändern: ebenso mit cp ersetzen, dann cd /home/webklar/apps && docker compose restart aircard-build.
  • deploy/api/*.js ändern: ebenso mit cp ersetzen, dann cd /home/webklar/apps && docker compose restart aircard-api.
  • nginx-Konfiguration ändern: mit cp ersetzen, dann docker exec webklar-aircard nginx -t && docker exec webklar-aircard nginx -s reload.
  • Traefik-Route ändern: Datei atomar ersetzen wie oben; Löschen der Datei entfernt die Route wieder.

www.aircard-ios.org

Solange dig +short www.aircard-ios.org @8.8.8.8 nichts liefert, gibt es nur die Hauptdomain. Zeigt der A-Record auf 91.99.156.85, werden die auskommentierten www-Labels am Dienst aircard in docker-compose.yml aktiviert (Backup vorher, dann docker compose config -q und docker compose up -d aircard). Sie leiten per 301 auf https://aircard-ios.org weiter und haben eigene Router, damit ein www-Fehler das Zertifikat der Hauptdomain nicht blockiert.

Tests

node --test deploy/webhook-server.test.js deploy/deploy.test.js
node --test "deploy/api/*.test.js"

Die Tests brauchen nur Node und starten die Server auf Port 0. Die API-Tests laufen gegen einen Fake-Appwrite im Speicher (deploy/api/fake-appwrite.js); das echte Appwrite wird nie angesprochen. Das Glob muss in Anführungszeichen stehen.