141 lines
5.4 KiB
Python
Executable File
141 lines
5.4 KiB
Python
Executable File
"""
|
|
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/<mod>.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]
|