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.
70 lines
3.0 KiB
Python
70 lines
3.0 KiB
Python
# actions/tiers/link.py
|
|
"""Port of tiers/link — @tier symlink structure for the node + each composition project."""
|
|
import os
|
|
from engine.descriptor import Meta, Arg
|
|
|
|
METADATA = Meta(summary="Create @tier symlink structure for the node + each composition project.",
|
|
args=[Arg("tier1_path", "Path to tier 1 storage root"),
|
|
Arg("tier2_path", "Path to tier 2 storage root"),
|
|
Arg("tier3_path", "Path to tier 3 storage root")])
|
|
|
|
def run(ctx, args):
|
|
log, s = ctx.log, ctx.sys
|
|
comp = ctx.paths.compositions
|
|
tiers = [os.path.realpath(args.tier1_path),
|
|
os.path.realpath(args.tier2_path),
|
|
os.path.realpath(args.tier3_path)]
|
|
|
|
def confirm(msg):
|
|
try:
|
|
return input(f"{msg} [y/N]: ").strip().lower() in ("y", "yes")
|
|
except EOFError: # non-interactive stdin = decline, like bash `read` at EOF
|
|
return False
|
|
|
|
def safe_remove(link, desc):
|
|
if os.path.islink(link):
|
|
s.sudo(["rm", link]); return True
|
|
if os.path.exists(link):
|
|
log.warn(f"Path exists but is not a symlink: {link}")
|
|
if confirm(f"Remove existing {desc} (NOT a symlink)?"):
|
|
s.sudo(["rm", "-rf", link]); return True
|
|
return False
|
|
return True
|
|
|
|
def mklink(target, link, desc):
|
|
s.sudo(["ln", "-snf", target, link])
|
|
log.success(f"Created {desc} symlink: {link} -> {target}")
|
|
|
|
log.info("Starting @tier setup...")
|
|
for t in tiers:
|
|
s.sudo(["mkdir", "-p", t])
|
|
# node-level @tierN -> tier roots
|
|
for i, n in enumerate((1, 2, 3)):
|
|
link = os.path.join(comp, f"@tier{n}")
|
|
if safe_remove(link, f"node-@tier{n}"):
|
|
mklink(tiers[i], link, f"node-@tier{n}")
|
|
# per-project (every composition subdir except dot/@)
|
|
for proj in sorted(os.listdir(comp)):
|
|
full = os.path.join(comp, proj)
|
|
if not os.path.isdir(full) or proj.startswith(".") or proj.startswith("@"):
|
|
continue
|
|
log.info(f"Processing project: {proj}")
|
|
for i, n in enumerate((1, 2, 3)):
|
|
base = tiers[i]
|
|
s.sudo(["mkdir", "-p", os.path.join(base, "shared")])
|
|
s.sudo(["mkdir", "-p", os.path.join(base, "shared", proj)])
|
|
s.sudo(["mkdir", "-p", os.path.join(base, proj)])
|
|
shared_link = os.path.join(base, proj, "shared")
|
|
if safe_remove(shared_link, "shared-internal"):
|
|
s.sudo(["ln", "-snf", "../shared", shared_link])
|
|
link = os.path.join(full, f"@tier{n}")
|
|
if safe_remove(link, f"@tier{n}"):
|
|
mklink(os.path.join(base, proj), link, f"@tier{n}")
|
|
env_link = os.path.join(full, ".env")
|
|
env_target = os.path.join(tiers[0], proj, ".env")
|
|
if safe_remove(env_link, ".env"):
|
|
if not os.path.isfile(env_target):
|
|
s.sudo(["touch", env_target])
|
|
mklink(env_target, env_link, ".env")
|
|
log.success("@tier setup completed.")
|