Stub Stub

SETUP

One command. Then sell.

Two targets, same codebase. Pick one. Deploy creates the database, runs migrations, and mints secrets — nothing manual, nothing hardcoded. Agents: the machine-readable version of this page is llms.txt.

Path A — Cloudflare (zero ops)

Worker + D1 + R2 in your account. Free tier covers a small shop. You can also start from the one-click installer.

$ git clone https://github.com/butttons/stub && cd stub
$ cd apps/app && pnpm install && pnpm run deploy

That one command resolves the D1 database by name (creating it on first run), applies all migrations, mints BETTER_AUTH_SECRET iff missing, and deploys. Payment provider keys are optional — without them the shop runs on manual payments and the Pay buttons simply stay hidden.

Path B — your own box via celld (full control)

A Durable-Objects node on your hardware; state lives as SQLite cells inside your S3 bucket. Needs an S3 bucket with conditional writes (R2, AWS S3, Tigris — not B2/MinIO/Spaces) and celld ≥ 0.6.2 on the box.

$ cd apps/app && source .celld.env # CELLD_BUCKET + S3 creds, gitignored
$ pnpm run celld:deploy # merges .dev.vars secrets, uploads, node adopts
$ ssh box 'celld d1 migrations apply stub ~/stub-app.jsonc --bucket "$CELLD_BUCKET" …' # on-box: fleet routing is loopback-local

Set CELLD_SSH + CELLD_REMOTE_CONFIG and the migrate step runs itself after every deploy. Supervise with Docker (--restart unless-stopped --stop-timeout 60); expose with any reverse proxy or tunnel pointing at the node's port.

First login

Every fresh database boots with one admin: admin / admin123. The first login forces a password change before anything else loads. There is no seed data — add products and payment modes in admin → items. Until payment modes exist, the till can't complete sales.

Updates

Migrations are additive and idempotent — data is never touched. Pull and redeploy on either path; the deploy step migrates first.

$ git pull && cd apps/app && pnpm run deploy # Cloudflare
$ git pull && source .celld.env && pnpm run celld:deploy # celld

Troubleshooting

Deploy says “expected a D1 ID”

Old flow. pnpm run deploy resolves the database by name now — update and retry.

Local dev reads an empty/wrong DB

cf dev reads ./.wrangler/state but bare cf d1 --local defaults to the global state dir. Always migrate with pnpm db:migrate (pins --persist-to).

celld health hangs before first deploy

Normal on 0.6.x — a node with no deployment answers nothing, not even health. Deploy first, then gate.

celld d1 talks to a dead node

Fleet leases outlive dead nodes and loopback addresses don't route remotely. Run migrations on the box; one node per bucket prefix, always.

Worker throws about #impl on celld

Fixed in-repo: the entry exports a plain { fetch } object, never a class instance. Unbound dispatch breaks private fields.

Secrets vs plain vars

Publishable IDs (STRIPE_PUBLISHABLE_KEY, RAZORPAY_KEY_ID) are plain vars — they're client-side public. Only true secrets go in secrets. .dev.vars and .celld.env are gitignored; verify with git check-ignore before committing.

For agents

Start at llms.txt (repo laws, payments model, both deploy paths, conventions). Key facts: cf CLI only, never bare wrangler; single object params outside src/lib/data; Tailwind standard scale; dev ports 4871 (app) / 4873 (docs).