feat(onboarding): add first-run environment checker (#1290)

* 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.
This commit is contained in:
Anso
2026-06-02 21:40:38 -04:00
committed by GitHub
parent a8f0ce9072
commit 5289f01bfd
11 changed files with 989 additions and 2 deletions
@@ -154,6 +154,32 @@ class SelfIdentityService {
return this.volumeNames.has(name);
}
/**
* Bind mounts on the running Sencho container, used by the environment
* checker to verify the compose directory is mounted at the same path on the
* host and inside the container. Returns null when Sencho is not running in
* Docker (dev / bare metal), where the 1:1 path-mapping concern does not
* apply. Throws when Sencho IS containerized (a container id was resolved at
* startup) but its own mounts cannot be read now, so the caller can report an
* unverified state instead of a false "not containerized". Re-inspects on
* each call rather than caching, because it runs only on an admin-triggered
* diagnostic.
*/
async getBindMounts(): Promise<Array<{ source: string; destination: string }> | null> {
const docker = DockerController.getInstance().getDocker();
const info = await this.resolveSelfInspect(docker);
if (!info) {
if (this.containerId) {
throw new Error('container self-inspect unavailable; cannot read mounts');
}
return null;
}
const mounts = (info.Mounts ?? []) as Array<{ Type?: string; Source?: string; Destination?: string }>;
return mounts
.filter(m => m.Type === 'bind' && m.Source && m.Destination)
.map(m => ({ source: m.Source as string, destination: m.Destination as string }));
}
/** Diagnostic snapshot used by route handlers when composing error responses. */
getIdentity(): {
containerId: string | null;