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
25 changes: 25 additions & 0 deletions .devcontainer/AUTH-PERSISTENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ provider names Octopus recognizes are `codex`, `gemini`, `opencode`, `copilot`,
the list — it's the orchestrator.** Recognized aliases: `claude`/`anthropic`/
`sonnet`, `codex`/`openai`, `gemini`/`google`, `local`→`ollama`.

## GitHub push auth (credential routing by org)

`git push` over HTTPS needs a token, and different GitHub orgs need different ones
(your personal `GITHUB_TOKEN` is read-only on org repos). Rather than configure each
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).
- `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):

| 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.

## Where we track keys / tokens

- **`secrets.example`** is the registry of every key the dev container expects,
Expand Down
3 changes: 3 additions & 0 deletions .devcontainer/on-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ optional() {
source "$1" || echo "⚠️ $(basename "$1") failed; continuing setup without it"
}

# Configure GitHub credential routing (org → token), so `git push` just works per repo
optional /workspace/.devcontainer/on-create/setup-git-credentials.sh

# Install Biome
optional /workspace/.devcontainer/on-create/setup-biome.sh

Expand Down
20 changes: 20 additions & 0 deletions .devcontainer/on-create/setup-git-credentials.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
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.

echo "🔑 Configuring GitHub credential routing (org → token)..."

helper="/workspace/.devcontainer/scripts/git-credential-org-router.sh"
chmod +x "$helper" 2>/dev/null || true

# Pass the repo path (hence org) to the helper, and replace any prior helper for
# this host context with ours.
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"
36 changes: 36 additions & 0 deletions .devcontainer/scripts/git-credential-org-router.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# git-credential-org-router — hand git the right GitHub token based on the repo's org.
#
# Wired up globally by .devcontainer/on-create/setup-git-credentials.sh:
# 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.
#
# Add an org by adding a case below + the matching token to your devcontainer secrets.

[ "${1:-}" = "get" ] || exit 0

host=""
path=""
while IFS='=' read -r key value; do
case "$key" in
host) host="$value" ;;
path) path="$value" ;;
esac
done

[ "$host" = "github.com" ] || exit 0
org="${path%%/*}"

case "$org" in
Blueprint-Talent) token="${BTG_GITHUB_TOKEN:-}" ;;
confiador) token="${CONFIADOR_GITHUB_TOKEN:-}" ;;
*) token="${GITHUB_TOKEN:-}" ;;
esac

[ -n "$token" ] || exit 0
printf 'username=x-access-token\npassword=%s\n' "$token"
11 changes: 11 additions & 0 deletions .devcontainer/secrets.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
# The value in secrets.d/<project> wins when the same key appears in both files.
# Both files use the same KEY=value format (no export, no quotes needed).

# ── 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)

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

# Context7 MCP server (https://context7.com)
Expand Down
Loading