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 .[/]") 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()