vcard4reseller/deploy/README.md
Thomas Peterson b316d0baf8 Docs: Wallet-Setup-Anleitung (Apple & Google) per OpenSSL
Schritt-für-Schritt zum Erzeugen der Zugangsdaten (Pass Type ID + Cert/Key/WWDR
als PEM via CSR; Google Issuer + Service-Account + Freigabe) und Eintragen der
Env-Variablen. Verlinkt aus deploy/README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:33:22 +02:00

118 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Deployment auf Hetzner Cloud (Terraform)
Provisioniert einen **Multi-Node-Test** mit nachgewiesener Skalierbarkeit:
```
Internet (HTTPS)
vcard4-caddy ── TLS: Portal-Domain (automatisch) + Custom-Domains (On-Demand)
(Reverse-Proxy + Load-Balancing)
/ \
vcard4-app-1 vcard4-app-2 (zustandslose App-Nodes, HTTP im privaten Netz)
\ /
┌─────────┴──────────┐ ┌────────┴───────────────┐
vcard4-db (MariaDB+Vol) Hetzner Object Storage (S3, Assets)
```
**Caddy** übernimmt TLS (Let's Encrypt) und Load-Balancing — kein Hetzner-LB nötig.
Für **Custom-Domains** der Firmenkunden (§11) macht Caddy *On-Demand-TLS* und fragt
vorher `GET /internal/tls-allowed?domain=…` in der App (Portal-Domain oder verifizierte
Domain aus der DB) → schützt vor unbegrenzten Zertifikatsanfragen.
App-Nodes sind zustandslos (Code + Docker), **State** liegt in DB (eigene VM) und
Object Storage (Hintergrund-PDFs/Schriften). Dadurch beliebig horizontal skalierbar.
## Voraussetzungen (einmalig, manuell)
1. **Terraform** ≥ 1.6 + Hetzner **API-Token** (Projekt → Security → API Tokens, Read&Write).
2. **Object Storage** in der Hetzner Console anlegen: Bucket + Access-Key/Secret
(hcloud/Terraform verwalten Object Storage derzeit nicht).
3. **Git-Repo** erreichbar für die Nodes (öffentlich oder Deploy-Token in der `repo_url`).
4. **JWT-Schlüsselpaar** einmal erzeugen — auf **allen** Nodes identisch:
```bash
# lokal im backend/-Container oder mit openssl
docker compose exec php php bin/console lexik:jwt:generate-keypair --overwrite
# → backend/config/jwt/private.pem & public.pem + Passphrase aus .env (JWT_PASSPHRASE)
```
Inhalt der beiden PEM-Dateien + Passphrase in `terraform.tfvars` eintragen.
5. **DNS**: A-Record `domain → caddy_ip` (+ optional `*.zone → caddy_ip` für Subdomains).
Entweder manuell **oder** automatisch über die **Hetzner DNS API** (`manage_dns = true`
+ `hetzner_dns_token` + `dns_zone_name`; Zone muss bei Hetzner DNS liegen).
## Deploy
```bash
cd deploy/terraform
cp terraform.tfvars.example terraform.tfvars # ausfüllen
terraform init
terraform plan
terraform apply
terraform output # load_balancer_ip etc.
```
cloud-init installiert auf jedem App-Node Docker, klont das Repo, schreibt
`.env.prod.local` + JWT-Keys, baut das SPA und startet
`deploy/compose/docker-compose.prod.yml`. Migrationen laufen **nur** auf `app-1`.
Deploy-Log auf dem Node: `/var/log/vcard4-deploy.log`.
## Skalierbarkeit verifizieren
```bash
# 1) Login über die Domain (Caddy → App-Nodes)
TOKEN=$(curl -s -X POST https://<domain>/api/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@vcard4reseller.de","password":"admin"}' | jq -r .token)
# 2) Health über Caddy (round-robin auf beide Nodes)
for i in $(seq 1 6); do curl -s https://<domain>/health; echo; done
# 3) Cross-Node-Beweis: Hintergrund über die Domain hochladen, dann
# auf BEIDEN Nodes rendern (per SSH) identische PDFs aus Object Storage:
ssh root@<app-1-ip> 'cd /opt/vcard4 && docker compose -f deploy/compose/docker-compose.prod.yml exec -T php php bin/console app:render-card erika-mustermann'
ssh root@<app-2-ip> 'cd /opt/vcard4 && docker compose -f deploy/compose/docker-compose.prod.yml exec -T php php bin/console app:render-card erika-mustermann'
```
## Skalieren
`app_count` erhöhen → `terraform apply` legt neue App-Nodes an. **Achtung:** die
Caddy-Upstreams stehen in der Caddyfile (per cloud-init beim ersten Boot gerendert) —
neue Nodes werden **nicht automatisch** aufgenommen. Optionen: Caddy-Node neu erstellen
(`terraform taint hcloud_server.caddy && apply`) oder Caddyfile auf dem Caddy-Node
aktualisieren + `docker exec caddy caddy reload`. (Später besser: Caddy-Config-Templating
per CI oder Service-Discovery.)
## Noch offen / Hinweise
- **TLS**: erledigt Caddy (Portal automatisch, Custom-Domains On-Demand). Erste
Zertifikatsausstellung dauert ein paar Sekunden nach korrektem DNS.
- **Trusted Proxies**: für korrekte absolute URLs hinter Caddy
`framework.trusted_proxies` auf `%env(TRUSTED_PROXIES)%` setzen.
- **Host-basiertes Routing** (Custom-Domain → richtige Firmenseite) ist §11-Folgearbeit;
Caddy stellt bereits Zertifikate für verifizierte Domains aus.
- **Seed**: optional einmalig `app:seed` auf `app-1` für Demo-Daten.
- **Updates**: neuen Stand ausrollen = auf den App-Nodes `git pull` + `docker compose ... up -d --build` (später per CI/Skript).
## Wallet-Pässe (Apple / Google) — optional
> **Ausführliche Schritt-für-Schritt-Anleitung: [`docs/WALLET-SETUP.md`](../docs/WALLET-SETUP.md).**
Auf der öffentlichen Profilseite erscheint ein QR „Zur Wallet hinzufügen" (Landing `/w/{code}`),
sobald die Zugangsdaten gesetzt sind. Ohne Konfiguration ist das Feature ausgeblendet.
**Apple Wallet** (kostenpflichtiger Apple-Developer-Account):
1. Pass Type ID anlegen, Zertifikat erzeugen → als PEM exportieren (`cert.pem` + `key.pem`).
2. Apple **WWDR**-Zwischenzertifikat als PEM (`wwdr.pem`).
3. PEM-Dateien außerhalb des Webroots ablegen, Env setzen:
`APPLE_WALLET_PASS_TYPE_ID`, `APPLE_WALLET_TEAM_ID`, `APPLE_WALLET_CERT_PATH`,
`APPLE_WALLET_KEY_PATH`, `APPLE_WALLET_KEY_PASSWORD`, `APPLE_WALLET_WWDR_PATH`, `APPLE_WALLET_ORG_NAME`.
**Google Wallet** (kostenlos):
1. In der Google Cloud Console die **Wallet API** + einen **Issuer** anlegen, Service-Account
mit Rolle „Wallet Object Issuer" erstellen, JSON-Key herunterladen.
2. Env setzen: `GOOGLE_WALLET_ISSUER_ID`, `GOOGLE_WALLET_SERVICE_ACCOUNT` (Pfad zur JSON),
optional `GOOGLE_WALLET_CLASS_SUFFIX`.
Hinweis: Selbstsignierte Test-Zertifikate erzeugen ein technisch valides `.pkpass`/JWT,
werden aber von Apple/Google **nicht akzeptiert** — für die Produktion echte Zugangsdaten nötig.
Over-the-air-Sync (APNs/Objekt-Patch) ist noch nicht umgesetzt (nur Pass-Erstellung).