Template
feat: add per-agent git identity tooling [#442]
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
This commit is contained in:
@@ -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 <agent-name>" >&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
|
||||
Executable
+105
@@ -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 "<agent> 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"
|
||||
Executable
+139
@@ -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 <agent> <repo-url> [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."
|
||||
Reference in New Issue
Block a user