diff --git a/CHANGELOG.md b/CHANGELOG.md index 011e83d..d30a65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.0.4 + + * Fix dash package detection issue + * Reorder which roles install first + # 0.0.3 * various bug fixes diff --git a/debian/changelog b/debian/changelog index 41c527b..86ae088 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,12 @@ +enroll (0.0.4) unstable; urgency=medium + + * Fix dash package detection issue + * Reorder which roles install first + + -- Miguel Jacq Mon, 15 Dec 2025 17:00:00 +1100 + enroll (0.0.3) unstable; urgency=medium * Initial package - -- Miguel Jacq Mon, 15 Dec 2025 12:00:00 +0000 + -- Miguel Jacq Mon, 15 Dec 2025 12:00:00 +1100 diff --git a/enroll/debian.py b/enroll/debian.py index d3f1563..58569e5 100644 --- a/enroll/debian.py +++ b/enroll/debian.py @@ -6,6 +6,8 @@ import os import subprocess # nosec from typing import Dict, List, Optional, Set, Tuple +_DIVERSION_PREFIX = "diversion by " + def _run(cmd: list[str]) -> str: p = subprocess.run(cmd, check=False, text=True, capture_output=True) # nosec @@ -18,9 +20,32 @@ def dpkg_owner(path: str) -> Optional[str]: p = subprocess.run(["dpkg", "-S", path], text=True, capture_output=True) # nosec if p.returncode != 0: return None - left = p.stdout.split(":", 1)[0].strip() - pkg = left.split(":", 1)[0].strip() - return pkg or None + + for raw in (p.stdout or "").splitlines(): + line = raw.strip() + if not line: + continue + + # dpkg diversion chatter; not an ownership line + if line.startswith(_DIVERSION_PREFIX): + continue + + # Expected: "[, ...][:]: " + if ":" not in line: + continue + + left, _ = line.split(":", 1) + + # If multiple pkgs listed, pick the first (common case is just one) + left = left.split(",", 1)[0].strip() + + # Strip any ":arch" suffix from left side + pkg = left.split(":", 1)[0].strip() + + if pkg and not pkg.startswith(_DIVERSION_PREFIX): + return pkg + + return None def list_manual_packages() -> List[str]: diff --git a/enroll/harvest.py b/enroll/harvest.py index 62f130c..78f7d1f 100644 --- a/enroll/harvest.py +++ b/enroll/harvest.py @@ -18,7 +18,7 @@ from .debian import ( read_pkg_md5sums, stat_triplet, ) -from .secrets import SecretPolicy +from .ignore import IgnorePolicy from .accounts import collect_non_system_users @@ -233,8 +233,8 @@ def _topdirs_for_package(pkg: str, pkg_to_etc_paths: Dict[str, List[str]]) -> Se return topdirs -def harvest(bundle_dir: str, policy: Optional[SecretPolicy] = None) -> str: - policy = policy or SecretPolicy() +def harvest(bundle_dir: str, policy: Optional[IgnorePolicy] = None) -> str: + policy = policy or IgnorePolicy() os.makedirs(bundle_dir, exist_ok=True) if hasattr(os, "geteuid") and os.geteuid() != 0: @@ -487,9 +487,7 @@ def harvest(bundle_dir: str, policy: Optional[SecretPolicy] = None) -> str: ) if not pkg_to_etc_paths.get(pkg, []) and not managed: - notes.append( - "No /etc files detected for this package." - ) + notes.append("No /etc files detected for this package.") pkg_snaps.append( PackageSnapshot( diff --git a/enroll/secrets.py b/enroll/ignore.py similarity index 98% rename from enroll/secrets.py rename to enroll/ignore.py index 06514e5..217497f 100644 --- a/enroll/secrets.py +++ b/enroll/ignore.py @@ -33,7 +33,7 @@ SENSITIVE_CONTENT_PATTERNS = [ @dataclass -class SecretPolicy: +class IgnorePolicy: deny_globs: list[str] = None max_file_bytes: int = 256_000 sample_bytes: int = 64_000 diff --git a/pyproject.toml b/pyproject.toml index dd76bd0..6b4d1b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "enroll" -version = "0.0.3" +version = "0.0.4" description = "Enroll a server's running state retrospectively into Ansible" authors = ["Miguel Jacq "] license = "GPL-3.0-or-later" diff --git a/tests/test_secrets.py b/tests/test_ignore.py similarity index 51% rename from tests/test_secrets.py rename to tests/test_ignore.py index f66d4cb..bba9f06 100644 --- a/tests/test_secrets.py +++ b/tests/test_ignore.py @@ -1,8 +1,9 @@ -from enroll.secrets import SecretPolicy +from enroll.ignore import IgnorePolicy -def test_secret_policy_denies_common_backup_files(): - pol = SecretPolicy() +def test_ignore_policy_denies_common_backup_files(): + pol = IgnorePolicy() assert pol.deny_reason("/etc/shadow-") == "denied_path" assert pol.deny_reason("/etc/passwd-") == "denied_path" assert pol.deny_reason("/etc/group-") == "denied_path" + assert pol.deny_reason("/foobar") == "unreadable"