YYayaw

Runbooks

Operational runbooks for common maintenance tasks.

Regenerate DB Actions

bun run generate:actions

Run 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.

  1. Attach the apex domain to the Vercel yayaw project:
vercel domains add yayaw.app
  1. Add www.yayaw.app as a project domain redirecting to yayaw.app with status 308.
  2. Keep retired .eu domains only as project-domain redirects with status 308: route yayaw.eu and www.yayaw.eu to yayaw.app. Companion projects should follow the same pattern, for example table.yayaw.eu to table.yayaw.app.
  3. Set production environment variables:
NEXT_PUBLIC_BASE_URL=https://yayaw.app
BETTER_AUTH_TRUSTED_ORIGINS=https://*.yayaw.app,https://*.vercel.app
  1. Keep a durable preview Git branch, attach preview.yayaw.app as the branch-backed Vercel preview domain for that branch, and set preview NEXT_PUBLIC_BASE_URL:
NEXT_PUBLIC_BASE_URL=https://preview.yayaw.app
  1. Do not keep retired preview hosts as active preview domains after preview.yayaw.app is verified.
  2. Redeploy the latest production and preview deployments so builds receive the public base URL.
  3. 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

  1. 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
  1. 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
  1. Start or update the runtime:
docker compose --env-file .env.self-host -f docker-compose.self-host.yml up --build -d
  1. Confirm Caddy can reach the app and that Host/X-Forwarded-* headers are preserved by opening the configured DEPLOYMENT_URL.
  2. Upload a media asset and confirm it is served from the configured STORAGE_PUBLIC_BASE_URL.
  3. Confirm bun run worker:page-ai is running through the compose worker service when PAGE_AI_QUEUE_DRIVER=db-worker.
  4. Back up Postgres and object storage together before every risky release.

Update Seeded Feature Flags

  1. Edit src/lib/scripts/seed.ts.
  2. Apply the seed:
bun run seed
  1. Re-run app quality checks:
bun run check
bunx tsc --noEmit
bun run build

Mirror 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.ts

Apply 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.ts

The 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:

  1. Copy English content/docs/*.mdx files to content/docs/en/table.
  2. Copy French content/docs/*.fr.mdx files to content/docs/fr/table, removing the .fr suffix.
  3. Copy content/docs/meta.json to content/docs/en/table/meta.json and content/docs/meta.fr.json to content/docs/fr/table/meta.json.
  4. Rewrite internal links from /docs/... to relative links such as ./setup so the pages resolve under /docs/table.
  5. Keep content/docs/en/table/meta.json and content/docs/fr/table/meta.json in the same order, then run bun run docs:check-translations.

Validate Billing Webhooks in Staging

  1. Better Auth Stripe plugin webhook:
stripe listen --forward-to https://<staging-domain>/api/auth/stripe/webhook
  1. Custom one-time webhook:
stripe listen --forward-to https://<staging-domain>/api/billing/stripe/webhook
  1. 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.paid

invoice.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-webhooks

Optional limit:

bun run billing:replay-webhooks -- --limit=20

Regenerate Fumadocs Source Artifacts

bun run docs:generate

Regenerate LLM Assistant Files

bun run docs:llm:generate

Run 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 check

If the docs change affects routes, imports, or MDX rendering, also run:

bunx tsc --noEmit
bun run build

Keep 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,admin

Revoke 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:check

If docs:llm:check fails, regenerate with bun run docs:llm:generate and review the generated assistant-file diff before committing.

Verify GitHub Code Access

  1. Confirm /dashboard/admin/billing-settings has GitHub repository access enabled.
  2. Confirm repository, GitHub App ID, and installation ID are present.
  3. Confirm BILLING_CODE_ACCESS_GITHUB_APP_PRIVATE_KEY exists in the target deployment environment.
  4. Use a paid test organization with code-access:read.
  5. Submit a GitHub username from /dashboard/organization/code-access.
  6. Confirm code_access_github_accounts records the invitation or active access state.
  7. 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

  1. Confirm the selected storage provider variables are configured: STORAGE_PROVIDER=supabase with Supabase keys, or STORAGE_PROVIDER=s3 with S3/MinIO keys and STORAGE_PUBLIC_BASE_URL.
  2. Upload an image from /dashboard/content/media.
  3. Confirm a media_assets row is created for the active organization.
  4. Confirm the public URL loads.
  5. Open the page editor and bind the asset to an image field.
  6. Publish the page and confirm the public page renders the selected asset.
  7. 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

  1. Set PUBLIC_DOMAIN_PROVIDER=manual-dns.
  2. Configure PUBLIC_DOMAIN_CNAME_TARGET or PUBLIC_DOMAIN_IPV4_TARGETS.
  3. Add a domain from organization settings or yayaw_org_domain_add.
  4. Publish the TXT challenge shown in the dashboard.
  5. Point the hostname to the reverse proxy.
  6. Run the check/verify action and confirm the domain becomes verified.
  7. Open the custom host and confirm public pages render while /dashboard, /auth, /api, /docs, /o, and /ingest stay blocked.

Reset Local Database

bun run db:reset

Troubleshooting Build Issues

  1. Regenerate artifacts:
bun run generate:actions
bun run docs:generate
  1. Re-run checks:
bun run check
bunx tsc --noEmit
bun run build

Troubleshoot esbuild / Drizzle Hangs

Symptoms:

  • bun run docs:generate does not finish
  • bun run db:generate or bun run db:push stays stuck

Actions:

  1. Re-run the command once to confirm timeout output from safe scripts.
  2. Reinstall dependencies:
rm -rf node_modules
bun install
  1. Retry:
bun run docs:generate
bun run db:generate
bun run db:push