status: active
timestamp: 2026-06-22
tags: [decision, env, secrets, single-source, automation, gh-org-secrets, minimum-manual]
Single env source: c:/D/oriz/.env ? auto-push to chirag127 GH Org Secrets ? apps consume at build
Master .env single source. GH Action pushes org secrets daily
Single env source: c:/D/oriz/.env ? GH Org Secrets
Decision
c:/D/oriz/.env (gitignored on master) is the single, authoritative source of every secret + config value used anywhere in the chirag127/oriz family.
A scheduled cron (.github/workflows/sync-env-to-org-secrets.yml on master) runs daily at 06:30 IST (or on-demand workflow_dispatch when c:/D/oriz/.env changes):
- Reads
templates/.env.exampleto enumerate canonical key names - For each key, reads the corresponding value from
c:/D/oriz/.env(loaded via secure GH-Action secret OR encrypted file — see “Bootstrapping” below) - Pushes each value to chirag127 GH Org Secrets:
gh secret set <KEY> --org chirag127 --visibility all --body "<VALUE>" - Every repo’s workflows + builds inherit the org secret automatically (no per-repo setup)
User mandate verbatim (2026-06-22 evening): “I want to set the single token for all of the authentication and all of the monetization and all of the everything. I want minimum number of environment variable to be set. … I will set them only one time. Throughout the organization of my GitHub. … The builder will generate the website and deploy it on whatever server or this repository’s .env file will also be served as a single source of truth for all of the environment variables. You will periodically push all of the environment variable from this repository to GitHub. … I don’t want to set environment variable manually for all of the apps and all of the websites.”
Bootstrap (one-time, manual — minimum manual work)
You manually populate c:/D/oriz/.env ONCE with the canonical values (~50-80 env vars total). Then never touch them per-repo again.
To run the sync script the first time:
- Generate a chirag127 GH Personal Access Token with
admin:orgscope (one-time, 1-year expiry) - Add it to
c:/D/oriz/.envasGH_ADMIN_PAT=ghp_... - Run
node scripts/sync-env-to-org-secrets.mjslocally — this pushes all current keys to org-level secrets - After local verification, the GH Action takes over on the daily cron
Sync script
c:/D/oriz/scripts/sync-env-to-org-secrets.mjs:
// 1. Parse templates/.env.example for canonical key names
// 2. Parse c:/D/oriz/.env for values
// 3. For each key, call: gh secret set <KEY> --org chirag127 --visibility all
// 4. Report diff (keys added, updated, deleted)
Bootstrapping the GH Action
The cron workflow needs access to c:/D/oriz/.env itself. Options:
A. encrypted-file approach (RECOMMENDED): encrypt .env with age or sops, commit the encrypted blob to master, decrypt in GH Action using a single bootstrap key stored as SOPS_AGE_KEY org secret. One bootstrap secret ? unlimited derived secrets.
B. manual-paste approach: GH Action accepts the .env content as an input on workflow_dispatch; you paste it once + it pushes all keys. Re-paste only when keys rotate.
Choosing (A): encrypted-file. Use sops + age. The unencrypted .env stays gitignored locally; the encrypted .env.enc is committed to master.
Consuming env vars in app builds
Every app’s CF Pages build inherits chirag127 org secrets automatically. Apps reference process.env.RAZORPAY_KEY_ID etc. — no per-app setup needed.
For client-side env vars (PUBLIC_*), Astro embeds them at build time via import.meta.env.PUBLIC_*. Same single source.
Implications
- Secret rotation: change once in
c:/D/oriz/.env+ commit the new encrypted file ? wait for daily cron OR trigger workflow_dispatch ? all 51+ repos rotated simultaneously - New secret: add to
templates/.env.example(key + comment) + add value toc:/D/oriz/.env+ sync ? instantly available org-wide - Audit: master commit log = secret rotation history
- No drift: per-repo manual secret writes are FORBIDDEN per
rules/security/github-org-level-secrets.md(already locked)
Supersedes-in-part
security/env-and-secrets-single-source.md — that file describes the two-track delivery (.env.example synced + GH Actions secrets set once). This file extends it with the AUTOMATED periodic-sync mechanism (was implicit; now explicit).
Cross-refs
- Env+secrets two-track decision ? [[security/env-and-secrets-single-source]]
- GH org-level secrets rule ? [[rules/github-org-level-secrets]]
- .env.example synced rule ? [[rules/env-example-synced-from-master]]
- No hardcoded secrets ? [[rules/no-hardcoded-secrets]]
Status: deployed 2026-06-22
First-run results (local bootstrap, 2026-06-22):
sops3.12.2 installed via wingetSecretsOPerationS.SOPSatC:\Users\C5420321\AppData\Local\Microsoft\WinGet\Packages\SecretsOPerationS.SOPS_*\sops.exeage1.3.1 installed via wingetFiloSottile.ageatC:\Users\C5420321\AppData\Local\Microsoft\WinGet\Packages\FiloSottile.age_*\age\age.exe- Age keypair generated at
c:/D/oriz/.sops-age-key.txt(gitignored). Public key:age1c40qjamejzrp9cajle9g0dss25mmsmyaq6uaa2pgmyr3pflsy4qspgw5c4 c:/D/oriz/.env(128 lines, 65 key=value pairs) encrypted ?c:/D/oriz/.env.enc(committed)SOPS_AGE_KEYpushed tochirag127/workspaceas a repo-level bootstrap secret (CI reads it from there)- Scope correction:
chirag127is a GitHub User account, not an Organization. Org-level secrets unavailable. Pivoted to per-repo Actions secrets across the oriz family (workspace + everyoriz-*repo = 42 repos). The scriptscripts/sync-env-to-org-secrets.mjsfans out automatically. - First sync pushed 31 secrets × 42 repos = 1302 repo-level secrets plus 1 bootstrap = 1335 total, 0 failures.
- One canonical key skipped automatically:
GITHUB_TOKEN(reserved name; GitHub auto-injects per workflow run). The script now hard-rejectsGITHUB_TOKENand anyGITHUB_*/ACTIONS_*prefix. - Cron:
30 1 * * *UTC (06:30 IST) daily, plusworkflow_dispatchand push triggers on.env.enc/templates/.env.example/ sync script changes. - Manual user step still required: generate a PAT with
reposcope at https://github.com/settings/tokens and rungh secret set GH_ADMIN_PAT --repo chirag127/workspace --body "ghp_...". The workflow fails fast with a clear error until that PAT is set. - 21 keys exist in
.env.encbut are not intemplates/.env.example(e.g.BING_SEARCH_API_KEY,APPWRITE_*,SSH_PRIVATE_KEY). They are listed by the script as warnings and not pushed until added to.env.example. Reconciling this drift is a follow-up task.