Skip to content

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_TOKEN CI 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:

  1. Installs wrangler.
  2. Pushes every RELAY_* value into the worker via wrangler secret put (idempotent).
  3. 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 → PagesSet up Pages if not already enabled. The pages CI job runs on every push to main:

  1. tools/build_docs.py walks results/**/*.yaml and generates index, per-CPU, per-GPU, per-distro pages.
  2. mkdocs buildsite/.
  3. 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_TOKEN in Settings → CI/CD → Variables, then click Play on relay-deploy-scheduled to push it to the worker immediately.
  • Auto: the monthly schedule does this for you.
  • Disable auto-rotation: drop the relay-deploy-scheduled job from .gitlab-ci.yml and 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 include schema: "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 RATELIMIT KV binding (worker refuses to start without it). Tune RATE_LIMIT_PER_WINDOW / RATE_LIMIT_WINDOW_SEC in worker.js.
  • Optional PULSE_HMAC_SECRET / X-Pulse-Sig shared-secret signing.
  • Optional TURNSTILE_SECRET slot 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>/pulse@v1.2 pulse.

See also

  • CLI reference
  • Configuration & .env
  • cloudflare-relay/worker.js — Worker source.
  • cloudflare-relay/wrangler.toml — Worker config.
  • .gitlab-ci.ymlrelay-deploy and relay-deploy-scheduled job definitions.