# Set up Formweh (instructions for an agent)

You are setting up Formweh, self-hosted forms, waitlists and surveys, on your
human's own Cloudflare account. One Worker, one D1 database, no build step;
it fits in Cloudflare's free tier. Follow the steps in order. Every step has
a check. If a check fails, stop and show your human the exact error; do not
improvise. (One exception: the deploy check in step 4 is allowed its retry
window.)

Humans: the README covers this same setup for people, with a one-click
button: https://github.com/duskresearch/formweh#readme

## Ask your human first

1. Nothing before deploying. Formweh's password is created in the browser on
   first run, by them.
2. Optional, for later: a custom subdomain for forms (for example
   forms.example.com) on a Cloudflare zone in the same account.

## Prerequisites (check, do not assume)

- Node 18 or newer: `node --version`
- Wrangler authenticated to the right account: `npx wrangler whoami`
  If not logged in, have your human run `npx wrangler login` themselves; it
  opens a browser. Never handle their Cloudflare credentials yourself.

## Steps

1. Get the code and dependencies.

       git clone https://github.com/duskresearch/formweh.git
       cd formweh && npm install

   Check: `ls migrations` lists numbered .sql files (at least 0001_init.sql).

2. Create the database and wire its id into the config.

       npx wrangler d1 create formweh-db

   The committed `wrangler.jsonc` contains the PROJECT'S OWN `database_id`
   and a `routes` block for formweh.com; both belong to the project's own
   instance, not yours. REPLACE the `database_id` with the id you just
   created, and DELETE the whole `routes` block.
   Check: `grep -o '"database_id": *"[0-9a-f-]*"' wrangler.jsonc` prints YOUR
   new id, and `grep -c '"routes"' wrangler.jsonc` prints 0.
   If `d1 create` says the name is taken, the human already runs Formweh. Ask
   whether this is a second instance; if so, pick fresh names for BOTH the
   worker (`name`) and the database, and never point a new instance at an
   existing database.

3. Build the tables.

       npx wrangler d1 migrations apply formweh-db --remote

   Check: the output lists the migrations as applied.

3b. Store a one-time claim token so only your human can finish setup. It is
   not a password; it burns on use.

       TOKEN=$(openssl rand -hex 16)
       npx wrangler d1 execute formweh-db --remote --command "INSERT INTO settings (key,value) VALUES ('setup_token','$TOKEN')"

   Check: the command reports 1 row written.

4. Deploy.

       npx wrangler deploy

   The output ends with a `*.workers.dev` URL; call it $APP. A warning about
   preview_urls can be ignored. A fresh workers.dev hostname can 404 for the
   first ~30 seconds while DNS propagates, so retry this check for up to a
   minute before treating it as a failure:

       curl -s -o /dev/null -w '%{http_code}' $APP/login   # expect 200

5. Hand over, and let your human claim it. Give them exactly this link:

       $APP/login?key=$TOKEN

   They open it, choose their password in the browser, and land in their
   dashboard. The token burns on use, so nobody else can claim the instance
   even if they find the URL first.
   Check, after they confirm: `curl -s $APP/login | grep -c 'Choose a password'`
   prints 0 (the setup form is gone; only login remains).

6. Prove a form works end to end. Have them create a form in the dashboard
   (or use a template), then:

       curl -s -H 'content-type: application/json' -H 'accept: application/json' \
         -d '{"email":"test@example.com","message":"hello"}' $APP/f/<slug>

   Check: the response is `{"ok":true,...}` and the submission appears in the
   inbox. Delete the test submission afterwards.

7. Custom domain, only if wanted: the Worker -> Settings -> Domains & Routes
   -> Add -> Custom Domain in the Cloudflare dashboard. Do NOT hand-edit a
   `routes` array into wrangler.jsonc. Verify DNS with `@1.1.1.1`, never the
   local resolver (a premature local lookup caches the miss for a long time).

8. Optional niceties, all from the dashboard Settings page: email
   notifications (through their own Cloudflare Email Service), Slack, Discord
   or webhook notifications, Turnstile spam protection (both keys), and the
   read-only HTTP API token.

## Troubleshooting

- "Database not initialised. Run the migrations": step 3 was skipped, or ran
  without `--remote`.
- Real submissions landing in the spam folder: Turnstile keys are set but the
  form never shows the widget. Hosted forms need BOTH keys; bring-your-own
  forms are only checked when they actually send a token.
- "exceeded D1's free tier daily limit": another D1 database on the same
  account spent the shared daily allowance; it resets at midnight UTC.
- Custom domain 522 or 1016: the zone is not in this account, or conflicting
  DNS records exist. Fix in the Cloudflare dashboard.

## Do not

- Leave the deploy unclaimed: step 5 happens immediately after step 4.
- Add a `routes` array to wrangler.jsonc.
- Put any secret in wrangler.jsonc.
- Point a new instance at an existing database.
- Continue past a failed check (step 4's retry window excepted).
