- any distro from any distro (debian, ubuntu, arch, artix, void, alpine, fedora) - YAML config for distro, arch, init, packages, users, services, bootloader - each distro backend knows how to bootstrap itself - squashfs + xorriso for ISO generation - sexy CLI with banner and colored output - examples for Artix+Euri and Project Horizon
84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
"""ISO generator — squashes rootfs and creates bootable ISO."""
|
|
|
|
from __future__ import annotations
|
|
|
|
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")
|
|
|
|
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)])
|
|
|
|
# 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"])
|
|
|
|
# create ISO layout
|
|
live_dir = iso_dir / "boot" / "x86_64"
|
|
live_dir.mkdir(parents=True, exist_ok=True)
|
|
_run(["cp", str(squash), str(live_dir / "filesystem.squashfs")])
|
|
|
|
# EFI boot
|
|
efi_dir = iso_dir / "EFI" / "BOOT"
|
|
efi_dir.mkdir(parents=True, exist_ok=True)
|
|
_run(["cp", "/usr/lib/grub/x86_64-efi/monolithic/grubx64.efi", str(efi_dir / "BOOTX64.EFI")])
|
|
|
|
# GRUB config
|
|
grub_cfg = iso_dir / "boot" / "grub" / "grub.cfg"
|
|
grub_cfg.parent.mkdir(parents=True, exist_ok=True)
|
|
grub_cfg.write_text(f"""
|
|
set timeout=10
|
|
set default=0
|
|
|
|
menuentry "{name}" {{
|
|
linux /boot/x86_64/vmlinuz boot=live components
|
|
initrd /boot/x86_64/initrd
|
|
}}
|
|
|
|
menuentry "{name} (copy to RAM)" {{
|
|
linux /boot/x86_64/vmlinuz boot=live components toram
|
|
initrd /boot/x86_64/initrd
|
|
}}
|
|
""")
|
|
|
|
# xorriso to create ISO
|
|
iso_path = output / f"{name.lower().replace(' ', '-')}-{version}-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/x86_64/filesystem.squashfs",
|
|
"-no-emul-boot", "-boot-load-size", "4",
|
|
"-boot-info-table",
|
|
"-eltorito-alt-boot",
|
|
"-e", "boot/x86_64/filesystem.squashfs",
|
|
"-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 _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, **kw)
|