Split host setup into explicit entity actions

This commit is contained in:
deytenit committed 2026-11-08 12:39:49 +03:00
1 parent a297e7c71e
commit ba9ef92051
51 files changed
+1023 -275

No files matched your search

+3 -1
View File
@@ -28,7 +28,7 @@ def _resolve_layout():
def _print_list(commons_actions, prop_actions):
print("Usage: apex <group>/<action> [args...]\n\nAvailable actions:")
print("Usage: apex <entity>/<action> [args...]\n\nAvailable actions:")
tag = {"commons": "", "local": " (local)", "override": " (local override)"}
for i in overlay.discover(commons_actions, prop_actions):
mark = f" [DISABLED: {i.disabled}]" if i.disabled else ""
@@ -44,6 +44,8 @@ def main(argv) -> int:
return 0
name, rest = argv[0], argv[1:]
if name == "compose":
name = "composition/run"
r = overlay.resolve(name, commons_actions, prop_actions)
if r is None:
sys.stderr.write(f"[global] [core] [ERROR] Unknown action: {name}\n")
+1 -1
View File
@@ -1,5 +1,5 @@
# engine/lib/crowdsec.py
"""ctx.crowdsec — install + config deploy + collections (port of configure/crowdsec)."""
"""ctx.crowdsec — install + config deploy + collections (port of config/crowdsec)."""
from __future__ import annotations
import os
import shutil
+3 -3
View File
@@ -125,13 +125,13 @@ class Host:
# --- read/write with sudo awareness ---
def _read(self, path: str, sudo: bool = False) -> str:
if sudo:
cp = self.sys.run(["sudo", "cat", path], check=True, capture=True)
cp = self.sys.sudo(["cat", path], check=True, capture=True)
return cp.stdout or ""
with open(path, encoding="utf-8") as f:
return f.read()
def backup(self, path: str, sudo: bool = False) -> Optional[str]:
exists = (self.sys.ok(["sudo", "test", "-f", path]) if sudo
exists = (self.sys.sudo(["test", "-f", path], check=False).returncode == 0 if sudo
else os.path.isfile(path))
if not exists:
return None
@@ -154,7 +154,7 @@ class Host:
anchor: Optional[str] = None,
old_pairs: Sequence[Tuple[str, str]] = (),
dry_run: bool = False, sudo: bool = True) -> bool:
exists = (self.sys.ok(["sudo", "test", "-f", target]) if sudo
exists = (self.sys.sudo(["test", "-f", target], check=False).returncode == 0 if sudo
else os.path.isfile(target))
if not exists:
self.log.error(f"Target file does not exist: {target}")
+3
View File
@@ -1,6 +1,7 @@
# engine/lib/sys.py
"""ctx.sys — command execution + systemd service management (port of root_sys.sh)."""
from __future__ import annotations
import os
import shutil
import subprocess
import time
@@ -21,6 +22,8 @@ class Sys:
stderr=subprocess.PIPE if capture else None)
def sudo(self, cmd: Sequence[str], **kw) -> subprocess.CompletedProcess:
if os.geteuid() == 0 and shutil.which("sudo") is None:
return self.run(cmd, **kw)
return self.run(["sudo", *cmd], **kw)
def ok(self, cmd: Sequence[str], **kw) -> bool:
+2 -2
View File
@@ -67,10 +67,10 @@ class Ufw:
self.log.warn("Neither docker nor host ufw rules directory found; nothing to apply.")
return
if not self.sys.ok(["sudo", "ufw", "--version"]):
self.log.error("ufw not installed/working via sudo. Run configure/base first.")
self.log.error("ufw not installed/working via sudo. Run config/packages first.")
raise SystemExit(1)
if not (os.path.isfile(UFW_DOCKER_BIN) and os.access(UFW_DOCKER_BIN, os.X_OK)):
self.log.error(f"ufw-docker not found at {UFW_DOCKER_BIN}. Run configure/base first.")
self.log.error(f"ufw-docker not found at {UFW_DOCKER_BIN}. Run config/ufw-docker first.")
raise SystemExit(1)
if config_dir:
+7 -1
View File
@@ -9,10 +9,12 @@ from __future__ import annotations
import ast
import importlib.util
import os
import re
from dataclasses import dataclass
from typing import List, Optional
ACTION_EXT = ".py"
ACTION_NAME = re.compile(r"[A-Za-z0-9_-]+/[A-Za-z0-9_-]+")
@dataclass
@@ -40,11 +42,15 @@ def _rel_names(root: Optional[str]):
continue
full = os.path.join(dirpath, fn)
rel = os.path.relpath(full, root)[: -len(ACTION_EXT)]
out[rel.replace(os.sep, "/")] = full
name = rel.replace(os.sep, "/")
if ACTION_NAME.fullmatch(name):
out[name] = full
return out
def resolve(name: str, commons_root: str, prop_root: Optional[str]) -> Optional[Resolved]:
if not ACTION_NAME.fullmatch(name):
return None
rel = name + ACTION_EXT
commons_path = os.path.join(commons_root, rel)
c = os.path.isfile(commons_path)