feat: wire backends to bundled binaries via _tool()

Backends now resolve package managers from euri_mklive/bin/ first,
falling back to system PATH. No more host_deps for package managers.
This commit is contained in:
2026-08-27 19:23:43 +02:00
parent 38f0c5c12e
commit f5f800c533
6 changed files with 47 additions and 24 deletions
+5 -4
View File
@@ -7,7 +7,7 @@ from .base import Distro
class Alpine(Distro):
name = "alpine"
host_deps = ["apk-tools", "squashfs-tools", "xorriso", "mtools", "dosfstools"]
host_deps = ["squashfs-tools", "xorriso", "mtools", "dosfstools"]
MIRROR = "https://dl-cdn.alpinelinux.org/alpine/latest-stable"
@@ -17,7 +17,7 @@ class Alpine(Distro):
init = config.get("init", "openrc")
cmd = [
"apk",
self._tool("apk"),
"-X", f"{mirror}/{branch}/main",
"-U",
"--allow-untrusted",
@@ -36,7 +36,7 @@ class Alpine(Distro):
def install_packages(self, rootfs: Path, packages: list[str]) -> None:
if not packages:
return
self._run_chroot(rootfs, ["apk", "add", *packages])
self._run_chroot(rootfs, [self._tool("apk"), "add", *packages])
def configure_base(self, rootfs: Path, config: dict) -> None:
hostname = config.get("hostname", "euri")
@@ -57,7 +57,8 @@ class Alpine(Distro):
self._run_chroot(rootfs, ["rc-update", "add", svc, "default"])
def install_bootloader(self, rootfs: Path, config: dict) -> None:
self._run_chroot(rootfs, ["apk", "add", "grub", "grub-efi", "os-prober"])
pm = self._tool("apk")
self._run_chroot(rootfs, [pm, "add", "grub", "grub-efi", "os-prober"])
self._run_chroot(rootfs, ["grub-install", "--target=i386-pc", "/dev/sda"])
self._run_chroot(rootfs, ["grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi"])
self._run_chroot(rootfs, ["grub-mkconfig", "-o", "/boot/grub/grub.cfg"])
+6 -8
View File
@@ -19,19 +19,16 @@ class Arch(Distro):
base_pkgs = ["base", "base-devel", f"{init}", *config.get("base_packages", [])]
if self.family == "artix":
# basestrap for Artix
elogind_pkg = f"elogind-{init}"
cmd = ["basestrap", "-i", str(rootfs), *base_pkgs, elogind_pkg]
cmd = [self._tool("pacstrap"), "-i", str(rootfs), *base_pkgs, elogind_pkg]
else:
# pacstrap for Arch
cmd = ["pacstrap", "-K", str(rootfs), *base_pkgs]
cmd = [self._tool("pacstrap"), "-K", str(rootfs), *base_pkgs]
self._run(cmd)
def install_packages(self, rootfs: Path, packages: list[str]) -> None:
# always include the package manager itself
pkgs = ["pacman", *packages]
self._run_chroot(rootfs, ["pacman", "-Syu", "--noconfirm", *pkgs])
pm = self._tool("pacman")
self._run_chroot(rootfs, [pm, "-Syu", "--noconfirm", *packages])
def configure_base(self, rootfs: Path, config: dict) -> None:
hostname = config.get("hostname", "euri")
@@ -73,7 +70,8 @@ class Arch(Distro):
def install_bootloader(self, rootfs: Path, config: dict) -> None:
init = config.get("init", "dinit")
self._run_chroot(rootfs, ["pacman", "-S", "--noconfirm", "grub", "efibootmgr", "os-prober"])
pm = self._tool("pacman")
self._run_chroot(rootfs, [pm, "-S", "--noconfirm", "grub", "efibootmgr", "os-prober"])
self._run_chroot(rootfs, ["grub-install", "--target=i386-pc", "/dev/sda"])
self._run_chroot(rootfs, ["grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--bootloader-id=grub"])
self._run_chroot(rootfs, ["grub-mkconfig", "-o", "/boot/grub/grub.cfg"])
+17 -1
View File
@@ -2,16 +2,18 @@
from __future__ import annotations
import shutil
import subprocess
from abc import ABC, abstractmethod
from pathlib import Path
_BUNDLED = Path(__file__).resolve().parent.parent / "bin"
class Distro(ABC):
"""Base class every distro backend must implement."""
name: str
# host packages needed to bootstrap (e.g. debootstrap, pacstrap)
host_deps: list[str]
@abstractmethod
@@ -30,6 +32,20 @@ class Distro(ABC):
def install_bootloader(self, rootfs: Path, config: dict) -> None:
"""Install and configure the bootloader inside the rootfs."""
@staticmethod
def _tool(name: str) -> str:
bundled = _BUNDLED / name
if bundled.is_file():
return str(bundled)
if bundled.is_dir():
script = bundled / name
if script.is_file():
return str(script)
path = shutil.which(name)
if path:
return path
return name
@staticmethod
def _run(cmd: list[str], **kw) -> subprocess.CompletedProcess:
print(f" \033[90m$\033[0m \033[1m{' '.join(cmd)}\033[0m")
+11 -3
View File
@@ -7,7 +7,7 @@ from .base import Distro
class Debian(Distro):
name = "debian"
host_deps = ["debootstrap", "squashfs-tools", "xorriso", "mtools", "dosfstools"]
host_deps = ["squashfs-tools", "xorriso", "mtools", "dosfstools"]
SUITES = {
"debian": "bookworm",
@@ -23,12 +23,20 @@ class Debian(Distro):
self.family = family
def bootstrap(self, rootfs: Path, config: dict) -> None:
import os
suite = config.get("suite", self.SUITES.get(self.family, "bookworm"))
mirror = config.get("mirror", self.MIRRORS.get(self.family))
variant = config.get("variant", "minbase")
debootstrap = self._tool("debootstrap")
env = {**os.environ}
debootstrap_dir = Path(debootstrap).parent
if (debootstrap_dir / "scripts").is_dir():
env["DEBOOTSTRAP_DIR"] = str(debootstrap_dir)
cmd = [
"debootstrap",
debootstrap,
"--variant", variant,
"--include", ",".join(config.get("base_packages", ["base-files", "apt"])),
"--arch", config.get("arch", "amd64"),
@@ -36,7 +44,7 @@ class Debian(Distro):
str(rootfs),
mirror,
]
self._run(cmd)
self._run(cmd, env=env)
def install_packages(self, rootfs: Path, packages: list[str]) -> None:
if not packages:
+3 -3
View File
@@ -7,7 +7,7 @@ from .base import Distro
class Fedora(Distro):
name = "fedora"
host_deps = ["dnf", "squashfs-tools", "xorriso", "mtools", "dosfstools"]
host_deps = ["squashfs-tools", "xorriso", "mtools", "dosfstools"]
GROUPS = {
"fedora": "@core",
@@ -23,7 +23,7 @@ class Fedora(Distro):
group = self.GROUPS.get(self.family, "@core")
cmd = [
"dnf",
self._tool("dnf5"),
"--installroot", str(rootfs),
"-y",
f"--releasever={release}",
@@ -38,7 +38,7 @@ class Fedora(Distro):
def install_packages(self, rootfs: Path, packages: list[str]) -> None:
if not packages:
return
self._run_chroot(rootfs, ["dnf", "-y", "install", *packages])
self._run_chroot(rootfs, [self._tool("dnf5"), "-y", "install", *packages])
def configure_base(self, rootfs: Path, config: dict) -> None:
hostname = config.get("hostname", "euri")
+5 -5
View File
@@ -8,7 +8,7 @@ from ..dinit import install_all
class Void(Distro):
name = "void"
host_deps = ["xbps-install", "squashfs-tools", "xorriso", "mtools", "dosfstools"]
host_deps = ["squashfs-tools", "xorriso", "mtools", "dosfstools"]
REPOS = {
"glibc": "https://repo-default.voidlinux.org/current",
@@ -26,7 +26,7 @@ class Void(Distro):
base_pkgs.append("dinit")
cmd = [
"xbps-install",
self._tool("xbps-install"),
"-S",
"-r", str(rootfs),
"-R", repo,
@@ -37,12 +37,12 @@ class Void(Distro):
# remove runit if we're using dinit
if init == "dinit":
self._run_chroot(rootfs, ["xbps-remove", "-y", "runit"])
self._run_chroot(rootfs, [self._tool("xbps-remove"), "-y", "runit"])
def install_packages(self, rootfs: Path, packages: list[str]) -> None:
if not packages:
return
self._run_chroot(rootfs, ["xbps-install", "-Sy", *packages])
self._run_chroot(rootfs, [self._tool("xbps-install"), "-Sy", *packages])
def configure_base(self, rootfs: Path, config: dict) -> None:
hostname = config.get("hostname", "euri")
@@ -78,7 +78,7 @@ class Void(Distro):
def install_bootloader(self, rootfs: Path, config: dict) -> None:
libc = config.get("libc", "musl")
pkgs = ["grub", "grub-x86_64-efi", "efibootmgr"]
self._run_chroot(rootfs, ["xbps-install", "-Sy", *pkgs])
self._run_chroot(rootfs, [self._tool("xbps-install"), "-Sy", *pkgs])
self._run_chroot(rootfs, ["grub-install", "--target=i386-pc", "/dev/sda"])
self._run_chroot(rootfs, ["grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--bootloader-id=Void"])
self._run_chroot(rootfs, ["grub-mkconfig", "-o", "/boot/grub/grub.cfg"])