feat: cast — the Coolify executor, extracted from the infra state repo #1
No reviewers
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#1
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/executor-extraction"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Extracts the Coolify executor out of
heavy-duty/infra— which was half tool, half state — into its own public, stateless CLI.rigbuilds the boxes.castfills them.infrais what they're filled with.Why
infracarried both a TypeScript executor and the private instance state (bindings, age secrets, tailnet ACL, runbooks). That mix is why nobody could say whether "infra" named a CLI or a runbook — and it meant the tool could never be public. This restores the same tool/state split rig already has.What changed beyond the move
Two things were required to make it genuinely stateless:
The cwd contract is gone.
environments.yaml,secrets/, and.coolify.envused to resolve against the working directory — the old README's own words were that running it from elsewhere "silently reads the wrong (or no) bindings/secrets file." Now they resolve against an explicit--state <dir>(or$CAST_STATE).BANNED_IN_PROD→forbidden_var_patterns. A hardcoded list of one product'sALLOW_*flags was the only product knowledge in the executor. It's now a generic, operator-owned policy in the state repo'senvironments.yaml:The guard now lives in private state, so a product-side manifest change cannot lower its own guard — and it's a pattern, not a list, so it catches siblings nobody has invented yet. Semantics are unchanged: presence, not value (
ALLOW_SEED=falsestill refuses the apply).Age identities resolve as
$CAST_AGE_KEY_FILE_<ENV>→~/.config/cast/age-<env>.key. That is the attended-vs-unattended apply mechanism, with no environment names known to the tool: an env whose key you never leave on disk can only be applied by someone who injects it.Publishability
This repo is public, so instance identity had to go: it's out of the test fixtures (
claude-hdb/incubator, the founder domains,hdb-coolify-fork) and out ofscripts/register-github-app.sh, which now takesAPP_NAMEandORGas arguments instead of baking them in.The executor's hard-won behavior notes — apply semantics, the Coolify 4.1.2 limitations verified against its source — are preserved in
docs/semantics.md.Verification
tscclean, biome clean, CI passing.cast --help;--stateand$CAST_STATEboth resolve.coolify.envandenvironments.yamlfrom the given directory, and a bad state dir names the exact path it looked in.environments.yaml, new guard field included.ALLOW_DB_RESET=falseis refused.Paired with
heavy-duty/infra— "infra is state, not code", which removes the code this extracts. Merge this one first.