aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers/specs/2026-10-19-sb-builder-phase1-design.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/specs/2026-10-19-sb-builder-phase1-design.md')
-rw-r--r--docs/superpowers/specs/2026-10-19-sb-builder-phase1-design.md335
1 files changed, 0 insertions, 335 deletions
diff --git a/docs/superpowers/specs/2026-10-19-sb-builder-phase1-design.md b/docs/superpowers/specs/2026-10-19-sb-builder-phase1-design.md
deleted file mode 100644
index 3ec3fc0..0000000
--- a/docs/superpowers/specs/2026-10-19-sb-builder-phase1-design.md
+++ /dev/null
@@ -1,335 +0,0 @@
-# sb-builder phase 1: core design
-
-Date: 2026-10-19
-Status: approved
-
-## Goal
-
-One Qt6 desktop GUI to drive the SlackBuild tooling on the build VM
-(`buildsystem`): slackrepo, mkhint, overlay sync (sbpull, sbshadow), full repo
-rebuild, and SBo orphan discovery. Long-running work runs as jobs on the VM
-that survive the GUI disconnecting, and the GUI can reattach to them later.
-
-## Phases
-
-Each phase gets its own spec, plan and implementation cycle.
-
-1. **Core** (this spec): remote helper with the job model, GUI shell with
- tabs, Dashboard, Jobs tab, VM control, config, notifications. Job kinds:
- `sbpull`, `sbshadow`.
-2. **Packages + slackrepo**: package browser over the large SBo-danix tree
- (search and filter by name, category, overlay, maintainer, build status);
- Build, Rebuild, Bulk update screens.
-3. **Full repo rebuild**: `slackrepo_setup_SBo-danix.sh`, destructive, guarded.
-4. **Hints**: mkhint browse, edit, update, create, `-F`, diff before save.
- Requires non-interactive flags in mkhint (it has about 10 `read -p`
- prompts and wraps slackrepo at `mkhint:1798`). Also: `--no-dl` must write
- `DOWNLOAD="no"`, which slackrepo reads, instead of `NODOWNLOAD=yes`,
- which it does not (keep reading old `NODOWNLOAD` lines for the NoDL column),
- and existing hints on the VM get migrated: every `NODOWNLOAD=yes` line in
- any hint slackrepo searches becomes `DOWNLOAD="no"` (an existing `DOWNLOAD`
- line is replaced, since `DOWNLOAD="no"` overrides it anyway), backed up to
- `.bak` first, idempotent. As of 2026-10-19 no hint carries the line.
- Superseded by `2026-10-20-sb-builder-phase4-design.md`: the hint logic
- moves into the helper instead of extending mkhint.
-5. **Orphans**: SBo orphan discovery (`EMAIL="NOEMAIL"` in `.info`, the
- `sbo-orphans` logic from slackbuilds-superrepo), including orphaned deps
- in REQUIRES. Needs an SBo-stable tree, which is not on the VM today.
-
-Dashboard tiles become entry points as their screens land: Packages opens
-the package browser (phase 2, select packages to build there), Built the
-build screen, Last pull the sync actions, Recent jobs the Jobs tab, SBo
-orphans the orphans screen (phase 5).
-
-Out of scope for all phases so far: overlay git operations (status, commit,
-push in my-slackbuilds and friends).
-
-## Stack
-
-- GUI: Python 3 + PyQt6 (stock in Slackware -current, 6.11), including
- `PyQt6.QtDBus` for notifications. No third-party packages.
-- Helper: Python 3 stdlib only (VM runs 3.12, so `tomllib` is available).
-- Config: TOML, read with `tomllib`.
-- License: GPLv2-only.
-
-## Architecture
-
-```
-desktop buildsystem (root)
-+----------------------+ ssh (ControlMaster) +---------------------------+
-| sb-builder (PyQt6) | ----------------------> | sbb-remote (py3 stdlib) |
-| tabs: Dashboard, | sbb-remote <cmd> | stats / jobs / |
-| Jobs (+later tabs) | <---------------------- | job-start / job-follow / |
-| QProcess per call | JSON on stdout | job-kill, flock, setsid |
-+----------------------+ | /var/lib/sb-builder/ |
- | +---------------------------+
- | virsh (local libvirt)
- v
- domain "buildsystem"
-```
-
-### Repository layout
-
-```
-sb_builder/__main__.py entry point, config load
-sb_builder/config.py GUI config: defaults, load, validate
-sb_builder/remote.py the only code that runs ssh; also virsh
-sb_builder/mainwindow.py tabs, menu, status bar, notifications
-sb_builder/dashboard.py Dashboard tab
-sb_builder/jobs.py Jobs tab
-sb_builder/fmt.py text formatting shared by the tabs
-sb-builder launcher (python3 -m sb_builder)
-remote/sbb-remote VM helper, single file
-config.example.toml GUI config example
-remote/config.example.toml helper config example
-install.sh local install; --remote deploys helper to VM
-tests/test_remote.py
-tests/test_gui.py
-```
-
-### Transport
-
-Every GUI call is a QProcess running:
-
-```
-ssh -o ControlMaster=auto -o ControlPath=<runtime dir>/sbb-%C -o ControlPersist=10m \
- <ssh_options...> <target> sbb-remote <cmd> [args...]
-```
-
-Calls are async. ssh exit 255 means the connection failed. Exit 127 means the
-helper is not installed.
-
-### Trust boundary
-
-The helper runs only a fixed table of job kinds. The GUI sends a kind name
-plus arguments; the helper validates each argument and builds argv itself.
-No shell strings and no paths cross ssh. Validation rules:
-
-- unknown kind: rejected
-- `sbpull` target: must be listed in the helper config `overlays`
-- flags: only the ones the kind declares (`--yes`, `--no`)
-- package names (phase 2 onward): `^[A-Za-z0-9._+-]+$`
-
-### Version check
-
-On connect the GUI runs `sbb-remote version`, which also returns the
-configured `overlays` (the GUI builds its sbpull menu from them). A mismatch with the GUI's own
-version shows a banner telling the user to run `./install.sh --remote`.
-
-## Helper: `sbb-remote`
-
-### State
-
-```
-<state_dir>/lock flock target, held for the whole job
-<state_dir>/jobs/<id>/meta.json kind, args, argv, pid, start, end, exit, status
-<state_dir>/jobs/<id>/log combined stdout+stderr, raw bytes
-```
-
-`<id>` is `YYYYMMDD-HHMMSS-<kind>`. `status` is one of `running`, `ok`,
-`failed`, `killed`, `lost`, `busy`.
-
-Logs are kept forever (`# ponytail:` comment in code; add pruning once disk
-usage is visible on the Dashboard).
-
-### Commands
-
-All print one JSON object on stdout, except `job-follow`.
-
-| command | behaviour |
-|---|---|
-| `version` | `{"version": "...", "overlays": [...]}` |
-| `stats` | Dashboard data: load, uptime, disk free for repo and state dirs, package counts (total, personal, pentesting, shadowed = overlay packages also present in the pristine `current` branch, as sbshadow computes it), last sync per overlay (time of the newest commit touching `<overlay>/` in `repo_dir`), slackrepo summary from its sqlite DB (built, failed, never built). Any missing source yields `null` for that field, never an error |
-| `jobs` | list of all jobs' meta. A job whose meta says `running` but whose pid is gone (`os.kill(pid, 0)` fails) is reported and rewritten as `lost` |
-| `job-start <kind> [args]` | validate; spawn `sbb-remote _run <id>` with `start_new_session=True`, `stdin=/dev/null`, stdout/stderr to `/dev/null`; wait up to 3s for `_run` to write status; return `{"id": ...}`, or `{"busy": "<running id>"}` after deleting the refused job's dir |
-| `job-follow <id> <offset>` | write log bytes from `offset` to stdout, keep following until the job is no longer `running`, then exit 0 |
-| `job-kill <id>` | SIGTERM to the job's process group; `_run` records `killed` |
-| `_run <id>` | internal, see below |
-
-### `_run`
-
-1. Take a non-blocking `fcntl.flock` on `<state_dir>/lock`. If held, write
- status `busy` and exit.
-2. Start the job argv with `stdin=/dev/null`, stdout and stderr to the log
- file, in its own process group. Write `pid` and status `running`.
-3. Wait. Write `end`, `exit` and final status (`ok` on 0, `killed` on
- SIGTERM, else `failed`).
-
-Jobs never read input. A tool that prompts anyway gets EOF on `read` and,
-under `set -e`, exits non-zero: the job fails, it never hangs and it never
-answers yes by accident. Destructive steps are confirmed in the GUI.
-
-### Phase 1 job kinds
-
-| kind | argv built by helper |
-|---|---|
-| `sbpull <overlay> [--yes\|--no]` | `bash -c '. "$1" && sbpull "$2" "$3"' _ <sbpull_script> <overlay> <flag>` |
-| `sbshadow [--yes\|--no]` | `sbshadow <flag>` |
-
-The flag is required in both kinds (a job has no stdin).
-
-### Helper config: `/etc/sb-builder/config.toml`
-
-```toml
-state_dir = "/var/lib/sb-builder"
-repo_dir = "/var/lib/sbopkg/SBo-danix"
-upstream_branch = "current"
-slackrepo_db = "/var/lib/slackrepo/SBo-danix/database_SBo-danix_x86_64.sqlite3"
-sbpull_script = "/usr/local/share/slackrepo_setup/sbpull.bash"
-overlays = ["personal", "pentesting"]
-```
-
-Missing file: these defaults. Unknown key or wrong type: every command
-returns `{"error": "config: <key>: <reason>"}` and exits 1.
-
-### Errors
-
-Top-level handler catches any exception, prints `{"error": "<message>"}`,
-exits 1.
-
-## Changes in slackrepo_setup
-
-Done in that repo, its own commit, redeployed to the VM.
-
-- `sbshadow --yes | --no`: no flag keeps the current interactive prompt.
- `--yes` applies without asking. `--no` prints the plan and leaves the tree
- unchanged (preview). `mkhint -F` runs in all cases, as it does today on
- abort.
-- `sbshadow` honours `SBO_REPO` to override the repo path, so it can be
- tested against a throwaway repo.
-- `sbpull <target> [--yes|--no]`: passes the flag to `sbshadow`, and returns
- `sbshadow`'s exit status (today it returns `popd`'s, so a failure would
- show as a successful job).
-
-GUI flow: `sbpull personal --no` pulls and shows the shadow plan in the log;
-the Jobs tab offers "Apply shadowing", which starts `sbshadow --yes`.
-
-## GUI
-
-### Main window
-
-- QTabWidget. Dashboard is pinned. Other tabs are closable and
- single-instance.
-- Menu: `File > Reload config`, `File > Quit`, `Open > Jobs` (later phases
- add entries).
-- Status bar: connection state (ok, down, VM off, VM paused, VM running but
- ssh unreachable, helper version mismatch), running job if any (click opens
- it in Jobs), transient messages.
-
-### Dashboard
-
-Grid of QGroupBox tiles filled from `stats`, plus `jobs` for the Jobs tile:
-
-- VM: load, uptime, disk free
-- Repo: total packages, personal, pentesting, shadowed
-- slackrepo: built, failed, never built
-- Sync: last pull per overlay
-- Jobs: running job and last 10 (status, duration)
-- Orphans: placeholder tile, filled in phase 5
-
-Refreshes every `refresh.dashboard` seconds while visible, plus a Refresh
-button. `null` fields show "n/a". When the connection is down the last
-values stay, greyed, with "stale since HH:MM".
-
-### Jobs tab
-
-- Left: job list (status icon, kind and args, start, duration), refreshed
- every `refresh.jobs` seconds.
-- Right: log view (QPlainTextEdit, ANSI escapes stripped, `\r` rewrites the
- current line), Kill button with confirm.
-- Toolbar `Start`: one entry per overlay for `sbpull <overlay> --no`, plus
- `sbshadow --no`, `sbshadow --yes` (confirm dialog).
-- After a `--no` run whose log lists `shadow:` or `unshadow:` lines, an
- "Apply shadowing" button starts `sbshadow --yes`.
-- Selecting a job starts `job-follow` from offset 0. If the stream drops
- while the job is still running, the next successful `jobs` poll restarts
- it from the last received offset (the poll itself backs off while the VM
- is down, see Error handling).
-- `job-start` returning busy: dialog "busy: <id>" with [Show job].
-
-### Notifications
-
-freedesktop `org.freedesktop.Notifications.Notify` via QtDBus. Events:
-`finished`, `failed`, `killed`, `lost`, and `vm-up` (VM started and ssh
-answering). Clicking a notification (`ActionInvoked`) raises the window and
-opens the job. The GUI tracks the job states it has seen, so each job end
-notifies once.
-
-### VM control
-
-When ssh fails and `vm.domain` is set, the GUI runs
-`virsh -c <libvirt_uri> domstate <domain>` locally:
-
-| domstate | behaviour |
-|---|---|
-| `shut off` | status "VM off", [Start VM] (automatic when `vm.autostart`): `virsh start`, then poll ssh with backoff, notify `vm-up` |
-| `paused` | status "VM paused", [Resume]: `virsh resume` |
-| `running` | status "VM running, ssh unreachable", keep retrying |
-| virsh missing or error | plain "down" |
-
-No shutdown control.
-
-### GUI config: `$XDG_CONFIG_HOME/sb-builder/config.toml`
-
-```toml
-[remote]
-target = "buildsystem" # any ssh destination
-ssh_options = [] # extra ssh args; ControlMaster options always added
-
-[refresh]
-dashboard = 30
-jobs = 5
-
-[notify]
-enabled = true
-events = ["finished", "failed", "killed", "lost", "vm-up"]
-
-[vm]
-libvirt_uri = "qemu:///system"
-domain = "buildsystem" # empty disables VM control
-autostart = false
-```
-
-Missing file: these defaults. Invalid key or type: dialog naming the key,
-then defaults are used. Read at startup and on `File > Reload config`. No
-settings dialog yet.
-
-## Error handling summary
-
-| case | behaviour |
-|---|---|
-| ssh down | status bar, VM check, stale tiles; the `jobs` poll interval doubles from `refresh.jobs` up to 30s, and resets once ssh answers |
-| helper missing or version mismatch | banner with `./install.sh --remote` |
-| busy | dialog with [Show job] |
-| follow drop | reconnect from offset, job unaffected |
-| helper exception | `{"error"}` shown in status bar |
-| `_run` died, VM rebooted mid-job | `lost`, red in list, notification |
-| config invalid | dialog naming key, defaults used |
-
-## Testing
-
-stdlib `unittest`, no frameworks.
-
-- `tests/test_remote.py`: imports `remote/sbb-remote` as a module with a
- temp `state_dir`. Covers argument validation (unknown kind, overlay not in
- config, unknown flag), lock busy, `lost` detection, `job-follow` from an
- offset, and a full `_run` of a fake kind patched into the kind table
- (ok, failed, killed).
-- `tests/test_gui.py`: config load and validation, `remote.parse`, GUI and
- helper versions equal, both example configs equal the defaults. Smoke tests
- under `QT_QPA_PLATFORM=offscreen` with a fake Remote: tabs, Dashboard from
- a canned `stats` dict with `null` fields, job-end notification sent once,
- VM-off offer, carriage-return handling in the log view.
-- slackrepo_setup: sbshadow `--yes` and `--no` verified in a throwaway git
- repo, the same way the current behaviour was tested.
-- First run against the real VM: manual, with the user.
-
-## Install
-
-- `install.sh`: symlinks the `sb-builder` launcher into `~/bin` (`BINDIR`
- overrides), so repo edits are live; copies `config.example.toml` to the
- config path only if no config exists.
-- `install.sh --remote`: scp `remote/sbb-remote` to `<target>:/usr/local/bin/`
- (`SBB_TARGET`, default `buildsystem`),
- create `/etc/sb-builder/config.toml` from the example only if missing.
- Asks before deploying. A SlackBuild comes later.