diff --git a/.devcontainer/AUTH-PERSISTENCE.md b/.devcontainer/AUTH-PERSISTENCE.md index db62fd7..0830c70 100644 --- a/.devcontainer/AUTH-PERSISTENCE.md +++ b/.devcontainer/AUTH-PERSISTENCE.md @@ -136,21 +136,23 @@ repo, a **global credential helper routes by org**: - `scripts/git-credential-org-router.sh` — reads the repo's org from the push request (`credential.useHttpPath=true` passes the path) and emits the matching token from the environment. Tokens are read at push time and **never written to disk** (not in - `.git/config`, the remote URL, or output). + `.git/config`, the remote URL, or output). The helper contains **no org names** — it + resolves the token env var per org as: (1) the **host map file** below, else (2) the + `_GITHUB_TOKEN` **convention** (org upper-cased, non-alphanumeric → `_`), else + (3) `GITHUB_TOKEN` fallback. - `on-create/setup-git-credentials.sh` wires it into **global** git config. `~/.gitconfig` isn't a persisted volume, so re-applying on each create is what makes it survive rebuilds. -Routing (edit the `case` in the helper to change it): +**The org list lives on the host, not in git.** The optional map file +`~/.config/devcontainer/github-token-map` (mounted read-only at +`/run/devcontainer-config/`) holds `org=ENV_VAR_NAME` lines — one per org whose token var +doesn't match the convention. Keeping it on the host mount means client/org names never +land in any repo's history. -| Org | Token | -|---|---| -| `Blueprint-Talent/*` | `BTG_GITHUB_TOKEN` | -| `confiador/*` | `CONFIADOR_GITHUB_TOKEN` | -| everything else | `GITHUB_TOKEN` | - -**To add an org:** add one `case` line in the helper + put the matching `*_GITHUB_TOKEN` -in your host secrets (common or per-project). Nothing repo-specific to maintain — routing -is by the remote's org, and each repo's `/workspace` is its own bind mount. +**To add an org:** define its PAT env var in host secrets, then either name it per the +convention (no map entry needed) or add an `org=VAR` line to the host map file. Nothing +repo-specific, and nothing to commit — routing is by the remote's org, and each repo's +`/workspace` is its own bind mount. ## Where we track keys / tokens diff --git a/.devcontainer/on-create/setup-git-credentials.sh b/.devcontainer/on-create/setup-git-credentials.sh index 599522c..e215489 100644 --- a/.devcontainer/on-create/setup-git-credentials.sh +++ b/.devcontainer/on-create/setup-git-credentials.sh @@ -4,7 +4,8 @@ set -e # Route GitHub HTTPS auth to the right token per repo org (see # scripts/git-credential-org-router.sh). Global config so it applies in every # repo in the container; ~/.gitconfig isn't a persisted volume, so re-applying -# here on each create is what makes it survive rebuilds. +# here on each create is what makes it survive rebuilds. The org→token map (if +# any) is read from the host mount at push time — not configured here. echo "🔑 Configuring GitHub credential routing (org → token)..." @@ -17,4 +18,4 @@ git config --global credential.https://github.com.useHttpPath true git config --global --unset-all credential.https://github.com.helper 2>/dev/null || true git config --global credential.https://github.com.helper "!$helper" -echo "✅ GitHub pushes route by org: Blueprint-Talent→BTG_GITHUB_TOKEN, confiador→CONFIADOR_GITHUB_TOKEN, else→GITHUB_TOKEN" +echo "✅ GitHub pushes route by org (map → convention → GITHUB_TOKEN fallback)" diff --git a/.devcontainer/scripts/git-credential-org-router.sh b/.devcontainer/scripts/git-credential-org-router.sh index 14edf48..00ae86a 100755 --- a/.devcontainer/scripts/git-credential-org-router.sh +++ b/.devcontainer/scripts/git-credential-org-router.sh @@ -5,12 +5,19 @@ # git config --global credential.https://github.com.useHttpPath true # so $path carries the org # git config --global credential.https://github.com.helper "!" # -# Git calls this with the operation ("get"/"store"/"erase") as $1 and the request -# fields (protocol/host/path/...) as key=value lines on stdin. We answer only "get", -# read the org from the first path segment, and emit the matching token from the env. -# Tokens are never stored on disk — they're read fresh from the environment each call. +# Resolution order for an org — NO org names or client info live in this file / in git: +# 1. Map: a `=` line in the host map file (for tokens whose env +# var name doesn't follow the convention). Host-managed, not in git. +# 2. Convention: _GITHUB_TOKEN, where is the org upper-cased with every +# non-alphanumeric char -> '_' (e.g. "my-org" -> MY_ORG_GITHUB_TOKEN). +# 3. Fallback: GITHUB_TOKEN. +# The token VALUE is read from the environment at push time and never stored on disk. # -# Add an org by adding a case below + the matching token to your devcontainer secrets. +# Map file (on the host mount, not in git): ~/.config/devcontainer/github-token-map +# in-container: /run/devcontainer-config/github-token-map (override with GITHUB_TOKEN_MAP) +# format: one `org=ENV_VAR_NAME` per line; # comments and blank lines ignored. + +MAP_FILE="${GITHUB_TOKEN_MAP:-/run/devcontainer-config/github-token-map}" [ "${1:-}" = "get" ] || exit 0 @@ -25,12 +32,26 @@ done [ "$host" = "github.com" ] || exit 0 org="${path%%/*}" +[ -n "$org" ] || exit 0 + +varname="" +# 1. explicit map (host-managed, not in git) +if [ -f "$MAP_FILE" ]; then + varname="$(sed -E 's/[[:space:]]*#.*$//' "$MAP_FILE" \ + | grep -E "^[[:space:]]*${org}[[:space:]]*=" \ + | head -1 \ + | sed -E "s/^[[:space:]]*${org}[[:space:]]*=[[:space:]]*//; s/[[:space:]]*\$//")" +fi +# 2. naming convention +if [ -z "$varname" ]; then + conv="$(printf '%s' "$org" | tr '[:lower:]' '[:upper:]' | tr -c 'A-Z0-9' '_')" + conv="${conv%_}" + varname="${conv}_GITHUB_TOKEN" +fi -case "$org" in - Blueprint-Talent) token="${BTG_GITHUB_TOKEN:-}" ;; - confiador) token="${CONFIADOR_GITHUB_TOKEN:-}" ;; - *) token="${GITHUB_TOKEN:-}" ;; -esac +token="${!varname:-}" +# 3. fallback +[ -n "$token" ] || token="${GITHUB_TOKEN:-}" [ -n "$token" ] || exit 0 printf 'username=x-access-token\npassword=%s\n' "$token" diff --git a/.devcontainer/secrets.example b/.devcontainer/secrets.example index 5a4cd3d..2951276 100644 --- a/.devcontainer/secrets.example +++ b/.devcontainer/secrets.example @@ -9,14 +9,16 @@ # ── GitHub push auth (credential routing by org) ───────────────────────────── # git push routes to the right token based on the repo's GitHub org — see -# AUTH-PERSISTENCE.md "GitHub push auth". GITHUB_TOKEN is the fallback (and is also -# auto-passed from the host shell via devcontainer.json remoteEnv if set there). -# Add a token here for any org you push to, then add a case to -# scripts/git-credential-org-router.sh. - -# GITHUB_TOKEN= # default / fallback (personal; read-only on org repos) -# BTG_GITHUB_TOKEN= # Blueprint-Talent/* (push) -# CONFIADOR_GITHUB_TOKEN= # confiador/* (push) +# AUTH-PERSISTENCE.md "GitHub push auth". For each org you push to, define a PAT +# env var here; the router resolves which one per repo: +# 1. an org→var line in the host map file (see below), else +# 2. the _GITHUB_TOKEN naming convention (org upper-cased, non-alnum → _), else +# 3. GITHUB_TOKEN (fallback; also auto-passed from the host shell via remoteEnv). +# Org names are NOT stored in git — the map lives only on the host: +# ~/.config/devcontainer/github-token-map (one `org=ENV_VAR_NAME` per line) + +# GITHUB_TOKEN= # default / fallback (personal; read-only on org repos) +# _GITHUB_TOKEN= # one per org with push rights; name per convention or map # ── AI Tools ─────────────────────────────────────────────────────────────────