#!/usr/bin/env bash # bw-run.sh — run a CLI command with credentials sourced from Bitwarden. # # Replaces the --env-file ~/.creds/.env pattern. Each agent identity # stores its own API keys/passwords in a Bitwarden collection. This script # fetches the credential, writes it to a temp env file, runs the CLI, and # cleans up — no credential material persists on disk. # # Usage: # bw-run.sh [args...] # # Examples: # bw-run.sh vp-techops redmine git.knownelement.com/reachableceo/redmine-cli:latest list --assigned-to-me -p 55 # bw-run.sh vp-techops discourse git.knownelement.com/reachableceo/discourse-cli:latest ls -c vp-techops # bw-run.sh reachableceo redmine git.knownelement.com/reachableceo/redmine-cli:latest whoami # # Prerequisites: # - BW_CLIENTID and BW_CLIENTSECRET exported (or in ~/.config/bw/env) # - Or BW_SESSION exported from a prior `bw unlock` # # Bitwarden item convention: # Collection: (or "shared" for cross-agent creds) # Item name: " " (e.g., "vp-techops Redmine") # Fields: URL, USERNAME, PASSWORD, API_KEY, and custom fields as needed set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" # shellcheck disable=SC1091 source "$HERE/lib/common.sh" ############################################################################### print_usage() { sed -n '2,26p' "$0" exit 0 } if [ $# -lt 3 ] || [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then print_usage fi AGENT="$1" SERVICE="$2" IMAGE="$3" shift 3 ITEM_NAME="${AGENT} ${SERVICE}" ############################################################################### # Ensure Bitwarden session is active ############################################################################### ensure_bw_session() { # If BW_SESSION is already set, trust it if [ -n "${BW_SESSION:-}" ]; then return 0 fi # Try machine-to-machine auth via client credentials if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then log_info "Authenticating to Bitwarden via API key..." if bw login --apikey >/dev/null 2>&1; then export BW_SESSION BW_SESSION=$(bw unlock --raw 2>/dev/null || true) if [ -z "$BW_SESSION" ]; then die "Bitwarden unlock failed after API-key login." fi return 0 fi fi # Try reading from the bw env file local bw_env="${HOME}/.config/bw/env" if [ -f "$bw_env" ]; then # shellcheck disable=SC1090 . "$bw_env" if [ -n "${BW_CLIENTID:-}" ] && [ -n "${BW_CLIENTSECRET:-}" ]; then log_info "Authenticating to Bitwarden via env file..." bw login --apikey >/dev/null 2>&1 || true export BW_SESSION BW_SESSION=$(bw unlock --raw 2>/dev/null || true) if [ -n "$BW_SESSION" ]; then return 0 fi fi fi die "No Bitwarden session. Set BW_SESSION or BW_CLIENTID/BW_CLIENTSECRET." } ############################################################################### ensure_bw_session log_info "Fetching credential: ${ITEM_NAME}" # Fetch the Bitwarden item and extract fields into env vars RAW_JSON=$(bw get item "$ITEM_NAME" 2>/dev/null) || die "Bitwarden item not found: ${ITEM_NAME}" # Build env file content from the JSON response ENV_CONTENT=$(echo "$RAW_JSON" | jq -r ' def safename(f): f | gsub("[^A-Za-z0-9_]"; "_") | ascii_upcase; .login as $login | # Standard fields ( if $login.uris[0].uri then "URL=\($login.uris[0].uri)\n" else "" end ) + ( if $login.username then "USERNAME=\($login.username)\n" else "" end ) + ( if $login.password then "PASSWORD=\($login.password)\n" else "" end ) + # Map known field names to common env var patterns ( .fields[]? | if .name == "API_KEY" or .name == "api_key" or .name == "apikey" then "API_KEY=\(.value)\n" elif .name == "token" or .name == "TOKEN" then "TOKEN=\(.value)\n" else "\(safename(.name))=\(.value)\n" end ) + # Also emit REDMINE_API_KEY / DISCOURSE_API_KEY etc. from the password field # (many CLI tools expect the key in the password field) "" ') || die "Failed to parse Bitwarden item JSON." if [ -z "$ENV_CONTENT" ]; then die "No credential fields found in Bitwarden item: ${ITEM_NAME}" fi # Also check if the password field IS the API key (common for CLI creds) # and add a service-specific key env var based on the service name SERVICE_UPPER=$(echo "$SERVICE" | tr '[:lower:]' '[:upper:]' | tr '-' '_') API_KEY_VAR="${SERVICE_UPPER}_API_KEY" # If the service-specific key var isn't already in ENV_CONTENT, try adding it from password if ! echo "$ENV_CONTENT" | grep -q "^${API_KEY_VAR}="; then PASSWORD_VAL=$(echo "$RAW_JSON" | jq -r '.login.password // empty') if [ -n "$PASSWORD_VAL" ]; then ENV_CONTENT="${API_KEY_VAR}=${PASSWORD_VAL} ${ENV_CONTENT}" fi fi # Write to temp file, cleaned up on exit ENVFILE=$(mktemp "/tmp/bw-${AGENT}-${SERVICE}-XXXXXX.env") trap 'rm -f "$ENVFILE"' EXIT chmod 600 "$ENVFILE" printf '%s' "$ENV_CONTENT" > "$ENVFILE" log_ok "Credential sourced. Running: ${IMAGE} $*" # Run the CLI with BW-sourced credentials docker run --rm --env-file "$ENVFILE" "$IMAGE" "$@"