Maintainer setup¶
This page is for the project owner — the person who forks Pulse and wants to host their own community dashboard. End users don't need any of this.
The whole setup is roughly 20 minutes one-time. After that, the relay redeploys itself on every push to main and rotates its own GitLab token monthly.
Architecture¶
Pulse TUI (no token) ──POST /submit──▶ Cloudflare Worker ──GitLab API──▶ MR on this repo
(holds GITLAB_TOKEN) (you review + merge)
One repo holds everything: the CLI source (src/pulse/), the Worker source (cloudflare-relay/), the submitted results (results/), and the MkDocs dashboard sources (docs/).
Step 1 — Create a Cloudflare API token¶
Cloudflare dashboard → My Profile → API Tokens → Create Token → Create Custom Token.
Permissions: - Account → Workers Scripts: Edit - Account → Workers KV Storage: Edit - Account → Account Settings: Read
Account Resources: Include your account. TTL: leave at default (no expiry) or set ~1 year.
Copy the token (shown only once). Also copy your Account ID from the dashboard sidebar.
Step 2 — Create the pulse-relay-worker service account¶
Service accounts don't consume a license seat and survive contributor turnover.
- Group/Admin → Service Accounts → New (or
POST /api/v4/groups/<id>/service_accounts). - Username:
pulse-relay-worker. - Add it as Maintainer of this repo (Manage → Members → Invite). Maintainer is required because the monthly rotation job overwrites the
RELAY_GITLAB_TOKENCI variable; it also covers MR creation. - Mint a personal access token for that account: scope
api,expires_at~6 weeks out (the monthly rotation extends it).
Step 3 — Set GitLab CI variables¶
Project → Settings → CI/CD → Variables. Add each as Masked + Protected:
| Variable | Value |
|---|---|
CLOUDFLARE_API_TOKEN |
from Step 1 |
CLOUDFLARE_ACCOUNT_ID |
from Step 1 |
RELAY_GITLAB_TOKEN |
the glpat-… from Step 2 |
RELAY_GITLAB_PROJECT |
<group>/<repo> slug, e.g. ai-sys0x/pulse |
RELAY_HMAC_SECRET (optional) |
openssl rand -hex 32 if you want to lock the relay to your client |
RELAY_TURNSTILE_SECRET (optional) |
from your Cloudflare Turnstile site, only if you wire it in |
Step 4 — Bootstrap the KV namespace (one-time, local)¶
The relay refuses to start without a RATELIMIT KV binding (rate-limits 10 submissions/min/IP). Create it once:
cp .env.example .env # then fill in CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID
set -a; source .env; set +a
npm install -g wrangler@latest
cd cloudflare-relay
wrangler kv namespace create RATELIMIT
# → id = "abcd1234..."
Paste the printed id into cloudflare-relay/wrangler.toml (replacing the existing id value under [[kv_namespaces]]). Commit + push.
Step 5 — Push to main. CI deploys.¶
The relay-deploy CI job triggers on any push to main that touches cloudflare-relay/**. It:
- Installs wrangler.
- Pushes every
RELAY_*value into the worker viawrangler secret put(idempotent). - Runs
wrangler deploy.
Watch the job in Build → Pipelines. On success, the worker is live at https://pulse-relay.<account>.workers.dev. Sanity-check:
curl https://pulse-relay.<account>.workers.dev/healthz
# → {"ok":true,"version":"1.0.0","schemas":["pulse-result.v1"]}
Then update PULSE_RELAY_URL (in your local .env and in the published .env.example if you want strangers' uvx pulse to find your worker).
Step 6 — Schedule the monthly redeploy + token rotation¶
Build → Pipeline schedules → New schedule:
- Description: Monthly pulse-relay redeploy + secret sync
- Cron: 0 4 1 * * (04:00 UTC, 1st of each month)
- Target branch: main
- Active: ✓
This runs the relay-deploy-scheduled job, which:
1. Calls POST /api/v4/personal_access_tokens/self/rotate?expires_at=…+6 weeks.
2. Sanity-checks the new token.
3. Pushes it to the worker via wrangler secret put and wrangler deploy.
4. PUTs the new value back into the RELAY_GITLAB_TOKEN CI variable.
Same job is also web-trigger-able (manual button) for immediate rotation post-incident.
Tradeoff to be aware of: GitLab atomically swaps the old token for the new one — if the worker-deploy step fails, the worker is left with an invalid token and submissions break until you intervene. The job order minimises this risk (worker first, then CI variable), but the inherent atomic-swap window can't be eliminated. Watch failed scheduled pipelines.
Step 7 — Enable GitLab Pages¶
Project → Deploy → Pages → Set up Pages if not already enabled. The pages CI job runs on every push to main:
tools/build_docs.pywalksresults/**/*.yamland generates index, per-CPU, per-GPU, per-distro pages.mkdocs build→site/.- Published to Pages.
Once a submission lands and gets merged, the dashboard appears at https://<group>.gitlab.io/<repo>/.
Per-deploy (after one-time setup)¶
Push to main with changes under cloudflare-relay/ → CI redeploys. That's the whole loop.
Token rotation¶
- Manual edit: change
RELAY_GITLAB_TOKENin Settings → CI/CD → Variables, then click Play onrelay-deploy-scheduledto push it to the worker immediately. - Auto: the monthly schedule does this for you.
- Disable auto-rotation: drop the
relay-deploy-scheduledjob from.gitlab-ci.ymland set the PAT's expiry far out instead.
Local rebuild / debug¶
set -a; source .env; set +a # CLOUDFLARE_* + RELAY_*
cd cloudflare-relay
wrangler deploy # same as what CI does
wrangler tail # live request log
Endpoints¶
POST /submit— body must includeschema: "pulse-result.v1". Returns{ok, mr_url}on success.GET /healthz—{ok, version, schemas}.
Abuse protection¶
- Built-in: 10 submissions/min/IP via the mandatory
RATELIMITKV binding (worker refuses to start without it). TuneRATE_LIMIT_PER_WINDOW/RATE_LIMIT_WINDOW_SECinworker.js. - Optional
PULSE_HMAC_SECRET/X-Pulse-Sigshared-secret signing. - Optional
TURNSTILE_SECRETslot for Cloudflare Turnstile. - All MRs branch under
pulse-results/<host>-<fp>-<ts>— easy to filter, mass-close, or auto-merge.
Cost¶
- Cloudflare Workers free tier: 100,000 requests/day.
- GitLab CI minutes for monthly rotation + per-push deploys: well within the free tier.
- GitLab Pages: free.
Total recurring cost: $0.
Updating¶
When you merge changes to Pulse, users get them on their next uvx cold-cache invocation. To pin a release, tag in git: uvx --from git+https://gitlab.com/<you>/[email protected] pulse.
See also¶
- CLI reference
- Configuration & .env
cloudflare-relay/worker.js— Worker source.cloudflare-relay/wrangler.toml— Worker config..gitlab-ci.yml—relay-deployandrelay-deploy-scheduledjob definitions.