# suse-professional-package-installer — spec Spec-first entries per CE OS Script Development standards, one per script, written before generation. --- ## `main.sh` (orchestrator) - **Name:** `main.sh` — top-level entry point; parses arguments, sources `lib/ce-common.sh`, invokes the requested subscript(s) in order. - **Inputs:** CLI args — an optional package-list file path, default `local/zypper.list` (gitignored, machine-specific real list — see `.gitignore` and `packages/*.example` below). - **Outputs:** stdout/log lines via `ce-common.sh` logging; exit 0 on full success, non-zero if any subscript reports failure. - **Error behaviour:** halt on missing/unreadable list file; otherwise log-and-continue between independent subscripts (a zypper failure doesn't block an unrelated later subscript), non-zero exit if any subscript failed. - **Privilege:** standard user — orchestrator never elevates itself or carries elevation between subscripts (per CE OS privilege model). - **Doc references:** `ceos_script_architecture.md` (orchestrator/ subscript model, privilege model). - **Distro target:** common (dispatches to distro-specific subscripts). - Exempt from the 100-line subscript boundary per the orchestrator exemption. --- ## `lib/ce-common.sh` (shared library) - **Name:** `ce-common.sh` — logging, privilege-command detection, zypper wrapper functions, cleanup trap, user confirmation. Promoted/ generalized from the private `tinkerpad-stt` deployment's copy — project-specific bits (`VOSK_HOME`, hardcoded installer name) stripped for public reuse. - **Inputs:** sourced only, not executed directly. Reads `CE_INSTALLER_NAME` env var (optional, defaults to `suse-pkg-install`). - **Outputs:** exported functions/vars for consuming scripts (`log_info`, `run_test`, `detect_priv_cmd`, `test_package_available`, `package_installed`, `install_packages`, `confirm_proceed`, etc.). `run_test(desc, cmd, mode=fail|warn)` is the shared PASS/FAIL/WARN check helper (matches the established pattern in `~/.local/opt/package-installer/`) — used both by `00_preflight.sh` and by `install_packages()` itself for a post-install presence check per package (warn-mode: a verification miss is logged, not fatal). - **Error behaviour:** `log_error` + `exit 1` on unrecoverable conditions (no priv command found, no zypper found). - **Privilege:** none itself — only detects what's available; escalation happens per-subscript at the point of use. - **Doc references:** `ceos_script_architecture.md`, `ceos_script_source_discipline.md` (never assume sudo vs doas). - **Distro target:** SUSE family (zypper only, scoped to this repo's name/purpose — unlike the private multi-manager `ce-common.sh`). - **Line count:** 188 non-blank lines — over the 100-line soft signal but within the 200-line hard ceiling for non-orchestrator scripts (RIS doc-set `65ppjj`, section `lin`, chunks `sft`/`hrd`/`exm` — supersedes `0nxwoh`, the original text which omitted the 200-line figure entirely; corrected 2026-08-06 per John's direct clarification during this project's review). Also arguably orchestrator-like itself, per John. Line limits are being deliberately not enforced strictly at this early-dev stage, with the intent to split this file further once real use/testing surfaces where the actual seams are — splitting now, before that, would be guessing at boundaries rather than finding them. --- ## `lib/00_preflight.sh` (subscript) - **Name:** `00_preflight.sh` — environment checks only, no system changes. This is the "dry run" step: it always runs, unconditionally, before any privileged subscript, and never itself prompts for privilege escalation. - **Inputs:** none. - **Outputs:** exit 0 if all `fail`-mode checks pass (warn-mode checks — internet reachability, disk space — log but don't block); exit 1 on the first fail-mode check that fails. - **Error behaviour:** halt immediately via `run_test`'s fail mode (not running as root, zypper present, sudo-or-doas present); warn and continue for environmental checks that aren't hard requirements. - **Privilege:** standard user only — this subscript makes no privileged calls itself, by design. - **Doc references:** matches the pre-flight phase pattern in `~/.local/opt/package-installer/20260726-201557-package-installer.sh` (the established local convention for this project's "dry-run and tests" behaviour, per John, 2026-08-06). - **Distro target:** SUSE family (zypper check), otherwise common. --- ## `tests/test_list_parsing.sh` (test script — kept separate, never merged into an install script) - **Name:** `test_list_parsing.sh` — exercises `read_package_list()` (comment stripping, whitespace trimming, blank-line skipping, comment-only-file rejection, missing-file rejection) with no zypper calls and no privilege escalation. - **Inputs:** none (writes its own fixture files to a `mktemp -d` registered via `register_temp_dir` for automatic cleanup). - **Outputs:** exit 0 if every `run_test` check passes; exit 1 (via `run_test`'s fail mode) on the first failing assertion. - **Error behaviour:** fail-fast — every assertion in this file is fail-mode, since a parsing regression should never be silently downgraded to a warning. - **Privilege:** standard user only. - **Doc references:** `ceos_script_architecture.md` ("Test scripts always remain separate — they are never merged into installation or configuration scripts"). - **Distro target:** common (pure bash, no package-manager calls). --- ## `lib/10_zypper-packages.sh` (subscript) - **Name:** `10_zypper-packages.sh` — installs every package listed in a given zypper package-list file. - **Inputs:** `$1` = path to a package-list file (one package name per line, `#`-prefixed comments and blank lines ignored). Defaults to `local/zypper.list` (gitignored) if not given. - **Outputs:** exit 0 if every listed package ends up installed or was already present; exit 1 if any install failed. Unavailable packages are warned about, not treated as fatal (matches existing `install_packages()` behaviour — a typo in one line shouldn't abort the whole run). - **Error behaviour:** halt immediately if the list file doesn't exist or is empty; per-package log-and-continue during install. - **Privilege:** requires escalation (installing system packages) — prompts for it at the point `install_packages()` first needs it, via `detect_priv_cmd`. Does not hold elevation before that point. - **Doc references:** `ceos_script_architecture.md` (privilege model — escalation isolated to the subscript that needs it). - **Distro target:** SUSE family (zypper). --- ## Deferred (not built this pass) - **`lib/20_pip-packages.sh`** — pip/pipx package-list subscript. Not needed for the immediate target (`poppler-tools`, a zypper package), and per the standing rule zypper/pip must stay fully separate subscripts, never combined. Spec entry to be written when an actual pip/pipx list is needed. - **Multi-distro (`apk`/`apt`/`nix`) support** — deliberately out of scope. This repo's name and purpose are SUSE-specific; a general multi-distro engine already exists privately as `ce-common.sh` in `~/.local/opt/tinkerpad-stt/lib/` and is not what this public repo is for. - **Textual list-authoring/validation TUI** — explicitly deferred until the plain-text list format has been used for real and any rough edges are known. --- ## Known gaps (Ambrosiana Gap Protocol — documented per `ceos_script_source_discipline.md`) - **No Ambrosiana-sourced zypper/Tumbleweed documentation.** Per the "no training-derived assumptions" rule, every zypper-specific behavior used here (`zypper --non-interactive search -x`, cache cleanup via `zypper clean --all`, `rpm -q` as the query mechanism) was verified live against the real machine, not sourced from Ambrosiana — it holds no zypper/Tumbleweed doc-set yet. Same gap already logged privately for `tinkerpad-stt` in `~/.local/opt/INSTALLER_NOTES.md`. Per protocol step 3 (no quality alternative exists yet): proceeding with elevated live-verification discipline instead of blocking on it; this note is that documentation requirement being met. Should travel into the README's "Known bugs" section at public-release time. - **Full CE OS deployment workflow not implemented.** The methodology's full sequence (full-stack discovery, conflict analysis, FHS Relocator, offline USB preparation, formal two-stage QC with a specialised local agent ahead of Claude Code) targets full service/system deployments. This project is a lightweight package-list installer, not a service deployer — no package here lands in a non-FHS default location the Relocator would need to catch, and there's no service being deployed to discover conflicts against. Treating those pieces as out of proportion to scope rather than a compliance gap, but flagging explicitly rather than silently omitting them — John's call to confirm. - **QC is single-stage in practice.** No specialised local agent is currently trusted to run a Stage 1 pass ahead of Claude Code — local fleet models aren't yet evaluated as reliable enough for this role. Claude Code performs both drafting and QC (shellcheck + manual review) in one pass, consistent with how this gap is already handled elsewhere in this project ecosystem.