update, uninstall and cleanup-apps silently dropped any flag they did
not know, so 'serverkit update --help' ran a real update. --help now
prints usage and an unknown flag fails before anything runs.
backend/cli.py gained `connect` (with status/doctor/disconnect) for the
ServerKit Cloud pairing but the `serverkit` wrapper never learned it, so
`serverkit connect` fell through to "unknown command" and the wrapper
dispatch census failed the Scripts CI suite. Add it to the help, the
dispatch case and bash completion; the passthrough test covers it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGmHp7mZ2wSeA6z8BXZVxh
`serverkit doctor` assumed the frontend is the `serverkit-frontend`
container. On a host install that container never exists: install.sh has
nginx serve frontend/dist from disk, and the container only lives under the
opt-in `legacy-frontend` compose profile. Doctor flagged a phantom issue on
every host install, and `--fix` "repaired" it with a bare `docker compose
up -d` in /opt/serverkit, which does not start that container at all: it
builds the whole all-in-one panel image, which would then collide with the
systemd backend on :5000. start/stop/restart/status/logs carried the same
assumption.
Add `frontend_mode`, which reads the layout off the serverkit vhost (a
`root .../frontend/dist` is static, a `proxy_pass` on / is the legacy
container, neither is unknown), and make every command key off it. Static
installs are checked for the bundle nginx actually serves; when it is
missing doctor names the command that restores it (`serverkit update`)
instead of promising an auto-fix. The legacy path targets the profile's
service (`docker compose --profile legacy-frontend up -d frontend`), never
the panel image.
Tests: static install passes with no compose call, missing bundle under
--fix hints at update and still never calls compose, frontend_mode
classifies all three layouts, and an awk guard fails the suite if a bare
compose call from $INSTALL_DIR comes back.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGmHp7mZ2wSeA6z8BXZVxh
Live test on the dev box caught 'serverkit list-apps --json' printing
the table: cmd_list_apps' hand-rolled flag mapping dropped everything
but --all, and cmd_list_users/cmd_list_servers dropped all args. All
three now forward "$@" (with -a kept as the legacy --all alias), and
test_cli.sh T20 drives the real executed-mode dispatch through a fake
venv with a recording python stub so any wrapper verb that eats flags
fails the suite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
serverkit migrate-db forwarded the wrapper's own name to cli.py, which
only defines db-migrate — the command has failed with 'No such command'
since the rename. Fix the forward and stop the drift class entirely:
- cmd_panel_cli forwards Click-only verbs verbatim (manifest, services,
apps, repair, login-url, support-bundle, db-migrate/status/history;
panel-status/panel-doctor where a native bash name collides)
- serverkit completion prints a bash completion script; install.sh and
update.sh drop it into /etc/bash_completion.d (best-effort), the
uninstall lib removes it
- test_cli.sh T17-T19: migrate-db forwarding, dispatch reachability,
and a two-way parity guard (cli.py verbs <-> dispatch <-> completion);
test_update.sh T29 covers refresh_bash_completion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The July 2 outage function had a unit test — with a populated fixture
only. This encodes the missing net:
- fresh-minimal-box fixture loop: every observation/discovery/snapshot/
report function in update.sh (17) and install.sh (16) must survive a
zero-app box under set -Eeuo pipefail; policy comment requires new
functions to be appended
- scripts/test/stubs.sh: shared failing-stub factories (systemctl exit
5, docker fail/empty, curl exit 22, GitHub rate-limit JSON)
- assertion sweep: 43 bare capture-then-compare sites now include exit
status in the assertion
- best-effort contract test: the run block's real APP_BASELINE ->
report chain exits 0 on a fresh box AND on a genuine regression
- fix(update): the loop immediately caught report_failure's trailing
'[ -n $UPDATE_LOG ] && printf' returning 1 when UPDATE_LOG is empty
(same species as the outage) — converted to if, added to the loop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- update --branch without a value errors loudly instead of dying
silently on shift (L5)
- cmd_start/cmd_restart mirror cmd_stop's systemctl/docker guards (L6)
- cmd_logs degrades past a dead/missing journalctl to docker logs (L7)
- cmd_add_site/remove_site/list_sites detect Debian vs conf.d nginx
layout; reloads are guarded with a manual-reload hint (L9)
- uninstall on a remnants-only box prints exact recovery commands (L4)
- portable port extraction (no grep -oP), read-at-EOF defaults,
compare_versions rejects unparsable as update-available, doctor
exits nonzero on issues (L12); BASH_SOURCE guard for testability
- new scripts/test/test_cli.sh (22 passed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>