Files
euri-mklive/euri_mklive/iso.py
T
c-ludenberg 97331b1449 horizon: dinit services, pkg manager binaries, GPL-2.0 licenses
- real dinit service files (connmand, sddm, ntpd, chrony, sshd)
- void backend properly swaps runit for dinit with service definitions
- ISO generation finds kernel/initrd, builds proper GRUB config
- ship all package manager binaries in ISO for offline use
- LICENSES/ directory with proper GPL-2.0 plain text for xbps, pacman, apt, dnf, apk, dpkg
- backends explicitly install their own package managers
- README updated with binary inclusion and license info
2026-08-26 19:12:09 +02:00

163 lines
5.4 KiB
Python

"""ISO generator — squashes rootfs and creates bootable ISO."""
from __future__ import annotations
import glob
import shutil
import subprocess
import tempfile
from pathlib import Path
def generate(rootfs: Path, config: dict, output: Path) -> Path:
"""Generate a bootable ISO from a rootfs."""
iso_cfg = config.get("iso", {})
label = iso_cfg.get("label", "EURI")
name = iso_cfg.get("name", "Euri Linux")
version = iso_cfg.get("version", "1.0")
distro = config.get("distro", "void")
libc = config.get("libc", "glibc")
print(f"\n\033[1;36m==>\033[0m \033[1mGenerating ISO: {name} {version}\033[0m")
workdir = Path(tempfile.mkdtemp(prefix="mklive-iso-"))
iso_dir = workdir / "iso"
iso_dir.mkdir()
# copy rootfs into ISO staging
_run(["cp", "-a", str(rootfs) + "/.", str(iso_dir)])
# ─── 1. find kernel and initrd in rootfs ───
vmlinuz = _find_file(iso_dir, "vmlinuz*") or _find_file(iso_dir, "vmlinux*")
if not vmlinuz:
raise FileNotFoundError("No kernel found in rootfs")
initrd = _find_file(iso_dir, "initramfs*") or _find_file(iso_dir, "initrd*")
if not initrd:
raise FileNotFoundError("No initramfs/initrd found in rootfs")
live_dir = iso_dir / "boot" / "live"
live_dir.mkdir(parents=True, exist_ok=True)
_run(["cp", str(vmlinuz), str(live_dir / "vmlinuz")])
_run(["cp", str(initrd), str(live_dir / "initrd")])
print(f" \033[90mkernel:\033[0m {vmlinuz.name}")
print(f" \033[90minitrd:\033[0m {initrd.name}")
# ─── 2. copy package manager binaries + licenses ───
pkg_bin_dir = iso_dir / "usr" / "bin"
pkg_bin_dir.mkdir(parents=True, exist_ok=True)
licenses_dir = iso_dir / "usr" / "share" / "licenses" / "mklive"
licenses_dir.mkdir(parents=True, exist_ok=True)
pkg_mgrs = {
"xbps-install": "/usr/bin/xbps-install",
"xbps-remove": "/usr/bin/xbps-remove",
"xbps-query": "/usr/bin/xbps-query",
"xbps-repo": "/usr/bin/xbps-repo",
"xbps-digest": "/usr/bin/xbps-digest",
"xbps-pkgdb": "/usr/bin/xbps-pkgdb",
"xbps-rindex": "/usr/bin/xbps-rindex",
"pacman": "/usr/bin/pacman",
"apt": "/usr/bin/apt",
"apt-get": "/usr/bin/apt-get",
"dpkg": "/usr/bin/dpkg",
"dnf": "/usr/bin/dnf",
"apk": "/sbin/apk",
}
shipped = []
for name_bin, src in pkg_mgrs.items():
full = rootfs / src
if full.exists():
_run(["cp", str(full), str(pkg_bin_dir / name_bin)])
shipped.append(name_bin)
if shipped:
(licenses_dir / "MANIFEST").write_text(
f"# Package Manager Binaries\n\n"
f"Shipped with this ISO for offline package management:\n\n"
+ "\n".join(f"- {b}" for b in shipped)
+ "\n\nSee individual LICENSE files for each tool.\n"
)
print(f" \033[90mpkg managers:\033[0m {', '.join(shipped)}")
# ─── 3. squashfs ───
squash = workdir / "filesystem.squashfs"
print(f"\033[1;36m==>\033[0m \033[1mCreating squashfs image...\033[0m")
_run(["mksquashfs", str(iso_dir), str(squash), "-comp", "xz", "-b", "1M", "-noappend"])
# ─── 4. GRUB config ───
grub_dir = iso_dir / "boot" / "grub"
grub_dir.mkdir(parents=True, exist_ok=True)
boot_params = f"boot=live components"
if libc == "musl":
boot_params += " musl"
grub_cfg = grub_dir / "grub.cfg"
grub_cfg.write_text(f"""\
set timeout=10
set default=0
set gfxmode=auto
insmod all_video
insmod gfxterm
terminal_output gfxterm
menuentry "{name}" {{
linux /boot/live/vmlinuz {boot_params}
initrd /boot/live/initrd
}}
menuentry "{name} (copy to RAM)" {{
linux /boot/live/vmlinuz {boot_params} toram
initrd /boot/live/initrd
}}
menuentry "{name} (verbose)" {{
linux /boot/live/vmlinuz {boot_params} debug
initrd /boot/live/initrd
}}
""")
print(f" \033[90mgrub cfg:\033[0m {grub_cfg.name}")
# ─── 5. EFI boot ───
efi_dir = iso_dir / "EFI" / "BOOT"
efi_dir.mkdir(parents=True, exist_ok=True)
efi_src = Path("/usr/lib/grub/x86_64-efi/monolithic/grubx64.efi")
if efi_src.exists():
_run(["cp", str(efi_src), str(efi_dir / "BOOTX64.EFI")])
# ─── 6. xorriso to create ISO ───
iso_path = output / f"{name.lower().replace(' ', '-')}-{version}-{distro}-{libc}-x86_64.iso"
print(f"\033[1;36m==>\033[0m \033[1mCreating ISO: {iso_path.name}\033[0m")
_run([
"xorriso", "-as", "mkisofs",
"-V", label,
"-o", str(iso_path),
"-J", "-joliet-long",
"-b", "boot/live/vmlinuz",
"-no-emul-boot", "-boot-load-size", "4",
"-boot-info-table",
"-eltorito-alt-boot",
"-e", "boot/live/vmlinuz",
"-no-emul-boot",
str(iso_dir),
])
size_mb = iso_path.stat().st_size / (1024 * 1024)
print(f"\n\033[1;32m==>\033[0m \033[1mISO ready: {iso_path} ({size_mb:.1f} MB)\033[0m")
return iso_path
def _find_file(root: Path, pattern: str) -> Path | None:
"""Find a file recursively by glob pattern."""
matches = glob.glob(str(root / "**" / pattern), recursive=True)
return Path(matches[0]) if matches else None
def _run(cmd: list[str], **kw) -> subprocess.CompletedProcess:
print(f" \033[90m$\033[0m \033[1m{' '.join(cmd)}\033[0m")
return subprocess.run(cmd, check=True, capture_output=True, **kw)