Runbooks
Operational runbooks for common maintenance tasks.
Regenerate DB Actions
bun run generate:actionsRun this after Drizzle schema changes. Commit generated files with the schema change so TypeScript and authz contract tests see the same database action surface as runtime.
Change Production Domain
Yayaw production is canonically served from https://yayaw.app.
- Attach the apex domain to the Vercel
yayawproject:
vercel domains add yayaw.app- Add
www.yayaw.appas a project domain redirecting toyayaw.appwith status308. - Keep retired
.eudomains only as project-domain redirects with status308: routeyayaw.euandwww.yayaw.eutoyayaw.app. Companion projects should follow the same pattern, for exampletable.yayaw.eutotable.yayaw.app. - Set production environment variables:
NEXT_PUBLIC_BASE_URL=https://yayaw.app
BETTER_AUTH_TRUSTED_ORIGINS=https://*.yayaw.app,https://*.vercel.app- Keep a durable
previewGit branch, attachpreview.yayaw.appas the branch-backed Vercel preview domain for that branch, and set previewNEXT_PUBLIC_BASE_URL:
NEXT_PUBLIC_BASE_URL=https://preview.yayaw.app- Do not keep retired preview hosts as active preview domains after
preview.yayaw.appis verified. - Redeploy the latest production and preview deployments so builds receive the public base URL.
- Update third-party callbacks and webhooks that store absolute origins, such as OAuth providers, Stripe webhooks, billing portal return URLs, email links, and analytics/site settings.
Deploy Self-Hosted Runtime
- Copy the example file and fill production secrets outside git:
cp .env.self-host.example .env.self-host
perl -0pi -e "s/^BETTER_AUTH_SECRET=$/BETTER_AUTH_SECRET=$(openssl rand -hex 32)/m" .env.self-host- Run the schema sync and seed setup:
docker compose --env-file .env.self-host -f docker-compose.self-host.yml --profile setup run --rm migrate- Start or update the runtime:
docker compose --env-file .env.self-host -f docker-compose.self-host.yml up --build -d- Confirm Caddy can reach the app and that
Host/X-Forwarded-*headers are preserved by opening the configuredDEPLOYMENT_URL. - Upload a media asset and confirm it is served from the configured
STORAGE_PUBLIC_BASE_URL. - Confirm
bun run worker:page-aiis running through the composeworkerservice whenPAGE_AI_QUEUE_DRIVER=db-worker. - Back up Postgres and object storage together before every risky release.
Update Seeded Feature Flags
- Edit
src/lib/scripts/seed.ts. - Apply the seed:
bun run seed- Re-run app quality checks:
bun run check
bunx tsc --noEmit
bun run buildMirror Yayaw Table Docs into CMS
Use the MCP-backed migration script only when an operator intentionally needs a
CMS mirror of the Fumadocs Yayaw Table docs under https://yayaw.app/table/docs.
The source of truth remains content/docs/{en,fr}/table. Keep the Table landing
and example pages authored directly in the CMS.
Dry-run the migration first:
YAYAW_MCP_LOCAL_USER_ID=<superadmin-user-id> \
YAYAW_MCP_ENV_FILE=/path/to/yayaw-env.txt \
bun --conditions react-server --env-file=.env \
src/lib/scripts/mcp/migrate-table-pages-to-cms.tsApply and publish the CMS pages after the dry-run is clean:
TABLE_CMS_APPLY=1 TABLE_CMS_PUBLISH=1 \
YAYAW_MCP_LOCAL_USER_ID=<superadmin-user-id> \
YAYAW_MCP_ENV_FILE=/path/to/yayaw-env.txt \
bun --conditions react-server --env-file=.env \
src/lib/scripts/mcp/migrate-table-pages-to-cms.tsThe script clones the public Yayaw-Table source, builds localized Puck page
documents for /table/docs and every /table/docs/* guide, then creates,
validates, and publishes them through the local stdio MCP server. Set
YAYAW_TABLE_SOURCE_DIR to reuse an existing checkout or YAYAW_TABLE_SOURCE_REF
to migrate a specific source branch. Set YAYAW_MCP_ENV_FILE when the local MCP
server should use an environment file other than .env. Set
YAYAW_MCP_REQUEST_TIMEOUT_MS if a slow tunnel or large CMS dataset needs more
than the default five-minute MCP request timeout. Set
TABLE_CMS_SKIP_EXISTING_PAGE_SCAN=1 to skip the non-essential global /table
page scan when running through a slow tunnel; each page is still checked by slug
before create/update. Set
TABLE_CMS_INCLUDE_SITE_PAGES=1 only if you intentionally want to regenerate
/table and /table/example from the source project instead of keeping those
pages CMS-authored.
Sync Yayaw Table Fumadocs Subproject
Use the Fumadocs subproject when Yayaw itself needs versioned developer docs for
Yayaw Table. These pages live in content/docs/{en,fr}/table and serve from
/[locale]/docs/table/*. This is distinct from the public CMS route
/[locale]/table/docs/*.
When importing from the upstream Yayaw-Table repository:
- Copy English
content/docs/*.mdxfiles tocontent/docs/en/table. - Copy French
content/docs/*.fr.mdxfiles tocontent/docs/fr/table, removing the.frsuffix. - Copy
content/docs/meta.jsontocontent/docs/en/table/meta.jsonandcontent/docs/meta.fr.jsontocontent/docs/fr/table/meta.json. - Rewrite internal links from
/docs/...to relative links such as./setupso the pages resolve under/docs/table. - Keep
content/docs/en/table/meta.jsonandcontent/docs/fr/table/meta.jsonin the same order, then runbun run docs:check-translations.
Validate Billing Webhooks in Staging
- Better Auth Stripe plugin webhook:
stripe listen --forward-to https://<staging-domain>/api/auth/stripe/webhook- Custom one-time webhook:
stripe listen --forward-to https://<staging-domain>/api/billing/stripe/webhook- Trigger events:
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted
stripe trigger invoice.payment_failed
stripe trigger invoice.paidinvoice.payment_failed and invoice.paid must reach the Better Auth Stripe
plugin endpoint at /api/auth/stripe/webhook. The custom one-time endpoint at
/api/billing/stripe/webhook intentionally processes
checkout.session.completed only.
Replay Failed One-Time Webhooks
When one-time webhook processing fails due to a transient issue, replay failed events:
bun run billing:replay-webhooksOptional limit:
bun run billing:replay-webhooks -- --limit=20Regenerate Fumadocs Source Artifacts
bun run docs:generateRegenerate LLM Assistant Files
bun run docs:llm:generateRun this after changing content/llm/llm-source.md. Do not edit AGENTS.md,
GEMINI.md, or .github/copilot-instructions.md manually.
Run a Full English Docs Pass
Use this when making broad documentation changes:
bun run docs:generate
bun run docs:check-links
bun run docs:check-translations
bun run docs:llm:generate
bun run docs:llm:check
bun run checkIf the docs change affects routes, imports, or MDX rendering, also run:
bunx tsc --noEmit
bun run buildKeep English docs canonical. When localized documentation is in scope, update
the French mirror in the same PR and keep docs:check-translations passing.
Manage Production MCP Keys
Issue a production MCP key for a user:
bun run mcp:key -- issue --email admin@example.com --permissions read,write,publish,adminRevoke a production MCP key:
bun run mcp:key -- revoke --key-id <api-key-id>Rotate keys by issuing the replacement first, updating the client secret, verifying yayaw_status, then revoking the old key.
Validate Documentation Integrity
bun run docs:check-links
bun run docs:check-translations
bun run docs:llm:checkIf docs:llm:check fails, regenerate with bun run docs:llm:generate and
review the generated assistant-file diff before committing.
Verify GitHub Code Access
- Confirm
/dashboard/admin/billing-settingshas GitHub repository access enabled. - Confirm repository, GitHub App ID, and installation ID are present.
- Confirm
BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEYexists in the target deployment environment. - Use a paid test organization with
code-access:read. - Submit a GitHub username from
/dashboard/organization/code-access. - Confirm
code_access_github_accountsrecords the invitation or active access state. - Confirm GitHub shows the repository invitation with read-only access.
For staging without a GitHub App, use BILLING_CODE_ACCESS_GITHUB_TOKEN only as
an explicit temporary fallback.
Verify Media Storage
- Confirm the selected storage provider variables are configured:
STORAGE_PROVIDER=supabasewith Supabase keys, orSTORAGE_PROVIDER=s3with S3/MinIO keys andSTORAGE_PUBLIC_BASE_URL. - Upload an image from
/dashboard/content/media. - Confirm a
media_assetsrow is created for the active organization. - Confirm the public URL loads.
- Open the page editor and bind the asset to an image field.
- Publish the page and confirm the public page renders the selected asset.
- If thumbnails are enabled for the asset type, confirm a thumbnail URL is created or a non-blocking thumbnail failure is recorded.
Verify Manual Public Domain DNS
- Set
PUBLIC_DOMAIN_PROVIDER=manual-dns. - Configure
PUBLIC_DOMAIN_CNAME_TARGETorPUBLIC_DOMAIN_IPV4_TARGETS. - Add a domain from organization settings or
yayaw_org_domain_add. - Publish the TXT challenge shown in the dashboard.
- Point the hostname to the reverse proxy.
- Run the check/verify action and confirm the domain becomes
verified. - Open the custom host and confirm public pages render while
/dashboard,/auth,/api,/docs,/o, and/ingeststay blocked.
Reset Local Database
bun run db:resetTroubleshooting Build Issues
- Regenerate artifacts:
bun run generate:actions
bun run docs:generate- Re-run checks:
bun run check
bunx tsc --noEmit
bun run buildTroubleshoot esbuild / Drizzle Hangs
Symptoms:
bun run docs:generatedoes not finishbun run db:generateorbun run db:pushstays stuck
Actions:
- Re-run the command once to confirm timeout output from safe scripts.
- Reinstall dependencies:
rm -rf node_modules
bun install- Retry:
bun run docs:generate
bun run db:generate
bun run db:push