vcard4reseller/deploy/terraform/cloud-init-app.yaml.tftpl
Thomas Peterson 79e996ab03 Deployment: Caddy-Edge (TLS + On-Demand für Custom-Domains) + Hetzner DNS
- Caddy ersetzt den Hetzner-LB: terminiert TLS (Portal-Domain automatisch) und
  load-balanced per reverse_proxy über die App-Nodes. Für Custom-Domains (§11)
  On-Demand-TLS, autorisiert über GET /internal/tls-allowed.
- TlsCheckController + DomainRepository::findVerifiedByHostname: erlaubt Zertifikate
  nur für Portal-Domain oder verifizierte Domains (Schutz vor Cert-Flooding).
- Terraform: hcloud_load_balancer entfernt, Caddy-Server + Firewall (80/443) +
  cloud-init-caddy (Caddyfile templated mit Upstreams/Domain/ACME).
- Optional Hetzner DNS via API (manage_dns): A-Record Portal + Wildcard → Caddy.
- nginx.prod: /internal zu Symfony geroutet; APP_PORTAL_DOMAIN-Env.

Validiert: Caddyfile (caddy validate), Terraform (validate), /internal/tls-allowed (200/403/400).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:13:29 +02:00

66 lines
2.5 KiB
Plaintext

#cloud-config
package_update: true
write_files:
- path: /opt/secrets/.env.prod.local
permissions: '0600'
content: |
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=${app_secret}
APP_PORTAL_DOMAIN=${domain}
DATABASE_URL="${database_url}"
CORS_ALLOW_ORIGIN=${cors_allow_origin}
TRUSTED_PROXIES=10.0.0.0/16
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=${jwt_passphrase}
S3_ENDPOINT=${s3_endpoint}
S3_REGION=${s3_region}
S3_BUCKET=${s3_bucket}
S3_KEY=${s3_key}
S3_SECRET=${s3_secret}
S3_PATH_STYLE=true
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
- path: /opt/secrets/private.pem.b64
permissions: '0600'
content: ${base64encode(jwt_private_key)}
- path: /opt/secrets/public.pem.b64
permissions: '0644'
content: ${base64encode(jwt_public_key)}
- path: /opt/secrets/deploy.vars
permissions: '0600'
content: |
REPO_URL=${repo_url}
REPO_BRANCH=${repo_branch}
DOMAIN=${domain}
RUN_MIGRATIONS=${run_migrations}
- path: /opt/deploy.sh
permissions: '0755'
content: |
#!/usr/bin/env bash
set -euo pipefail
. /opt/secrets/deploy.vars
export DEBIAN_FRONTEND=noninteractive
command -v docker >/dev/null 2>&1 || curl -fsSL https://get.docker.com | sh
apt-get update && apt-get install -y git
rm -rf /opt/vcard4
git clone --branch "$REPO_BRANCH" --depth 1 "$REPO_URL" /opt/vcard4
cd /opt/vcard4
cp /opt/secrets/.env.prod.local backend/.env.prod.local
mkdir -p backend/config/jwt
base64 -d /opt/secrets/private.pem.b64 > backend/config/jwt/private.pem
base64 -d /opt/secrets/public.pem.b64 > backend/config/jwt/public.pem
chmod 640 backend/config/jwt/private.pem
# SPA bauen (Profil-/QR-Links zeigen auf die öffentliche Domain)
docker run --rm -e VITE_PUBLIC_BASE="https://$DOMAIN" -v "$PWD/frontend":/app -w /app node:25-alpine sh -c "npm ci && npm run build"
chown -R 1000:1000 /opt/vcard4
COMPOSE="docker compose --project-directory /opt/vcard4 -f deploy/compose/docker-compose.prod.yml"
$COMPOSE up -d --build
sleep 20
if [ "$RUN_MIGRATIONS" = "true" ]; then
$COMPOSE exec -T php php bin/console doctrine:migrations:migrate --no-interaction || true
fi
$COMPOSE exec -T php php bin/console cache:clear || true
runcmd:
- bash /opt/deploy.sh > /var/log/vcard4-deploy.log 2>&1