Created
February 17, 2026 17:23
-
-
Save adhishthite/982d7f3b2464fa1ea69eb1e1cfa08ca5 to your computer and use it in GitHub Desktop.
BWS .env push/pull scripts - sync secrets with Bitwarden Secrets Manager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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