Split host setup into explicit entity actions
This commit is contained in:
51 files changed
+1023
-275
No files matched your search
+3
-1
@@ -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,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
@@ -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}")
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user