Runtime half: convergent deploy + unified active lifecycle
Convergent deploy (prefix-based prune): castle deploy now removes orphaned castle-* systemd units/timers no longer in the registry. Full deploy only; partial (--name) deploys preserve siblings. Fixes the orphaned-timer / stale- path drift class. Container runner: derive --name from the service name (castle-<name>) instead of the image name; skip the <PREFIX>_DATA_DIR env injection for containers (avoids colliding with image env namespaces like NEO4J_*). Unified active lifecycle (core/lifecycle.py): install/uninstall keep their names but become activate/deactivate, dispatching by behavior — tool→PATH, daemon/self-serving-frontend/job→systemd, static frontend→served. is_active reports a uniform state (PATH-independent for tools). The CLI install/uninstall and service enable/disable route through the extracted core. API + dashboard: surface a uniform `active` state on programs alongside `installed`; ProgramDetail shows active/inactive. Tests: test_deploy_prune (6), test_lifecycle (5). 168 tests pass; lint clean.
This commit is contained in:
@@ -342,6 +342,13 @@ def _program_from_spec(
|
||||
if comp.source and (comp.stack or comp.commands):
|
||||
installed = shutil.which(name) is not None
|
||||
|
||||
# Uniform lifecycle state (on PATH / running / served) — needs full config.
|
||||
active: bool | None = None
|
||||
if config is not None:
|
||||
from castle_core.lifecycle import is_active
|
||||
|
||||
active = is_active(name, config)
|
||||
|
||||
return ProgramSummary(
|
||||
id=name,
|
||||
description=comp.description,
|
||||
@@ -355,6 +362,7 @@ def _program_from_spec(
|
||||
commands=_declared_commands_dict(comp),
|
||||
system_dependencies=comp.system_dependencies,
|
||||
installed=installed,
|
||||
active=active,
|
||||
actions=available_actions(comp),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user