Files
apex.ermnvldmr.com/actions/compose.py
T
deytenit 6aaef35780 fix: resolve 17 confirmed bash-parity drifts from port review
Adversarial parity review of every ported action against its bash source
confirmed 17 real behavioral drifts (of 22 claimed); all fixed here:

- tiers/chown: defaults were compositions/apex/tierN (one level too deep);
  now the tier roots. shared/ base reset is non-recursive again.
- compose: --dry-run was swallowed by argparse.REMAINDER; now stripped.
- host.write_file: rendered files land 0644 (tempfile's 0600 made fresh
  systemd units/crowdsec configs owner-only).
- systemd.deploy_units: top-level non-hidden files only.
- ufw: missing docker/host config dir exits 66; missing /etc/ufw target
  aborts; empty rules dir warns; dotfile .rules excluded.
- sync/packages: Telegram error-trap parity and failed notify exits 1.
  Same notify-failure exit for backup/run and sync/repository.
  repository regains the node-dir guard.
- tiers/useradd: no more secret-bearing .env.bak per run.
- tiers/link: EOF on confirm prompt declines and continues.
- extract-traefik-certs: sudo mkdir inside the notify envelope.
- generate-happ-subscriptions: realpath parent-exists guards + docker check.
- configure/crowdsec: bouncer install goes through install_packages.
- lint-docker-compose: malformed/unreadable anchor-IP inputs are lint errors.
- core compose: APEX_RESTIC_REPOSITORY now ?error; RESTIC_COMPRESSION
  per-node via APEX_RESTIC_COMPRESSION (default auto).
- sys.service_exists line-anchored; tpl docstring corrected.
2026-07-05 10:50:40 +03:00

48 lines
2.2 KiB
Python

# actions/compose.py
"""Port of compose — orchestrate docker compose across the node. apex core first
(owns networks), then services alpha; reverse on down. Exports derived APEX_* vars and
layers --env-file (.env secrets + apex.env committed scalars)."""
import os
from engine.descriptor import Meta, Arg, Flag, Rest
METADATA = Meta(summary="Orchestrate docker compose across the node (apex core first, services next).",
args=[Arg("action", "up | down | restart | ..."),
Flag("--dry-run", "Print commands only."),
Rest("extra", "Extra args passed through to docker compose.")])
def run(ctx, args):
log, comp, action = ctx.log, ctx.paths.compositions, args.action
# argparse.REMAINDER swallows flags placed after the action, so strip --dry-run
# out of the passthrough args like bash did ("apex compose up --dry-run" must
# preview, not hand docker compose its own --dry-run).
extra = [a for a in (args.extra or []) if a != "--dry-run"]
dry_run = args.dry_run or len(extra) != len(args.extra or [])
env = dict(os.environ)
env.update({k: v for k, v in ctx.vars().items() if k.startswith("APEX_")})
def sequence():
core = os.path.join(comp, "apex")
others = sorted(
os.path.join(comp, d) for d in os.listdir(comp)
if os.path.isdir(os.path.join(comp, d)) and not d.startswith((".", "@")) and d != "apex"
and os.path.isfile(os.path.join(comp, d, "docker-compose.yml")))
seq = ([core] if os.path.isfile(os.path.join(core, "docker-compose.yml")) else []) + others
return list(reversed(seq)) if action == "down" else seq
def run_one(dirpath):
name = os.path.basename(dirpath)
base = ["docker", "compose"]
for ef in (".env", "apex.env"):
if os.path.isfile(os.path.join(dirpath, ef)):
base += ["--env-file", ef]
cmd = base + (["up", "-d"] if action == "up" else [action]) + extra
if dry_run:
log.info(f"[DRY-RUN] (cd {dirpath} && {' '.join(cmd)})")
else:
log.info(f"{action} {name}...")
ctx.sys.run(cmd, cwd=dirpath, env=env, check=True)
for d in sequence():
run_one(d)
log.success(f"Orchestrated {action}.")