#!/usr/bin/env bash # setup-git-auth.sh — make git push/fetch fully non-interactive on this machine. # # Mike's requirement: git must NEVER sit at an interactive credential prompt # (Git Credential Manager popups hang automation/background pushes). This script # primes the git "store" credential helper with the shared azcomputerguru Gitea # API token (from the SOPS vault), scoped to each repo's actual remote host. # # Properties: # - Idempotent + fast-path: if every managed repo already has a stored # credential for its remote host, it exits WITHOUT touching the vault. # - Conservative: only switches a repo to the `store` helper when the current # helper is empty or the prompting GCM `manager` (so a Mac osxkeychain setup # that already works silently is left untouched). # - Fail-silent: always exits 0; never blocks a session. # # Runs from the SessionStart hook (backgrounded) and from onboarding. # See: .claude/memory/feedback_git_noninteractive_auth.md set -u # --- locate repo root + identity ------------------------------------------------ CT_ROOT="${CLAUDE_PROJECT_DIR:-}" if [ -z "$CT_ROOT" ]; then # two levels up from this script: .claude/scripts/ -> repo root CT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." 2>/dev/null && pwd)" fi IDENTITY="$CT_ROOT/.claude/identity.json" VAULT="$CT_ROOT/.claude/scripts/vault.sh" CRED_FILE="$HOME/.git-credentials" GIT_USER="azcomputerguru" # Extract a flat string field from identity.json without requiring jq. json_field() { grep -oE "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$IDENTITY" 2>/dev/null | head -1 | sed -E 's/.*:[[:space:]]*"([^"]*)"/\1/'; } VAULT_PATH="$(json_field vault_path)" # Candidate repos to make non-interactive: this repo + the vault repo. REPOS=("$CT_ROOT") [ -n "$VAULT_PATH" ] && [ -d "$VAULT_PATH/.git" ] && REPOS+=("$VAULT_PATH") # --- derive scheme + host (authority) from a remote URL ------------------------- remote_authority() { # echoes "scheme host[:port]" or nothing local url="$1" scheme rest auth host case "$url" in http://*|https://*) scheme="${url%%://*}";; *) return 0;; # ssh/git@ remotes don't use the credential store esac rest="${url#*://}" auth="${rest%%/*}" # strip path host="${auth##*@}" # strip any userinfo [ -n "$host" ] && printf '%s %s' "$scheme" "$host" } # Does the cred file already have an entry for this scheme://user@host ? have_cred() { # $1=scheme $2=host [ -f "$CRED_FILE" ] || return 1 grep -qE "^$1://$GIT_USER:[^@]*@$2$" "$CRED_FILE" 2>/dev/null } # --- fast path: everything already configured? --------------------------------- needs_priming=0 for repo in "${REPOS[@]}"; do url="$(git -C "$repo" remote get-url origin 2>/dev/null)" || continue read -r scheme host <<<"$(remote_authority "$url")" [ -n "${host:-}" ] || continue have_cred "$scheme" "$host" || needs_priming=1 done # --- fetch token only if needed ------------------------------------------------ TOKEN="" if [ "$needs_priming" -eq 1 ] && [ -f "$VAULT" ]; then TOKEN="$(bash "$VAULT" get-field services/gitea.sops.yaml credentials.api.api-token 2>/dev/null | tr -d '\r\n ')" # Fallback for machines missing PyYAML/yq: parse the full decrypted entry. if ! printf '%s' "$TOKEN" | grep -qE '^[0-9a-f]{40}$'; then TOKEN="$(bash "$VAULT" get services/gitea.sops.yaml 2>/dev/null | grep -oE 'api-token:[[:space:]]*[0-9a-f]{40}' | grep -oE '[0-9a-f]{40}' | head -1)" fi fi # --- configure each repo ------------------------------------------------------- touch "$CRED_FILE" 2>/dev/null && chmod 600 "$CRED_FILE" 2>/dev/null || true for repo in "${REPOS[@]}"; do url="$(git -C "$repo" remote get-url origin 2>/dev/null)" || continue read -r scheme host <<<"$(remote_authority "$url")" [ -n "${host:-}" ] || continue # Prime the store entry if missing and we have a token. if ! have_cred "$scheme" "$host" && [ -n "$TOKEN" ]; then printf '%s://%s:%s@%s\n' "$scheme" "$GIT_USER" "$TOKEN" "$host" >>"$CRED_FILE" fi # Only seize the helper away from the prompting GCM (or an unset helper). helper="$(git -C "$repo" config --get credential.helper 2>/dev/null)" case "$helper" in ""|*manager*) git -C "$repo" config --local --unset-all credential.helper 2>/dev/null || true git -C "$repo" config --local credential.helper store 2>/dev/null || true ;; esac done exit 0