Skip to content

feat: push results from CI to patchbay-server#3

Open
Frando wants to merge 17 commits intomainfrom
feat/server-push
Open

feat: push results from CI to patchbay-server#3
Frando wants to merge 17 commits intomainfrom
feat/server-push

Conversation

@Frando
Copy link
Member

@Frando Frando commented Mar 20, 2026

No description provided.

Frando and others added 17 commits March 19, 2026 23:47
Add a standalone `patchbay-serve` binary for hosting run results remotely.
CI pipelines can push test results via `POST /api/push/{project}` (tar.gz)
and get a link back for viewing in the devtools UI.

- Push endpoint with Bearer token auth, stores runs as {project}/{date}-{uuid}
- run.json manifest for CI context (branch, commit, PR link)
- /runs index page listing all pushed runs with PR links
- Automatic TLS via ACME (--acme-domain + --acme-email)
- Background retention watcher (--retention) deletes oldest runs over limit
- GitHub Actions snippet and deployment docs in testing.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add patchbay-serve.service with hardening (ProtectSystem, NoNewPrivileges, etc.)
- Restructure testing.md: local tests → CI integration → patchbay-serve deployment
- Move server docs to end as its own section with install/systemd instructions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The UI doesn't support ?run= query params for deep linking. Fix CI
snippet to use /runs#path fragment (scrolls to run entry). Fix runs
index "View" link to point to / where the sidebar lists all runs.
Add :target CSS highlight so fragment-linked runs are visually marked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add react-router-dom with HashRouter for URL-based navigation:
- /#/ — runs index page listing all runs with links to each
- /#/run/{name} — deep link to a specific sim run
- /#/inv/{name} — deep link to an invocation (combined view)

The dropdown in the topbar navigates via the router. The patchbay
title links back to the runs index. Selection is fully derived from
the URL — no more local state for run selection.

Push endpoint now returns `first_run` (first discovered sim name)
so CI can build a direct deep link into the UI. CI snippet updated
to use `/#/run/{first_run}` for the PR comment link.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Push creates {project}-{date}-{uuid} (flat, no nesting) so the dir
  name IS the invocation. CI link: /#/inv/{invocation}
- Invocation view now has a "sims" tab listing all runs in the group,
  with links to each. Perf tab still available for combined results.
- RunsIndex auto-redirects: single run → /#/run/{name}, single
  invocation → /#/inv/{name}. Preserves old UX for local dev.
- Drop first_run from push response (unnecessary)
- Simplify retention (flat dir scan)
- Fix multi-sim e2e test to click perf tab explicitly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests the full push flow: run a sim, start patchbay-serve with
--accept-push, tar+gz the output and POST to /api/push/{project},
verify the response, open the deep link /#/inv/{invocation}, verify
the sims tab lists the run, click through to topology view, and
check auth rejection without API key. Also verifies the server-side
/runs index page renders manifest fields (branch, PR link).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Step-by-step systemd deployment (create user, install binary,
configure unit, start) and GitHub Actions snippet for pushing
results and commenting on PRs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rustls requires an explicit CryptoProvider when no default is
selected via crate features. Install ring as the default provider
before any TLS operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IPv6-only bind doesn't work on hosts without IPv6 enabled.
Use 0.0.0.0:80 and 0.0.0.0:443 for the ACME redirect and
TLS listeners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace --bind with --http-bind (default 0.0.0.0:8080) and
--https-bind (default 0.0.0.0:4443). Service template uses
80/443 with CAP_NET_BIND_SERVICE for privileged ports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TLS-ALPN-01 challenges require port 443; bail if --https-bind uses a
different port. Warn if --http-bind is not port 80 since browsers
won't follow the redirect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GET /api/runs/{run}/events.json REST endpoint for fetching all
events as a JSON array. Client now loads initial data via REST and only
opens SSE connections for runs with status "running". Runs list uses
polling instead of SSE. Tabs showing completed runs use zero persistent
connections, fixing hangs when many tabs are open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lab::test_guard() returns an RAII guard that records test outcome.
Calling .ok() marks success; dropping without .ok() (panic or early
error return) marks failure. The writer reads the status on shutdown
and LabInner::drop() synchronously patches state.json so the final
status is always written, even during panic unwind.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TestGuard now emits a TestCompleted lab event (with passed: true/false)
so the test result is visible in the timeline. The _lab node column is
sorted before device nodes in the timeline grid.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LabInner::drop now blocks on the writer task handle so it always gets
to do its final flush (drain events, set status, write state.json).
As a fallback, if state.json still doesn't exist (test finished before
the first 1-second flush interval), drop creates a minimal state.json
with the correct status. Fixes fast tests showing "running" forever.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the fragile thread-spawn + Handle::try_current approach with a
shared Arc<Mutex<LabState>> between the writer task and LabInner. The
writer updates the shared state on every event. LabInner::drop reads
it, sets the final status, and writes state.json synchronously — no
async runtime needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant