Skip to content

Instantly share code, notes, and snippets.

@adhishthite
Created February 17, 2026 17:23
Show Gist options
  • Select an option

  • Save adhishthite/982d7f3b2464fa1ea69eb1e1cfa08ca5 to your computer and use it in GitHub Desktop.

Select an option

Save adhishthite/982d7f3b2464fa1ea69eb1e1cfa08ca5 to your computer and use it in GitHub Desktop.
BWS .env push/pull scripts - sync secrets with Bitwarden Secrets Manager
#!/usr/bin/env bash
# ============================================================================
# bws-env-push.sh — Push .env secrets INTO Bitwarden Secrets Manager
#
# Usage:
# ./bws-env-push.sh <.env path> <BWS project-id> [prefix]
#
# Examples:
# ./bws-env-push.sh .env 9a8b7c6d-1234-5678-abcd-ef0123456789
# ./bws-env-push.sh .env 9a8b7c6d-1234-5678-abcd-ef0123456789 ELASTICGPT_ANALYTICS_
#
# What it does:
# 1. Reads your .env file (skips comments, blank lines)
# 2. For each KEY=VALUE, creates a BWS secret named [PREFIX]KEY with that value
# 3. If a secret with that name already exists, updates it
# 4. Dry-run mode by default. Pass --execute to actually push.
#
# Prerequisites:
# brew install bitwarden/tap/bws jq
# export BWS_ACCESS_TOKEN="your-token-here"
# ============================================================================
set -euo pipefail
# --- Args -------------------------------------------------------------------
EXECUTE=false
POSITIONAL=()
for arg in "$@"; do
case $arg in
--execute) EXECUTE=true ;;
*) POSITIONAL+=("$arg") ;;
esac
done
ENV_FILE="${POSITIONAL[0]:-}"
PROJECT_ID="${POSITIONAL[1]:-}"
PREFIX="${POSITIONAL[2]:-}"
if [[ -z "$ENV_FILE" || -z "$PROJECT_ID" ]]; then
echo "Usage: $0 <.env path> <BWS project-id> [prefix] [--execute]"
echo ""
echo " --execute Actually push to BWS (default is dry-run)"
echo " prefix Optional prefix to prepend to each key name"
echo ""
echo "Example:"
echo " $0 .env 9a8b7c6d-... ELASTICGPT_ANALYTICS_ --execute"
exit 1
fi
# --- Preflight checks -------------------------------------------------------
if [[ ! -f "$ENV_FILE" ]]; then
echo "❌ File not found: $ENV_FILE"
exit 1
fi
if ! command -v bws &>/dev/null; then
echo "❌ bws CLI not found. Install: brew install bitwarden/tap/bws"
exit 1
fi
if ! command -v jq &>/dev/null; then
echo "❌ jq not found. Install: brew install jq"
exit 1
fi
if [[ -z "${BWS_ACCESS_TOKEN:-}" ]]; then
echo "❌ BWS_ACCESS_TOKEN not set."
echo " Run: export BWS_ACCESS_TOKEN=\"your-token-here\""
exit 1
fi
# --- Fetch existing secrets for dedup ----------------------------------------
echo "🔍 Fetching existing BWS secrets..."
declare -A EXISTING_SECRETS
while IFS= read -r line; do
key=$(echo "$line" | jq -r '.key // empty')
id=$(echo "$line" | jq -r '.id // empty')
if [[ -n "$key" && -n "$id" ]]; then
EXISTING_SECRETS["$key"]="$id"
fi
done < <(bws secret list --output json 2>/dev/null | jq -c '.[] | {key: .key, id: .id}')
echo " Found ${#EXISTING_SECRETS[@]} existing secrets."
# --- Parse .env and prepare operations ---------------------------------------
echo ""
if [[ "$EXECUTE" == "false" ]]; then
echo "📋 DRY RUN (pass --execute to push for real)"
fi
echo ""
created=0
updated=0
skipped=0
while IFS= read -r line || [[ -n "$line" ]]; do
# Skip blank lines
[[ -z "$line" ]] && continue
# Skip comments
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Parse KEY=VALUE
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*) ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
# Strip surrounding quotes
if [[ "$value" =~ ^\"(.*)\"$ ]]; then
value="${BASH_REMATCH[1]}"
elif [[ "$value" =~ ^\'(.*)\'$ ]]; then
value="${BASH_REMATCH[1]}"
fi
# Skip empty values
if [[ -z "$value" ]]; then
echo " ⏭ ${PREFIX}${key} (empty value, skipping)"
((skipped++))
continue
fi
bws_name="${PREFIX}${key}"
if [[ -v "EXISTING_SECRETS[$bws_name]" ]]; then
# Update existing
secret_id="${EXISTING_SECRETS[$bws_name]}"
if [[ "$EXECUTE" == "true" ]]; then
bws secret edit "$secret_id" --value "$value" --output json >/dev/null 2>&1
echo " ✏️ ${bws_name} (updated)"
else
echo " ✏️ ${bws_name} → UPDATE (exists, id: ${secret_id:0:8}...)"
fi
((updated++))
else
# Create new
if [[ "$EXECUTE" == "true" ]]; then
bws secret create "$bws_name" "$value" "$PROJECT_ID" --output json >/dev/null 2>&1
echo " ✅ ${bws_name} (created)"
else
echo " ✅ ${bws_name} → CREATE"
fi
((created++))
fi
fi
done < "$ENV_FILE"
# --- Summary -----------------------------------------------------------------
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
if [[ "$EXECUTE" == "true" ]]; then
echo "✅ Push complete."
else
echo "📋 Dry run complete. Run with --execute to push."
fi
echo " Created: $created"
echo " Updated: $updated"
echo " Skipped: $skipped (empty values)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [[ "$EXECUTE" == "false" && $((created + updated)) -gt 0 ]]; then
echo "To push for real:"
echo " $0 $ENV_FILE $PROJECT_ID ${PREFIX:+$PREFIX }--execute"
fi
#!/usr/bin/env bash
# ============================================================================
# bws-env-sync.sh — Pull BWS secrets into an existing .env, preserving structure
#
# Usage:
# ./bws-env-sync.sh <.env path> <BWS prefix>
#
# Example:
# ./bws-env-sync.sh ./apps/analytics/.env ELASTICGPT_ANALYTICS_
#
# Prerequisites:
# 1. Install BWS CLI:
# brew install bitwarden/tap/bws
#
# 2. Set your access token:
# export BWS_ACCESS_TOKEN="your-token-here"
# Or add to ~/.zshrc for persistence.
#
# 3. Have a .env file with empty/placeholder values like:
# # Okta Configuration
# NEXT_PUBLIC_OKTA_ISSUER_URL=""
# NEXT_PUBLIC_OKTA_CLIENT_ID=""
#
# # Elasticsearch Configuration
# ELASTICSEARCH_URL=""
# ELASTICSEARCH_API_KEY=""
# ELASTICSEARCH_INDEX="chats"
#
# NODE_ENV=development
#
# What happens:
# - Fetches all BWS secrets whose name starts with your prefix
# - Walks your .env line by line
# - If a key matches a BWS secret name, fills in the BWS value
# - Comments, blank lines, non-matching keys are left untouched
# - Backs up original to .env.bak, writes filled version to .env
# ============================================================================
set -euo pipefail
# --- Args -------------------------------------------------------------------
if [[ $# -lt 2 ]]; then
echo "Usage: $0 <.env path> <BWS prefix>"
echo "Example: $0 .env ELASTICGPT_ANALYTICS_"
exit 1
fi
ENV_FILE="$1"
PREFIX="$2"
# --- Preflight checks -------------------------------------------------------
if [[ ! -f "$ENV_FILE" ]]; then
echo "❌ File not found: $ENV_FILE"
exit 1
fi
if ! command -v bws &>/dev/null; then
echo "❌ bws CLI not found."
echo " Install: brew install bitwarden/tap/bws"
exit 1
fi
if ! command -v jq &>/dev/null; then
echo "❌ jq not found."
echo " Install: brew install jq"
exit 1
fi
if [[ -z "${BWS_ACCESS_TOKEN:-}" ]]; then
echo "❌ BWS_ACCESS_TOKEN not set."
echo " Run: export BWS_ACCESS_TOKEN=\"your-token-here\""
exit 1
fi
# --- Fetch secrets -----------------------------------------------------------
echo "🔑 Fetching BWS secrets with prefix: ${PREFIX}..."
declare -A SECRETS
secret_count=0
while IFS= read -r line; do
key=$(echo "$line" | jq -r '.key // empty')
value=$(echo "$line" | jq -r '.value // empty')
if [[ -n "$key" ]]; then
SECRETS["$key"]="$value"
((secret_count++))
fi
done < <(bws secret list --output json 2>/dev/null | jq -c '.[] | select(.key | startswith("'"$PREFIX"'")) | {key: .key, value: .value}')
if [[ $secret_count -eq 0 ]]; then
echo "⚠️ No secrets found with prefix '${PREFIX}'"
echo " Check your prefix and BWS_ACCESS_TOKEN."
echo ""
echo " Available prefixes (first 10):"
bws secret list --output json 2>/dev/null | jq -r '.[].key' | sed 's/_[^_]*$//' | sort -u | head -10
exit 1
fi
echo " Found $secret_count secrets."
# --- Backup original ---------------------------------------------------------
BACKUP="${ENV_FILE}.bak"
cp "$ENV_FILE" "$BACKUP"
echo "📋 Backed up: $ENV_FILE → $BACKUP"
# --- Process .env ------------------------------------------------------------
TEMP_FILE=$(mktemp)
filled=0
kept=0
empty_warning=()
while IFS= read -r line || [[ -n "$line" ]]; do
# Blank lines
if [[ -z "$line" ]]; then
echo "" >> "$TEMP_FILE"
continue
fi
# Comments
if [[ "$line" =~ ^[[:space:]]*# ]]; then
echo "$line" >> "$TEMP_FILE"
continue
fi
# Key=Value (handles KEY=value, KEY="value", KEY='value', KEY=)
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*) ]]; then
key="${BASH_REMATCH[1]}"
existing_value="${BASH_REMATCH[2]}"
if [[ -v "SECRETS[$key]" ]]; then
bws_value="${SECRETS[$key]}"
# Quote the value if it contains spaces or special chars
if [[ "$bws_value" =~ [[:space:]\#\$\&\|\;\>\<] ]]; then
echo "${key}=\"${bws_value}\"" >> "$TEMP_FILE"
else
echo "${key}=${bws_value}" >> "$TEMP_FILE"
fi
((filled++))
else
echo "$line" >> "$TEMP_FILE"
((kept++))
# Warn if value looks empty
stripped="${existing_value#\"}"
stripped="${stripped%\"}"
stripped="${stripped#\'}"
stripped="${stripped%\'}"
if [[ -z "$stripped" ]]; then
empty_warning+=("$key")
fi
fi
else
# Pass through anything else
echo "$line" >> "$TEMP_FILE"
fi
done < "$ENV_FILE"
# --- Write result ------------------------------------------------------------
mv "$TEMP_FILE" "$ENV_FILE"
# --- Summary -----------------------------------------------------------------
echo ""
echo "✅ Done."
echo " Filled from BWS: $filled"
echo " Kept as-is: $kept"
echo " Output: $ENV_FILE"
echo " Backup: $BACKUP"
if [[ ${#empty_warning[@]} -gt 0 ]]; then
echo ""
echo "⚠️ These keys had empty values and NO BWS match:"
for k in "${empty_warning[@]}"; do
echo " - $k"
done
echo " Add them to BWS with prefix '${PREFIX}' or fill manually."
fi
echo ""
echo "To revert: mv $BACKUP $ENV_FILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment