v0.3.0: recommended vs custom mode, DE/WM explanations, better UX

This commit is contained in:
2026-08-27 21:16:55 +02:00
parent a52f17e0ab
commit 781de7ae6c
2 changed files with 185 additions and 56 deletions
+184 -55
View File
@@ -195,6 +195,145 @@ def banner() -> None:
console.print()
def select_setup_mode() -> str:
"""Pick recommended or custom mode."""
console.print(Panel(
"[bold]How would you like to configure your ISO?[/]\n\n"
" [cyan]Recommended[/] — pick a desktop, we handle the rest\n"
" (void + dinit + musl, base packages included)\n\n"
" [white]Custom[/] — choose every detail yourself\n"
" (any distro, init, libc, packages, etc.)",
border_style="cyan",
padding=(0, 1),
))
console.print()
choice = questionary.select(
"Setup mode:",
choices=[
"Recommended — easy setup, just pick your desktop",
"Custom — full control over everything",
],
).ask()
if choice is None:
sys.exit(0)
console.print()
return "recommended" if "Recommended" in choice else "custom"
def run_recommended() -> dict:
"""Simplified flow — pick DE/WM, we handle the rest."""
banner()
mode = select_mode()
# auto-pick distro/init/libc/arch
distro = "void"
init = "dinit"
libc = "musl"
arch = "x86_64"
console.print("[bold]Recommended setup[/]\n")
console.print(f" Distro: [cyan]void[/] (independent, fast, musl)")
console.print(f" Init: [cyan]dinit[/] (modern service manager)")
console.print(f" Libc: [cyan]musl[/] (small, secure)")
console.print(f" Arch: [cyan]x86_64[/]")
console.print()
# DE/WM selection with explanations
de_name, de_pkgs = select_de_wm(distro, explain=True)
# auto-pick package groups
auto_groups = ["Base", "Networking", "Audio", "Firmware"]
pkgs = []
for g in auto_groups:
pkgs.extend(COMMON_PACKAGES[g])
console.print(f" [dim]Auto-selected groups:[/] {', '.join(auto_groups)}")
# custom packages
console.print()
custom = questionary.text(
"Additional packages (comma-separated, or empty):",
default="",
).ask()
if custom and custom.strip():
for p in custom.split(","):
p = p.strip()
if p:
pkgs.append(p)
# merge
seen = set()
merged = []
for p in pkgs + de_pkgs:
if p not in seen:
seen.add(p)
merged.append(p)
cfg = {
"distro": distro,
"arch": arch,
"init": init,
"libc": libc,
"hostname": "euri",
"packages": merged,
"de_wm": de_name,
"services": [],
"users": [],
"iso": {
"name": "Euri Linux",
"version": "1.0",
"label": "EURI",
},
"_mode": mode,
}
if not confirm_build(cfg):
sys.exit(0)
return cfg
def run_custom() -> dict:
"""Full control flow — choose everything."""
banner()
mode = select_mode()
distro = select_distro()
init = select_init(distro)
libc = select_libc(distro)
arch = select_arch()
pkgs = select_packages()
de_name, de_pkgs = select_de_wm(distro, explain=False)
iso = configure_iso()
# merge packages: base groups + DE/WM
seen = set()
merged = []
for p in pkgs + de_pkgs:
if p not in seen:
seen.add(p)
merged.append(p)
cfg = {
"distro": distro,
"arch": arch,
"init": init,
"libc": libc,
"hostname": iso["hostname"],
"packages": merged,
"de_wm": de_name,
"services": [],
"users": [],
"iso": {
"name": iso["name"],
"version": iso["version"],
"label": iso["label"],
},
"_mode": mode,
}
if not confirm_build(cfg):
sys.exit(0)
return cfg
def select_mode() -> str:
"""Select root or fakeroot mode."""
root = _check_root()
@@ -265,10 +404,10 @@ def select_init(distro: str) -> str:
"""Pick init system."""
inits = DISTRO_INFO[distro]["inits"]
if len(inits) == 1:
console.print(f" Init system: [cyan]{inits[0]}[/] (only option)\n")
return inits[0]
console.print("[bold]Step 2: Init system[/]\n")
console.print("[bold]Init system[/]\n")
console.print(" [dim]Init = how services start. systemd is most common.[/]\n")
choice = questionary.select(
"Pick init system:",
choices=inits,
@@ -283,10 +422,10 @@ def select_libc(distro: str) -> str:
"""Pick libc."""
libcs = DISTRO_INFO[distro]["libc"]
if len(libcs) == 1:
console.print(f" Libc: [cyan]{libcs[0]}[/] (only option)\n")
return libcs[0]
console.print("[bold]Step 3: Libc[/]\n")
console.print("[bold]Libc[/]\n")
console.print(" [dim]Libc = C library. glibc = wide compatibility, musl = smaller/stricter.[/]\n")
choice = questionary.select(
"Pick libc:",
choices=libcs,
@@ -299,9 +438,8 @@ def select_libc(distro: str) -> str:
def select_arch() -> str:
"""Pick architecture."""
console.print("[bold]Step 4: Architecture[/]\n")
choice = questionary.select(
"Pick architecture:",
"Architecture:",
choices=["x86_64", "aarch64"],
default="x86_64",
).ask()
@@ -313,7 +451,7 @@ def select_arch() -> str:
def select_packages() -> list[str]:
"""Pick packages interactively."""
console.print("[bold]Step 5: Packages[/]\n")
console.print("[bold]Packages[/]\n")
selected: list[str] = []
@@ -334,6 +472,9 @@ def select_packages() -> list[str]:
sys.exit(0)
if pick:
groups.append(gname)
console.print(f" [green]+ {gname}[/] ({len(COMMON_PACKAGES[gname])} packages)")
console.print()
for group_name in groups:
selected.extend(COMMON_PACKAGES[group_name])
@@ -347,7 +488,6 @@ def select_packages() -> list[str]:
deduped.append(p)
# custom packages
console.print()
custom = questionary.text(
"Additional packages (comma-separated, or empty):",
default="",
@@ -359,13 +499,14 @@ def select_packages() -> list[str]:
seen.add(p)
deduped.append(p)
console.print(f" [green]{len(deduped)} packages selected[/]\n")
console.print(f" [green]{len(deduped)} packages total[/]\n")
return deduped
def configure_iso() -> dict:
"""Configure ISO metadata."""
console.print("[bold]Step 6: ISO metadata[/]\n")
console.print("[bold]ISO metadata[/]\n")
console.print(" [dim]These identify your ISO image. Defaults are fine for testing.[/]\n")
name = questionary.text("ISO name:", default="Euri Linux").ask()
if name is None:
@@ -420,9 +561,13 @@ def confirm_build(cfg: dict) -> bool:
return questionary.confirm("Start build?", default=True).ask() or sys.exit(0)
def select_de_wm(distro: str) -> tuple[str, list[str]]:
"""Pick a DE or window manager — returns (name, distro-specific packages)."""
console.print("[bold]Step 5b: Desktop Environment / Window Manager[/]\n")
def select_de_wm(distro: str, explain: bool = False) -> tuple[str, list[str]]:
"""Pick a DE or window manager — returns (name, distro-specific packages).
If explain=True, show longer descriptions for new users.
If explain=False, show compact table for power users.
"""
console.print("[bold]Desktop Environment / Window Manager[/]\n")
# map distro to family
distro_family = {
@@ -432,17 +577,36 @@ def select_de_wm(distro: str) -> tuple[str, list[str]]:
"fedora": "fedora", "rocky": "fedora", "almalinux": "fedora",
}.get(distro, "arch")
# explanation panel for new users
if explain:
console.print(Panel(
"[bold]What is a DE/WM?[/]\n\n"
" A [bold]Desktop Environment (DE)[/] is a full graphical workspace —\n"
" taskbar, file manager, settings, apps. (GNOME, KDE, XFCE)\n\n"
" A [bold]Window Manager (WM)[/] is just window management —\n"
" lighter, keyboard-driven, you build your own workflow.\n"
" (Sway, i3, Hyprland, bspwm, openbox)\n\n"
" [dim]Both work fine. DE = ready to use. WM = more control.[/]",
border_style="dim",
padding=(0, 1),
))
console.print()
# show table
de_table = Table(show_header=True, border_style="dim", pad_edge=False)
de_table.add_column("#", style="cyan", width=3)
de_table.add_column("DE/WM", style="bold")
de_table.add_column("Type")
de_table.add_column("Description")
de_table.add_column("Description" if explain else "Info")
for i, (name, info) in enumerate(DE_WM_PACKAGES.items(), 1):
pkgs = info.get(distro_family, [])
status = f"[green]{len(pkgs)} pkgs[/]" if pkgs else "[red]unavailable[/]"
server = info.get("server", "")
de_table.add_row(str(i), name, server, f"{info['desc']} ({status})")
if explain:
de_table.add_row(str(i), name, server, f"{info['desc']} ({status})")
else:
short = info["desc"].split("")[0].strip() if "" in info["desc"] else info["desc"][:30]
de_table.add_row(str(i), name, server, f"{short} ({status})")
console.print(de_table)
console.print()
@@ -484,44 +648,9 @@ def build_progress(cfg: dict, func, *args, **kwargs):
def run_interactive() -> dict:
"""Full interactive TUI flow. Returns a config dict."""
"""Interactive TUI flow — routes to recommended or custom."""
banner()
mode = select_mode()
distro = select_distro()
init = select_init(distro)
libc = select_libc(distro)
arch = select_arch()
pkgs = select_packages()
de_name, de_pkgs = select_de_wm(distro)
iso = configure_iso()
# merge packages: base groups + DE/WM
seen = set()
merged = []
for p in pkgs + de_pkgs:
if p not in seen:
seen.add(p)
merged.append(p)
cfg = {
"distro": distro,
"arch": arch,
"init": init,
"libc": libc,
"hostname": iso["hostname"],
"packages": merged,
"de_wm": de_name,
"services": [],
"users": [],
"iso": {
"name": iso["name"],
"version": iso["version"],
"label": iso["label"],
},
"_mode": mode,
}
if not confirm_build(cfg):
sys.exit(0)
return cfg
mode = select_setup_mode()
if mode == "recommended":
return run_recommended()
return run_custom()
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "euri-mklive"
version = "0.2.0"
version = "0.3.0"
description = "Universal CLI ISO builder — any distro, from any distro"
requires-python = ">=3.10"
dependencies = ["pyyaml>=6", "click>=8", "rich>=13", "questionary>=2"]