mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 06:07:58 +00:00
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:
@@ -63,3 +63,28 @@ describe('GET /api/diagnostics', () => {
|
||||
expect(res.body.config.cloud_backup_secret_key).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/diagnostics/environment', () => {
|
||||
it('requires authentication', async () => {
|
||||
const res = await request(app).get('/api/diagnostics/environment');
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('rejects a non-admin', async () => {
|
||||
const res = await request(app).get('/api/diagnostics/environment').set('Authorization', viewerAuthHeader);
|
||||
expect(res.status).toBe(403);
|
||||
expect(res.body.code).toBe('ADMIN_REQUIRED');
|
||||
});
|
||||
|
||||
it('returns the environment report for an admin', async () => {
|
||||
const res = await request(app).get('/api/diagnostics/environment').set('Authorization', adminAuthHeader);
|
||||
expect(res.status).toBe(200);
|
||||
expect(Array.isArray(res.body.checks)).toBe(true);
|
||||
const ids = (res.body.checks as Array<{ id: string }>).map(c => c.id);
|
||||
expect(ids).toEqual(['docker_socket', 'docker_compose', 'compose_dir', 'path_mapping', 'tls', 'disk_space']);
|
||||
for (const c of res.body.checks as Array<{ status: string; detail: string }>) {
|
||||
expect(['pass', 'warn', 'fail']).toContain(c.status);
|
||||
expect(typeof c.detail).toBe('string');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user