type: runbook
status: active
timestamp: 2026-06-20
tags: [runbook, auth, secrets, publish, deploy]

Auth setup — log in once, publish + deploy forever

Login commands + dashboard URLs for publish/deploy; tokens in envpact vault

Auth setup — log in once, publish + deploy forever

Run these commands in your terminal (Git Bash on Windows, or any POSIX shell). An AI agent should not run these because it would mean pasting credentials into chat. After each section completes, the relevant tool is authenticated on this machine and an agent can use it without seeing the token.

If you’re rotating a leaked credential: revoke first, reissue second, login third. The dashboard URL for revocation is at the top of each section.


Inventory

ToolWhy we need itToken nameWhere it goes
GitHub (gh)push, PR, repo create/rename, workflow_dispatchGITHUB_TOKEN (auto in CI)gh keyring (already done)
npmpublish 6 packagesNPM_TOKEN~/.npmrc + envpact
Cloudflare (wrangler)deploy 10 sites to Pages, R2 bucketsCLOUDFLARE_API_TOKEN + ACCOUNT_IDwrangler config + GH Actions org-secrets + envpact
Firebase (firebase)deploy oriz-journal hosting, manage Firestore rulesservice account JSONFIREBASE_SERVICE_ACCOUNT_ORIZ_APP GH secret + envpact
Turso (turso)the warm-cache for me.oriz.in lifestreamTURSO_AUTH_TOKEN_*envpact + Cloudflare Pages env
envpact (envpact-cli)the vault that holds everything elseENVPACT_VAULT_TOKEN~/.envpactrc
EmailJS (dashboard)contact-form deliveryservice ID + template ID + public keyenvpact
Web3Forms (dashboard)contact-form delivery (alt)access keyenvpact
reCAPTCHA v3 (dashboard)spam protection on contact formssite key + secretenvpact
Cronitor (dashboard)heartbeat alerts on broken ingestersper-monitor URLsenvpact
Lichess + Last.fm + ListenBrainz + Open Library + Hardcover (dashboards)lifestream ingest read tokensvariousenvpact

0. Verify GitHub already works

You’re already authenticated with gh. Sanity-check:

gh auth status

Should print ✓ Logged in to github.com account chirag127 (keyring). If it doesn’t:

gh auth login --hostname github.com --git-protocol https --web

A browser opens. Approve. Done.


1. npm — publish access

Dashboards: https://www.npmjs.com/login · https://www.npmjs.com/settings/chirag127/tokens

npm login

Browser flow opens. Authenticate as chirag127. After completion:

npm whoami
# → chirag127

If you’d rather use a granular token (recommended for CI):

  1. Open https://www.npmjs.com/settings/chirag127/tokens/granular-access-tokens/new
  2. Token name: oriz-publish-2026-06
  3. Expiration: 365 days
  4. Permissions: Read and write
  5. Packages: select @chirag127/firebase-init, auth-ui, contact-form, sidebar, oriz-family, oriz-kit once they exist (or “All packages owned by this user” if you want simplicity)
  6. Click Generate
  7. Copy the token, then:
    echo "//registry.npmjs.org/:_authToken=<paste-token-here>" >> ~/.npmrc
  8. Verify: npm whoami should print chirag127.

Then store the token in envpact (see §6) so CI can publish too.


2. Cloudflare — Pages deploys + R2

Dashboard: https://dash.cloudflare.com/profile/api-tokens

Wrangler login (interactive — easiest)

npx wrangler@latest login

Browser flow. Approve.

npx wrangler whoami
# → prints account email + ID

This is enough for everything you do from your laptop. CI (GitHub Actions) needs a separate API token because it has no browser.

API token (for CI / GitHub Actions)

  1. Open https://dash.cloudflare.com/profile/api-tokensCreate Token
  2. Use the “Edit Cloudflare Workers” template (covers Pages too)
  3. Add additional permissions if you want R2:
    • Account → Workers R2 Storage → Edit
  4. Token name: oriz-deploy-2026-06
  5. Save the token immediately — Cloudflare shows it once.
  6. Get your account ID:
    npx wrangler whoami | grep "Account ID"

Set as GitHub Actions org-level secrets so the master matrix deploy can use them:

gh secret set CLOUDFLARE_API_TOKEN  --org chirag127 --visibility all --body "<paste-token>"
gh secret set CLOUDFLARE_ACCOUNT_ID --org chirag127 --visibility all --body "<paste-id>"

3. Firebase — oriz-journal hosting + Firestore rules

Dashboards:

Local CLI

npm install -g firebase-tools
firebase login

Browser flow.

firebase projects:list
# → should include oriz-app
firebase use oriz-app

Service account JSON (for CI)

  1. Console → ⚙ → Project settings → Service accounts tab
  2. Click Generate new private key → downloads a JSON file
  3. Save as ~/secrets/firebase-oriz-app-2026-06.json (out of any git repo!)
  4. Push as a GitHub secret:
    gh secret set FIREBASE_SERVICE_ACCOUNT_ORIZ_APP \
      --repo chirag127/oriz \
      --body "$(cat ~/secrets/firebase-oriz-app-2026-06.json)"
  5. Delete the local JSON file once it’s in envpact + the GH secret:
    rm ~/secrets/firebase-oriz-app-2026-06.json

Public Firebase config (PUBLIC_FIREBASE_* env vars used in client bundles):

  1. Console → ⚙ → Project settings → General tab → scroll to Your apps
  2. Pick the web app → SDK setup and configuration → Config
  3. Copy the JSON; the values map 1:1 to env var names:
    apiKey            → PUBLIC_FIREBASE_API_KEY
    authDomain        → PUBLIC_FIREBASE_AUTH_DOMAIN  (= auth.oriz.in)
    projectId         → PUBLIC_FIREBASE_PROJECT_ID   (= oriz-app)
    storageBucket     → PUBLIC_FIREBASE_STORAGE_BUCKET
    messagingSenderId → PUBLIC_FIREBASE_MESSAGING_SENDER_ID
    appId             → PUBLIC_FIREBASE_APP_ID
  4. Store these in envpact (§6). They’re safe in client bundles — that’s why they’re prefixed PUBLIC_.

4. Turso — lifestream warm cache for me.oriz.in

Dashboard: https://app.turso.tech/

curl -sSfL https://get.tur.so/install.sh | bash
turso auth signup    # or `turso auth login` if you already have an account
turso db create lifestream
turso db tokens create lifestream                   # write token (server-only)
turso db tokens create lifestream --read-only       # read token (browser-safe)
turso db show lifestream                            # prints the libsql:// URL

You now have three values:

Store all three in envpact under the oriz-me project (§6). The me.oriz.in Cloudflare Pages env reads these at build time + edge runtime.


5. envpact — the vault that holds everything else

Repo: https://github.com/chirag127/envpact-cli Dashboard: https://github.com/chirag127/envpact-secrets (private)

npm install -g envpact-cli
envpact login

Browser flow → GitHub OAuth.

Setting a secret

cd /path/to/oriz-family-site         # any repo with an envpact project ID
envpact set NPM_TOKEN <paste-value>
envpact set CLOUDFLARE_API_TOKEN <paste-value>
# ...etc

Reading secrets into a local .env

npx envpact-cli@latest               # writes .env from the vault

This is the line in AGENTS.md every site already has. You don’t need to do this manually most of the time — prebuild scripts do it.

CI integration

In GitHub Actions, the family already uses chirag127/envpact-action@v0:

- uses: chirag127/envpact-action@v0
  with: { project: ${{ matrix.site }} }
  env: { ENVPACT_VAULT_TOKEN: ${{ secrets.ENVPACT_VAULT_TOKEN }} }

The org-level ENVPACT_VAULT_TOKEN secret needs to exist:

gh secret set ENVPACT_VAULT_TOKEN --org chirag127 --visibility all --body "<paste-vault-token>"

The vault token comes from https://github.com/chirag127/envpact-secrets → Settings → Personal access token (or whatever envpact’s docs say currently — check the README of envpact-cli; this is the single source of truth).


6. EmailJS — contact form delivery

Dashboard: https://dashboard.emailjs.com/

  1. Create an account (free tier: 200 emails/month).
  2. Add a service (Gmail, Outlook, custom SMTP).
  3. Create a template. Use placeholders {{from_name}} {{from_email}} {{subject}} {{message}}.
  4. Note three values:
    • Service ID (e.g. service_a1b2c3)
    • Template ID (e.g. template_x1y2z3)
    • Public Key (e.g. abc123def)
  5. Store in envpact:
    envpact set PUBLIC_EMAILJS_SERVICE_ID  service_a1b2c3
    envpact set PUBLIC_EMAILJS_TEMPLATE_ID template_x1y2z3
    envpact set PUBLIC_EMAILJS_PUBLIC_KEY  abc123def

These are safe to expose in browser bundles (that’s why PUBLIC_*) — EmailJS rate-limits per public key.


7. Web3Forms — contact form delivery (alt)

Dashboard: https://web3forms.com/

Some sites use Web3Forms instead of EmailJS (@chirag127/contact-form package supports it natively).

  1. Sign up with the email you want to receive form submissions on.
  2. Create an access key. (Free, unlimited.)
  3. Store in envpact (per-site):
    envpact set --project oriz-blog       PUBLIC_WEB3FORMS_KEY <paste-key>
    envpact set --project oriz-finance    PUBLIC_WEB3FORMS_KEY <paste-key>
    # ... etc per site that has a contact form

The key is safe in browser bundles because Web3Forms validates the domain it was submitted from on the server side. Do NOT need to gate it.


8. reCAPTCHA v3 — spam protection (optional)

Dashboard: https://www.google.com/recaptcha/admin/create

  1. Type: reCAPTCHA v3
  2. Domains: oriz.in, me.oriz.in, blog.oriz.in, etc. (one entry per site or a wildcard if Google allows)
  3. Note two values:
    • Site key (public, used in browser)
    • Secret key (server-only, used in Pages Functions)
  4. Store in envpact:
    envpact set PUBLIC_RECAPTCHA_SITE_KEY <paste-site-key>
    envpact set RECAPTCHA_SECRET_KEY     <paste-secret-key>

Used by functions/api/verify-recaptcha.ts in each site that opts in.


9. Cronitor — heartbeat alerts (optional, free 5 monitors)

Dashboard: https://cronitor.io/

  1. Sign up (free tier).
  2. Create monitors for: daily-build, sync-firestore, snapshot-weekly, build-resume, plus ingesters.
  3. Each monitor has a ping URL (e.g. https://cronitor.link/p/<id>/daily-build).
  4. Store the ping URLs in envpact:
    envpact set CRONITOR_DAILY_BUILD     https://cronitor.link/p/.../daily-build
    envpact set CRONITOR_SYNC_FIRESTORE  https://cronitor.link/p/.../sync-firestore

Add a final curl -fsSL "$CRONITOR_..." step in each GH Actions workflow. Failures DM you; absent pings DM you (heartbeat semantics).


10. Lifestream ingest read tokens (me.oriz.in only)

Each ingester needs read access to one external service. Get a token, store in envpact under the oriz-me project, NOT the master project.

ServiceWhereWhat kind of token
Last.fmhttps://www.last.fm/api/account/createAPI key (read-only by default)
ListenBrainzhttps://listenbrainz.org/profile/User token
Lichesshttps://lichess.org/account/oauth/tokenRead-only OAuth token
Open Library(no token needed for reads)
Hardcoverhttps://hardcover.app/account/apiAPI token
simklhttps://simkl.com/settings/developerClient ID + secret
AniListhttps://anilist.co/api/v2/oauth/clientClient ID for OAuth
Steam Webhttps://steamcommunity.com/dev/apikeyAPI key
Stravahttps://www.strava.com/settings/apiOAuth (paywalled for new devs since 2025; use Fitbit fallback)
Fitbithttps://dev.fitbit.com/apps/newOAuth refresh token
GitHub webhooksper-repo Settings → Webhookswebhook secret (for HMAC validation)
cd /c/D/oriz/repos/oriz/own/prod/apps/personal/oriz-cs-me-app
envpact set LASTFM_API_KEY            <paste>
envpact set LISTENBRAINZ_USER_TOKEN   <paste>
envpact set LICHESS_TOKEN             <paste>
envpact set HARDCOVER_TOKEN           <paste>
envpact set SIMKL_CLIENT_ID           <paste>
envpact set ANILIST_CLIENT_ID         <paste>
envpact set STEAM_API_KEY             <paste>
envpact set FITBIT_CLIENT_ID          <paste>
envpact set FITBIT_CLIENT_SECRET      <paste>
envpact set FITBIT_REFRESH_TOKEN      <paste>
envpact set GITHUB_WEBHOOK_SECRET     <paste>

Per the self-healing ingester contract, none of these go into code. Each ingester reads them from the env binding at runtime.


11. Lanyard (Discord presence) — me.oriz.in /api/now (optional)

Dashboard: https://discord.com/developers/applications

  1. Join the public Lanyard Discord server: https://discord.gg/UrXF2cfJ7F
  2. Get your Discord user ID: enable Developer Mode in Discord → right-click your avatar → Copy User ID
  3. No token needed — Lanyard’s API is public for users in the bot’s server.
  4. Store the user ID:
    cd /c/D/oriz/repos/oriz/own/prod/apps/personal/oriz-cs-me-app
    envpact set DISCORD_USER_ID <paste-user-id>

Order to do this in (first time)

If you’re starting from zero and want everything wired:

  1. ✓ §0 GitHub (already done)
  2. §5 envpact — get the vault working first; everything else stores into it
  3. §1 npm — so oriz-kit and the 5 packages can publish
  4. §2 Cloudflare — wrangler + GH Actions secrets so the deploy matrix works
  5. §3 Firebase — needed for oriz-journal hosting + auth on every site
  6. §6 EmailJS or §7 Web3Forms — pick one for contact forms
  7. §4 Turso — only if you’re starting the lifestream lifelong project on me.oriz.in
  8. §10 lifestream ingest tokens — only when you wire each ingester
  9. §8 reCAPTCHA, §9 Cronitor, §11 Lanyard — defer until specific features need them

Stop after §1–§3 if you just want “publish the packages + deploy the sites once.” Everything from §4 onwards is for specific features.


After auth, what an agent CAN do without seeing tokens

Once these commands have been run on your machine:

CommandReads which authAgent can run?
gh repo create / rename / deletegh keyringyes (with per-action confirm)
gh workflow rungh keyringyes
git push origin maingh credential helper / SSHyes
npm publish~/.npmrcyes
npx wrangler pages deploywrangler configyes
firebase deployfirebase loginyes
turso db ...turso authyes
envpact set / get~/.envpactrcyes

The agent never sees the actual token strings. It just runs the command; the underlying tool reads its own credential store.


Rotation cheatsheet (when a credential leaks)

If you suspect any token is compromised — e.g. you pasted it into a chat window — revoke + reissue in this order:

  1. Revoke at the dashboard URL listed at the top of the relevant section above.
  2. Reissue a fresh token at the same dashboard.
  3. Re-login locally (npm login, wrangler login, firebase login, etc.) OR overwrite the stored token (e.g. ~/.npmrc).
  4. Update envpact so CI gets the new value: envpact set <NAME> <new-token>.
  5. Verify with the tool’s whoami / sanity-check command from the section above.
  6. (Optional) Audit the dashboard’s recent-activity log for any uses of the old token between leak and revocation.

A leaked token is not an emergency if you rotate within minutes. It IS an emergency if you find it in a transcript a week later. Habit: treat any token that has ever entered a chat as compromised.


What this runbook does NOT cover

Add sections here as new tools come online.


Edit on GitHub · Back to index