From 7b4e40dba58a63c83a825e7b1b1ea0679d9640ce Mon Sep 17 00:00:00 2001 From: Vladimir Eremin Date: Sun, 13 Sep 2026 15:39:23 +0300 Subject: [PATCH] fix(configure): gracefully handle missing ufw dirs and skip redundant apt calls --- actions/configure/base.py | 12 ++++++++---- actions/configure/ufw.py | 14 +++++++------- engine/lib/ufw.py | 31 +++++++++++++++++-------------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/actions/configure/base.py b/actions/configure/base.py index 0a808a4..ce39036 100644 --- a/actions/configure/base.py +++ b/actions/configure/base.py @@ -11,12 +11,16 @@ def run(ctx, args): log.info("Starting base system initialization...") if not os.path.isfile("/etc/debian_version"): log.error("Requires a Debian-based system (Debian/Ubuntu)."); raise SystemExit(1) - log.info("Updating package lists..."); s.sudo(["apt-get", "update", "-y"]) - log.info("Installing essential packages...") - s.sudo(["apt-get", "install", "-y", "curl", "wget", "git", "rsyslog"]) + needed = [p for p in ["curl", "wget", "git", "rsyslog"] if not shutil.which(p)] + if needed: + log.info("Updating package lists..."); s.sudo(["apt-get", "update", "-y"]) + log.info(f"Installing missing essential packages: {', '.join(needed)}...") + s.sudo(["apt-get", "install", "-y", "-o", "DPkg::Lock::Timeout=60", *needed]) + else: + log.info("Essential packages already installed.") s.ensure_running("rsyslog") if not shutil.which("ufw"): - log.info("Installing UFW..."); s.sudo(["apt-get", "install", "-y", "ufw"]) + log.info("Installing UFW..."); s.sudo(["apt-get", "install", "-y", "-o", "DPkg::Lock::Timeout=60", "ufw"]) else: log.info("UFW already installed.") from engine.lib.ufw import resolve_ufw_docker_bin diff --git a/actions/configure/ufw.py b/actions/configure/ufw.py index bdd47d3..751723f 100644 --- a/actions/configure/ufw.py +++ b/actions/configure/ufw.py @@ -13,12 +13,12 @@ def run(ctx, args): docker_dir = os.path.join(cfg, "docker") host_dir = os.path.join(cfg, "host") ctx.log.info("Configuring UFW rules...") - # Mirrors the bash branch structure: host dir is passed unconditionally (a missing - # one exits 66 inside apply); both dirs missing = warn + success, like bash. - if os.path.isdir(docker_dir): - ctx.ufw.apply(docker_dir, host_dir, dry_run=args.dry_run) - else: + has_docker = os.path.isdir(docker_dir) + has_host = os.path.isdir(host_dir) + if not has_docker: ctx.log.warn(f"Docker rules dir not found at {docker_dir}. Skipping ufw-docker rules.") - if os.path.isdir(host_dir): - ctx.ufw.apply(docker_dir, host_dir, dry_run=args.dry_run) + if not has_host: + ctx.log.warn(f"Host rules dir not found at {host_dir}. Skipping host rules.") + if has_docker or has_host: + ctx.ufw.apply(docker_dir if has_docker else None, host_dir if has_host else None, dry_run=args.dry_run) ctx.log.success("Configured ufw.") diff --git a/engine/lib/ufw.py b/engine/lib/ufw.py index 0169273..c925760 100644 --- a/engine/lib/ufw.py +++ b/engine/lib/ufw.py @@ -45,14 +45,16 @@ class Ufw: anchor="# End required lines", old_pairs=OLD_PAIRS, dry_run=dry_run, sudo=True) - def apply(self, rules_dir: str, config_dir=None, dry_run: bool = False) -> None: - # EX_NOINPUT guards first, tools second — same order/codes as root_template_ufw. - if not os.path.isdir(rules_dir): + def apply(self, rules_dir: str | None = None, config_dir: str | None = None, dry_run: bool = False) -> None: + if rules_dir and not os.path.isdir(rules_dir): self.log.error(f"Rules directory does not exist: {rules_dir}") raise SystemExit(66) if config_dir and not os.path.isdir(config_dir): self.log.error(f"Config directory does not exist: {config_dir}") raise SystemExit(66) + if not rules_dir and not config_dir: + 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.") raise SystemExit(1) @@ -72,17 +74,18 @@ class Ufw: self._inject_file("/etc/ufw/before.rules", os.path.join(config_dir, "before.rules"), dry_run) self._inject_file("/etc/ufw/after.rules", os.path.join(config_dir, "after.rules"), dry_run) - rule_files = [fn for fn in sorted(os.listdir(rules_dir)) - if fn.endswith(".rules") and not fn.startswith(".")] - if not rule_files: - self.log.warn(f"No .rules files found in {rules_dir}, nothing to apply.") - for fn in rule_files: - for rule in clean_rules(open(os.path.join(rules_dir, fn)).read()): - self.log.info(f"Applying ufw-docker rule ({fn}): {rule}") - if dry_run: - self.log.info(f"[DRY-RUN] sudo {UFW_DOCKER_BIN} {rule}") - else: - self.sys.run(["sudo", UFW_DOCKER_BIN, *rule.split()], check=True) + if rules_dir: + rule_files = [fn for fn in sorted(os.listdir(rules_dir)) + if fn.endswith(".rules") and not fn.startswith(".")] + if not rule_files: + self.log.warn(f"No .rules files found in {rules_dir}, nothing to apply.") + for fn in rule_files: + for rule in clean_rules(open(os.path.join(rules_dir, fn)).read()): + self.log.info(f"Applying ufw-docker rule ({fn}): {rule}") + if dry_run: + self.log.info(f"[DRY-RUN] sudo {UFW_DOCKER_BIN} {rule}") + else: + self.sys.run(["sudo", UFW_DOCKER_BIN, *rule.split()], check=True) self.log.success("All ufw rules applied.") self.log.warn("Run `sudo systemctl restart ufw` to apply. Ensure ssh is allowed via plain ufw!")