From 8e2f183288f8dcc66fba10b069d6016ec9ace4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Wed, 25 Mar 2026 01:00:00 +0100 Subject: [PATCH] Enable CD deployment to Hetzner (task 11.8) CD workflow: - Uncommented deploy step - Copy docker-compose.yml + Caddyfile to server via SCP - Login to ghcr.io with DEPLOY_GHCR_TOKEN (classic PAT, read:packages) - Download Germany BRouter segments on first deploy (~750MB) - Pull images, restart services, verify Infrastructure: - BRouter segments as bind mount (./segments) not Docker volume - Added ORIGIN, PLANNER_URL, SESSION_SECRET, NODE_ENV to Journal - Added NODE_ENV to Planner - Disabled Garage for now (no media storage yet) Required secrets: DEPLOY_HOST, DEPLOY_SSH_KEY, DEPLOY_GHCR_TOKEN Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/cd.yml | 63 ++++++++++++++++++++++--------- infrastructure/docker-compose.yml | 27 ++++++------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index cefcdd3..13ce590 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -59,20 +59,49 @@ jobs: ghcr.io/trails-cool/brouter:latest ghcr.io/trails-cool/brouter:${{ github.sha }} - # Deploy step - uncomment when Hetzner server is provisioned - # deploy: - # name: Deploy to Hetzner - # needs: [build-images, build-brouter] - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v6 - # - name: Deploy via SSH - # uses: appleboy/ssh-action@v1 - # with: - # host: ${{ secrets.DEPLOY_HOST }} - # username: root - # key: ${{ secrets.DEPLOY_SSH_KEY }} - # script: | - # cd /opt/trails-cool - # docker compose pull - # docker compose up -d + deploy: + name: Deploy to Hetzner + needs: [build-images, build-brouter] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Copy files to server + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: root + key: ${{ secrets.DEPLOY_SSH_KEY }} + source: "infrastructure/docker-compose.yml,infrastructure/Caddyfile" + target: /opt/trails-cool + strip_components: 1 + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: root + key: ${{ secrets.DEPLOY_SSH_KEY }} + script: | + cd /opt/trails-cool + + # Login to ghcr.io to pull private images + echo "${{ secrets.DEPLOY_GHCR_TOKEN }}" | docker login ghcr.io -u stigi --password-stdin + + # Download BRouter segments if missing + if [ ! -d /opt/trails-cool/segments ] || [ -z "$(ls /opt/trails-cool/segments/*.rd5 2>/dev/null)" ]; then + mkdir -p /opt/trails-cool/segments + echo "Downloading Germany BRouter segments..." + for tile in E5_N45 E5_N50 E10_N45 E10_N50; do + [ -f "/opt/trails-cool/segments/${tile}.rd5" ] || \ + wget -q "https://brouter.de/brouter/segments4/${tile}.rd5" -O "/opt/trails-cool/segments/${tile}.rd5" + done + fi + + # Pull latest images and restart + docker compose pull + docker compose up -d --remove-orphans + + # Verify services are running + sleep 10 + docker compose ps diff --git a/infrastructure/docker-compose.yml b/infrastructure/docker-compose.yml index b6228af..e18b887 100644 --- a/infrastructure/docker-compose.yml +++ b/infrastructure/docker-compose.yml @@ -19,12 +19,12 @@ services: restart: unless-stopped environment: DOMAIN: ${DOMAIN:-trails.cool} + ORIGIN: https://${DOMAIN:-trails.cool} + PLANNER_URL: https://planner.${DOMAIN:-trails.cool} DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails - S3_ENDPOINT: http://garage:3900 - S3_BUCKET: trails-media - S3_ACCESS_KEY: ${S3_ACCESS_KEY:-} - S3_SECRET_KEY: ${S3_SECRET_KEY:-} JWT_SECRET: ${JWT_SECRET:-change-me-in-production} + SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production} + NODE_ENV: production PORT: 3000 depends_on: postgres: @@ -36,6 +36,7 @@ services: environment: BROUTER_URL: http://brouter:17777 DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails + NODE_ENV: production PORT: 3001 depends_on: postgres: @@ -47,8 +48,7 @@ services: image: ghcr.io/trails-cool/brouter:latest restart: unless-stopped volumes: - - brouter_segments:/data/segments - - brouter_profiles:/data/profiles + - ./segments:/data/segments # Segments can be pulled from: # - https://brouter.de/brouter/segments4/ (official, weekly updates) # - A trails.cool CDN mirror (later) @@ -68,17 +68,14 @@ services: timeout: 5s retries: 5 - garage: - image: dxflrs/garage:v1.0 - restart: unless-stopped - volumes: - - garage_data:/var/lib/garage - - ./garage.toml:/etc/garage.toml:ro + # garage: + # image: dxflrs/garage:v1.0 + # restart: unless-stopped + # volumes: + # - garage_data:/var/lib/garage + # - ./garage.toml:/etc/garage.toml:ro volumes: caddy_data: caddy_config: pgdata: - brouter_segments: - brouter_profiles: - garage_data: