From f30e95d9d0b5e14a444088260f9d1aecc0617c53 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Thu, 13 Aug 2026 10:58:55 -0500 Subject: [PATCH] feat: add per-agent git identity tooling [#442] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three scripts that let multiple AI agents share a single Linux account while maintaining per-identity attribution for all git operations: - bw-git-credential.sh: git credential helper that sources Gitea tokens from Bitwarden based on the repo's user.email config. Handles both push (reads repo config) and clone (reads AGENT_CLONE_AS env var). - agent-profile.sh: sourceable context switcher. Sets git author/ committer identity, registers tea login from BW, exports AGENT_NAME. - clone-as.sh: clone wrapper that authenticates with the agent's BW token, sets per-repo identity, strips token from remote URL. No SSH key juggling, no tokens on disk, no multiple Linux accounts. All credential material stays in Bitwarden. 💘 Generated with Crush Assisted-by: Crush:glm-5.2 --- scripts/agent-profile.sh | 135 ++++++++++++++++++++++++++++++++++ scripts/bw-git-credential.sh | 105 ++++++++++++++++++++++++++ scripts/clone-as.sh | 139 +++++++++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+) create mode 100644 scripts/agent-profile.sh create mode 100755 scripts/bw-git-credential.sh create mode 100755 scripts/clone-as.sh diff --git a/scripts/agent-profile.sh b/scripts/agent-profile.sh new file mode 100644 index 0000000..0d67c2c --- /dev/null +++ b/scripts/agent-profile.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# agent-profile.sh — switch agent identity context. +# +# Source this script to become a specific agent. Sets git author/committer +# identity, registers a tea login (from Bitwarden), and exports the agent +# name for other tools (bw-run.sh, clone-as.sh, etc.). +# +# Usage: +# . agent-profile.sh vp-techops +# . agent-profile.sh vp-secops +# . agent-profile.sh reachableceo (back to Charles's identity) +# +# Prerequisites: +# - Bitwarden session active (BW_SESSION exported) OR BW_CLIENTID/ +# BW_CLIENTSECRET in ~/.config/bw/env +# - Agent credentials provisioned in Bitwarden (see agent-identity-bootstrap.md) + +# Do NOT set -euo pipefail — this script is sourced, and the caller controls +# their own shell options. We guard each operation explicitly. + +AGENT_PROFILE_NAME="${1:-}" + +if [ -z "$AGENT_PROFILE_NAME" ]; then + echo "Usage: . agent-profile.sh " >&2 + echo " e.g. . agent-profile.sh vp-techops" >&2 + echo " . agent-profile.sh reachableceo" >&2 + # shellcheck disable=SC2317 # reachable when executed (not sourced) + return 1 2>/dev/null || exit 1 +fi + +# --------------------------------------------------------------------------- +# Display name mapping (for git author name) +# --------------------------------------------------------------------------- + +case "$AGENT_PROFILE_NAME" in + vp-techops) _display="VP TechOps" ;; + vp-secops) _display="VP SecOps" ;; + vp-techcompliance) _display="VP TechCompliance" ;; + coo) _display="Chief Operating Officer" ;; + svp-knel) _display="SVP KNEL" ;; + svp-tctc) _display="SVP TCTC" ;; + reachableceo) _display="Charles N Wyble" ;; + *) + _display="$AGENT_PROFILE_NAME" + ;; +esac + +# Domain mapping +_domain="turnsys.com" +if [ "$AGENT_PROFILE_NAME" = "reachableceo" ]; then + _email="reachableceo@turnsys.com" +else + _email="${AGENT_PROFILE_NAME}@${_domain}" +fi + +# --------------------------------------------------------------------------- +# Export agent identity environment variables +# --------------------------------------------------------------------------- + +export AGENT_NAME="$AGENT_PROFILE_NAME" +export AGENT_DISPLAY="$_display" +export AGENT_EMAIL="$_email" +export GIT_AUTHOR_NAME="$_display" +export GIT_COMMITTER_NAME="$_display" +export GIT_AUTHOR_EMAIL="$_email" +export GIT_COMMITTER_EMAIL="$_email" + +# --------------------------------------------------------------------------- +# Ensure Bitwarden session is active +# --------------------------------------------------------------------------- + +_bw_session_setup() { + if [ -n "${BW_SESSION:-}" ]; then + return 0 + fi + + local bw_env="${HOME}/.config/bw/env" + if [ -f "$bw_env" ]; then + # shellcheck disable=SC1090 + . "$bw_env" + fi + + if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then + bw login --apikey >/dev/null 2>&1 || true + BW_SESSION=$(BW_PASSWORD="${BW_PASSWORD:-}" bw unlock --raw 2>/dev/null || true) + if [ -n "$BW_SESSION" ]; then + export BW_SESSION + return 0 + fi + fi + + echo "agent-profile: WARNING — no BW session, tea login will not be configured" >&2 + return 1 +} + +# --------------------------------------------------------------------------- +# Register tea login for this agent (if credentials exist in BW) +# --------------------------------------------------------------------------- + +_tea_setup() { + # Only set up tea for agents that have Gitea credentials in BW + local token + token=$(bw get password "${AGENT_NAME} Gitea" 2>/dev/null || echo "") + if [ -z "$token" ]; then + # Not an error — some agents may not have Gitea access yet + return 0 + fi + + # Check if login already exists + if tea login list --output csv 2>/dev/null | grep -q "^${AGENT_NAME},"; then + # Already registered — just set as default + tea login default "$AGENT_NAME" >/dev/null 2>&1 || true + return 0 + fi + + # Register the login + tea login add \ + --name "$AGENT_NAME" \ + --url "https://git.knownelement.com" \ + --token "$token" \ + --ssh-host "git.knownelement.com" \ + >/dev/null 2>&1 || true + + tea login default "$AGENT_NAME" >/dev/null 2>&1 || true +} + +if _bw_session_setup; then + _tea_setup +fi + +# Clean up helper functions from the environment +unset -f _bw_session_setup _tea_setup 2>/dev/null || true +unset _display _email _domain 2>/dev/null || true + +echo "agent-profile: now operating as ${AGENT_PROFILE_NAME} (${AGENT_DISPLAY})" >&2 diff --git a/scripts/bw-git-credential.sh b/scripts/bw-git-credential.sh new file mode 100755 index 0000000..fb8a2b7 --- /dev/null +++ b/scripts/bw-git-credential.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# bw-git-credential.sh — Git credential helper that sources Gitea tokens from Bitwarden. +# +# This eliminates the need for per-agent SSH keys or on-disk tokens for git +# operations. Each repo has a git identity (user.email) that identifies which +# agent is working. This helper reads that identity, fetches the matching +# Gitea API token from Bitwarden, and provides it to git. +# +# Setup (once per environment, in ~/.gitconfig): +# [credential "https://git.knownelement.com"] +# helper = /path/to/bw-git-credential.sh +# +# How it works: +# - During git push (in an existing repo): reads `git config user.email` +# to determine the agent, fetches " Gitea" from Bitwarden. +# - During git clone (no repo yet): reads AGENT_CLONE_AS env var, which +# clone-as.sh sets before calling git clone. +# - If neither is set, exits silently (lets other credential helpers run). + +set -euo pipefail + +GITEA_HOST="git.knownelement.com" + +# --------------------------------------------------------------------------- +# Read git credential input from stdin +# --------------------------------------------------------------------------- + +declare -A cred +while IFS='=' read -r key value; do + # Empty line terminates the credential block + [ -z "$key" ] && break + cred["$key"]="$value" +done + +host="${cred[protocol]:-}://${cred[host]:-}" + +# Only handle our Gitea instance +if [ "${cred[host]:-}" != "$GITEA_HOST" ]; then + exit 0 +fi + +# --------------------------------------------------------------------------- +# Determine which agent identity to use +# --------------------------------------------------------------------------- + +agent="" + +# During clone: clone-as.sh sets this env var +if [ -n "${AGENT_CLONE_AS:-}" ]; then + agent="$AGENT_CLONE_AS" +else + # During push/fetch: read the repo's configured identity + agent_email=$(git config user.email 2>/dev/null || echo "") + if [ -n "$agent_email" ]; then + agent=$(echo "$agent_email" | cut -d@ -f1) + fi +fi + +if [ -z "$agent" ]; then + # No agent identity — let other credential helpers handle it + exit 0 +fi + +# --------------------------------------------------------------------------- +# Ensure Bitwarden session is active +# --------------------------------------------------------------------------- + +if [ -z "${BW_SESSION:-}" ]; then + # Try to establish from the env file + bw_env="${HOME}/.config/bw/env" + if [ -f "$bw_env" ]; then + # shellcheck disable=SC1090 + . "$bw_env" + fi + + if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then + bw login --apikey >/dev/null 2>&1 || true + export BW_SESSION + BW_SESSION=$(BW_PASSWORD="${BW_PASSWORD:-}" bw unlock --raw 2>/dev/null || true) + fi +fi + +if [ -z "${BW_SESSION:-}" ]; then + echo "bw-git-credential: no BW session — cannot fetch token for $agent" >&2 + exit 1 +fi + +# --------------------------------------------------------------------------- +# Fetch the Gitea token from Bitwarden +# --------------------------------------------------------------------------- + +item_name="$agent Gitea" +token=$(bw get password "$item_name" 2>/dev/null || echo "") + +if [ -z "$token" ]; then + echo "bw-git-credential: no Gitea token in BW for: $item_name" >&2 + exit 1 +fi + +# --------------------------------------------------------------------------- +# Output credentials for git +# --------------------------------------------------------------------------- + +echo "username=$agent" +echo "password=$token" diff --git a/scripts/clone-as.sh b/scripts/clone-as.sh new file mode 100755 index 0000000..41e6777 --- /dev/null +++ b/scripts/clone-as.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# clone-as.sh — clone a git repository as a specific agent identity. +# +# Wraps `git clone` so the clone authenticates with the agent's Gitea token +# (from Bitwarden) and the resulting repo has the correct per-repo git +# identity (user.name + user.email) set automatically. +# +# Usage: +# clone-as.sh [target-dir] +# +# Examples: +# clone-as.sh vp-techops https://git.knownelement.com/KNEL/PFVCluster.git +# clone-as.sh vp-techops https://git.knownelement.com/KNEL/PFVCluster.git ~/projects/pfv +# clone-as.sh reachableceo ssh://git@git.knownelement.com:29418/KNEL/PFVCluster.git +# +# After cloning, subsequent `git push` uses bw-git-credential.sh (configured +# in ~/.gitconfig) which reads the repo's user.email to fetch the token. + +set -euo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +# shellcheck disable=SC1091 +source "$HERE/lib/common.sh" + +############################################################################### + +if [ $# -lt 2 ]; then + sed -n '2,22p' "$0" + exit 1 +fi + +CLONE_AGENT="$1" +CLONE_URL="$2" +CLONE_DIR="${3:-}" + +# --------------------------------------------------------------------------- +# Display name mapping +# --------------------------------------------------------------------------- + +case "$CLONE_AGENT" in + vp-techops) display="VP TechOps" ;; + vp-secops) display="VP SecOps" ;; + vp-techcompliance) display="VP TechCompliance" ;; + coo) display="Chief Operating Officer" ;; + svp-knel) display="SVP KNEL" ;; + svp-tctc) display="SVP TCTC" ;; + reachableceo) display="Charles N Wyble" ;; + *) display="$CLONE_AGENT" ;; +esac + +if [ "$CLONE_AGENT" = "reachableceo" ]; then + email="reachableceo@turnsys.com" +else + email="${CLONE_AGENT}@turnsys.com" +fi + +# --------------------------------------------------------------------------- +# If SSH URL, clone directly (SSH key auth, no credential helper needed) +# --------------------------------------------------------------------------- + +if [[ "$CLONE_URL" == ssh://* ]] || [[ "$CLONE_URL" == git@* ]]; then + log_info "Cloning (SSH): $CLONE_URL as $CLONE_AGENT" + + if [ -n "$CLONE_DIR" ]; then + git clone "$CLONE_URL" "$CLONE_DIR" + cd "$CLONE_DIR" + else + git clone "$CLONE_URL" + # Derive repo dir name from URL + repo_base=$(basename "$CLONE_URL" .git) + cd "$repo_base" + fi + + # Set per-repo identity + git config user.name "$display" + git config user.email "$email" + log_ok "Cloned to $(pwd) — identity: $display <$email>" + exit 0 +fi + +# --------------------------------------------------------------------------- +# HTTPS URL — need credential helper for clone authentication +# --------------------------------------------------------------------------- + +# Ensure Bitwarden session +if [ -z "${BW_SESSION:-}" ]; then + bw_env="${HOME}/.config/bw/env" + if [ -f "$bw_env" ]; then + # shellcheck disable=SC1090 + . "$bw_env" + fi + if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then + bw login --apikey >/dev/null 2>&1 || true + export BW_SESSION + BW_SESSION=$(BW_PASSWORD="${BW_PASSWORD:-}" bw unlock --raw 2>/dev/null || true) + fi +fi + +if [ -z "${BW_SESSION:-}" ]; then + die "No BW session — cannot fetch Gitea token for $CLONE_AGENT" +fi + +# Fetch token for clone auth +token=$(bw get password "$CLONE_AGENT Gitea" 2>/dev/null || echo "") +if [ -z "$token" ]; then + die "No Gitea token in BW for: $CLONE_AGENT Gitea" +fi + +log_info "Cloning (HTTPS): $CLONE_URL as $CLONE_AGENT" + +# Set AGENT_CLONE_AS so bw-git-credential.sh knows which agent this is +# (needed only if the credential helper is wired; we also inject directly) +export AGENT_CLONE_AS="$CLONE_AGENT" + +# Inject token into URL for clone auth, then strip after clone +# (avoids storing the token in .git/config) +url_path="${CLONE_URL#https://}" +injected_url="https://${CLONE_AGENT}:${token}@${url_path}" + +if [ -n "$CLONE_DIR" ]; then + git clone "$injected_url" "$CLONE_DIR" + cd "$CLONE_DIR" +else + git clone "$injected_url" + repo_base=$(basename "$CLONE_URL" .git) + cd "$repo_base" +fi + +# Strip the token from the remote URL (so it's not stored in .git/config) +remote_url=$(git remote get-url origin 2>/dev/null || echo "") +clean_url="https://${remote_url#https://*@}" +git remote set-url origin "$clean_url" + +# Set per-repo identity +git config user.name "$display" +git config user.email "$email" + +log_ok "Cloned to $(pwd) — identity: $display <$email>" +log_info "Remote URL sanitized (token stripped). Push will use bw-git-credential.sh."