2026-08-26 19:01:19 +02:00
|
|
|
"""Rootfs builder — creates the root filesystem."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from .distros import get as get_distro
|
2026-08-27 20:18:30 +02:00
|
|
|
from .distros.base import set_mode
|
2026-08-26 19:01:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def build(config: dict, output: Path) -> Path:
|
|
|
|
|
"""Build a rootfs and return its path."""
|
2026-08-27 20:18:30 +02:00
|
|
|
set_mode(config.get("_mode", "root"))
|
2026-08-26 19:01:19 +02:00
|
|
|
distro_name = config["distro"]
|
2026-08-27 21:54:36 +02:00
|
|
|
|
|
|
|
|
# map distro name to backend (horizon = void)
|
|
|
|
|
backend_name = "void" if distro_name == "horizon" else distro_name
|
|
|
|
|
distro_cls = get_distro(backend_name)
|
2026-08-26 19:01:19 +02:00
|
|
|
|
|
|
|
|
# artix vs arch
|
2026-08-27 21:54:36 +02:00
|
|
|
if backend_name in ("artix",):
|
2026-08-26 19:01:19 +02:00
|
|
|
distro = distro_cls(family="artix")
|
2026-08-27 21:54:36 +02:00
|
|
|
elif backend_name in ("debian", "ubuntu"):
|
|
|
|
|
distro = distro_cls(family=backend_name)
|
|
|
|
|
elif backend_name in ("fedora", "rocky", "almalinux"):
|
|
|
|
|
distro = distro_cls(family=backend_name)
|
2026-08-26 19:01:19 +02:00
|
|
|
else:
|
|
|
|
|
distro = distro_cls()
|
|
|
|
|
|
|
|
|
|
rootfs = Path(tempfile.mkdtemp(prefix="mklive-"))
|
|
|
|
|
print(f"\n\033[1;36m==>\033[0m \033[1mBuilding rootfs in {rootfs}\033[0m")
|
|
|
|
|
|
|
|
|
|
# 1. bootstrap
|
|
|
|
|
print(f"\033[1;36m==>\033[0m \033[1m[1/4] Bootstrapping {distro_name}...\033[0m")
|
|
|
|
|
distro.bootstrap(rootfs, config)
|
|
|
|
|
|
|
|
|
|
# 2. install packages
|
|
|
|
|
pkgs = config.get("packages", [])
|
|
|
|
|
if pkgs:
|
|
|
|
|
print(f"\033[1;36m==>\033[0m \033[1m[2/4] Installing {len(pkgs)} packages...\033[0m")
|
|
|
|
|
distro.install_packages(rootfs, pkgs)
|
|
|
|
|
else:
|
|
|
|
|
print(f"\033[1;36m==>\033[0m \033[1m[2/4] No extra packages to install\033[0m")
|
|
|
|
|
|
|
|
|
|
# 3. configure
|
|
|
|
|
print(f"\033[1;36m==>\033[0m \033[1m[3/4] Configuring base system...\033[0m")
|
|
|
|
|
distro.configure_base(rootfs, config)
|
|
|
|
|
|
2026-08-27 21:54:36 +02:00
|
|
|
# 3b. overlay files (if any) — artools-style root-overlay merge
|
|
|
|
|
overlay = config.get("overlay")
|
|
|
|
|
if overlay:
|
|
|
|
|
overlay_path = Path(overlay)
|
|
|
|
|
if overlay_path.is_dir():
|
|
|
|
|
import subprocess
|
|
|
|
|
subprocess.run(["cp", "-a", f"{overlay_path}/.", str(rootfs)],
|
|
|
|
|
check=False, capture_output=True)
|
|
|
|
|
print(f" \033[90m merged:\033[0m overlay -> rootfs")
|
|
|
|
|
|
|
|
|
|
# 3c. write /etc/os-release only if user provided a name
|
|
|
|
|
iso_cfg = config.get("iso", {})
|
|
|
|
|
iso_name = iso_cfg.get("name", "")
|
|
|
|
|
if iso_name:
|
|
|
|
|
iso_version = iso_cfg.get("version", "1.0")
|
|
|
|
|
os_id = iso_name.lower().replace(" ", "-").replace("/", "-")
|
|
|
|
|
osrelease = rootfs / "etc" / "os-release"
|
|
|
|
|
osrelease.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
osrelease.write_text(
|
|
|
|
|
f'NAME="{iso_name}"\n'
|
|
|
|
|
f'VERSION="{iso_version}"\n'
|
|
|
|
|
f'ID={os_id}\n'
|
|
|
|
|
f'VERSION_ID="{iso_version}"\n'
|
|
|
|
|
f'ID_LIKE="{config["distro"]}"\n'
|
|
|
|
|
f'PRETTY_NAME="{iso_name} {iso_version}"\n'
|
|
|
|
|
)
|
|
|
|
|
print(f" \033[90m wrote:\033[0m /etc/os-release")
|
|
|
|
|
|
2026-08-26 19:01:19 +02:00
|
|
|
# 4. bootloader
|
|
|
|
|
print(f"\033[1;36m==>\033[0m \033[1m[4/4] Installing bootloader...\033[0m")
|
|
|
|
|
distro.install_bootloader(rootfs, config)
|
|
|
|
|
|
|
|
|
|
return rootfs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup(rootfs: Path) -> None:
|
|
|
|
|
"""Remove the temporary rootfs."""
|
|
|
|
|
shutil.rmtree(rootfs, ignore_errors=True)
|