mirror of https://github.com/langgenius/dify.git
465 lines
14 KiB
Bash
Executable File
465 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ================================================================
|
|
# Dify Environment Variables Synchronization Script
|
|
#
|
|
# Features:
|
|
# - Synchronize latest settings from .env.example to .env
|
|
# - Preserve custom settings in existing .env
|
|
# - Add new environment variables
|
|
# - Detect removed environment variables
|
|
# - Create backup files
|
|
# ================================================================
|
|
|
|
set -eo pipefail # Exit on error and pipe failures (safer for complex variable handling)
|
|
|
|
# Error handling function
|
|
# Arguments:
|
|
# $1 - Line number where error occurred
|
|
# $2 - Error code
|
|
handle_error() {
|
|
local line_no=$1
|
|
local error_code=$2
|
|
echo -e "\033[0;31m[ERROR]\033[0m Script error: line $line_no with error code $error_code" >&2
|
|
echo -e "\033[0;31m[ERROR]\033[0m Debug info: current working directory $(pwd)" >&2
|
|
exit $error_code
|
|
}
|
|
|
|
# Set error trap
|
|
trap 'handle_error ${LINENO} $?' ERR
|
|
|
|
# Color settings for output
|
|
readonly RED='\033[0;31m'
|
|
readonly GREEN='\033[0;32m'
|
|
readonly YELLOW='\033[1;33m'
|
|
readonly BLUE='\033[0;34m'
|
|
readonly NC='\033[0m' # No Color
|
|
|
|
# Logging functions
|
|
# Print informational message in blue
|
|
# Arguments: $1 - Message to print
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
# Print success message in green
|
|
# Arguments: $1 - Message to print
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
# Print warning message in yellow
|
|
# Arguments: $1 - Message to print
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
|
|
}
|
|
|
|
# Print error message in red to stderr
|
|
# Arguments: $1 - Message to print
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
}
|
|
|
|
# Check for required files and create .env if missing
|
|
# Verifies that .env.example exists and creates .env from template if needed
|
|
check_files() {
|
|
log_info "Checking required files..."
|
|
|
|
if [[ ! -f ".env.example" ]]; then
|
|
log_error ".env.example file not found"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f ".env" ]]; then
|
|
log_warning ".env file does not exist. Creating from .env.example."
|
|
cp ".env.example" ".env"
|
|
log_success ".env file created"
|
|
fi
|
|
|
|
log_success "Required files verified"
|
|
}
|
|
|
|
# Create timestamped backup of .env file
|
|
# Creates env-backup directory if needed and backs up current .env file
|
|
create_backup() {
|
|
local timestamp=$(date +"%Y%m%d_%H%M%S")
|
|
local backup_dir="env-backup"
|
|
|
|
# Create backup directory if it doesn't exist
|
|
if [[ ! -d "$backup_dir" ]]; then
|
|
mkdir -p "$backup_dir"
|
|
log_info "Created backup directory: $backup_dir"
|
|
fi
|
|
|
|
if [[ -f ".env" ]]; then
|
|
local backup_file="${backup_dir}/.env.backup_${timestamp}"
|
|
cp ".env" "$backup_file"
|
|
log_success "Backed up existing .env to $backup_file"
|
|
fi
|
|
}
|
|
|
|
# Detect differences between .env and .env.example (optimized for large files)
|
|
detect_differences() {
|
|
log_info "Detecting differences between .env and .env.example..."
|
|
|
|
# Create secure temporary directory
|
|
local temp_dir=$(mktemp -d)
|
|
local temp_diff="$temp_dir/env_diff"
|
|
|
|
# Store diff file path as global variable
|
|
declare -g DIFF_FILE="$temp_diff"
|
|
declare -g TEMP_DIR="$temp_dir"
|
|
|
|
# Initialize difference file
|
|
> "$temp_diff"
|
|
|
|
# Use awk for efficient comparison (much faster for large files)
|
|
local diff_count=$(awk -F= '
|
|
BEGIN { OFS="\x01" }
|
|
FNR==NR {
|
|
if (!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && /=/) {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1)
|
|
key = $1
|
|
value = substr($0, index($0,"=")+1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
|
|
env_values[key] = value
|
|
}
|
|
next
|
|
}
|
|
{
|
|
if (!/^[[:space:]]*#/ && !/^[[:space:]]*$/ && /=/) {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1)
|
|
key = $1
|
|
example_value = substr($0, index($0,"=")+1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", example_value)
|
|
|
|
if (key in env_values && env_values[key] != example_value) {
|
|
print key, env_values[key], example_value > "'$temp_diff'"
|
|
diff_count++
|
|
}
|
|
}
|
|
}
|
|
END { print diff_count }
|
|
' .env .env.example)
|
|
|
|
if [[ $diff_count -gt 0 ]]; then
|
|
log_success "Detected differences in $diff_count environment variables"
|
|
# Show detailed differences
|
|
show_differences_detail
|
|
else
|
|
log_info "No differences detected"
|
|
fi
|
|
}
|
|
|
|
# Parse environment variable line
|
|
# Extracts key-value pairs from .env file format lines
|
|
# Arguments:
|
|
# $1 - Line to parse
|
|
# Returns:
|
|
# 0 - Success, outputs "key|value" format
|
|
# 1 - Skip (empty line, comment, or invalid format)
|
|
parse_env_line() {
|
|
local line="$1"
|
|
local key=""
|
|
local value=""
|
|
|
|
# Skip empty lines or comment lines
|
|
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && return 1
|
|
|
|
# Split by =
|
|
if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then
|
|
key="${BASH_REMATCH[1]}"
|
|
value="${BASH_REMATCH[2]}"
|
|
|
|
# Remove leading and trailing whitespace
|
|
key=$(echo "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
value=$(echo "$value" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
|
|
if [[ -n "$key" ]]; then
|
|
echo "$key|$value"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Show detailed differences
|
|
show_differences_detail() {
|
|
log_info ""
|
|
log_info "=== Environment Variable Differences ==="
|
|
|
|
# Read differences from the already created diff file
|
|
if [[ ! -s "$DIFF_FILE" ]]; then
|
|
log_info "No differences to display"
|
|
return
|
|
fi
|
|
|
|
# Display differences
|
|
local count=1
|
|
while IFS=$'\x01' read -r key env_value example_value; do
|
|
echo ""
|
|
echo -e "${YELLOW}[$count] $key${NC}"
|
|
echo -e " ${GREEN}.env (current)${NC} : ${env_value}"
|
|
echo -e " ${BLUE}.env.example (recommended)${NC}: ${example_value}"
|
|
|
|
# Analyze value changes
|
|
analyze_value_change "$env_value" "$example_value"
|
|
((count++))
|
|
done < "$DIFF_FILE"
|
|
|
|
echo ""
|
|
log_info "=== Difference Analysis Complete ==="
|
|
log_info "Note: Consider changing to the recommended values above."
|
|
log_info "Current implementation preserves .env values."
|
|
echo ""
|
|
}
|
|
|
|
# Analyze value changes
|
|
analyze_value_change() {
|
|
local current_value="$1"
|
|
local recommended_value="$2"
|
|
|
|
# Analyze value characteristics
|
|
local analysis=""
|
|
|
|
# Empty value check
|
|
if [[ -z "$current_value" && -n "$recommended_value" ]]; then
|
|
analysis=" ${RED}→ Setting from empty to recommended value${NC}"
|
|
elif [[ -n "$current_value" && -z "$recommended_value" ]]; then
|
|
analysis=" ${RED}→ Recommended value changed to empty${NC}"
|
|
# Numeric check - using arithmetic evaluation for robust comparison
|
|
elif [[ "$current_value" =~ ^[0-9]+$ && "$recommended_value" =~ ^[0-9]+$ ]]; then
|
|
# Use arithmetic evaluation to handle leading zeros correctly
|
|
if (( 10#$current_value < 10#$recommended_value )); then
|
|
analysis=" ${BLUE}→ Numeric increase (${current_value} < ${recommended_value})${NC}"
|
|
elif (( 10#$current_value > 10#$recommended_value )); then
|
|
analysis=" ${YELLOW}→ Numeric decrease (${current_value} > ${recommended_value})${NC}"
|
|
fi
|
|
# Boolean check
|
|
elif [[ "$current_value" =~ ^(true|false)$ && "$recommended_value" =~ ^(true|false)$ ]]; then
|
|
if [[ "$current_value" != "$recommended_value" ]]; then
|
|
analysis=" ${BLUE}→ Boolean value change (${current_value} → ${recommended_value})${NC}"
|
|
fi
|
|
# URL/endpoint check
|
|
elif [[ "$current_value" =~ ^https?:// || "$recommended_value" =~ ^https?:// ]]; then
|
|
analysis=" ${BLUE}→ URL/endpoint change${NC}"
|
|
# File path check
|
|
elif [[ "$current_value" =~ ^/ || "$recommended_value" =~ ^/ ]]; then
|
|
analysis=" ${BLUE}→ File path change${NC}"
|
|
else
|
|
# Length comparison
|
|
local current_len=${#current_value}
|
|
local recommended_len=${#recommended_value}
|
|
if [[ $current_len -ne $recommended_len ]]; then
|
|
analysis=" ${YELLOW}→ String length change (${current_len} → ${recommended_len} characters)${NC}"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$analysis" ]]; then
|
|
echo -e "$analysis"
|
|
fi
|
|
}
|
|
|
|
# Synchronize .env file with .env.example while preserving custom values
|
|
# Creates a new .env file based on .env.example structure, preserving existing custom values
|
|
# Global variables used: DIFF_FILE, TEMP_DIR
|
|
sync_env_file() {
|
|
log_info "Starting partial synchronization of .env file..."
|
|
|
|
local new_env_file=".env.new"
|
|
local preserved_count=0
|
|
local updated_count=0
|
|
|
|
# Pre-process diff file for efficient lookup
|
|
local lookup_file=""
|
|
if [[ -f "$DIFF_FILE" && -s "$DIFF_FILE" ]]; then
|
|
lookup_file="${DIFF_FILE}.lookup"
|
|
# Create sorted lookup file for fast search
|
|
sort "$DIFF_FILE" > "$lookup_file"
|
|
log_info "Created lookup file for $(wc -l < "$DIFF_FILE") preserved values"
|
|
fi
|
|
|
|
# Use AWK for efficient processing (much faster than bash loop for large files)
|
|
log_info "Processing $(wc -l < .env.example) lines with AWK..."
|
|
|
|
local preserved_keys_file="${TEMP_DIR}/preserved_keys"
|
|
local awk_preserved_count_file="${TEMP_DIR}/awk_preserved_count"
|
|
local awk_updated_count_file="${TEMP_DIR}/awk_updated_count"
|
|
|
|
awk -F'=' -v lookup_file="$lookup_file" -v preserved_file="$preserved_keys_file" \
|
|
-v preserved_count_file="$awk_preserved_count_file" -v updated_count_file="$awk_updated_count_file" '
|
|
BEGIN {
|
|
preserved_count = 0
|
|
updated_count = 0
|
|
|
|
# Load preserved values if lookup file exists
|
|
if (lookup_file != "") {
|
|
while ((getline line < lookup_file) > 0) {
|
|
split(line, parts, "\x01")
|
|
key = parts[1]
|
|
value = parts[2]
|
|
preserved_values[key] = value
|
|
}
|
|
close(lookup_file)
|
|
}
|
|
}
|
|
|
|
# Process each line
|
|
{
|
|
# Check if this is an environment variable line
|
|
if (/^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*=/) {
|
|
# Extract key
|
|
key = $1
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
|
|
|
|
# Check if key should be preserved
|
|
if (key in preserved_values) {
|
|
print key "=" preserved_values[key]
|
|
print key > preserved_file
|
|
preserved_count++
|
|
} else {
|
|
print $0
|
|
updated_count++
|
|
}
|
|
} else {
|
|
# Not an env var line, preserve as-is
|
|
print $0
|
|
}
|
|
}
|
|
|
|
END {
|
|
print preserved_count > preserved_count_file
|
|
print updated_count > updated_count_file
|
|
}
|
|
' .env.example > "$new_env_file"
|
|
|
|
# Read counters and preserved keys
|
|
if [[ -f "$awk_preserved_count_file" ]]; then
|
|
preserved_count=$(cat "$awk_preserved_count_file")
|
|
fi
|
|
if [[ -f "$awk_updated_count_file" ]]; then
|
|
updated_count=$(cat "$awk_updated_count_file")
|
|
fi
|
|
|
|
# Show what was preserved
|
|
if [[ -f "$preserved_keys_file" ]]; then
|
|
while read -r key; do
|
|
[[ -n "$key" ]] && log_info " Preserved: $key (.env value)"
|
|
done < "$preserved_keys_file"
|
|
fi
|
|
|
|
# Clean up lookup file
|
|
[[ -n "$lookup_file" ]] && rm -f "$lookup_file"
|
|
|
|
# Replace the original .env file
|
|
if mv "$new_env_file" ".env"; then
|
|
log_success "Successfully created new .env file"
|
|
else
|
|
log_error "Failed to replace .env file"
|
|
rm -f "$new_env_file"
|
|
return 1
|
|
fi
|
|
|
|
# Clean up difference file and temporary directory
|
|
if [[ -n "${TEMP_DIR:-}" ]]; then
|
|
rm -rf "${TEMP_DIR}"
|
|
unset TEMP_DIR
|
|
fi
|
|
if [[ -n "${DIFF_FILE:-}" ]]; then
|
|
unset DIFF_FILE
|
|
fi
|
|
|
|
log_success "Partial synchronization of .env file completed"
|
|
log_info " Preserved .env values: $preserved_count"
|
|
log_info " Updated to .env.example values: $updated_count"
|
|
}
|
|
|
|
# Detect removed environment variables
|
|
detect_removed_variables() {
|
|
log_info "Detecting removed environment variables..."
|
|
|
|
if [[ ! -f ".env" ]]; then
|
|
return
|
|
fi
|
|
|
|
# Use temporary files for efficient lookup
|
|
local temp_dir="${TEMP_DIR:-$(mktemp -d)}"
|
|
local temp_example_keys="$temp_dir/example_keys"
|
|
local temp_current_keys="$temp_dir/current_keys"
|
|
local cleanup_temp_dir=""
|
|
|
|
# Set flag if we created a new temp directory
|
|
if [[ -z "${TEMP_DIR:-}" ]]; then
|
|
cleanup_temp_dir="$temp_dir"
|
|
fi
|
|
|
|
# Get keys from .env.example and .env, sorted for comm
|
|
awk -F= '!/^[[:space:]]*#/ && /=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1}' .env.example | sort > "$temp_example_keys"
|
|
awk -F= '!/^[[:space:]]*#/ && /=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1); print $1}' .env | sort > "$temp_current_keys"
|
|
|
|
# Get keys from existing .env and check for removals
|
|
local removed_vars=()
|
|
while IFS= read -r var; do
|
|
removed_vars+=("$var")
|
|
done < <(comm -13 "$temp_example_keys" "$temp_current_keys")
|
|
|
|
# Clean up temporary files if we created a new temp directory
|
|
if [[ -n "$cleanup_temp_dir" ]]; then
|
|
rm -rf "$cleanup_temp_dir"
|
|
fi
|
|
|
|
if [[ ${#removed_vars[@]} -gt 0 ]]; then
|
|
log_warning "The following environment variables have been removed from .env.example:"
|
|
for var in "${removed_vars[@]}"; do
|
|
log_warning " - $var"
|
|
done
|
|
log_warning "Consider manually removing these variables from .env"
|
|
else
|
|
log_success "No removed environment variables found"
|
|
fi
|
|
}
|
|
|
|
# Show statistics
|
|
show_statistics() {
|
|
log_info "Synchronization statistics:"
|
|
|
|
local total_example=$(grep -c "^[^#]*=" .env.example 2>/dev/null || echo "0")
|
|
local total_env=$(grep -c "^[^#]*=" .env 2>/dev/null || echo "0")
|
|
|
|
log_info " .env.example environment variables: $total_example"
|
|
log_info " .env environment variables: $total_env"
|
|
}
|
|
|
|
# Main execution function
|
|
# Orchestrates the complete synchronization process in the correct order
|
|
main() {
|
|
log_info "=== Dify Environment Variables Synchronization Script ==="
|
|
log_info "Execution started: $(date)"
|
|
|
|
# Check prerequisites
|
|
check_files
|
|
|
|
# Create backup
|
|
create_backup
|
|
|
|
# Detect differences
|
|
detect_differences
|
|
|
|
# Detect removed variables (before sync)
|
|
detect_removed_variables
|
|
|
|
# Synchronize environment file
|
|
sync_env_file
|
|
|
|
# Show statistics
|
|
show_statistics
|
|
|
|
log_success "=== Synchronization process completed successfully ==="
|
|
log_info "Execution finished: $(date)"
|
|
}
|
|
|
|
# Execute main function only when script is run directly
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi |