Scaffold Python orchestrator + split install.sh / finalize.sh
Chunk 1 of the C3 refactor: replace the two-pass install model (archinstall CLI + custom shell dance + chroot bash installer) with a single Python orchestrator that owns phase ordering, using archinstall as a library subsystem. Splits the bash entry point into two: - install.sh: online entry point. Ensures omarchy runtime + base.packages are installed/up to date, then exec's finalize.sh. - finalize.sh: the in-target portion (preflight + packaging + config + login + post-install). Called by install.sh (online) AND by the orchestrator after arch-chroot -u $USER (offline). Adds the orchestrator skeleton under install/orchestrator/: - main.py: entry point, builds + runs the phase list - context.py: InstallContext (parsed configurator JSON + invocation paths) - phases.py: phase state machine (logging + state.json + error wrapping) - phases_impl.py: stubbed phase implementations (filled in by chunks 2-6) - archinstall_adapter.py: thin compat wall around archinstall lib imports (only this module imports from archinstall.*) - ui.py: gum subprocess wrappers so the orchestrator keeps the same styled-terminal UX as the bash installer Updates bin/omarchy-install to dispatch: - --config <json> in args → python -m orchestrator.main (ISO install) - anything else → bash install.sh (online rerun on installed system) Concrete phase logic lands in subsequent chunks. All phases currently raise NotImplementedError; the orchestrator imports cleanly and --help works as a smoke check.
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
# Python bytecode (orchestrator)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
+21
-2
@@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
# omarchy:summary=Run the Omarchy installer (shipped via omarchy-installer)
|
# omarchy:summary=Run the Omarchy installer (shipped via omarchy-installer)
|
||||||
# omarchy:group=install
|
# omarchy:group=install
|
||||||
# omarchy:args=[install.sh args]
|
# omarchy:args=[--config <json> --creds <json> ...] | [install.sh args]
|
||||||
|
|
||||||
exec bash "$OMARCHY_PATH/install.sh" "$@"
|
set -eEo pipefail
|
||||||
|
|
||||||
|
# Dispatcher:
|
||||||
|
# --config <json> → Python orchestrator (ISO install)
|
||||||
|
# anything else → bash install.sh (online rerun on an installed system)
|
||||||
|
#
|
||||||
|
# The Python orchestrator owns the ISO install end-to-end: partitioning,
|
||||||
|
# pacstrap, bootloader, user creation, package install, and finally calling
|
||||||
|
# finalize.sh inside the chroot. install.sh is the simpler online path:
|
||||||
|
# ensures the omarchy runtime is up to date, then exec's finalize.sh.
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--config|--config=*)
|
||||||
|
cd "${OMARCHY_PATH:-/usr/share/omarchy}/install"
|
||||||
|
exec python -m orchestrator.main "$@"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
exec bash "${OMARCHY_PATH:-/usr/share/omarchy}/install.sh" "$@"
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# The in-target portion of an Omarchy install: preflight checks, package-level
|
||||||
|
# setup, system configuration, login wiring, post-install steps. Run AFTER the
|
||||||
|
# orchestrator (offline mode) or install.sh (online mode) has ensured the
|
||||||
|
# omarchy runtime + omarchy-base.packages set is already installed.
|
||||||
|
#
|
||||||
|
# Called by:
|
||||||
|
# - install.sh (online mode, after pacman -Syu)
|
||||||
|
# - orchestrator/main.py (offline mode, via arch-chroot -u $USER)
|
||||||
|
#
|
||||||
|
# Anything that *must* happen as root pre-user lives in the orchestrator; this
|
||||||
|
# script runs as the install user.
|
||||||
|
|
||||||
|
set -eEo pipefail
|
||||||
|
|
||||||
|
_OMARCHY_INSTALLER_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
|
||||||
|
export OMARCHY_PATH="${OMARCHY_PATH:-$_OMARCHY_INSTALLER_DIR}"
|
||||||
|
export OMARCHY_INSTALL="$OMARCHY_PATH/install"
|
||||||
|
export OMARCHY_INSTALL_LOG_FILE="${OMARCHY_INSTALL_LOG_FILE:-/var/log/omarchy-install.log}"
|
||||||
|
export PATH="$OMARCHY_PATH/bin:$PATH"
|
||||||
|
|
||||||
|
source "$OMARCHY_INSTALL/helpers/mode.sh"
|
||||||
|
detect_install_mode
|
||||||
|
export_legacy_mode_flags
|
||||||
|
|
||||||
|
source "$OMARCHY_INSTALL/helpers/all.sh"
|
||||||
|
|
||||||
|
source "$OMARCHY_INSTALL/preflight/all.sh"
|
||||||
|
source "$OMARCHY_INSTALL/packaging/all.sh"
|
||||||
|
source "$OMARCHY_INSTALL/config/all.sh"
|
||||||
|
source "$OMARCHY_INSTALL/login/all.sh"
|
||||||
|
source "$OMARCHY_INSTALL/post-install/all.sh"
|
||||||
+14
-21
@@ -1,15 +1,19 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Online install entry point. Run by the user on an existing system to install
|
||||||
|
# / refresh Omarchy. Ensures the omarchy runtime + omarchy-base.packages set
|
||||||
|
# is up to date, then hands off to finalize.sh for the actual configure work.
|
||||||
|
#
|
||||||
|
# Offline installs (from the ISO) skip this script entirely: the Python
|
||||||
|
# orchestrator handles partitioning, base install, package install, then calls
|
||||||
|
# finalize.sh directly inside the chroot.
|
||||||
|
|
||||||
set -eEo pipefail
|
set -eEo pipefail
|
||||||
|
|
||||||
# Derive OMARCHY_PATH from the script location so install.sh works the same
|
|
||||||
# whether it's run from /usr/share/omarchy/install.sh (package mode) or
|
|
||||||
# $HOME/.local/share/omarchy/install.sh (git mode). An explicit OMARCHY_PATH
|
|
||||||
# in the caller env still wins.
|
|
||||||
_OMARCHY_INSTALLER_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
|
_OMARCHY_INSTALLER_DIR=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
|
||||||
export OMARCHY_PATH="${OMARCHY_PATH:-$_OMARCHY_INSTALLER_DIR}"
|
export OMARCHY_PATH="${OMARCHY_PATH:-$_OMARCHY_INSTALLER_DIR}"
|
||||||
export OMARCHY_INSTALL="$OMARCHY_PATH/install"
|
export OMARCHY_INSTALL="$OMARCHY_PATH/install"
|
||||||
export OMARCHY_INSTALL_LOG_FILE="/var/log/omarchy-install.log"
|
export OMARCHY_INSTALL_LOG_FILE="${OMARCHY_INSTALL_LOG_FILE:-/var/log/omarchy-install.log}"
|
||||||
export PATH="$OMARCHY_PATH/bin:$PATH"
|
export PATH="$OMARCHY_PATH/bin:$PATH"
|
||||||
|
|
||||||
source "$OMARCHY_INSTALL/helpers/mode.sh"
|
source "$OMARCHY_INSTALL/helpers/mode.sh"
|
||||||
@@ -18,29 +22,18 @@ export_legacy_mode_flags
|
|||||||
|
|
||||||
source "$OMARCHY_INSTALL/helpers/all.sh"
|
source "$OMARCHY_INSTALL/helpers/all.sh"
|
||||||
|
|
||||||
# The install scripts assume the full Omarchy default install set is present
|
# The finalize scripts assume the omarchy runtime + the default install set are
|
||||||
# (preflight guards check for limine; config scripts call omarchy-* commands;
|
# already on disk. In online mode we install them here; offline mode asserts
|
||||||
# user scripts call apps from omarchy-base.packages; etc.).
|
# (the orchestrator pacstraps them before calling finalize.sh directly).
|
||||||
#
|
|
||||||
# omarchy itself only hard-depends on the bricking set (~18 packages). The
|
|
||||||
# rest of the default install set lives in install/omarchy-base.packages so
|
|
||||||
# users can remove non-essential apps without pacman blocking on a depend.
|
|
||||||
#
|
|
||||||
# Online: install omarchy + everything in omarchy-base.packages now.
|
|
||||||
# Offline: the ISO pacstraps the same set before user creation; just assert.
|
|
||||||
_omarchy_runtime_pkg="${OMARCHY_RUNTIME_PACKAGE:-omarchy}"
|
_omarchy_runtime_pkg="${OMARCHY_RUNTIME_PACKAGE:-omarchy}"
|
||||||
mapfile -t _omarchy_base_pkgs < <(grep -v '^#\|^$' "$OMARCHY_PATH/install/omarchy-base.packages")
|
mapfile -t _omarchy_base_pkgs < <(grep -v '^#\|^$' "$OMARCHY_PATH/install/omarchy-base.packages")
|
||||||
if install_mode_is offline; then
|
if install_mode_is offline; then
|
||||||
pacman -Q "$_omarchy_runtime_pkg" >/dev/null 2>&1 || {
|
pacman -Q "$_omarchy_runtime_pkg" >/dev/null 2>&1 || {
|
||||||
echo "Error: $_omarchy_runtime_pkg must be pacstrapped before omarchy-install runs in offline mode" >&2
|
echo "Error: $_omarchy_runtime_pkg must be installed before install.sh runs in offline mode" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
sudo pacman -Syu --noconfirm --needed "$_omarchy_runtime_pkg" "${_omarchy_base_pkgs[@]}"
|
sudo pacman -Syu --noconfirm --needed "$_omarchy_runtime_pkg" "${_omarchy_base_pkgs[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source "$OMARCHY_INSTALL/preflight/all.sh"
|
exec bash "$OMARCHY_PATH/finalize.sh"
|
||||||
source "$OMARCHY_INSTALL/packaging/all.sh"
|
|
||||||
source "$OMARCHY_INSTALL/config/all.sh"
|
|
||||||
source "$OMARCHY_INSTALL/login/all.sh"
|
|
||||||
source "$OMARCHY_INSTALL/post-install/all.sh"
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Thin compatibility wall around the archinstall Python library.
|
||||||
|
|
||||||
|
ONLY this module imports from archinstall. Everything else uses these functions.
|
||||||
|
If archinstall's API churns, the blast radius is contained here.
|
||||||
|
|
||||||
|
Tested against archinstall 4.3 (Python 3.14).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
# Phase 2 will populate this module. For now we declare the contract so the
|
||||||
|
# rest of the orchestrator can be wired up against the eventual surface area.
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_live() -> None:
|
||||||
|
"""pacman-key init/populate, mount checks, etc. Currently a no-op stub."""
|
||||||
|
raise NotImplementedError("populated in Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_disk() -> None:
|
||||||
|
raise NotImplementedError("populated in Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def create_partitions_and_mounts(install_ctx) -> None:
|
||||||
|
raise NotImplementedError("populated in Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def install_base_system(install_ctx) -> None:
|
||||||
|
raise NotImplementedError("populated in Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def install_limine_bootloader(install_ctx) -> None:
|
||||||
|
raise NotImplementedError("populated in Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def create_users(install_ctx) -> None:
|
||||||
|
raise NotImplementedError("populated in Chunk 2")
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""Install context: parsed configurator output + invocation paths."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class InstallContext:
|
||||||
|
config_path: Path
|
||||||
|
creds_path: Path
|
||||||
|
full_name: str
|
||||||
|
email: str
|
||||||
|
encrypt: bool
|
||||||
|
|
||||||
|
user_configuration: dict
|
||||||
|
user_credentials: dict
|
||||||
|
|
||||||
|
target: Path = Path("/mnt")
|
||||||
|
omarchy_path: Path = Path("/usr/share/omarchy")
|
||||||
|
state_dir: Path = Path("/run/omarchy-install")
|
||||||
|
log_path: Path = Path("/var/log/omarchy-install.log")
|
||||||
|
target_log_path: Path = Path("/mnt/var/log/omarchy-install.log")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_args(cls, args) -> "InstallContext":
|
||||||
|
config_path = Path(args.config)
|
||||||
|
creds_path = Path(args.creds)
|
||||||
|
return cls(
|
||||||
|
config_path=config_path,
|
||||||
|
creds_path=creds_path,
|
||||||
|
full_name=_read_text(args.full_name_file),
|
||||||
|
email=_read_text(args.email_file),
|
||||||
|
encrypt=_read_text(args.encrypt_file).lower() in ("true", "yes", "1"),
|
||||||
|
user_configuration=json.loads(config_path.read_text()),
|
||||||
|
user_credentials=json.loads(creds_path.read_text()),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self) -> str:
|
||||||
|
return self.user_credentials["users"][0]["username"]
|
||||||
|
|
||||||
|
|
||||||
|
def _read_text(path: str | None) -> str:
|
||||||
|
if not path:
|
||||||
|
return ""
|
||||||
|
p = Path(path)
|
||||||
|
if not p.exists():
|
||||||
|
return ""
|
||||||
|
return p.read_text().strip()
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
"""Omarchy install orchestrator.
|
||||||
|
|
||||||
|
Single tool that owns the full install phase ordering, with archinstall used as
|
||||||
|
a library subsystem (not as the top-level installer).
|
||||||
|
|
||||||
|
Usage (typically invoked by bin/omarchy-install on the live ISO):
|
||||||
|
|
||||||
|
omarchy-install \\
|
||||||
|
--config user_configuration.json \\
|
||||||
|
--creds user_credentials.json \\
|
||||||
|
--full-name-file user_full_name.txt \\
|
||||||
|
--email-file user_email_address.txt \\
|
||||||
|
--encrypt-file user_encrypt_installation.txt
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from . import archinstall_adapter as arch
|
||||||
|
from .context import InstallContext
|
||||||
|
from .phases import PhaseError, run
|
||||||
|
from .ui import error, info
|
||||||
|
|
||||||
|
|
||||||
|
def build_phases():
|
||||||
|
"""Phase order. Each entry is (display name, callable taking InstallContext).
|
||||||
|
|
||||||
|
The ordering is the whole point of this orchestrator: package-install
|
||||||
|
hooks (limine-mkinitcpio-hook, in particular) and useradd happen at
|
||||||
|
points where their prerequisites are guaranteed to be in place.
|
||||||
|
"""
|
||||||
|
from .phases_impl import (
|
||||||
|
prepare_live,
|
||||||
|
cleanup_disk,
|
||||||
|
partition_and_mount,
|
||||||
|
install_base,
|
||||||
|
install_bootloader,
|
||||||
|
write_limine_config,
|
||||||
|
install_early_omarchy_packages,
|
||||||
|
create_user,
|
||||||
|
install_omarchy_runtime,
|
||||||
|
run_chroot_finalizer,
|
||||||
|
validate_boot,
|
||||||
|
finish,
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
("Preparing live environment", prepare_live),
|
||||||
|
("Cleaning install disk", cleanup_disk),
|
||||||
|
("Partitioning + mounting", partition_and_mount),
|
||||||
|
("Installing base system", install_base),
|
||||||
|
("Installing bootloader", install_bootloader),
|
||||||
|
("Writing Limine config", write_limine_config),
|
||||||
|
("Installing Omarchy keyring + settings", install_early_omarchy_packages),
|
||||||
|
("Creating user", create_user),
|
||||||
|
("Installing Omarchy runtime", install_omarchy_runtime),
|
||||||
|
("Finalizing in chroot", run_chroot_finalizer),
|
||||||
|
("Validating boot setup", validate_boot),
|
||||||
|
("Finishing", finish),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(argv):
|
||||||
|
p = argparse.ArgumentParser(prog="omarchy-install")
|
||||||
|
p.add_argument("--config", required=True, help="archinstall user_configuration.json")
|
||||||
|
p.add_argument("--creds", required=True, help="archinstall user_credentials.json")
|
||||||
|
p.add_argument("--full-name-file", help="text file with the user's full name")
|
||||||
|
p.add_argument("--email-file", help="text file with the user's email address")
|
||||||
|
p.add_argument("--encrypt-file", help="text file holding 'true' if root encryption enabled")
|
||||||
|
return p.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None) -> int:
|
||||||
|
args = parse_args(argv or sys.argv[1:])
|
||||||
|
ctx = InstallContext.from_args(args)
|
||||||
|
|
||||||
|
info(f"Installing Omarchy for {ctx.username} → {ctx.target}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(ctx, build_phases())
|
||||||
|
except PhaseError:
|
||||||
|
error("Installation halted.")
|
||||||
|
return 1
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
error("Installation interrupted.")
|
||||||
|
return 130
|
||||||
|
|
||||||
|
info("Installation complete.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"""Phase state machine. Each phase is a (name, callable) pair; callables take
|
||||||
|
the InstallContext and either return cleanly or raise to abort the install."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .context import InstallContext
|
||||||
|
from .ui import error, info
|
||||||
|
|
||||||
|
|
||||||
|
PhaseFn = Callable[[InstallContext], None]
|
||||||
|
|
||||||
|
|
||||||
|
class PhaseError(Exception):
|
||||||
|
"""Raised when a phase fails. Wrapped with the phase name."""
|
||||||
|
|
||||||
|
|
||||||
|
def run(ctx: InstallContext, phases: list[tuple[str, PhaseFn]]) -> None:
|
||||||
|
ctx.state_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
state_path = ctx.state_dir / "state.json"
|
||||||
|
state = {"started_at": time.time(), "phases": []}
|
||||||
|
_write_state(state_path, state)
|
||||||
|
|
||||||
|
for name, fn in phases:
|
||||||
|
info(f"› {name}")
|
||||||
|
started = time.time()
|
||||||
|
try:
|
||||||
|
fn(ctx)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
elapsed = time.time() - started
|
||||||
|
state["phases"].append({
|
||||||
|
"name": name,
|
||||||
|
"status": "failed",
|
||||||
|
"elapsed": elapsed,
|
||||||
|
"error": str(exc),
|
||||||
|
})
|
||||||
|
_write_state(state_path, state)
|
||||||
|
|
||||||
|
error(f"Phase '{name}' failed after {elapsed:.1f}s: {exc}")
|
||||||
|
traceback.print_exc()
|
||||||
|
raise PhaseError(f"phase {name} failed: {exc}") from exc
|
||||||
|
|
||||||
|
elapsed = time.time() - started
|
||||||
|
state["phases"].append({"name": name, "status": "ok", "elapsed": elapsed})
|
||||||
|
_write_state(state_path, state)
|
||||||
|
|
||||||
|
state["finished_at"] = time.time()
|
||||||
|
_write_state(state_path, state)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_state(path: Path, state: dict) -> None:
|
||||||
|
path.write_text(json.dumps(state, indent=2, default=str))
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""Concrete phase implementations.
|
||||||
|
|
||||||
|
Each function takes the InstallContext and either returns or raises. Phases are
|
||||||
|
small wrappers — heavy lifting lives in archinstall_adapter (for Arch substrate
|
||||||
|
work) and helpers.* (for Omarchy-specific work).
|
||||||
|
|
||||||
|
Most are stubbed until Chunks 2–6 land. Keeping them here so the phase wiring
|
||||||
|
in main.py is testable end-to-end as a smoke check ('every phase imports
|
||||||
|
cleanly') from Chunk 1 onwards.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from . import archinstall_adapter as arch
|
||||||
|
from .context import InstallContext
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_live(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_disk(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def partition_and_mount(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def install_base(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def install_bootloader(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def write_limine_config(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 3")
|
||||||
|
|
||||||
|
|
||||||
|
def install_early_omarchy_packages(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 4")
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 2")
|
||||||
|
|
||||||
|
|
||||||
|
def install_omarchy_runtime(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 4")
|
||||||
|
|
||||||
|
|
||||||
|
def run_chroot_finalizer(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 5")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_boot(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 6")
|
||||||
|
|
||||||
|
|
||||||
|
def finish(ctx: InstallContext) -> None:
|
||||||
|
raise NotImplementedError("Chunk 6")
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Thin gum wrapper so the orchestrator keeps the same terminal UX as the
|
||||||
|
existing bash installer."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def style(text: str, *, foreground: str | None = None, padding: str | None = None) -> None:
|
||||||
|
cmd = ["gum", "style"]
|
||||||
|
if foreground:
|
||||||
|
cmd += ["--foreground", foreground]
|
||||||
|
if padding:
|
||||||
|
cmd += ["--padding", padding]
|
||||||
|
cmd.append(text)
|
||||||
|
subprocess.run(cmd, check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm(prompt: str, *, default: bool = True) -> bool:
|
||||||
|
cmd = ["gum", "confirm", "--default" if default else "--no-default", prompt]
|
||||||
|
return subprocess.run(cmd).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def info(text: str) -> None:
|
||||||
|
style(text, foreground="3", padding="1 0 0 4")
|
||||||
|
|
||||||
|
|
||||||
|
def error(text: str) -> None:
|
||||||
|
style(text, foreground="1", padding="1 0 0 4")
|
||||||
Reference in New Issue
Block a user