mirror of
https://github.com/kossakovsky/n8n-install.git
synced 2026-03-07 22:33:11 +00:00
- Added RUN_N8N_IMPORT_COMPLETE variable to .env.example to track the completion status of n8n workflow imports. - Updated 03_generate_secrets.sh to manage user prompts for importing workflows and preserve the import status across sessions. - Refined the logic for determining whether to prompt the user for importing workflows based on previous completion status.
643 lines
28 KiB
Bash
Executable File
643 lines
28 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
# Source the utilities file
|
|
source "$(dirname "$0")/utils.sh"
|
|
|
|
# Check for openssl
|
|
if ! command -v openssl &> /dev/null; then
|
|
log_error "openssl could not be found. Please ensure it is installed and available in your PATH." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Configuration ---
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|
PROJECT_ROOT="$( cd "$SCRIPT_DIR/.." &> /dev/null && pwd )"
|
|
TEMPLATE_FILE="$PROJECT_ROOT/.env.example"
|
|
OUTPUT_FILE="$PROJECT_ROOT/.env"
|
|
DOMAIN_PLACEHOLDER="yourdomain.com"
|
|
|
|
# Variables to generate: varName="type:length"
|
|
# Types: password (alphanum), secret (base64), hex, base64, alphanum
|
|
declare -A VARS_TO_GENERATE=(
|
|
["FLOWISE_PASSWORD"]="password:32"
|
|
["N8N_ENCRYPTION_KEY"]="secret:64" # base64 encoded, 48 bytes -> 64 chars
|
|
["N8N_USER_MANAGEMENT_JWT_SECRET"]="secret:64" # base64 encoded, 48 bytes -> 64 chars
|
|
["POSTGRES_PASSWORD"]="password:32"
|
|
["POSTGRES_NON_ROOT_PASSWORD"]="password:32"
|
|
["JWT_SECRET"]="base64:64" # 48 bytes -> 64 chars
|
|
["DASHBOARD_PASSWORD"]="password:32" # Supabase Dashboard
|
|
["CLICKHOUSE_PASSWORD"]="password:32"
|
|
["MINIO_ROOT_PASSWORD"]="password:32"
|
|
["LANGFUSE_SALT"]="secret:64" # base64 encoded, 48 bytes -> 64 chars
|
|
["NEXTAUTH_SECRET"]="secret:64" # base64 encoded, 48 bytes -> 64 chars
|
|
["ENCRYPTION_KEY"]="hex:64" # Langfuse Encryption Key (32 bytes -> 64 hex chars)
|
|
["GRAFANA_ADMIN_PASSWORD"]="password:32"
|
|
# From MD file (ensure they are in template if needed)
|
|
["SECRET_KEY_BASE"]="base64:64" # 48 bytes -> 64 chars
|
|
["VAULT_ENC_KEY"]="alphanum:32"
|
|
["LOGFLARE_LOGGER_BACKEND_API_KEY"]="secret:64" # base64 encoded, 48 bytes -> 64 chars
|
|
["LOGFLARE_API_KEY"]="secret:64" # base64 encoded, 48 bytes -> 64 chars
|
|
["PROMETHEUS_PASSWORD"]="password:32" # Added Prometheus password
|
|
["SEARXNG_PASSWORD"]="password:32" # Added SearXNG admin password
|
|
["LETTA_SERVER_PASSWORD"]="password:32" # Added Letta server password
|
|
["LANGFUSE_INIT_USER_PASSWORD"]="password:32"
|
|
["LANGFUSE_INIT_PROJECT_PUBLIC_KEY"]="langfuse_pk:32"
|
|
["LANGFUSE_INIT_PROJECT_SECRET_KEY"]="langfuse_sk:32"
|
|
["WEAVIATE_PASSWORD"]="password:32" # Password for Caddy basic auth
|
|
["WEAVIATE_API_KEY"]="secret:48" # API Key for Weaviate service (36 bytes -> 48 chars base64)
|
|
["NEO4J_AUTH_PASSWORD"]="password:32" # Added Neo4j password
|
|
)
|
|
|
|
# Initialize existing_env_vars and attempt to read .env if it exists
|
|
log_info "Initializing environment configuration..."
|
|
declare -A existing_env_vars
|
|
|
|
if [ -f "$OUTPUT_FILE" ]; then
|
|
log_info "Found existing $OUTPUT_FILE. Reading its values to use as defaults and preserve current settings."
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
if [[ -n "$line" && ! "$line" =~ ^\s*# && "$line" == *"="* ]]; then
|
|
varName=$(echo "$line" | cut -d'=' -f1 | xargs)
|
|
varValue=$(echo "$line" | cut -d'=' -f2-)
|
|
# Repeatedly unquote "value" or 'value' to get the bare value
|
|
_tempVal="$varValue"
|
|
while true; do
|
|
if [[ "$_tempVal" =~ ^\"(.*)\"$ ]]; then # Check double quotes
|
|
_tempVal="${BASH_REMATCH[1]}"
|
|
continue
|
|
fi
|
|
if [[ "$_tempVal" =~ ^\'(.*)\'$ ]]; then # Check single quotes
|
|
_tempVal="${BASH_REMATCH[1]}"
|
|
continue
|
|
fi
|
|
break # No more surrounding quotes of these types
|
|
done
|
|
varValue="$_tempVal"
|
|
existing_env_vars["$varName"]="$varValue"
|
|
fi
|
|
done < "$OUTPUT_FILE"
|
|
fi
|
|
|
|
# Install Caddy
|
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --yes --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
|
|
apt install -y caddy
|
|
|
|
# Check for caddy
|
|
if ! command -v caddy &> /dev/null; then
|
|
log_error "caddy could not be found. Please ensure it is installed and available in your PATH." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Prompt for the domain name
|
|
DOMAIN="" # Initialize DOMAIN variable
|
|
|
|
# Try to get domain from existing .env file first
|
|
# Check if USER_DOMAIN_NAME is set in existing_env_vars and is not empty
|
|
if [[ -v existing_env_vars[USER_DOMAIN_NAME] && -n "${existing_env_vars[USER_DOMAIN_NAME]}" ]]; then
|
|
DOMAIN="${existing_env_vars[USER_DOMAIN_NAME]}"
|
|
# Ensure this value is carried over to generated_values for writing and template processing
|
|
# If it came from existing_env_vars, it might already be there, but this ensures it.
|
|
generated_values["USER_DOMAIN_NAME"]="$DOMAIN"
|
|
else
|
|
while true; do
|
|
echo ""
|
|
prompt_text="Enter the primary domain name for your services (e.g., example.com): " # Simplified prompt
|
|
read -p "$prompt_text" DOMAIN_INPUT
|
|
|
|
DOMAIN_TO_USE="$DOMAIN_INPUT" # Direct assignment, no default fallback
|
|
|
|
# Validate domain input
|
|
if [[ -z "$DOMAIN_TO_USE" ]]; then
|
|
log_error "Domain name cannot be empty. This field is mandatory." >&2 # Clarified error
|
|
continue # Ask again
|
|
fi
|
|
|
|
# Basic check for likely invalid domain characters (very permissive)
|
|
if [[ "$DOMAIN_TO_USE" =~ [^a-zA-Z0-9.-] ]]; then
|
|
log_warning "Warning: Domain name contains potentially invalid characters: '$DOMAIN_TO_USE'" >&2
|
|
fi
|
|
|
|
echo ""
|
|
read -p "Are you sure '$DOMAIN_TO_USE' is correct? (y/N): " confirm_domain
|
|
if [[ "$confirm_domain" =~ ^[Yy]$ ]]; then
|
|
DOMAIN="$DOMAIN_TO_USE" # Set the final DOMAIN variable
|
|
generated_values["USER_DOMAIN_NAME"]="$DOMAIN" # Using USER_DOMAIN_NAME
|
|
log_info "Domain set to '$DOMAIN'. It will be saved in .env."
|
|
break # Confirmed, exit loop
|
|
else
|
|
log_info "Please try entering the domain name again."
|
|
# No default domain suggestion to retry with.
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Prompt for user email
|
|
echo ""
|
|
echo "Please enter your email address. This email will be used for:"
|
|
echo " - Login to Flowise"
|
|
echo " - Login to Supabase"
|
|
echo " - Login to SearXNG"
|
|
echo " - Login to Grafana"
|
|
echo " - Login to Prometheus"
|
|
echo " - SSL certificate generation with Let's Encrypt"
|
|
|
|
if [[ -n "${existing_env_vars[LETSENCRYPT_EMAIL]}" ]]; then
|
|
USER_EMAIL="${existing_env_vars[LETSENCRYPT_EMAIL]}"
|
|
log_info "Using existing email from .env: $USER_EMAIL"
|
|
else
|
|
while true; do
|
|
echo ""
|
|
read -p "Email: " USER_EMAIL
|
|
|
|
# Validate email input
|
|
if [[ -z "$USER_EMAIL" ]]; then
|
|
log_error "Email cannot be empty." >&2
|
|
continue # Ask again
|
|
fi
|
|
|
|
# Basic email format validation
|
|
if [[ ! "$USER_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
|
|
log_warning "Warning: Email format appears to be invalid: '$USER_EMAIL'" >&2
|
|
fi
|
|
|
|
echo ""
|
|
read -p "Are you sure '$USER_EMAIL' is correct? (y/N): " confirm_email
|
|
if [[ "$confirm_email" =~ ^[Yy]$ ]]; then
|
|
break # Confirmed, exit loop
|
|
else
|
|
log_info "Please try entering the email address again."
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Prompt for OpenAI API key (optional)
|
|
echo ""
|
|
echo "OpenAI API Key (optional). This key will be used for:"
|
|
echo " - Supabase: AI services to help with writing SQL queries, statements, and policies"
|
|
echo " - Crawl4AI: Default LLM configuration for web crawling capabilities"
|
|
echo " You can skip this by leaving it empty."
|
|
|
|
if [[ -v existing_env_vars[OPENAI_API_KEY] ]]; then # -v checks if variable is set (even if empty)
|
|
OPENAI_API_KEY="${existing_env_vars[OPENAI_API_KEY]}"
|
|
if [[ -n "$OPENAI_API_KEY" ]]; then
|
|
log_info "Using existing OpenAI API Key from .env."
|
|
else
|
|
log_info "Found empty OpenAI API Key in .env. You can provide one now or leave empty."
|
|
echo ""
|
|
read -p "OpenAI API Key: " OPENAI_API_KEY # Allow update if it was empty
|
|
fi
|
|
else
|
|
echo ""
|
|
read -p "OpenAI API Key: " OPENAI_API_KEY
|
|
fi
|
|
|
|
# Logic for n8n workflow import (RUN_N8N_IMPORT and RUN_N8N_IMPORT_COMPLETE)
|
|
echo ""
|
|
# Determine the current state of RUN_N8N_IMPORT_COMPLETE from .env
|
|
# This variable tracks if the import has ever been successfully initiated.
|
|
run_n8n_import_complete_from_env="false"
|
|
if [[ "${existing_env_vars[RUN_N8N_IMPORT_COMPLETE]}" == "true" ]]; then
|
|
run_n8n_import_complete_from_env="true"
|
|
fi
|
|
|
|
# These will be the final values decided for this run and to be saved in .env
|
|
# 'final_run_n8n_import_decision' is for the current session's import action
|
|
# 'final_run_n8n_import_complete_status' is the persistent flag
|
|
final_run_n8n_import_decision="false"
|
|
final_run_n8n_import_complete_status="$run_n8n_import_complete_from_env" # Preserve existing 'true' state by default
|
|
|
|
if [[ "$run_n8n_import_complete_from_env" == "true" ]]; then
|
|
final_run_n8n_import_decision="false"
|
|
# final_run_n8n_import_complete_status remains "true"
|
|
else
|
|
# RUN_N8N_IMPORT_COMPLETE is false or not set, so we ask the user.
|
|
echo "Do you want to import 300 ready-made workflows for n8n? This process may take about 30 minutes to complete."
|
|
echo ""
|
|
read -p "Import workflows? (y/n): " import_workflow_choice
|
|
|
|
if [[ "$import_workflow_choice" =~ ^[Yy]$ ]]; then
|
|
final_run_n8n_import_decision="true"
|
|
final_run_n8n_import_complete_status="true" # Mark as complete for this and future runs
|
|
else
|
|
final_run_n8n_import_decision="false"
|
|
# final_run_n8n_import_complete_status remains "false" because user declined and it wasn't true before.
|
|
fi
|
|
fi
|
|
|
|
# Prompt for number of n8n workers
|
|
echo "" # Add a newline for better formatting
|
|
log_info "Configuring n8n worker count..."
|
|
if [[ -n "${existing_env_vars[N8N_WORKER_COUNT]}" ]]; then
|
|
N8N_WORKER_COUNT_CURRENT="${existing_env_vars[N8N_WORKER_COUNT]}"
|
|
log_info "Found existing N8N_WORKER_COUNT in .env: $N8N_WORKER_COUNT_CURRENT"
|
|
echo ""
|
|
read -p "Do you want to change the number of n8n workers? Current: $N8N_WORKER_COUNT_CURRENT. (Enter new number, or press Enter to keep current): " N8N_WORKER_COUNT_INPUT_RAW
|
|
if [[ -z "$N8N_WORKER_COUNT_INPUT_RAW" ]]; then
|
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
|
log_info "Keeping N8N_WORKER_COUNT at $N8N_WORKER_COUNT."
|
|
else
|
|
# Validate the new input
|
|
if [[ "$N8N_WORKER_COUNT_INPUT_RAW" =~ ^0*[1-9][0-9]*$ ]]; then
|
|
N8N_WORKER_COUNT_TEMP="$((10#$N8N_WORKER_COUNT_INPUT_RAW))" # Sanitize (e.g. 01 -> 1)
|
|
if [[ "$N8N_WORKER_COUNT_TEMP" -ge 1 ]]; then
|
|
echo ""
|
|
read -p "Update n8n workers to $N8N_WORKER_COUNT_TEMP? (y/N): " confirm_change
|
|
if [[ "$confirm_change" =~ ^[Yy]$ ]]; then
|
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_TEMP"
|
|
else
|
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
|
log_info "Change declined. Keeping N8N_WORKER_COUNT at $N8N_WORKER_COUNT."
|
|
fi
|
|
else # Should not happen with regex but as a safeguard
|
|
log_warning "Invalid input '$N8N_WORKER_COUNT_INPUT_RAW'. Number must be positive. Keeping $N8N_WORKER_COUNT_CURRENT."
|
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
|
fi
|
|
else
|
|
log_warning "Invalid input '$N8N_WORKER_COUNT_INPUT_RAW'. Please enter a positive integer. Keeping $N8N_WORKER_COUNT_CURRENT."
|
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_CURRENT"
|
|
fi
|
|
fi
|
|
else
|
|
while true; do
|
|
echo ""
|
|
read -p "Enter the number of n8n workers to run (e.g., 1, 2, 3; default is 1): " N8N_WORKER_COUNT_INPUT_RAW
|
|
N8N_WORKER_COUNT_CANDIDATE="${N8N_WORKER_COUNT_INPUT_RAW:-1}" # Default to 1 if empty
|
|
|
|
if [[ "$N8N_WORKER_COUNT_CANDIDATE" =~ ^0*[1-9][0-9]*$ ]]; then
|
|
N8N_WORKER_COUNT_VALIDATED="$((10#$N8N_WORKER_COUNT_CANDIDATE))"
|
|
if [[ "$N8N_WORKER_COUNT_VALIDATED" -ge 1 ]]; then
|
|
echo ""
|
|
read -p "Run $N8N_WORKER_COUNT_VALIDATED n8n worker(s)? (y/N): " confirm_workers
|
|
if [[ "$confirm_workers" =~ ^[Yy]$ ]]; then
|
|
N8N_WORKER_COUNT="$N8N_WORKER_COUNT_VALIDATED"
|
|
break
|
|
else
|
|
log_info "Please try entering the number of workers again."
|
|
fi
|
|
else # Should not be reached if regex is correct
|
|
log_error "Number of workers must be a positive integer." >&2
|
|
fi
|
|
else
|
|
log_error "Invalid input '$N8N_WORKER_COUNT_CANDIDATE'. Please enter a positive integer (e.g., 1, 2)." >&2
|
|
fi
|
|
done
|
|
fi
|
|
# Ensure N8N_WORKER_COUNT is definitely set (should be by logic above)
|
|
N8N_WORKER_COUNT="${N8N_WORKER_COUNT:-1}"
|
|
|
|
log_info "Generating secrets and creating .env file..."
|
|
|
|
# --- Helper Functions ---
|
|
# Usage: gen_random <length> <characters>
|
|
gen_random() {
|
|
local length="$1"
|
|
local characters="$2"
|
|
head /dev/urandom | tr -dc "$characters" | head -c "$length"
|
|
}
|
|
|
|
# Usage: gen_password <length>
|
|
gen_password() {
|
|
gen_random "$1" 'A-Za-z0-9'
|
|
}
|
|
|
|
# Usage: gen_hex <length> (length = number of hex characters)
|
|
gen_hex() {
|
|
local length="$1"
|
|
local bytes=$(( (length + 1) / 2 )) # Calculate bytes needed
|
|
openssl rand -hex "$bytes" | head -c "$length"
|
|
}
|
|
|
|
# Usage: gen_base64 <length> (length = number of base64 characters)
|
|
gen_base64() {
|
|
local length="$1"
|
|
# Estimate bytes needed: base64 encodes 3 bytes to 4 chars.
|
|
# So, we need length * 3 / 4 bytes. Use ceil division.
|
|
local bytes=$(( (length * 3 + 3) / 4 ))
|
|
openssl rand -base64 "$bytes" | head -c "$length" # Truncate just in case
|
|
}
|
|
|
|
# --- Main Logic ---
|
|
|
|
if [ ! -f "$TEMPLATE_FILE" ]; then
|
|
log_error "Template file not found at $TEMPLATE_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Associative array to store generated values
|
|
declare -A generated_values
|
|
|
|
# Pre-populate generated_values with non-empty values from existing_env_vars
|
|
for key_from_existing in "${!existing_env_vars[@]}"; do
|
|
if [[ -n "${existing_env_vars[$key_from_existing]}" ]]; then
|
|
generated_values["$key_from_existing"]="${existing_env_vars[$key_from_existing]}"
|
|
fi
|
|
done
|
|
|
|
# Store user input values (potentially overwriting if user was re-prompted and gave new input)
|
|
generated_values["FLOWISE_USERNAME"]="$USER_EMAIL"
|
|
generated_values["DASHBOARD_USERNAME"]="$USER_EMAIL"
|
|
generated_values["LETSENCRYPT_EMAIL"]="$USER_EMAIL"
|
|
generated_values["RUN_N8N_IMPORT"]="$final_run_n8n_import_decision"
|
|
generated_values["RUN_N8N_IMPORT_COMPLETE"]="$final_run_n8n_import_complete_status"
|
|
generated_values["PROMETHEUS_USERNAME"]="$USER_EMAIL"
|
|
generated_values["SEARXNG_USERNAME"]="$USER_EMAIL"
|
|
generated_values["LANGFUSE_INIT_USER_EMAIL"]="$USER_EMAIL"
|
|
generated_values["N8N_WORKER_COUNT"]="$N8N_WORKER_COUNT"
|
|
generated_values["WEAVIATE_USERNAME"]="$USER_EMAIL" # Set Weaviate username for Caddy
|
|
if [[ -n "$OPENAI_API_KEY" ]]; then
|
|
generated_values["OPENAI_API_KEY"]="$OPENAI_API_KEY"
|
|
fi
|
|
|
|
# Create a temporary file for processing
|
|
TMP_ENV_FILE=$(mktemp)
|
|
# Ensure temp file is cleaned up on exit
|
|
trap 'rm -f "$TMP_ENV_FILE"' EXIT
|
|
|
|
# Track whether our custom variables were found in the template
|
|
declare -A found_vars
|
|
found_vars["FLOWISE_USERNAME"]=0
|
|
found_vars["DASHBOARD_USERNAME"]=0
|
|
found_vars["LETSENCRYPT_EMAIL"]=0
|
|
found_vars["RUN_N8N_IMPORT"]=0
|
|
found_vars["RUN_N8N_IMPORT_COMPLETE"]=0
|
|
found_vars["PROMETHEUS_USERNAME"]=0
|
|
found_vars["SEARXNG_USERNAME"]=0
|
|
found_vars["OPENAI_API_KEY"]=0
|
|
found_vars["LANGFUSE_INIT_USER_EMAIL"]=0
|
|
found_vars["N8N_WORKER_COUNT"]=0
|
|
found_vars["WEAVIATE_USERNAME"]=0
|
|
found_vars["vEO4J_AUTH_USERNAME"]=0
|
|
|
|
# Read template, substitute domain, generate initial values
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
# Substitute domain placeholder
|
|
processed_line=$(echo "$line" | sed "s/$DOMAIN_PLACEHOLDER/$DOMAIN/g")
|
|
|
|
# Check if it's a variable assignment line (non-empty, not comment, contains '=')
|
|
if [[ -n "$processed_line" && ! "$processed_line" =~ ^\s*# && "$processed_line" == *"="* ]]; then
|
|
varName=$(echo "$processed_line" | cut -d'=' -f1 | xargs) # Trim whitespace
|
|
currentValue=$(echo "$processed_line" | cut -d'=' -f2-)
|
|
|
|
# If already have a non-empty value from existing .env or prior generation/user input, use it
|
|
if [[ -n "${generated_values[$varName]}" ]]; then
|
|
processed_line="${varName}=\"${generated_values[$varName]}\""
|
|
# Check if this is one of our user-input derived variables that might not have a value yet
|
|
# (e.g. OPENAI_API_KEY if user left it blank). These are handled by `found_vars` later if needed.
|
|
# Or, if variable needs generation AND is not already populated (or is empty) in generated_values
|
|
elif [[ -v VARS_TO_GENERATE["$varName"] && -z "${generated_values[$varName]}" ]]; then
|
|
IFS=':' read -r type length <<< "${VARS_TO_GENERATE[$varName]}"
|
|
newValue=""
|
|
case "$type" in
|
|
password|alphanum) newValue=$(gen_password "$length") ;;
|
|
secret|base64) newValue=$(gen_base64 "$length") ;;
|
|
hex) newValue=$(gen_hex "$length") ;;
|
|
langfuse_pk) newValue="pk-lf-$(gen_hex "$length")" ;;
|
|
langfuse_sk) newValue="sk-lf-$(gen_hex "$length")" ;;
|
|
*) log_warning "Unknown generation type '$type' for $varName" ;;
|
|
esac
|
|
|
|
if [[ -n "$newValue" ]]; then
|
|
processed_line="${varName}=\"${newValue}\"" # Quote generated values
|
|
generated_values["$varName"]="$newValue" # Store newly generated
|
|
else
|
|
# Keep original line structure but ensure value is empty if generation failed
|
|
# but it was in VARS_TO_GENERATE
|
|
processed_line="${varName}=\""
|
|
generated_values["$varName"]="" # Explicitly mark as empty in generated_values
|
|
fi
|
|
# For variables from the template that are not in VARS_TO_GENERATE and not already in generated_values
|
|
# store their template value if it's a direct assignment (not a ${...} substitution)
|
|
# This allows them to be used in later ${VAR} substitutions if they are referenced.
|
|
else
|
|
# This 'else' block is for lines from template not covered by existing values or VARS_TO_GENERATE.
|
|
# Check if it is one of the user input vars - these are handled by found_vars later if not in template.
|
|
is_user_input_var=0 # Reset for each line
|
|
user_input_vars=("FLOWISE_USERNAME" "DASHBOARD_USERNAME" "LETSENCRYPT_EMAIL" "RUN_N8N_IMPORT" "RUN_N8N_IMPORT_COMPLETE" "PROMETHEUS_USERNAME" "SEARXNG_USERNAME" "OPENAI_API_KEY" "LANGFUSE_INIT_USER_EMAIL" "N8N_WORKER_COUNT" "WEAVIATE_USERNAME")
|
|
for uivar in "${user_input_vars[@]}"; do
|
|
if [[ "$varName" == "$uivar" ]]; then
|
|
is_user_input_var=1
|
|
# Mark as found if it's in template, value taken from generated_values if already set or blank
|
|
found_vars["$varName"]=1
|
|
if [[ -v generated_values[$varName] ]]; then # if it was set (even to empty by user)
|
|
processed_line="${varName}=\"${generated_values[$varName]}\""
|
|
else # Not set in generated_values, keep template's default if any, or make it empty
|
|
if [[ "$currentValue" =~ ^\$\{.*\} || -z "$currentValue" ]]; then # if template is ${VAR} or empty
|
|
processed_line="${varName}=\"\""
|
|
else # template has a default simple value
|
|
processed_line="${varName}=\"$currentValue\"" # Use template's default, and quote it
|
|
# Don't add to generated_values here, let the original logic handle it if needed
|
|
fi
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ $is_user_input_var -eq 0 ]]; then # Not a user input var, not in VARS_TO_GENERATE, not in existing
|
|
trimmed_value=$(echo "$currentValue" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'//")
|
|
if [[ -n "$varName" && -n "$trimmed_value" && "$trimmed_value" != "\${INSTANCE_DOMAIN}" && "$trimmed_value" != "\${SUBDOMAIN_WILDCARD_CERT}" && ! "$trimmed_value" =~ ^\\$\\{ ]]; then # Check for other placeholders
|
|
# Only store if not already in generated_values and not a placeholder reference
|
|
if [[ -z "${generated_values[$varName]}" ]]; then
|
|
generated_values["$varName"]="$trimmed_value"
|
|
fi
|
|
fi
|
|
# processed_line remains as is (from template, after domain sub) for these cases
|
|
fi
|
|
fi
|
|
fi
|
|
echo "$processed_line" >> "$TMP_ENV_FILE"
|
|
done < "$TEMPLATE_FILE"
|
|
|
|
# Generate placeholder Supabase keys (always generate these)
|
|
log_info "Generating Supabase JWT keys..."
|
|
|
|
# Function to create a JWT token
|
|
create_jwt() {
|
|
local role=$1
|
|
local jwt_secret=$2
|
|
local now=$(date +%s)
|
|
local exp=$((now + 315360000)) # 10 years from now (seconds)
|
|
|
|
# Create header (alg=HS256, typ=JWT)
|
|
local header='{"alg":"HS256","typ":"JWT"}'
|
|
# Create payload with role, issued at time, and expiry
|
|
local payload="{\"role\":\"$role\",\"iss\":\"supabase\",\"iat\":$now,\"exp\":$exp}"
|
|
|
|
# Base64url encode header and payload
|
|
local b64_header=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
|
local b64_payload=$(echo -n "$payload" | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
|
|
|
# Create signature
|
|
local signature_input="$b64_header.$b64_payload"
|
|
local signature=$(echo -n "$signature_input" | openssl dgst -sha256 -hmac "$jwt_secret" -binary | base64 -w 0 | tr '/+' '_-' | tr -d '=')
|
|
|
|
# Combine to form JWT
|
|
echo -n "$b64_header.$b64_payload.$signature" # Use echo -n to avoid trailing newline
|
|
}
|
|
|
|
# Get JWT secret from previously generated values
|
|
JWT_SECRET_TO_USE="${generated_values["JWT_SECRET"]}"
|
|
|
|
if [[ -z "$JWT_SECRET_TO_USE" ]]; then
|
|
# This should ideally have been generated by VARS_TO_GENERATE if it was missing
|
|
# and JWT_SECRET is in VARS_TO_GENERATE. For safety, generate if truly empty.
|
|
log_warning "JWT_SECRET was empty, attempting to generate it now."
|
|
# Assuming JWT_SECRET definition is 'base64:64'
|
|
JWT_SECRET_TO_USE=$(gen_base64 64)
|
|
generated_values["JWT_SECRET"]="$JWT_SECRET_TO_USE"
|
|
fi
|
|
|
|
# Generate the actual JWT tokens using the JWT_SECRET_TO_USE, if not already set
|
|
if [[ -z "${generated_values[ANON_KEY]}" ]]; then
|
|
generated_values["ANON_KEY"]=$(create_jwt "anon" "$JWT_SECRET_TO_USE")
|
|
else
|
|
log_info "Using existing ANON_KEY."
|
|
fi
|
|
|
|
if [[ -z "${generated_values[SERVICE_ROLE_KEY]}" ]]; then
|
|
generated_values["SERVICE_ROLE_KEY"]=$(create_jwt "service_role" "$JWT_SECRET_TO_USE")
|
|
else
|
|
log_info "Using existing SERVICE_ROLE_KEY."
|
|
fi
|
|
|
|
# Add any custom variables that weren't found in the template
|
|
for var in "FLOWISE_USERNAME" "DASHBOARD_USERNAME" "LETSENCRYPT_EMAIL" "RUN_N8N_IMPORT" "RUN_N8N_IMPORT_COMPLETE" "OPENAI_API_KEY" "PROMETHEUS_USERNAME" "SEARXNG_USERNAME" "LANGFUSE_INIT_USER_EMAIL" "N8N_WORKER_COUNT" "WEAVIATE_USERNAME"; do
|
|
if [[ ${found_vars["$var"]} -eq 0 && -v generated_values["$var"] ]]; then
|
|
# Before appending, check if it's already in TMP_ENV_FILE to avoid duplicates
|
|
if ! grep -q -E "^${var}=" "$TMP_ENV_FILE"; then
|
|
echo "${var}=\"${generated_values[$var]}\"" >> "$TMP_ENV_FILE" # Ensure quoting
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Second pass: Substitute generated values referenced like ${VAR}
|
|
# We'll process the substitutions line by line to avoid escaping issues
|
|
|
|
# Copy the temporary file to the output
|
|
cp "$TMP_ENV_FILE" "$OUTPUT_FILE"
|
|
|
|
log_info "Applying variable substitutions..."
|
|
|
|
# Process each generated value
|
|
for key in "${!generated_values[@]}"; do
|
|
value="${generated_values[$key]}"
|
|
|
|
# Create a temporary file for this value to avoid escaping issues
|
|
value_file=$(mktemp)
|
|
echo -n "$value" > "$value_file"
|
|
|
|
# Create a new temporary file for the output
|
|
new_output=$(mktemp)
|
|
|
|
# Process each line in the file
|
|
while IFS= read -r line; do
|
|
# Replace ${KEY} format
|
|
if [[ "$line" == *"\${$key}"* ]]; then
|
|
placeholder="\${$key}"
|
|
replacement=$(cat "$value_file")
|
|
line="${line//$placeholder/$replacement}"
|
|
fi
|
|
|
|
# Replace $KEY format
|
|
if [[ "$line" == *"$"$key* ]]; then
|
|
placeholder="$"$key
|
|
replacement=$(cat "$value_file")
|
|
line="${line//$placeholder/$replacement}"
|
|
fi
|
|
|
|
# Handle specific cases
|
|
if [[ "$key" == "ANON_KEY" && "$line" == "ANON_KEY="* ]]; then
|
|
line="ANON_KEY=\"$(cat "$value_file")\""
|
|
fi
|
|
|
|
if [[ "$key" == "SERVICE_ROLE_KEY" && "$line" == "SERVICE_ROLE_KEY="* ]]; then
|
|
line="SERVICE_ROLE_KEY=\"$(cat "$value_file")\""
|
|
fi
|
|
|
|
if [[ "$key" == "ANON_KEY" && "$line" == "SUPABASE_ANON_KEY="* ]]; then
|
|
line="SUPABASE_ANON_KEY=\"$(cat "$value_file")\""
|
|
fi
|
|
|
|
if [[ "$key" == "SERVICE_ROLE_KEY" && "$line" == "SUPABASE_SERVICE_ROLE_KEY="* ]]; then
|
|
line="SUPABASE_SERVICE_ROLE_KEY=\"$(cat "$value_file")\""
|
|
fi
|
|
|
|
if [[ "$key" == "JWT_SECRET" && "$line" == "SUPABASE_JWT_SECRET="* ]]; then
|
|
line="SUPABASE_JWT_SECRET=\"$(cat "$value_file")\""
|
|
fi
|
|
|
|
if [[ "$key" == "POSTGRES_PASSWORD" && "$line" == "SUPABASE_POSTGRES_PASSWORD="* ]]; then
|
|
line="SUPABASE_POSTGRES_PASSWORD=\"$(cat "$value_file")\""
|
|
fi
|
|
|
|
# Write the processed line to the new file
|
|
echo "$line" >> "$new_output"
|
|
done < "$OUTPUT_FILE"
|
|
|
|
# Replace the output file with the new version
|
|
mv "$new_output" "$OUTPUT_FILE"
|
|
|
|
# Clean up
|
|
rm -f "$value_file"
|
|
done
|
|
|
|
# Hash passwords using caddy with bcrypt
|
|
PROMETHEUS_PLAIN_PASS="${generated_values["PROMETHEUS_PASSWORD"]}"
|
|
SEARXNG_PLAIN_PASS="${generated_values["SEARXNG_PASSWORD"]}"
|
|
WEAVIATE_PLAIN_PASS="${generated_values["WEAVIATE_PASSWORD"]}"
|
|
|
|
if [[ -n "${generated_values[PROMETHEUS_PASSWORD_HASH]}" ]]; then
|
|
log_info "PROMETHEUS_PASSWORD_HASH already exists. Skipping re-hashing."
|
|
elif [[ -n "$PROMETHEUS_PLAIN_PASS" ]]; then
|
|
PROMETHEUS_HASH=$(caddy hash-password --algorithm bcrypt --plaintext "$PROMETHEUS_PLAIN_PASS" 2>/dev/null)
|
|
if [[ $? -eq 0 && -n "$PROMETHEUS_HASH" ]]; then
|
|
echo "PROMETHEUS_PASSWORD_HASH='$PROMETHEUS_HASH'" >> "$OUTPUT_FILE"
|
|
generated_values["PROMETHEUS_PASSWORD_HASH"]="$PROMETHEUS_HASH" # Store for consistency, though primarily written to file
|
|
else
|
|
log_warning "Failed to hash Prometheus password using caddy."
|
|
fi
|
|
else
|
|
log_warning "Prometheus password was not generated or found, skipping hash."
|
|
fi
|
|
|
|
if [[ -n "${generated_values[SEARXNG_PASSWORD_HASH]}" ]]; then
|
|
log_info "SEARXNG_PASSWORD_HASH already exists. Skipping re-hashing."
|
|
elif [[ -n "$SEARXNG_PLAIN_PASS" ]]; then
|
|
SEARXNG_HASH=$(caddy hash-password --algorithm bcrypt --plaintext "$SEARXNG_PLAIN_PASS" 2>/dev/null)
|
|
if [[ $? -eq 0 && -n "$SEARXNG_HASH" ]]; then
|
|
echo "SEARXNG_PASSWORD_HASH='$SEARXNG_HASH'" >> "$OUTPUT_FILE"
|
|
generated_values["SEARXNG_PASSWORD_HASH"]="$SEARXNG_HASH"
|
|
else
|
|
log_warning "Failed to hash SearXNG password using caddy."
|
|
fi
|
|
else
|
|
log_warning "SearXNG password was not generated or found, skipping hash."
|
|
fi
|
|
|
|
if [[ -n "${generated_values[WEAVIATE_PASSWORD_HASH]}" ]]; then
|
|
log_info "WEAVIATE_PASSWORD_HASH already exists. Skipping re-hashing."
|
|
elif [[ -n "$WEAVIATE_PLAIN_PASS" ]]; then
|
|
WEAVIATE_HASH=$(caddy hash-password --algorithm bcrypt --plaintext "$WEAVIATE_PLAIN_PASS" 2>/dev/null)
|
|
if [[ $? -eq 0 && -n "$WEAVIATE_HASH" ]]; then
|
|
echo "WEAVIATE_PASSWORD_HASH='$WEAVIATE_HASH'" >> "$OUTPUT_FILE"
|
|
generated_values["WEAVIATE_PASSWORD_HASH"]="$WEAVIATE_HASH"
|
|
else
|
|
log_warning "Failed to hash Weaviate password using caddy."
|
|
fi
|
|
else
|
|
log_warning "Weaviate password was not generated or found, skipping hash."
|
|
fi
|
|
|
|
if [ $? -eq 0 ]; then
|
|
log_success ".env file generated successfully in the project root ($OUTPUT_FILE)."
|
|
else
|
|
log_error "Failed to generate .env file." >&2
|
|
rm -f "$OUTPUT_FILE" # Clean up potentially broken output file
|
|
exit 1
|
|
fi
|
|
|
|
# Uninstall caddy
|
|
apt remove -y caddy
|
|
|
|
exit 0 |