Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions .devcontainer/AUTH-PERSISTENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
`<ORG>_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

Expand Down
5 changes: 3 additions & 2 deletions .devcontainer/on-create/setup-git-credentials.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)..."

Expand All @@ -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)"
41 changes: 31 additions & 10 deletions .devcontainer/scripts/git-credential-org-router.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "!<this script>"
#
# 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 `<org>=<ENV_VAR>` line in the host map file (for tokens whose env
# var name doesn't follow the convention). Host-managed, not in git.
# 2. Convention: <ORG>_GITHUB_TOKEN, where <ORG> 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

Expand All @@ -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"
18 changes: 10 additions & 8 deletions .devcontainer/secrets.example
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ORG>_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)
# <ORG>_GITHUB_TOKEN= # one per org with push rights; name per convention or map

# ── AI Tools ─────────────────────────────────────────────────────────────────

Expand Down
Loading