Repository navigation
feat: the hub as a server: React Router on Express, SQLite, Redis, sign-in, live stream - #29
Merged
Merged
Conversation
…ient addresses and security headers
AmrMsCLL
marked this pull request as ready for review
November 5, 2026 13:26
AmrMsCLL
added a commit
to Project-Graphite/actions
that referenced
this pull request
Nov 5, 2026
…rding them in the hub (#11) The changelog workflow stops writing YAML into graphite-hub. The hub now reads GitHub Releases directly (Project-Graphite/graphite-hub#29). - Removed the `node scripts/compile.mjs` and **Record releases** steps. Tagging the next version and publishing its GitHub Release are unchanged. - Added `platform` to the App token's repositories. The hub's new `scripts/sync.mjs` reads the listed projects from `platform/projects/*.yml` instead of the hub's `registry.json`. **Merge order:** merge this before graphite-hub#29. Until #29 merges, the hub's current `sync.mjs` still reads `registry.json` and works without the removed steps. If #29 merges first, every project's changelog run fails at `compile.mjs`, which no longer exists, after its tags and releases have already been published. **Stale docs:** `README.md` lines 61 and 172–179 still say the workflow records entries under `releases/` in graphite-hub. That's phase 8 documentation and needs permission.
AmrMsCLL
added a commit
to Project-Graphite/platform
that referenced
this pull request
Nov 5, 2026
…ery rollout reported to the hub (#49) The platform half of the hub cutover, which is phase 2 step 7 of the hub server plan. It pairs with Project-Graphite/graphite-hub#29. ## Changes - **`deploy/graphite-hub/`**: the hub as a compose project. The app extends `shared/app.yaml`; Redis extends `shared/redis-password.yaml` with `noeviction`, a 96 MB maxmemory and a 128M limit. The app's `/data` volume holds SQLite and is in the nightly backup through `graphite.backup.volumes`. Health is checked on `/health/ready`. Secrets are `${NAME:?}`, and SMTP and alerts are optional. - **`values`** starts with `ENABLED=false` and `PROXIED=true`. Deploy treats it as down, so merging this changes nothing on the VPS and the apex stays on Pages. Sizing follows the plan: app 1024M limit, 512M reserve, 1.5 CPUs. - **`projects/graphite-hub.yml`**: `deploy.kind` goes from `pages` to `compose`. The hub repository has no `.graphite.yml`, so the sync leaves this file alone. - **`host/graphite-report`** runs from an exit trap in `graphite-up`, so every rollout is reported to the hub: agent deploys and console redeploys, successes and failures. It posts to the hub container's edge address, signed with `DEPLOY_REPORT_SECRET` from `/etc/graphite/agent.env` using the agent's scheme (HMAC over `stamp.body`), plus the host's status code and response time. It reports nothing for edge and backup, which have no host, or while the hub isn't running. A failed report never changes the rollout's exit code. - **`host/bootstrap.sh`** installs `graphite-report`. - **deploy/console/compose.yaml** passes `DEPLOY_REPORT_SECRET` (optional) to the console, which uses it to read the hub's `/internal/metrics` for the Alerts page (Project-Graphite/console#30). ## Checked - `check-values.py`: 0 problems. `check-memory.py`: reserved 1320M of 3000M. `deploy.py check`: 0 problems. `validate.py`: 13 projects, 0 problems. - `graphite-up` passes `sh -n`. The report script parses the console's plain, single-quoted and double-quoted env values. Its signature matches the hub's `sign()` exactly. ## Not in this PR - `host/` files reach the VPS only through `bootstrap.sh`. After merge, copy `graphite-up` and `graphite-report` to `/srv/graphite/bin/` by hand (see the hub PR's cutover steps). - `README.md` still describes the chart and Pages for the hub. That's phase 8 documentation.
…f failing every request
AmrMsCLL
added a commit
to Project-Graphite/platform
that referenced
this pull request
Nov 5, 2026
**The cutover itself. Merge last, by hand, after the steps in Project-Graphite/graphite-hub#29.** Removes `ENABLED=false` from `deploy/graphite-hub/values`. On merge, Deploy: 1. asks the agent to bring up `graphite-hub` (app and Redis). The agent refuses and waits if any required secret is missing from `/etc/graphite/graphite-hub.env`; 2. waits for `/health/ready`, then publishes the Traefik route for `project-graphite.com`; 3. points the apex at the VPS through the Cloudflare proxy (`PROXIED=true`), replacing the CNAME to `graphite-hub.pages.dev`. The rollout is then reported to the hub by `graphite-report`, so the hub's own first deploy is its first activity row. **Rollback while the Pages project still exists:** in Cloudflare DNS, set `project-graphite.com` back to a proxied CNAME to `graphite-hub.pages.dev`. Pages still holds its last deployment. Stacked on #49; it retargets to `main` when #49 merges and its branch is deleted.
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Phase 2 of the hub rebuild (
.claude/.Plans/as2026-11-04-graphite-hub-server.md), steps 1 to 6: the hub becomes one Node 24 server, React Router framework mode on Express, with SQLite for records and Redis for the bus, the stream, sessions and rate limits. Every current public route keeps its markup. Step 7, the deploy folder and the cutover, is a separate platform PR and owner work, listed below.Commits
/data), localcompose.yamlwithDEV_LOGINand Redis.node:sqlitewith ordered SQL migrations; the five current profiles are seeded fromregistry.json. Redis client; the bus writes the activity row first, then the 7-dayeventsstream (its id is the row'sseq), then publishes per topic. The snapshot is rebuilt when the bus says something changed.?graphiteshows the control. GitHub OAuth (read:org), organisation membership required, owner fromrole: admin. Sessions in Redis for 7 days with the GitHub token sealed; membership re-checked at most once a minute. Sign-out checks CSRF and Origin.CF-Connecting-IPis trusted only from Cloudflare's ranges, refreshed daily. CSP with a per-request nonce, HSTS and the rest.errors, and the owner is emailed on a new or reopened fingerprint, at most once a day.platform/projects/*.ymlvalidated with platform's own schema through Ajv 2020-12. Org members, repository stats, and the webhook: HMAC check, idempotent by delivery id, 202 at once, processed afterwards. A.graphite.ymlpush dispatches the platform sync. Hourly full sync as the fallback.rules.mjs.releases/(YAML history) is removed, andscripts/sync.mjsonly cuts tags, reading listed projects from platform./internal/deploysuses the agent's signing scheme (HMAC overstamp.body, 5-minute skew, replay set) and refuses anything that came through Traefik. Uptime pings every 30 s.toPublicEventallow-lists fields per kind./internal/metricsreports error rate, p95 and live connections.GET /live?topics=as SSE, with replay afterLast-Event-ID, 25 s heartbeat, 12-topic limit and 100 ms coalescing. Visitors get only public projections. The client hook revalidates the loaders. Also the nightly lifecycle job (uptime roll-ups, retention).sitemap.xml,llms.txt, the old_redirectsas 301s, and a real 404 status.pipeline.ymlon the shared pipeline (imageghcr.io/project-graphite/graphite-hub/site:sha-…), plus types, format and bundle size.Verified
npm run lint,format:check,typecheck: clean.npm test: 50 pass (the newserver/*.test.tsspecs and the existingscripts/rules.test.mjs).projects/*.yml. The listed set gives the same 9 projects, paths and URLs as the current site.releasesevent made an open tab revalidate.Bundle budget
150 KB of compressed JavaScript per page (owner decision). Pages load 116–120 KB today, about 107 KB of it React 19 and React Router.
Cutover steps
The PRs, in merge order: Project-Graphite/actions#11 → Project-Graphite/platform#49 → Project-Graphite/console#30 → this PR → Project-Graphite/platform#50.
A. Before merging anything
Create an OAuth App: homepage
https://project-graphite.com, callbackhttps://project-graphite.com/auth/callback.Give the platform GitHub App Organisation → Members: read and accept the new permission on the installation. Without it the hub cannot list members.
Generate four values:
SESSION_SECRET,GITHUB_WEBHOOK_SECRET,DEPLOY_REPORT_SECRETandREDIS_PASSWORD, each withnode -e "console.log(require('crypto').randomBytes(32).toString('base64url'))".Enter them in the console's Secrets page:
/graphite-hub/:GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,GITHUB_WEBHOOK_SECRET,SESSION_SECRET,DEPLOY_REPORT_SECRET,REDIS_PASSWORD,PLATFORM_APP_CLIENT_ID,PLATFORM_APP_PRIVATE_KEY; optionallySMTP_URL,ALERTS_EMAIL,ALERTS_FROM./agent/:DEPLOY_REPORT_SECRET(the same value).Add
DEPLOY_REPORT_SECRET=<the same value>to/etc/graphite/console.envon the VPS over SSH. The Secrets page refuses/console/; the console's own secrets are placed by hand.B. Merge, in order
6. actions#11. The changelog workflow stops writing YAML into the hub.
7. platform#49. The hub's deploy folder lands disabled; the console redeploys with the new variable.
8. On the VPS, install the host files (they only travel through
bootstrap.sh):ghcr.io/project-graphite/graphite-hub/site:sha-…and records it in platform'svalues; Deploy still holds the hub disabled. Then make the newgraphite-hub/sitepackage public in the organisation's packages, matchingconsole/appandstrata/app, so the VPS can pull it.C. Cutover
11. platform#50 retargets to
mainonce #49's branch is deleted. Mark it ready and merge it. Deploy starts the hub, waits for readiness, routes the apex and points DNS at the VPS through Cloudflare.12. Check:
-
https://project-graphite.com/health/readyanswersready.- The response headers carry
Content-Security-PolicyandX-Request-Id.-
/?graphiteshows sign in, and signing in works.- The console shows
graphite-hub-app-1andgraphite-hub-redis-1healthy, with no hub alerts.13. Create the organisation webhook: payload
https://project-graphite.com/internal/github, content type JSON, secretGITHUB_WEBHOOK_SECRET. Events: push, release, star, fork, member, membership, organization, repository, meta. The ping under Recent Deliveries should answer 202.14. Once the site has run cleanly for a day, delete the
graphite-hubPages project in Cloudflare.Rollback before step 14: in Cloudflare DNS, set the apex back to a proxied CNAME to
graphite-hub.pages.dev. After step 14: setIMAGE_TAGindeploy/graphite-hub/valuesback to the last goodsha-.