refactor(env): explicit defaults.env with placeholders; drop auto-injection
A service/job's env is now exactly its defaults.env — castle injects no hidden
convention vars. Values support ${port}/${data_dir}/${name} placeholders
(resolved at deploy, alongside ${secret:…}), so a program's own env var names
map to castle's computed values without hardcoding.
Why: the auto-injected <PREFIX>_PORT/<PREFIX>_DATA_DIR were a guess at the
program's env names — right for castle-scaffolded services, dead weight for
adopted ones (lakehouse carried two dead vars; notification-bridge/backup jobs
too). They also weren't visible in the config editor (computed at deploy), which
was the source of the 'four env vars but the UI shows none' mystery.
- core: resolve_env_vars gains a context (${port}/${data_dir}/${name});
deploy builds env from defaults.env only — no <PREFIX>_* injection, no
port_env. Removed the port_env field and the dead _env_prefix helper.
- cli: 'service/job create' gains repeatable --env KEY=VALUE (replaces
--port-env); 'program create' scaffolds <PREFIX>_PORT/_DATA_DIR: ${…} for new
daemons.
- app: removed the 'Port env' field; the Environment editor (defaults.env) is
the single place, with a placeholder hint.
- live migration: central-context/castle-api/power-graph/protonmail mapped their
real vars explicitly; lakehouse → just LAKEHOUSED_DAEMON_PORT: ${port}, data
stays in ~/.lakehoused. Verified all services healthy on their ports, dead
vars gone, zero failed units.
- docs: registry.md/design.md/stack guides + findings updated to the explicit
model.
core 94 / cli 24 / api 52 green; ruff + app build clean.
This commit is contained in:
@@ -12,6 +12,7 @@ import argparse
|
||||
from castle_cli.config import load_config, save_config
|
||||
from castle_cli.manifest import (
|
||||
CaddySpec,
|
||||
DefaultsSpec,
|
||||
ExposeSpec,
|
||||
HttpExposeSpec,
|
||||
HttpInternal,
|
||||
@@ -25,6 +26,17 @@ from castle_cli.manifest import (
|
||||
)
|
||||
|
||||
|
||||
def _defaults(env_args: list[str] | None) -> DefaultsSpec | None:
|
||||
"""Parse repeated --env KEY=VALUE into a DefaultsSpec, or None."""
|
||||
if not env_args:
|
||||
return None
|
||||
env: dict[str, str] = {}
|
||||
for item in env_args:
|
||||
key, _, value = item.partition("=")
|
||||
env[key.strip()] = value
|
||||
return DefaultsSpec(env=env)
|
||||
|
||||
|
||||
def _run_spec(runner: str, target: str, name: str) -> RunPython | RunCommand:
|
||||
if runner == "command":
|
||||
return RunCommand(runner="command", argv=target.split() or [name])
|
||||
@@ -54,7 +66,7 @@ def run_service_create(args: argparse.Namespace) -> int:
|
||||
if args.port is not None:
|
||||
expose = ExposeSpec(
|
||||
http=HttpExposeSpec(
|
||||
internal=HttpInternal(port=args.port, port_env=args.port_env),
|
||||
internal=HttpInternal(port=args.port),
|
||||
health_path=args.health,
|
||||
)
|
||||
)
|
||||
@@ -76,13 +88,14 @@ def run_service_create(args: argparse.Namespace) -> int:
|
||||
expose=expose,
|
||||
proxy=proxy,
|
||||
manage=ManageSpec(systemd=SystemdSpec()),
|
||||
defaults=_defaults(args.env),
|
||||
)
|
||||
save_config(config)
|
||||
|
||||
print(f"Created service '{name}'.")
|
||||
print(f" runs: {args.runner} ({args.run or args.program or name})")
|
||||
if expose:
|
||||
print(f" port: {args.port}" + (f" (env: {args.port_env})" if args.port_env else ""))
|
||||
print(f" port: {args.port}")
|
||||
if proxy and proxy.caddy:
|
||||
if proxy.caddy.path_prefix:
|
||||
print(f" proxy: {proxy.caddy.path_prefix}")
|
||||
@@ -109,6 +122,7 @@ def run_job_create(args: argparse.Namespace) -> int:
|
||||
run=run,
|
||||
schedule=args.schedule,
|
||||
manage=ManageSpec(systemd=SystemdSpec()),
|
||||
defaults=_defaults(args.env),
|
||||
)
|
||||
save_config(config)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user