v0.3.0: recommended vs custom mode, DE/WM explanations, better UX
This commit is contained in:
+184
-55
@@ -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
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user