Skip to content

feat(ci): dashboard TLS + kill /grind + multi-org dashboard sections - #67

Open
charlielye wants to merge 4 commits into
mainfrom
cl/dashboard-tls-kill-grind
Open

feat(ci): dashboard TLS + kill /grind + multi-org dashboard sections#67
charlielye wants to merge 4 commits into
mainfrom
cl/dashboard-tls-kill-grind

Conversation

@charlielye

@charlielye charlielye commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Two related dashboard changes, both hardening/generalising the CI dashboard now that it serves multiple orgs and repos.

1. Kill the /grind web endpoint + front the dashboard with TLS

  • Remove the /grind Flask route.
  • docker-compose.yml + Caddyfile so the dashboard runs behind Caddy (Let's Encrypt TLS) in one stack, replacing the hand-written docker run systemd unit that lived only on the host. gunicorn binds loopback; Caddy terminates 443. Secrets move to /etc/rkapp.env. Cutover steps are in the compose header. (Needs the 443 SG rule — separate iac change.)

2. Multi-org dashboard sections

The dashboard was monorepo-shaped: flat sections (prs, next, releases…) with no repo axis, so multiple orgs/repos collide and one repo's runs evict another's under the 1000-entry cap.

  • log_ci_run: section ZSETs are keyed ci-run-<org>/<repo>/<section> (from GITHUB_REPOSITORY); adds a repo field to the run blob for metrics GROUP BY; puts a sliding 90-day TTL on the ZSET; self-registers the dashboard string in a ci-run-sections index set.
  • rk.py landing page enumerates that index (pruning any section whose ZSET has expired — retired >90d), groups by org/repo, links each. /section/<path:section> for the slashes. get_section_data unchanged.
  • ci.sh CI_DASHBOARD classifier: mainline branch → its own name (main, v5), any tag → tags, else prs. releasestags, deflakelocal.

Back-compat: callers without GITHUB_REPOSITORY still write ci-run-<section>; old unscoped keys age out via the new TTL and are ignored by the index-driven landing page.

Follow-up (not in this PR)

The trigger service's runner.py still sets CI_DASHBOARD="prs", so forge-triggered runs show under <repo>/prs until a small ref-classifier lands there (that file is on the trigger branch, not main). The dashboard infra here is ready for it.

Remove the /grind Flask route: it built a shell command from URL query params
(only cmd was shlex.quoted; commit/cpus/time/etc. interpolated raw into bash -c),
an unauthenticated-by-default RCE on the ci host. It was unused, and wired to a
stale /aztec-packages checkout. Flaky tests are still ground from the CLI via
./ci.sh grind-test; drop the now-dead grind link in run_test_cmd.

Add a docker-compose.yml + Caddyfile so the dashboard runs behind Caddy (TLS via
Let's Encrypt) in one stack, replacing the hand-written docker-run systemd unit
that lived only on the host. gunicorn binds loopback; Caddy terminates 443.
Secrets move to /etc/rkapp.env (the leaked GH_TOKEN must be rotated). Cutover
steps are in the compose header.
The dashboard was monorepo-shaped: runs grouped into flat sections (prs, next,
releases...) with no repo axis, so multiple orgs/repos collide. Now:

- log_ci_run keys each section ZSET as ci-run-<org>/<repo>/<section> (from
  GITHUB_REPOSITORY), adds a repo field to the blob for metrics, puts a sliding
  90-day TTL on the ZSET, and self-registers the dashboard string in a
  ci-run-sections index set.
- rk.py landing page enumerates that index (pruning any section whose ZSET has
  expired — retired >90d), groups by org/repo, and links each. /section takes a
  <path:> so slashed dashboard names route. get_section_data is unchanged
  (ci-run-<name> already).
- CI_DASHBOARD classifier (ci.sh multi_job_run): mainline branch -> its own name
  (main, v5), any tag -> tags, else prs. releases->tags and deflake->local
  renames complete the section model.

Back-compat: callers without GITHUB_REPOSITORY still write ci-run-<section>;
old unscoped keys age out via the new TTL and are ignored by the index-driven
landing page. The trigger runner's CI_DASHBOARD (still 'prs') is the one
coupled follow-up, on the trigger branch where runner.py lives.
deploy.sh now rolls out the compose stack (rkapp on loopback + Caddy TLS): rsync,
build the image first, retire the legacy systemd rkapp unit (idempotent, so
steady-state redeploys skip it), compose up, liveness-check. Requires
/etc/rkapp.env (app secrets) to already exist. Drop the dead /aztec-packages
mount from the compose (REPO_PATH/grind are gone). ci.sh log fallback fetches
over https, since 80 now redirects and curl won't resend inline basic-auth across
an http->https redirect.
… branch

The section derivation was duplicated between ci.sh's multi_job_run and
log_ci_run's fallback, and the two disagreed (log_ci_run didn't know about
mainline-branch or vN sections). The trunk name was also hardcoded and drifted:
ci.sh used 'main' while source_refname used 'master' for CI_FULL.

Add two shared helpers in source_refname:
- ci_default_branch: resolves the repo's trunk (DEFAULT_BRANCH env override ->
  local origin/HEAD -> git ls-remote --symref -> 'main'). The remote query is
  what makes it work on the build container's shallow fetch, where origin/HEAD
  is never set, and it is host-agnostic (GitHub, Forgejo).
- ci_dashboard_section: the single source of truth for the section — 'tags' for
  a semver tag, the ref's own name for the default branch or a configured
  mainline branch (CI_MAINLINE_BRANCHES, default ^v[0-9]+$), else 'prs'.

ci.sh and log_ci_run now both call ci_dashboard_section, and CI_FULL keys off
ci_default_branch instead of the stale literal 'master'.
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