#!/bin/bash # Safe .env updater with atomic writes # Usage: ./scripts/update-env.sh KEY1=value1 KEY2=value2 ... set -euo pipefail # Configuration ENV_FILE="${ENV_FILE:-.env}" BACKUP_DIR="${BACKUP_DIR:-.}" # Validate arguments if [ $# -eq 0 ]; then echo "Usage: $0 KEY=value [KEY2=value2 ...]" echo "Example: $0 PANGOLIN_SITE_ID=abc123 PANGOLIN_NEWT_ID=xyz789" exit 1 fi # Validate .env file exists if [ ! -f "$ENV_FILE" ]; then echo "Error: $ENV_FILE not found" exit 1 fi # Create backup with timestamp TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="${BACKUP_DIR}/.env.backup.${TIMESTAMP}" cp "$ENV_FILE" "$BACKUP_FILE" echo "Backup created: $BACKUP_FILE" # Create temporary file TMP_FILE="${ENV_FILE}.tmp" cp "$ENV_FILE" "$TMP_FILE" # Process each KEY=value argument for arg in "$@"; do # Validate format if [[ ! "$arg" =~ ^[A-Z_][A-Z0-9_]*=.*$ ]]; then echo "Error: Invalid format '$arg'. Expected KEY=value" rm "$TMP_FILE" exit 1 fi KEY="${arg%%=*}" VALUE="${arg#*=}" echo "Updating $KEY" # Check if key exists if grep -q "^${KEY}=" "$TMP_FILE"; then # Update existing key (handles multi-line values via sed) # Use | as delimiter to avoid conflicts with / in values sed -i "s|^${KEY}=.*|${KEY}=${VALUE}|" "$TMP_FILE" else # Append new key echo "" >> "$TMP_FILE" echo "${KEY}=${VALUE}" >> "$TMP_FILE" fi done # Validate updated file (basic format check) if ! grep -qE '^[A-Z_][A-Z0-9_]*=' "$TMP_FILE"; then echo "Error: Updated file has invalid format" rm "$TMP_FILE" exit 1 fi # Atomic move (overwrites original) mv "$TMP_FILE" "$ENV_FILE" echo "✓ $ENV_FILE updated successfully" echo "Backup available at: $BACKUP_FILE" # Show updated values echo "" echo "Updated environment variables:" for arg in "$@"; do KEY="${arg%%=*}" VALUE="${arg#*=}" # Mask secrets (show first 8 chars only) if [[ "$KEY" == *"SECRET"* ]] || [[ "$KEY" == *"PASSWORD"* ]] || [[ "$KEY" == *"TOKEN"* ]]; then MASKED="${VALUE:0:8}..." echo " $KEY=$MASKED" else echo " $KEY=$VALUE" fi done