Skip to content

Wire Coolify magic vars into patch.py + add CI test#1

Merged
lunarthegrey merged 1 commit into
mainfrom
coolify-native-compose
Apr 22, 2026
Merged

Wire Coolify magic vars into patch.py + add CI test#1
lunarthegrey merged 1 commit into
mainfrom
coolify-native-compose

Conversation

@lunarthegrey
Copy link
Copy Markdown

Summary

  • Expands .github/scripts/patch.py to wire Coolify's SERVICE_URL_* / SERVICE_USER_* / SERVICE_PASSWORD_* magic vars, inject SERVICE_URL_<NAME>_<PORT> declarations so Traefik discovers each service's port, add a Ghost healthcheck, delete caddy/, strip stale refs from .env.example, and overwrite README.md from the new README.coolify.md.
  • Restores upstream's ${ADMIN_DOMAIN:+https://${ADMIN_DOMAIN}} conditional — the previous patch collapsed it to plain ${ADMIN_DOMAIN}, which Ghost 6 stamped as empty-string (distinct from "unset" and known to break admin redirects).
  • Adds .github/workflows/patch-test.yml + .github/scripts/test-patch.sh — fetches upstream on every PR, applies patch.py, validates with docker compose config --quiet, re-applies to assert idempotency.
  • sync.yml now runs the same docker compose config gate before pushing, and uses --force-with-lease instead of --force.
  • .github/renovate.json5 gets a regex manager for GHOST_VERSION in .env.example — no-op while commented, fires once a user pins.

Why

The previous patch only stripped Caddy and collapsed one env-var default syntax. The patched compose.yml still left Ghost un-routable in Coolify (no port exposure for Traefik), forced users to manually set DOMAIN and both MySQL passwords (which Coolify auto-generates via magic vars), and carried the empty-string admin_url bug. A fresh Coolify user pointing at this repo would get a deployable resource that doesn't actually reach Ghost.

Caveats / not tested yet

  • I have not deployed this to a live Coolify instance. docker compose config validates the YAML but not Coolify's runtime magic-var substitution. The SERVICE_URL_GHOST_2368: "" mapping-syntax declaration is inferred from Coolify's docs + official templates (which use list syntax); one sanity-check deploy should confirm Traefik picks up the port.
  • Multi-FQDN for analytics/ActivityPub means users configure three domains in the Coolify UI. Fediverse discovery via @user@primary-domain needs an additional Traefik label for /.well-known/webfinger — documented as an opt-in step in README.coolify.md rather than automated.
  • scripts/migrate.sh is left as-is (bare-metal-only, not Coolify-runnable); mentioned in the new README for context.

Test plan

  • bash .github/scripts/test-patch.sh passes locally (fetches upstream, applies patch, docker compose config --quiet exits 0, idempotency diff-clean across 3 runs)
  • shellcheck .github/scripts/test-patch.sh clean
  • renovate.json5 parses as valid JSON5; regex pattern matches uncommented GHOST_VERSION=… only (commented lines + leading whitespace rejected)
  • Deploy this branch to a Coolify sandbox; verify Ghost admin loads at the Coolify-assigned FQDN, passwords never entered manually, healthcheck shows green
  • With COMPOSE_PROFILES=analytics: set FQDN on traffic-analytics, verify /api/v1/page_hit reaches the service
  • With COMPOSE_PROFILES=activitypub: set FQDN on activitypub, verify webfinger resolves on the ActivityPub FQDN (primary-domain webfinger is opt-in per README)
  • Manually trigger sync.yml post-merge; confirm the new docker compose config step passes and the --force-with-lease push succeeds

🤖 Generated with Claude Code

The previous patch only stripped Caddy and collapsed admin_url's
:+ conditional — leaving Ghost un-routable in Coolify (no port exposure,
no Traefik discovery), stamping admin_url as an empty string (which
Ghost 6 treats differently from "unset"), and requiring users to hand-set
DOMAIN / DATABASE_PASSWORD when Coolify already auto-generates these.

patch.py now swaps Ghost's url + MySQL credentials to Coolify's
SERVICE_URL_* / SERVICE_USER_* / SERVICE_PASSWORD_* magic vars, injects
SERVICE_URL_<NAME>_<PORT> declarations so Traefik finds the right port
on each service, adds a Ghost healthcheck for Coolify's UI indicator,
preserves upstream's \${ADMIN_DOMAIN:+...} conditional, deletes the
unused caddy/ directory, strips stale refs from .env.example, and
overwrites README.md from the new README.coolify.md source.

All edits are idempotent and fail loudly on missing anchors. A new
test-patch.sh + patch-test.yml workflow fetches upstream on every PR,
applies patch.py, validates with docker compose config --quiet, and
re-applies to assert idempotency — catching upstream restructures
before the nightly sync lands them on main.

sync.yml switches to --force-with-lease and runs the same
docker compose config gate before pushing. Renovate gets a regex
manager for GHOST_VERSION in .env.example (no-op while commented,
fires once a user pins).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lunarthegrey lunarthegrey force-pushed the coolify-native-compose branch from a198f97 to 06d1f1c Compare April 22, 2026 03:52
@lunarthegrey lunarthegrey merged commit 0dcd56d into main Apr 22, 2026
2 checks passed
@lunarthegrey lunarthegrey deleted the coolify-native-compose branch April 22, 2026 03:57
lunarthegrey added a commit that referenced this pull request Apr 24, 2026
Live deploy on Coolify showed a garbage env-var row named
"ADMIN_DOMAIN:+https://${ADMIN_DOMAIN}" — Coolify's parser doesn't
handle the nested ${ADMIN_DOMAIN:+...} conditional and extracted the
inner text as a literal var name.

Replace upstream's conditional with ${admin__url:-$SERVICE_URL_GHOST}:
a clean single-var reference Coolify shows as one editable row, with
a sensible fallback to the primary URL so Ghost always boots with a
valid admin URL whether the user sets one or not.

This reverses the G4 decision from #1 (preserving upstream's :+
syntax). That decision was based on theoretical concern about
Ghost 6's empty-string admin_url handling; the Coolify parsing bug
is a concrete problem that overrides the theoretical one. Defaulting
admin_url to $SERVICE_URL_GHOST sidesteps the empty-string question
entirely — Ghost always gets a real URL.

test-patch.sh: flip the admin_url assertion to the new form; guard
against the old :+ pattern leaking back in. Function definition
moved up so earlier assertions can use it.

README.coolify.md: rewrite the "Optional: separate admin domain"
section around the new admin__url env var.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant