""" SysDeck - Python Bridge Helpers Author: Jeremy Anderson (https://dcos.net) This package contains helper scripts invoked from the JS bridge client via cockpit.spawn. Each module is standalone and runnable as a CLI: python3 /usr/lib/sysdeck/bridge/containers.py list Invocation contract: absolute path, no PYTHONPATH, no -m flag. The installed layout is flat files at /usr/lib/sysdeck/bridge/.py, and bridge.js calls each helper by absolute path. The helpers exist for operations that are too complex for a single CLI call — e.g. cross-referencing podman and systemd, or aggregating TPM PCR banks into a single JSON document. """ import os import subprocess from typing import Literal __version__ = "0.4.6" __author__ = "Jeremy Anderson" __url__ = "https://dcos.net" # ── Distro detection ──────────────────────────────────────────────── # # Step-down: check /etc/os-release (standard across all modern distros), # then fall back to checking which package manager is available. # Returns a normalized distro identifier for use in dispatch tables. DistroId = Literal["arch", "gentoo", "lunar", "sourcemage", "void", "alpine", "opensuse", "debian", "fedora", "rhel", "unknown"] def detect_distro() -> DistroId: """Detect the running Linux distribution. Priority order: 1. Parse /etc/os-release ID/ID_LIKE fields. 2. Fall back to package-manager presence (pacman → arch, emerge → gentoo, lunar → lunar, sorcery → sourcemage, xbps-query → void, apk → alpine, zypper → opensuse, apt → debian, dnf → fedora). Returns one of: 'arch', 'gentoo', 'lunar', 'sourcemage', 'void', 'alpine', 'opensuse', 'debian', 'fedora', 'rhel', 'unknown'. """ # Try /etc/os-release first (present on all modern distros). try: with open("/etc/os-release", encoding="utf-8") as fh: os_release = dict( line.split("=", 1) if "=" in line else ("", "") for line in fh if "=" in line ) dist_id = os_release.get("ID", "").strip().strip('"').lower() id_like = os_release.get("ID_LIKE", "").strip().strip('"').lower() # Direct match on ID. id_map = {"arch": "arch", "archlinux": "arch", "gentoo": "gentoo", "funtoo": "gentoo", "lunar": "lunar", "sourcemage": "sourcemage", "void": "void", "alpine": "alpine", "postmarketos": "alpine", "opensuse": "opensuse", "opensuse-leap": "opensuse", "opensuse-tumbleweed": "opensuse", "sles": "opensuse", "debian": "debian", "ubuntu": "debian", "linuxmint": "debian", "pop": "debian", "fedora": "fedora", "rhel": "rhel", "centos": "rhel", "rocky": "rhel", "alma": "rhel"} if dist_id in id_map: return id_map[dist_id] # Fall back to ID_LIKE. for like in id_like.split(): if like in id_map: return id_map[like] except (FileNotFoundError, PermissionError): pass # Fall back to package manager presence. for cmd, distro in [("pacman", "arch"), ("emerge", "gentoo"), ("lunar", "lunar"), ("sorcery", "sourcemage"), ("xbps-query", "void"), ("apk", "alpine"), ("zypper", "opensuse"), ("apt", "debian"), ("dnf", "fedora")]: try: subprocess.run([cmd, "--version"], capture_output=True, check=True) return distro except (subprocess.CalledProcessError, FileNotFoundError): continue return "unknown" # Detect once at import time — shared across all bridge modules. DISTRO: DistroId = detect_distro() # ── Package manager detection ─────────────────────────────────────── # # Returns the manager id for the system's distro, matching the ten # backends bridge/packages.py steps down through: Arch → pacman, # Gentoo → emerge, Lunar → lunar, SourceMage → sorcery, Void → xbps, # Alpine → apk, openSUSE → zypper, Fedora/RHEL → dnf, Debian → apt. PkgManager = Literal["pacman", "emerge", "lunar", "sorcery", "xbps", "apk", "zypper", "dnf", "apt", "unknown"] def detect_pkg_manager() -> PkgManager: """Detect the system package manager based on distro.""" pkg_map: dict[DistroId, PkgManager] = { "arch": "pacman", "gentoo": "emerge", "lunar": "lunar", "sourcemage": "sorcery", "void": "xbps", "alpine": "apk", "opensuse": "zypper", "debian": "apt", "fedora": "dnf", "rhel": "dnf", } return pkg_map.get(DISTRO, "unknown") PKG_MANAGER: PkgManager = detect_pkg_manager() # ── Service management ────────────────────────────────────────────── # # All three target distros use systemd, so this is uniform. # Kept here for documentation and future extension (e.g. openrc on Artix). def service_cmd(action: str, unit: str) -> list[str]: """Build a systemctl command. All supported distros use systemd.""" return ["systemctl", action, unit]