* feat: add compose discovery for setup preflight and sidebar empty state
Expose read-only compose discovery via GET /api/stacks/discovery and setup
diagnostics. Replace the blank sidebar with path-aware discovery and move
adopt into a dedicated dialog with a three-tab Create Stack flow.
* test: assert post-setup handoff via sessionStorage read-back
The Setup preflight test spied on Storage.prototype.setItem to check the
post-setup adopt handoff. When the jsdom storage probe fails and the test
harness swaps in its in-memory storage stub (which does not extend Storage),
that stub's setItem never touches Storage.prototype, so the spy records zero
calls and the assertion fails even though the component wrote the value.
Read the value back with sessionStorage.getItem instead, matching how every
other storage test in the suite asserts. This is robust to both the native
jsdom storage and the in-memory fallback.
* fix(setup): surface compose discovery as a preflight check row
Drop the Setup discovery banner and non-working Review button. Show
counts as a pass row in EnvironmentChecks (Setup only) and keep
Enter Sencho as the handoff that opens adopt when candidates exist.
* test(setup): cover zero-count discovery row omission
* fix(stacks): widen adopt scan to any yaml and rename into place
Homelab layouts often use nginx.yml or plex.yml. Surface those for
adopt (except overrides), rename to compose.yaml on move so stacks
register, and reset the confirm UI when a move fails.
* feat: block self-stack lifecycle ops with UI and preflight guardrails
Refuse update, deploy, down, stop, and delete when the stack matches Sencho's compose project.
Return 409 self_stack_protected. Expose isSelf on /statuses and disable guarded UI actions.
Add SelfStackProtectedDialog and self-managed-stack preflight warning.
Closes#1564
* fix: add missing stackSelfFlags mock to useSidebarContextMenu test
The production hook now reads stackListState.stackSelfFlags[file], but the
test mock did not include it, causing 6 tests to fail with TypeError:
Cannot read properties of undefined (reading 'web.yml').
* fix: harden self-stack protection during startup
Add a global environment preflight warning when Sencho is managed inside COMPOSE_DIR.
Align status decoration and route guards on Docker label fallback detection.
Block rollback and service-level stop on the protected self stack.
* fix: add self_stack_location to diagnostics-route expected check IDs
* feat(onboarding): add first-run environment checker
Add a preflight that checks whether the host can run Docker deploys before a
deploy fails for an avoidable reason. It verifies the Docker engine is reachable
and permitted, the Compose plugin is present, the compose directory is writable
and mounted at a matching host path, the dashboard is behind TLS, and the
compose volume has disk headroom. Each result that needs attention carries a
specific fix rather than a generic error, and the checks never block: an
operator who knows their setup can continue.
The checks run as the final step of first-boot setup and can be re-run any time
from the Recovery settings tab. A new admin-only endpoint,
GET /api/diagnostics/environment, backs both surfaces.
* fix(onboarding): distinguish unverified path mapping and support parent binds
Treat a container whose self-inspect fails as an unverified path-mapping warning
instead of a false "not containerized" pass, so an unverifiable mapping never
reads as healthy. Resolve the compose directory through the longest-prefix bind
mount and compare the host path it resolves to, so a parent bind such as
-v /opt:/opt correctly covers COMPOSE_DIR=/opt/compose instead of warning that
the directory is not bind-mounted.
* test(e2e): advance the setup wizard past the environment step in loginAs
The first-run setup helper clicked "Initialize console" and immediately waited
for the dashboard, but setup now shows an environment-preflight step before
landing the console. Click "Enter Sencho" to complete onboarding before
asserting the dashboard, so the first test on a fresh instance passes.
* feat(recovery): add safe-mode recovery surface and emergency CLI
Add a read-only Recovery tab under Settings (admin-only) backed by a new
GET /api/diagnostics endpoint reporting app version, database integrity,
encryption-key status, Docker reachability, account and SSO counts, and
non-secret configuration. The endpoint loads without Docker or live metrics
so it stays available when the dashboard does not, requires a genuine admin
session, and builds its config block from a non-secret allowlist so no
credentials are ever exposed.
Expand the emergency command-line toolkit beyond the two-factor reset with
seven host-level commands: reset-password, create-emergency-admin,
clear-sessions, disable-sso, diagnostics, validate-db, and backup-data. Each
prints its result, exits with a meaningful status code, and writes an audit
entry where it changes state.
Document the toolkit in a new operator guide and link it from the recovery
and two-factor pages.
* feat(recovery): download the emergency command reference as a text file
The recovery commands are needed exactly when the dashboard is unreachable,
so reading them only in-app is a chicken-and-egg problem. Add a Download
button to the command-line section that saves the full
`docker compose exec sencho ...` reference as a text file, letting operators
keep it on hand before they need it. Reuses a shared download helper with the
existing diagnostics export.
* fix(recovery): harden diagnostics, backup, and emergency-admin against edge cases
Address findings from an independent review of the recovery toolkit:
- DiagnosticsService now degrades instead of throwing when a queried table is
missing or corrupt: each read falls back and is folded into database.ok, so a
broken database reports "problem detected" rather than failing the whole
endpoint or showing a misleading healthy state with zeroed counts.
- backup-data refuses a destination that resolves to the live database, which
would otherwise report success while producing no separate copy.
- create-emergency-admin now applies the same username rule as the user-
management route, extracted to a shared helper so both stay in sync.
Adds tests for a missing read table, a malformed emergency-admin username, and
the backup same-target rejection.