Files
Jyotisha/docs/operations/self-hosted-identity.md
linmeng f4e35974c6
Deploy staging to test server / deploy (push) Failing after 14m49s
Revert "merge: sync GitHub staging to Gitea"
This reverts commit a55c69115d, reversing
changes made to 02c9c9f3d6.
2026-07-30 14:38:17 +08:00

4.7 KiB

Self-hosted identity operations

Staging uses Better Auth and the private local PostgreSQL cluster for both identity and business data. Production remains on Supabase; this runbook does not authorize a production cutover.

Staging mode

Keep these values exactly as shown:

AUTH_PROVIDER=self-hosted
SELF_HOSTED_IDENTITY_ENABLED=true
AUTH_USER_ORIGIN=https://staging.jyotisha.chat

Staging has one browser identity surface on the main site. The same Better Auth user session serves ordinary pages and /admin; there is no independent admin origin, secret, cookie, or login host. Server routes translate that session into PostgreSQL request claims. Admin authorization then reads the persisted identity.users.role value and permits only admin; viewer and ordinary users receive 403. The main auth route continues to return 404 for Better Auth /api/auth/admin plugin endpoints, and unknown hosts fail closed with 421.

Use the tracked staging identity example as a list of names only. Replace bracketed values directly on the server and keep /opt/jyotisha-staging/.env.staging owned by deploy with mode 0600.

Generate BETTER_AUTH_USER_SECRET locally on the server:

openssl rand -base64 32

Do not reuse it as a PostgreSQL password. IDENTITY_DATABASE_URL, APP_DATABASE_URL, and ADMIN_DATABASE_URL use their matching passwords from .env.staging.database, percent-encoded only in each URL password component. All three must point to the private Compose hostname postgres:5432/jyotisha; never publish PostgreSQL on a host port.

The Resend key must be staging-only. RESEND_FROM_EMAIL must use a sender/domain verified in Resend. CI never receives this key and uses an in-memory sender. ADMIN_EMAILS remains relevant only to the legacy Supabase production path; it is not self-hosted admin authorization.

Validate without printing values:

cd /opt/jyotisha-staging
chmod 600 .env.staging
bash deploy/validate-staging-env.sh .env.staging

Migration and smoke checks

Apply the reviewed PostgreSQL migrations through the existing Migrate Staging Database workflow before deploying the web image. Better Auth users are transactionally projected into auth.users, which creates their business profile through the existing trigger.

After deployment, verify the single-domain contract:

curl -fsS https://staging.jyotisha.chat/login >/dev/null
test "$(curl -sS -o /dev/null -w '%{http_code}' https://staging.jyotisha.chat/api/admin/session)" = 401
test "$(curl -sS -o /dev/null -w '%{http_code}' https://staging.jyotisha.chat/api/account)" = 401

An anonymous /admin request redirects to /login. An authenticated non-admin, including a persisted viewer, must not render the admin layout and every /api/admin/* route must independently return 403. Promote a staging user only through a reviewed database operation; the persisted role must include admin before the main-site session can enter the backend.

Optional import rehearsal

Export Supabase Auth users to a JSON array in the supported fixture shape, then run a redacted dry-run first:

cd /opt/jyotisha-staging/frontend
node scripts/import-supabase-auth-users.mjs /secure/path/auth-users.json

The summary contains only counts. It preserves UUID, normalized email, verification timestamps, display metadata, and created/updated timestamps. It intentionally ignores passwords, sessions, JWTs, provider secrets, and Supabase platform fields.

Apply only after reviewing the dry-run and taking a local encrypted staging backup:

set -a
. ../.env.staging
set +a
node scripts/import-supabase-auth-users.mjs /secure/path/auth-users.json --apply
unset IDENTITY_DATABASE_URL

Reruns are idempotent by UUID and the whole import is transactional. Duplicate canonical emails abort before database writes.

Rollback and rotation

An application rollback must use a previously validated staging image and does not reverse database migrations. Existing self-hosted sessions and data remain in PostgreSQL; do not delete identity or business rows during application rollback. Returning staging to Supabase requires a separate reviewed data-reconciliation and provider-switch change.

Rotating BETTER_AUTH_USER_SECRET invalidates all self-hosted browser sessions, including admins. Restart the web service and verify login, anonymous admin API rejection, admin access, and viewer rejection. Rotate a leaked Resend key in Resend first, replace the server value, then restart. Never print old or new values.

Production AUTH_PROVIDER=self-hosted remains blocked until data reconciliation passes, production backups and restore drills exist, operational monitoring is ready, and a separate reviewed production cutover plan is approved.