From 723167d2f289ae7ccf23e85d67ade420a609b4a8 Mon Sep 17 00:00:00 2001 From: johba Date: Thu, 26 Mar 2026 21:16:08 +0000 Subject: [PATCH] fix: Docker stack: edge proxy + staging container from bootstrap (#1) - Add edge (Caddy) service to docker-compose.yml as reverse proxy for Forgejo (/forgejo/*), Woodpecker (/ci/*), and staging (default) - Replace placeholder staging service with Caddy-based static file server - Generate docker/Caddyfile template during disinto init - Generate default "Nothing shipped yet" staging page in docker/staging-seed/ - Add caddy_data and staging-site volumes - Staging container seeds default page on first boot; CI overwrites later Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/disinto | 114 +++++++++++++++++++++++++++++++++++++++----- tests/smoke-init.sh | 4 +- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/bin/disinto b/bin/disinto index 5fa230d..be51d27 100755 --- a/bin/disinto +++ b/bin/disinto @@ -260,25 +260,44 @@ services: networks: - disinto-net - # Staging deployment slot — activated by Woodpecker staging pipeline (#755). - # Profile-gated: only starts when explicitly targeted by deploy commands. - # Customize image/ports/volumes for your project after init. - staging: - image: alpine:3 - profiles: ["staging"] - security_opt: - - apparmor=unconfined - environment: - DEPLOY_ENV: staging + # Edge proxy — reverse proxies Forgejo, Woodpecker, and staging. + # IP-only at bootstrap; domain + Let's Encrypt added later via vault. + edge: + image: caddy:alpine + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./docker/Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + depends_on: + - forgejo + - woodpecker + - staging + networks: + - disinto-net + + # Staging container — static file server for project staging artifacts. + # CI pipelines write to the staging-site volume to update content. + # Seeds default page on first boot; CI overwrites volume contents later. + staging: + image: caddy:alpine + restart: unless-stopped + volumes: + - staging-site:/srv/site + - ./docker/staging-seed:/srv/seed:ro + command: ["sh", "-c", "cp -n /srv/seed/* /srv/site/ 2>/dev/null; caddy file-server --root /srv/site --listen :80"] networks: - disinto-net - command: ["echo", "staging slot — replace with project image"] volumes: forgejo-data: woodpecker-data: agent-data: project-repos: + caddy_data: + staging-site: networks: disinto-net: @@ -308,6 +327,77 @@ COMPOSEEOF echo "Created: ${compose_file}" } +# Generate docker/Caddyfile for the edge proxy. +generate_caddyfile() { + local caddyfile="${FACTORY_ROOT}/docker/Caddyfile" + + if [ -f "$caddyfile" ]; then + echo "Caddyfile: ${caddyfile} (already exists, skipping)" + return + fi + + cat > "$caddyfile" <<'CADDYEOF' +# Caddyfile — generated by disinto init +# IP-only at bootstrap; domain + Let's Encrypt added later via vault. + +:80 { + handle /forgejo/* { + uri strip_prefix /forgejo + reverse_proxy forgejo:3000 + } + + handle /ci/* { + uri strip_prefix /ci + reverse_proxy woodpecker:8000 + } + + handle { + reverse_proxy staging:80 + } +} +CADDYEOF + + echo "Created: ${caddyfile}" +} + +# Generate default staging page in the staging-site volume seed directory. +generate_staging_page() { + local staging_dir="${FACTORY_ROOT}/docker/staging-seed" + local index_file="${staging_dir}/index.html" + + if [ -f "$index_file" ]; then + echo "Staging: ${index_file} (already exists, skipping)" + return + fi + + mkdir -p "$staging_dir" + + cat > "$index_file" <<'HTMLEOF' + + + + + + Staging + + + +
+

Nothing shipped yet

+

This staging site will update automatically when CI pushes new artifacts.

+
+ + +HTMLEOF + + echo "Created: ${index_file}" +} + # Generate docker/agents/ files if they don't already exist. generate_agent_docker() { local docker_dir="${FACTORY_ROOT}/docker/agents" @@ -1394,6 +1484,8 @@ p.write_text(text) forge_port="${forge_port:-3000}" generate_compose "$forge_port" generate_agent_docker + generate_caddyfile + generate_staging_page # Create empty .env so docker compose can parse the agents service # env_file reference before setup_forge generates the real tokens (#769) touch "${FACTORY_ROOT}/.env" diff --git a/tests/smoke-init.sh b/tests/smoke-init.sh index b0a6cf0..365be65 100644 --- a/tests/smoke-init.sh +++ b/tests/smoke-init.sh @@ -26,7 +26,9 @@ pass() { printf 'PASS: %s\n' "$*"; } cleanup() { rm -rf "$MOCK_BIN" "$MOCK_STATE" /tmp/smoke-test-repo \ "${FACTORY_ROOT}/projects/smoke-repo.toml" \ - "${FACTORY_ROOT}/docker-compose.yml" + "${FACTORY_ROOT}/docker-compose.yml" \ + "${FACTORY_ROOT}/docker/Caddyfile" \ + "${FACTORY_ROOT}/docker/staging-seed" # Restore .env only if we created the backup if [ -f "${FACTORY_ROOT}/.env.smoke-backup" ]; then mv "${FACTORY_ROOT}/.env.smoke-backup" "${FACTORY_ROOT}/.env"