feat(web): managed server setup progress & recovery UX (PLO-94)#51
Merged
Conversation
…ls (PLO-92) Dashboard-managed server setup needs a persistent SSH management channel, which means the control plane must hold a credential that can reach the server. This adds the credential store and lifecycle that bounds it — the foundation the SSH bootstrap runner (a follow-up) builds on. It does not open any SSH connection yet. New `internal/serversetup` module (mirrors `secrets`): - ssh_management_keys table (migration 00019): server-scoped, one credential per server, holding the SHA256 fingerprint, public authorized_keys line, the SEALED private key (AES-256-GCM via the existing crypto box + APP_MASTER_KEY), rotation state, last-used/rotated/revoked timestamps, and created_by. No raw bootstrap credential is ever a column. - The private key is write-only: no RPC returns it. ServerSetupService exposes only GetManagementKey / RotateManagementKey / RevokeManagementKey (metadata + public key). Provision, MarkUsed, RecordFailedAuth, and OpenPrivateKey are in-process only, for the future SSH runner. - Workspace-scoped authorization (resolved from the server) before every mutation, with the audit record committed in the same transaction. Provision, use, rotation, revocation, and failed-auth are each audited; reads are not. - New authz actions (server_setup.run / key.rotate / key.revoke / key.read): running managed setup and the destructive lifecycle ops are owner/admin only; reading non-secret metadata is allowed for every role. New `internal/platform/sshkeys` generates the ed25519 keypair (distinct from the agent's job-signing key), returning the sealed-at-rest private PEM, the authorized_keys line, and the fingerprint. Unit tests cover encryption, redaction, authorize-before-mutate, audit behavior, rotation, revocation, and cross-workspace denial. Docs (server-management.md, data-and-api.md) updated to record the shipped storage module and the new table.
Add the dashboard-managed setup path that complements the one-line installer: the user enters fresh-VPS connection details and Plorigo prepares the server for them over SSH, as an asynchronous, audited run. Builds on the serversetup credential store (PLO-92). ServerSetupService gains StartSetup / GetSetupRun / ListSetupEvents: - StartSetup validates host/port/username + a one-time bootstrap credential (password or private key), creates a run (auditing start vs retry), and dispatches the bootstrap on a background goroutine carrying the caller's principal. The raw bootstrap credential is held only on the stack for the attempt — never written to the DB, never logged. - The run connects over SSH (TOFU: pins the host key on first connect, refuses a later mismatch), then executes ordered steps via an SSHExecutor port: detect OS (Ubuntu 22.04/24.04 only, else a clear reason), check root/sudo, preflight (apt lock, ports 80/443, Docker present/old, UFW), drive scripts/install-agent.sh with a minted registration token, provision the non-root `plorigo` user + its authorized_keys + a wildcard-free scoped sudoers drop-in (validated with visudo), and wait for the agent's first heartbeat. - Ordered, redacted status/log lines persist to server_setup_runs / server_setup_events (migration 00020) and are polled by the dashboard, mirroring deployment events. The install command carries the token but is never emitted; only the installer's own (self-redacted) output is streamed. - TOFU host-key fingerprint is pinned on a new servers.host_key_fingerprint column. The step runner depends only on an SSHExecutor port, so it is fully unit-tested with a fake executor across the required scenarios: success, unsupported OS, missing root/sudo, apt lock, Docker already installed, old Docker, occupied ports, UFW active, agent heartbeat timeout, transport failure, and a provisioning failure — plus a check that the registration token never leaks into an event. The real x/crypto/ssh dialer is the thin, manually-verified boundary. The agents module is reached through an adapter wired in internal/app, so serversetup never imports it. The durable job queue isn't built yet, so the run is an in-process goroutine that persists its steps; a process restart mid-run leaves the run for the user to retry. Docs (server-management.md, jobs-and-realtime.md, data-and-api.md) updated.
Connect Server now offers two paths: run a one-line command yourself, or let Plorigo prepare a fresh Ubuntu box over SSH (the PLO-93 ServerSetupService). - Two-tab ConnectServerDialog (features/servers/connect/): manual command, or a managed SSH form collecting host, port, username, and a one-time bootstrap credential (password or private key). The credential lives in client state only until StartSetup is called, then is cleared on success AND failure — never re-displayed, logged, or put in error text. - SetupProgress: a live step timeline polled from GetSetupRun / ListSetupEvents (the deployment-events poll pattern), with the server-redacted raw log one click away, plain-English failure summaries, and recovery actions (retry, or fall back to the command). At most one server record is created and reused across retries and tab switches. - Server cards distinguish no agent / setting up / ready / degraded / failed setup, and expose Rotate key / Revoke SSH access when an active managed credential exists. - Wiring: setupClient + ServerSetupService dev proxy + useSetupRun/useSetupEvents/ useManagementKey hooks + setup status tones. - Adds a vitest + @testing-library/react harness (none existed) and component tests covering manual setup, managed success, managed failure, retry, and the rotate/revoke affordances; runs in CI (Web test step). - Updates docs/architecture/dashboard.md.
# Conflicts: # internal/platform/database/db/models.go # internal/policy/service.go
The managed SSH setup form was only reachable from the page-level Connect server button, so a server first connected with a command — or one whose password later changed — had no way to (re-)run setup. The backend StartSetup is already re-runnable (reuses the pinned host key, re-provisions the management credential), so this exposes it from the server card. - ConnectServerDialog gains an optional existingServer prop: opens on the SSH tab, locks the name, and reuses the existing record instead of creating one. - ServerCardActions adds a Set up over SSH / Re-run setup action. - ServersPage drives a second, keyed dialog in existing-server mode, sharing the managed-run tracking so the card shows live setup progress.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the dashboard UX for dashboard-managed server setup (drives PLO-93's
ServerSetupService). Stacked on #50 (PLO-93) — review/merge that first; this PR's base isfeature/plo-93.What
Connect Server now has two paths (
features/servers/connect/):StartSetup, then streams progress.Live progress + recovery (
SetupProgress):plorigouser → waiting for heartbeat → ready), polled fromGetSetupRun/ListSetupEvents(the same poll-the-events pattern as deployments; stops on a terminal status).Server cards distinguish no agent / setting up / ready / degraded / failed setup, and expose Rotate key / Revoke SSH access when an active managed credential exists (
RotateManagementKey/RevokeManagementKey, confirmed dialogs).Security
The one-time bootstrap credential is held in client state only until
StartSetupis called, then cleared on success and failure — never re-displayed, logged, or placed in error text. Setup-event messages are redacted server-side; the UI never renders a token/password/key.Tests
There was no web test harness — this adds vitest + @testing-library/react (jsdom) and component tests covering manual setup, managed success, managed failure, retry, and rotate/revoke (incl. revoked-key gating). Wired into CI as a
Web teststep;pnpm-lock.yamlupdated.Wiring & docs
setupClient+ theServerSetupServicedev proxy entry +useSetupRun/useSetupEvents/useManagementKeyhooks + setup status tones;docs/architecture/dashboard.mdupdated.Verification
pnpm --dir apps/web typecheck✓ ·lint✓ (0 errors) ·test✓ (8) ·build✓.The actual SSH bootstrap is exercised by PLO-93's backend; this PR is UI-only.