#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 # Hetzner-Privatnetz-NIC (nicht eth0) sicher per DHCP hochziehen (für DB-Zugriff). # Manchmal kommt das private Interface beim ersten Boot nicht hoch → DB unerreichbar. PRIV=$(ls /sys/class/net | grep -E '^(enp|ens)' | grep -v '^eth0$' | head -1 || true) if [ -n "${PRIV:-}" ] && ! ip -4 addr show "$PRIV" | grep -q 'inet 10\.'; then printf '[Match]\nName=%s\n[Network]\nDHCP=ipv4\n' "$PRIV" > "/etc/systemd/network/10-$PRIV.network" ip link set "$PRIV" up || true systemctl restart systemd-networkd || true for i in $(seq 1 30); do ip -4 addr | grep -q 'inet 10\.' && break; sleep 2; done fi COMPOSE="docker compose --project-directory /opt/vcard4 -f deploy/compose/docker-compose.prod.yml" $COMPOSE up -d --build sleep 20 # PHP-Abhängigkeiten installieren: vendor/ ist gitignored und /app ist als Volume # gemountet (überdeckt ein im Image gebautes vendor) → hier in den Container hinein. $COMPOSE exec -T -e COMPOSER_HOME=/tmp/composer php composer install --no-dev --optimize-autoloader --no-interaction --no-scripts if [ "$RUN_MIGRATIONS" = "true" ]; then $COMPOSE exec -T php php bin/console doctrine:migrations:migrate --no-interaction || true # Erst-Befüllung (idempotent: überspringt, wenn admin@vcard4reseller.de existiert) $COMPOSE exec -T php php bin/console app:seed || true fi $COMPOSE exec -T php php bin/console cache:clear || true runcmd: - bash /opt/deploy.sh > /var/log/vcard4-deploy.log 2>&1