feat(installer): add one-file installer build and custom install ISO
Replace old_installer.py with a curses TUI installer packaged as a one-file PyInstaller binary (python/installer/build.py, wrapped by python/installer/package.nix). The default .#installer is patched to run on foreign Linux live media; the new .#installer-nixos variant keeps its Nix store interpreter so it runs on NixOS. Add systems/iso, a minimal NixOS install CD with kernel 6.18 and ZFS 2.4 matching the deployed systems and the installer on PATH; build it with nix build .#iso. Shared logging and subprocess helpers move out of common.py into python/logging_config.py and python/process.py.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""Small subprocess helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
from subprocess import run
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CommandError(RuntimeError):
|
||||
"""Raised when an external command fails."""
|
||||
|
||||
def __init__(self, command: Sequence[str], returncode: int, stderr: str) -> None:
|
||||
"""Store command failure details."""
|
||||
command_text = " ".join(command)
|
||||
super().__init__(f"Failed to run command {command_text!r}: exit {returncode}\n{stderr}")
|
||||
self.command = command
|
||||
self.returncode = returncode
|
||||
self.stderr = stderr
|
||||
|
||||
|
||||
class MissingCommandsError(RuntimeError):
|
||||
"""Raised when required external commands are not available."""
|
||||
|
||||
def __init__(self, commands: Sequence[str]) -> None:
|
||||
"""Store missing command details."""
|
||||
missing = ", ".join(commands)
|
||||
super().__init__(f"Missing required installer commands: {missing}")
|
||||
self.commands = commands
|
||||
|
||||
|
||||
def require_commands(commands: Iterable[str]) -> None:
|
||||
"""Raise when one or more executables are missing from PATH."""
|
||||
missing_commands = sorted({command for command in commands if shutil.which(command) is None})
|
||||
if missing_commands:
|
||||
raise MissingCommandsError(missing_commands)
|
||||
|
||||
|
||||
def run_output(command: Sequence[str]) -> str:
|
||||
"""Run a command and return stdout."""
|
||||
logger.debug("running command=%s", command)
|
||||
result = run(command, capture_output=True, text=True, check=False)
|
||||
if result.returncode != 0:
|
||||
raise CommandError(command, result.returncode, result.stderr)
|
||||
|
||||
return result.stdout
|
||||
Reference in New Issue
Block a user