> ## Documentation Index
> Fetch the complete documentation index at: https://docs.burnsidesteps.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get started

> Deploy the Burnside Steps relay to your own Cloudflare account and get feedback landing in your tracker.

Getting from a cloned repository to feedback landing in your tracker. These are the steps that need
a human: creating credentials, approving an OAuth login, and pasting a script tag onto a site.
Everything else is a build or a deploy.

This page covers self-hosting on **Cloudflare Workers**. To run the relay as a Node process instead,
see [Self-hosting on Node](/self-hosting-node). To skip hosting entirely, see
[burnsidesteps.com](https://burnsidesteps.com).

## You end up owning a fork

Both paths leave you running your own copy. Nothing phones home, and nothing updates itself. Pulling
a future release is a `git pull` from upstream when you decide to, and nothing notifies you that
there is one. That is the trade for having no account, no per-seat pricing, and no vendor between
you and your data.

## Prerequisites

`git`, Node 24 (or 26 and later) and pnpm 9 or later, plus a Cloudflare account. The free plan is
enough. `wrangler` is deliberately **not** installed globally: it is a devDependency of `relay/`
and runs through pnpm. Its own floor is lower, Node 22: its binary exits immediately below it.

**Platforms.** These commands are written for macOS and Linux, and for WSL on Windows. `rm` and
`openssl` are not available in `cmd.exe` or a default PowerShell. Where this page uses
`openssl rand -hex 32`, this works anywhere Node does:

```bash theme={null}
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```

Clone the repository and install dependencies:

```bash theme={null}
git clone https://github.com/greenwichsteps/burnside-steps.git
cd burnside-steps
pnpm install
```

`pnpm install` prints a few `Failed to create bin at ... burnside-relay` warnings. They are expected
on a fresh checkout: `hosted` and `www` depend on `relay`, whose `bin` points at
`relay/dist/server.js`, which nothing has built yet. The install exits 0 and the warnings stop after
the first build.

## Have an agent do it

The steps on this page are the ones an agent follows. If you would rather hand it over, paste this
into Claude Code, Cursor, or any agent with terminal access:

> Set up Burnside Steps on my site. The repo is github.com/greenwichsteps/burnside-steps. Deploy the
> relay to my Cloudflare account, connect it to my Linear workspace, and add the widget to my site.
> Create the KV namespace and paste its id into relay/wrangler.jsonc before the first deploy, and
> set ADMIN\_SETUP\_TOKEN as a secret before it too. Both orderings matter.

Swap "my Linear workspace" for "my GitHub repository" or "my GitLab project". The last step still
needs you either way: claiming the admin panel and pasting a destination credential are deliberately
manual.

The two extra sentences are not padding. Steps 3 and 4 below both have to happen before the first
deploy, and an agent handed only the first paragraph will deploy a relay that cannot store a config
or claim a panel.

## 1. Create an API credential for your destination

Feedback can land in Linear, GitHub, or GitLab. Create a credential for whichever you use, and
keep it somewhere safe. It is pasted once into the admin panel and is never stored in the repo.

**Linear.** Settings, then Account, then Security and Access, then New API key. Scope it either
to full access or to write and create-issue permissions on the teams that will receive feedback.
You also need the **team UUID** for each project, which you can copy from the Linear UI.

**GitHub.** A personal access token with `repo` scope, or a fine-grained token with issue write
permission on the target repository. You need the repository **owner** and **name**.

**GitLab.** A personal or project access token with `api` scope. You need the numeric **project
id**, and for self-managed GitLab, the **host** of your instance.

## 2. Authenticate wrangler with Cloudflare

```bash theme={null}
pnpm --dir relay exec wrangler login
```

This opens a browser window for OAuth approval.

## 3. Create the CONFIG KV namespace

The relay stores project configuration and destination tokens in a Workers KV namespace. This is
what the admin panel writes to.

```bash theme={null}
pnpm --dir relay exec wrangler kv namespace create CONFIG
```

The `CONFIG` binding in `relay/wrangler.jsonc` ships without an `id`, so add the one you just
created:

```jsonc theme={null}
"kv_namespaces": [
  { "binding": "CONFIG", "id": "paste-your-id-here" }
]
```

**Delete the production config.** `wrangler.production.jsonc` in `relay/` is the Greenwich Steps
deploy config, kept in the repo so the project deploys from its own source. It is not useful to
you, and the repo's tests expect the template to carry no account-specific values, so remove it
now that you have pasted your own id:

```bash theme={null}
rm relay/wrangler.production.jsonc
```

The guards that depend on it then report as skipped, and `pnpm test` stays green.

## 4. Set the admin setup token, before the first deploy

The first-run endpoint that creates an admin password is unauthenticated by nature: anyone who
found the URL before you set a password could otherwise claim the panel. It is gated behind an
`ADMIN_SETUP_TOKEN` secret.

Generate a long random value, for example with `openssl rand -hex 32`, and set it as a Worker
secret **before deploying**:

```bash theme={null}
pnpm --dir relay exec wrangler secret put ADMIN_SETUP_TOKEN
```

Keep the value. You enter it once, on the admin setup screen in step 6.

## Run it locally first, if you want to

Optional, and not part of the deploy path. `pnpm dev` starts wrangler's local server on
**[http://localhost:8787](http://localhost:8787)**, serving the demo page and the admin panel from your own machine.

Local runs do not read the secrets you set with `wrangler secret put`, which only exist on a
deployed Worker. They read `relay/.dev.vars`, which is gitignored. Copy the template and fill it
in:

```bash theme={null}
cp relay/.dev.vars.example relay/.dev.vars
```

One thing to know about the admin panel in local dev: its session cookie is named
`__Host-bs_admin`, and a browser takes a `__Host-` cookie only from an origin it treats as secure.
Current Chrome and Firefox count `http://localhost` as secure, so the panel works in both. Safari
does not: it accepts the sign-in and then drops the cookie, so you cannot stay signed in there, and
`http://127.0.0.1` is no different. To use the panel in Safari, serve local dev over TLS with a
certificate Safari trusts, or use a deployed relay. The demo page and the widget work in every
browser.

## 5. Deploy

```bash theme={null}
pnpm deploy
```

This builds the widget and admin panel into `relay/public/`, then runs `wrangler deploy`. Note
the resulting URL, for example `https://burnside-steps.<your-subdomain>.workers.dev`.

## 6. Claim the admin panel

Visit `https://<relay-host>/admin`.

1. **First visit is setup.** Enter the `ADMIN_SETUP_TOKEN` from step 4, then choose a password of
   at least 8 characters. There is no separate account system: one password gates the panel. The
   token must be at least 24 characters, and setup is refused outright if it is shorter, however
   correctly it is typed.
2. **Delete the setup token.** It is needed once, but it stays a credential that claims the panel
   for as long as it exists: if the credentials are ever cleared, whoever still has that token can
   claim the panel before you do.

   ```bash theme={null}
   pnpm --dir relay exec wrangler secret delete ADMIN_SETUP_TOKEN
   ```

   Self-hosting with Docker or npm: remove the line from your `.env` and restart.
3. **Sign in** with that password.
4. On **Integrations**, paste the credential from step 1 and test the connection.
5. On **Projects**, add a project. Set its allowed origins, its destination, and any tag to label
   mapping. Project ids and origins live here, not in the repo, so adding a site later needs no
   redeploy.

The admin password is stored hashed in KV, never in the repo. The session cookie is signed and
scoped to the same origin, so the panel is not reachable cross-origin.

## 7. Install the script tag on a site

Add this before `</body>`, or through your platform's snippet or header-scripts feature:

```html theme={null}
<script src="https://<relay-host>/widget.js" type="module" data-project="<project-id>"></script>
```

`<project-id>` must match a project you added in step 6, and that project's allowed origins must
include the exact origin of the site, for example `https://staging.acme.com`.

For a **live** site, add `data-mode="query"`. The button then appears only after someone visits
any URL with `?feedback=on`, which persists for that browser tab. Share that link with the people
you want giving feedback.

There is no `integrity` attribute by design: the script is first-party, served from a relay you
control, and an SRI hash would break every installed site on each widget deploy.

## 8. Verify end to end

Open a site with the tag installed, submit one piece of real feedback with an annotation, and
confirm the issue appears in your tracker with the screenshot attached. This is the one check no
automated test covers, because it exercises your tracker's handling of a real payload.

If your destination is GitHub and the issue arrives with no image, nothing has failed. GitHub has
no API for attaching an image to an issue, so the relay stores screenshots itself and needs an R2
bucket, which is not part of the steps above. See **Enable screenshots for GitHub** under Optional.
Linear and GitLab upload the screenshot through their own APIs and need nothing extra.

## Optional

**Enable screenshots for GitHub.** Only GitHub needs this. Linear and GitLab upload the screenshot
through their own APIs and work with no extra setup. GitHub has no API for attaching an image to
an issue, so the relay stores the screenshot itself and links to it. Without a bucket, GitHub
issues arrive text-only. Nothing raises an error and nothing is logged: the issue body simply
reads `_Screenshot unavailable._` where the image would have been.

```bash theme={null}
pnpm --dir relay exec wrangler r2 bucket create burnside-shots
```

Then add the binding to `relay/wrangler.jsonc`, beside the existing `kv_namespaces` block:

```jsonc theme={null}
"r2_buckets": [{ "binding": "SHOTS", "bucket_name": "burnside-shots" }]
```

The binding has to be named `SHOTS`. Redeploy with `pnpm deploy`.

Screenshot URLs are unguessable but public: anyone holding one can open the image, because the
`<img>` GitHub renders carries no cookie. Signing them means only URLs the relay itself produced
are served, so a guessed or altered path is refused:

```bash theme={null}
pnpm --dir relay exec wrangler secret put SHOT_SIGNING_KEY   # paste: openssl rand -hex 32
```

Set it before you file anything you care about. The signature covers the stored path, so
**changing or removing the key later breaks every screenshot already linked from an existing
issue** - the image stops loading, though the issue text is untouched. Leaving it unset is
supported and screenshots still work; it means any correctly-guessed path is served.

**Expire old screenshots.** Entirely your choice, and off unless you ask for it. The bucket is
yours, so screenshots stay until you remove them, and your licence does not affect this either
way. To have R2 expire them for you:

```bash theme={null}
pnpm --dir relay exec wrangler r2 bucket lifecycle add burnside-shots expire-shots --expire-days 90
```

Ninety days is an example, not a recommendation. R2 applies the rule itself, so nothing runs in
your Worker and it keeps working whether or not the relay is up. To inspect or remove the rule:

```bash theme={null}
pnpm --dir relay exec wrangler r2 bucket lifecycle list burnside-shots
pnpm --dir relay exec wrangler r2 bucket lifecycle remove burnside-shots --name expire-shots
```

**Activate a Pro licence.** A one-time Pro licence lifts the project cap and removes the "Powered
by Burnside Steps" badge. After purchase you receive a key by email. Open the admin panel, go to
the **Licence** tab, and paste it. Activation is offline: the relay verifies the key's signature
and never calls out to check it.

The project cap lifts at once. The badge clears from your sites within a minute rather than
instantly, because widget settings sit in each visitor's browser cache for that long, your own
included. If you reload your site straight after activating and the badge is still there, that is
the cache and not the licence.

**Attach a custom domain.** Free in the Cloudflare dashboard, under Workers, your worker, then
Domains. Worth doing if a network blocks `*.workers.dev`, which some corporate filters and ad
blockers do.

**Rotate a destination token.** Paste a new one into the admin panel's Integrations tab. No code
change and no redeploy.

**Reset the admin password.** There is no self-service reset. Clearing the credentials re-opens
first-run setup, so the order matters: set a fresh token first, claim the panel, then remove the
token again. Doing it the other way round leaves the panel claimable by anyone holding the old
token, for as long as it takes you to notice.

```bash theme={null}
# 1. A new token, at least 24 characters. Setup will refuse a shorter one.
openssl rand -hex 32 | pnpm --dir relay exec wrangler secret put ADMIN_SETUP_TOKEN

# 2. Clear the credentials. --remote matters: without it wrangler deletes from a local dev
#    namespace rather than the one your deployed Worker reads from.
pnpm --dir relay exec wrangler kv key delete "admin:credentials" --binding=CONFIG --remote

# 3. Claim the panel again at /admin with the new token and a new password, THEN:
pnpm --dir relay exec wrangler secret delete ADMIN_SETUP_TOKEN
```

Between steps 2 and 3 the panel is unclaimed and anyone with the token can take it. Keep that
window short, and do not skip step 3.

## Which steps need a human

| Step | Why                                                                               |
| ---- | --------------------------------------------------------------------------------- |
| 1    | Creating an API credential in your tracker                                        |
| 2    | Approving the Cloudflare OAuth prompt in a browser                                |
| 3    | Creating the KV namespace and recording its id                                    |
| 4    | Choosing and setting the admin setup token, before the first deploy               |
| 6    | Entering the setup token, choosing a password, pasting the destination credential |
| 7    | Adding the script tag to each site                                                |
