Files
DECNET/decnet/cli/lifecycle.py

148 lines
5.4 KiB
Python

from __future__ import annotations
import subprocess # nosec B404
from typing import Optional
import typer
from rich.table import Table
from decnet.env import DECNET_INGEST_LOG_FILE
from . import utils as _utils
from .gating import _agent_mode_active, _require_master_mode
from .utils import console, log
def register(app: typer.Typer) -> None:
@app.command()
def redeploy(
log_file: str = typer.Option(DECNET_INGEST_LOG_FILE, "--log-file", "-f", help="Path to the DECNET log file"),
) -> None:
"""Check running DECNET services and relaunch any that are down."""
log.info("redeploy: checking services")
registry = _utils._service_registry(str(log_file))
table = Table(title="DECNET Services", show_lines=True)
table.add_column("Service", style="bold cyan")
table.add_column("Status")
table.add_column("PID", style="dim")
table.add_column("Action")
relaunched = 0
for name, match_fn, launch_args in registry:
pid = _utils._is_running(match_fn)
if pid is not None:
table.add_row(name, "[green]UP[/]", str(pid), "")
else:
try:
subprocess.Popen( # nosec B603
launch_args,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.STDOUT,
start_new_session=True,
)
table.add_row(name, "[red]DOWN[/]", "", "[green]relaunched[/]")
relaunched += 1
except (FileNotFoundError, subprocess.SubprocessError) as exc:
table.add_row(name, "[red]DOWN[/]", "", f"[red]failed: {exc}[/]")
console.print(table)
if relaunched:
console.print(f"[green]{relaunched} service(s) relaunched.[/]")
else:
console.print("[green]All services running.[/]")
@app.command()
def status() -> None:
"""Show running deckies and the state of every ``decnet-*`` unit.
Prefers systemd (``systemctl list-units 'decnet-*.service'``) so
agents, masters and mixed hosts all get one consistent view of
what's installed, loaded, and active. Falls back to the psutil
cmdline registry on boxes without systemd (dev laptops, CI
containers, non-systemd init) so `decnet status` is still useful
there.
"""
log.info("status command invoked")
from decnet.engine import status as _status
_status()
units = _utils._systemd_units()
if units is not None:
_render_systemd_units(units)
else:
_render_psutil_fallback()
def _render_systemd_units(units: list[dict]) -> None:
svc_table = Table(title="DECNET Services (systemd)", show_lines=True)
svc_table.add_column("Unit", style="bold cyan")
svc_table.add_column("Load")
svc_table.add_column("Active")
svc_table.add_column("Sub")
svc_table.add_column("Description", style="dim")
if not units:
console.print(
"[yellow]No decnet-* systemd units loaded. "
"Run `sudo decnet init` to install them.[/]"
)
return
def _active_style(active: str) -> str:
if active == "active":
return "[green]active[/]"
if active == "failed":
return "[red]failed[/]"
return f"[yellow]{active}[/]"
for u in sorted(units, key=lambda x: x.get("unit", "")):
svc_table.add_row(
u.get("unit", ""),
u.get("load", ""),
_active_style(u.get("active", "")),
u.get("sub", ""),
u.get("description", ""),
)
console.print(svc_table)
def _render_psutil_fallback() -> None:
registry = _utils._service_registry(str(DECNET_INGEST_LOG_FILE))
if _agent_mode_active():
registry = [r for r in registry if r[0] not in {"Mutator", "Profiler", "API"}]
svc_table = Table(
title="DECNET Services (psutil fallback — systemd unavailable)",
show_lines=True,
)
svc_table.add_column("Service", style="bold cyan")
svc_table.add_column("Status")
svc_table.add_column("PID", style="dim")
for name, match_fn, _launch_args in registry:
pid = _utils._is_running(match_fn)
if pid is not None:
svc_table.add_row(name, "[green]UP[/]", str(pid))
else:
svc_table.add_row(name, "[red]DOWN[/]", "")
console.print(svc_table)
@app.command()
def teardown(
all_: bool = typer.Option(False, "--all", help="Tear down all deckies and remove network"),
id_: Optional[str] = typer.Option(None, "--id", help="Tear down a specific decky by name"),
) -> None:
"""Stop and remove deckies."""
_require_master_mode("teardown")
if not all_ and not id_:
console.print("[red]Specify --all or --id <name>.[/]")
raise typer.Exit(1)
log.info("teardown command invoked all=%s id=%s", all_, id_)
from decnet.engine import teardown as _teardown
_teardown(decky_id=id_)
log.info("teardown complete all=%s id=%s", all_, id_)
if all_:
_utils._kill_all_services()