freealberta/config.sh

983 lines
33 KiB
Bash
Executable File

#!/bin/bash
cat << "EOF"
██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗
██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝
██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗
██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝
╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝
███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
Configuration Wizard
EOF
# Get the absolute path of the script directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ENV_FILE="$SCRIPT_DIR/.env"
MKDOCS_YML="$SCRIPT_DIR/mkdocs/mkdocs.yml" # Fixed path - removed extra 'docs' directory
TUNNEL_CONFIG_DIR="$SCRIPT_DIR/configs/cloudflare"
TUNNEL_CONFIG_FILE="$TUNNEL_CONFIG_DIR/tunnel-config.yml"
SERVICES_YAML="$SCRIPT_DIR/configs/homepage/services.yaml"
MAIN_HTML="$SCRIPT_DIR/mkdocs/docs/overrides/main.html"
MAP_ENV_FILE="$SCRIPT_DIR/map/.env" # Add the map's .env file path
DOCKER_COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml"
echo "Looking for .env file at: $ENV_FILE"
# Function to generate a random secure password
generate_password() {
local length=${1:-16}
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()-_=+' | head -c "$length"
}
# Function to safely update environment variables in .env file
update_env_var() {
local key=$1
local value=$2
# More robust method to handle special characters in passwords
if grep -q "^$key=" "$ENV_FILE"; then
# Create a temporary file
local tmpfile=$(mktemp)
# Process the .env file line by line
while IFS= read -r line; do
if [[ "$line" =~ ^$key= ]]; then
echo "$key=$value" >> "$tmpfile"
else
echo "$line" >> "$tmpfile"
fi
done < "$ENV_FILE"
# Replace the original file with the temporary file
mv "$tmpfile" "$ENV_FILE"
echo "Updated $key in .env file"
else
# Add new key-value pair if it doesn't exist
echo "$key=$value" >> "$ENV_FILE"
echo "Added $key to .env file"
fi
}
# Function to create a timestamped backup of the .env file
backup_env_file() {
if [ -f "$ENV_FILE" ]; then
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="$ENV_FILE.backup_$timestamp"
echo "Creating backup of current .env file to: $backup_file"
if cp "$ENV_FILE" "$backup_file"; then
echo "Backup created successfully!"
return 0
else
echo "Failed to create backup file. Proceeding with caution..."
return 1
fi
fi
}
# Function to get all used ports on the system
get_used_ports() {
local used_ports=()
# Try different methods to get used ports, in order of preference
if command -v ss >/dev/null 2>&1; then
# Use ss (preferred, modern replacement for netstat)
while IFS= read -r line; do
if [[ "$line" =~ :([0-9]+)[[:space:]] ]]; then
used_ports+=("${BASH_REMATCH[1]}")
fi
done < <(ss -tuln 2>/dev/null | grep -E ':[0-9]+[[:space:]]')
elif command -v netstat >/dev/null 2>&1; then
# Use netstat (fallback)
while IFS= read -r line; do
if [[ "$line" =~ :([0-9]+)[[:space:]] ]]; then
used_ports+=("${BASH_REMATCH[1]}")
fi
done < <(netstat -tuln 2>/dev/null | grep -E ':[0-9]+[[:space:]]')
elif command -v lsof >/dev/null 2>&1; then
# Use lsof (another fallback)
while IFS= read -r line; do
if [[ "$line" =~ :([0-9]+)[[:space:]] ]]; then
used_ports+=("${BASH_REMATCH[1]}")
fi
done < <(lsof -i -P -n 2>/dev/null | grep LISTEN | grep -E ':[0-9]+[[:space:]]')
else
echo "Warning: No port scanning tools available (ss, netstat, or lsof)"
return 1
fi
# Remove duplicates and sort
printf '%s\n' "${used_ports[@]}" | sort -nu
}
# Function to check if a specific port is available
is_port_available() {
local port=$1
local used_ports_list=$2
# Check if port is in the used ports list
if echo "$used_ports_list" | grep -q "^$port$"; then
return 1 # Port is in use
else
return 0 # Port is available
fi
}
# Function to find next available port starting from a given port
find_next_available_port() {
local start_port=$1
local used_ports_list=$2
local max_attempts=${3:-100} # Maximum ports to check
for ((i=0; i<max_attempts; i++)); do
local test_port=$((start_port + i))
# Skip ports below 1024 (system ports) and above 65535
if [ "$test_port" -lt 1024 ] || [ "$test_port" -gt 65535 ]; then
continue
fi
if is_port_available "$test_port" "$used_ports_list"; then
echo "$test_port"
return 0
fi
done
echo "Error: Could not find available port starting from $start_port" >&2
return 1
}
# Function to initialize available ports for services
initialize_available_ports() {
echo "Scanning system for used ports to avoid conflicts..."
# Get list of all used ports
local used_ports_list
used_ports_list=$(get_used_ports)
if [ $? -ne 0 ] || [ -z "$used_ports_list" ]; then
echo "Warning: Could not scan system ports. Using default ports."
echo "You may need to manually resolve conflicts later."
return 1
fi
echo "Found $(echo "$used_ports_list" | wc -l) ports currently in use"
# Define desired starting ports for each service
local -A service_ports=(
["CODE_SERVER_PORT"]=8888
["LISTMONK_PORT"]=9000
["LISTMONK_DB_PORT"]=5432
["MKDOCS_PORT"]=4000
["MKDOCS_SITE_SERVER_PORT"]=4001
["N8N_PORT"]=5678
["NOCODB_PORT"]=8090
["HOMEPAGE_PORT"]=3010
["GITEA_WEB_PORT"]=3030
["GITEA_SSH_PORT"]=2222
["MAP_PORT"]=3000
["MINI_QR_PORT"]=8089
)
# Find available ports for each service
local -A available_ports
for service in "${!service_ports[@]}"; do
local preferred_port=${service_ports[$service]}
local available_port
if is_port_available "$preferred_port" "$used_ports_list"; then
available_port=$preferred_port
echo "$service: Using preferred port $preferred_port"
else
available_port=$(find_next_available_port "$preferred_port" "$used_ports_list")
if [ $? -eq 0 ]; then
echo "⚠️ $service: Port $preferred_port in use, using $available_port instead"
else
echo "$service: Could not find available port, using $preferred_port (may conflict)"
available_port=$preferred_port
fi
fi
available_ports[$service]=$available_port
# Add the assigned port to used_ports_list to avoid double-assignment
used_ports_list=$(printf '%s\n%s' "$used_ports_list" "$available_port" | sort -nu)
done
# Export the available ports as global variables
for service in "${!available_ports[@]}"; do
export "$service=${available_ports[$service]}"
done
echo "Port assignment completed successfully!"
return 0
}
# Function to initialize the .env file with default values
initialize_env_file() {
echo "Initializing new .env file with available ports..."
# Initialize available ports before creating the .env file
initialize_available_ports
cat > "$ENV_FILE" << EOL
# Never share this file publicly. It contains sensitive information.
# This file is used to configure various applications and services.
# Generated by Changemaker Config Wizard on $(date)
# User and Group Configuration
USER_NAME=coder
USER_ID=1000
GROUP_ID=1000
# Port Configuration (automatically assigned to avoid conflicts)
CODE_SERVER_PORT=${CODE_SERVER_PORT:-8888}
LISTMONK_PORT=${LISTMONK_PORT:-9000}
LISTMONK_DB_PORT=${LISTMONK_DB_PORT:-5432}
MKDOCS_PORT=${MKDOCS_PORT:-4000}
MKDOCS_SITE_SERVER_PORT=${MKDOCS_SITE_SERVER_PORT:-4001}
N8N_PORT=${N8N_PORT:-5678}
NOCODB_PORT=${NOCODB_PORT:-8090}
HOMEPAGE_PORT=${HOMEPAGE_PORT:-3010}
GITEA_WEB_PORT=${GITEA_WEB_PORT:-3030}
GITEA_SSH_PORT=${GITEA_SSH_PORT:-2222}
MAP_PORT=${MAP_PORT:-3000}
MINI_QR_PORT=${MINI_QR_PORT:-8089}
# Domain Configuration
BASE_DOMAIN=https://changeme.org
DOMAIN=changeme.org
LISTMONK_HOSTNAME=listmonk.changeme.org
N8N_HOST=n8n.changeme.org
GITEA_DOMAIN=git.changeme.org
GITEA_ROOT_URL=https://git.changeme.org
HOMEPAGE_VAR_BASE_URL=https://changeme.org
# Cookie and CORS Configuration
COOKIE_DOMAIN=.changeme.org
ALLOWED_ORIGINS=https://map.changeme.org,http://localhost:3000
# Cloudflare Configuration
CF_API_TOKEN=your_cloudflare_api_token
CF_ZONE_ID=your_cloudflare_zone_id
CF_TUNNEL_ID=your_cloudflared_tunnel_id
CF_DOMAIN=changeme.org
CF_ACCOUNT_ID=your_cloudflare_account_id
# Database Configuration (PostgreSQL for Listmonk)
POSTGRES_USER=listmonk
POSTGRES_PASSWORD=changeMe
POSTGRES_DB=listmonk
# Listmonk Database Performance Settings
LISTMONK_DB_MAX_OPEN=25
LISTMONK_DB_MAX_IDLE=25
LISTMONK_DB_MAX_LIFETIME=300s
# Listmonk Admin Configuration
LISTMONK_ADMIN_USER=admin
LISTMONK_ADMIN_PASSWORD=changeMe
# N8N Configuration
N8N_USER_EMAIL=admin@example.com
N8N_USER_PASSWORD=changeMe
N8N_ENCRYPTION_KEY=changeMe
GENERIC_TIMEZONE=UTC
# Nocodb Configuration
NOCODB_JWT_SECRET=changeMe
NOCODB_DB_NAME=nocodb
NOCODB_DB_USER=noco
NOCODB_DB_PASSWORD=changeMe
# Gitea Database Configuration
GITEA_DB_PASSWD=changeMe
GITEA_DB_ROOT_PASSWORD=changeMe
EOL
echo "New .env file created with conflict-free port assignments."
# Show port assignments summary
echo ""
echo "=== Port Assignments Summary ==="
echo "Code Server: ${CODE_SERVER_PORT:-8888}"
echo "Listmonk: ${LISTMONK_PORT:-9000}"
echo "Listmonk DB: ${LISTMONK_DB_PORT:-5432}"
echo "MkDocs: ${MKDOCS_PORT:-4000}"
echo "MkDocs Site: ${MKDOCS_SITE_SERVER_PORT:-4001}"
echo "N8N: ${N8N_PORT:-5678}"
echo "NocoDB: ${NOCODB_PORT:-8090}"
echo "Homepage: ${HOMEPAGE_PORT:-3010}"
echo "Gitea Web: ${GITEA_WEB_PORT:-3030}"
echo "Gitea SSH: ${GITEA_SSH_PORT:-2222}"
echo "Map: ${MAP_PORT:-3000}"
echo "Mini QR: ${MINI_QR_PORT:-8089}"
echo "================================"
}
# Function to update the site_url in mkdocs.yml
update_mkdocs_yml() {
local new_domain=$1
if [ ! -f "$MKDOCS_YML" ]; then
echo "Warning: mkdocs.yml not found at $MKDOCS_YML"
return 1
fi
echo "Updating site_url in mkdocs.yml..."
# Create a backup of the mkdocs.yml file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${MKDOCS_YML}.backup_${timestamp}"
cp "$MKDOCS_YML" "$backup_file"
echo "Created backup of mkdocs.yml at $backup_file"
# Update the site_url value - handle both http and https
sed -i "s|^site_url:.*|site_url: https://$new_domain|" "$MKDOCS_YML"
if grep -q "site_url: https://$new_domain" "$MKDOCS_YML"; then
echo "✅ Updated site_url in mkdocs.yml to: https://$new_domain"
return 0
else
echo "Warning: Failed to update site_url in mkdocs.yml"
return 1
fi
}
# Function to update service URLs in services.yaml
update_services_yaml() {
local new_domain=$1
if [ ! -f "$SERVICES_YAML" ]; then
echo "Warning: services.yaml not found at $SERVICES_YAML"
return 1
fi
echo "Updating service URLs in services.yaml..."
# Create a backup of the services.yaml file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${SERVICES_YAML}.backup_${timestamp}"
cp "$SERVICES_YAML" "$backup_file"
echo "Created backup of services.yaml at $backup_file"
# Define service name to subdomain mapping
# This approach is URL-agnostic - it doesn't matter what the current URLs are
declare -A service_mappings=(
["Code Server"]="code.$new_domain"
["Listmonk"]="listmonk.$new_domain"
["NocoDB"]="db.$new_domain"
["Map Server"]="map.$new_domain"
["Main Site"]="$new_domain"
["MkDocs (Live)"]="docs.$new_domain"
["Mini QR"]="qr.$new_domain"
["n8n"]="n8n.$new_domain"
["Gitea"]="git.$new_domain"
)
# Process each service mapping
for service_name in "${!service_mappings[@]}"; do
local target_url="https://${service_mappings[$service_name]}"
# Use awk to find and update the href for each specific service
# This finds the service by name and updates its href regardless of current URL
awk -v service="$service_name" -v new_url="$target_url" '
BEGIN { in_service = 0 }
# Check if we found the service name
/- [^:]+:/ {
if ($0 ~ ("- " service ":")) {
in_service = 1
} else {
in_service = 0
}
}
# If we are in the target service and find href line, update it
in_service && /href:/ {
gsub(/href: "[^"]*"/, "href: \"" new_url "\"")
}
# Print the line (modified or not)
{ print }
' "$SERVICES_YAML" > "${SERVICES_YAML}.tmp"
# Replace the original file with the updated version
mv "${SERVICES_YAML}.tmp" "$SERVICES_YAML"
echo " ✓ Updated $service_name -> $target_url"
done
echo "✅ All service URLs updated to use domain: $new_domain"
return 0
}
# Function to update the login URL in main.html
update_main_html() {
local new_domain=$1
if [ ! -f "$MAIN_HTML" ]; then
echo "Warning: main.html not found at $MAIN_HTML"
return 1
fi
echo "Updating login URL in main.html..."
# Create a backup of the main.html file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${MAIN_HTML}.backup_${timestamp}"
cp "$MAIN_HTML" "$backup_file"
echo "Created backup of main.html at $backup_file"
# Update the login button href - handle current domain
sed -i "s|href=\"https://homepage\.[^\"]*\"|href=\"https://homepage.$new_domain\"|g" "$MAIN_HTML"
# Also update any albertademocracytaskforce.org references
sed -i "s|albertademocracytaskforce\.org|$new_domain|g" "$MAIN_HTML"
# Update any test.com or changeme.org references
sed -i "s|test\.com|$new_domain|g" "$MAIN_HTML"
sed -i "s|changeme\.org|$new_domain|g" "$MAIN_HTML"
echo "✅ Updated login URL in main.html to: https://homepage.$new_domain"
return 0
}
# Function to get Cloudflare API Token and Zone ID
get_cloudflare_credentials() {
echo ""
echo "To use Cloudflare tunnels, you'll need to configure your credentials."
echo ""
echo "=== Cloudflare Configuration ==="
echo ""
echo "You'll need:"
echo "1. Your Cloudflare API Token (with Zone.DNS and Account.Cloudflare Tunnel permissions)"
echo "2. Your Cloudflare Zone ID (found in the Cloudflare dashboard overview)"
echo "3. Your Cloudflare Account ID (found in the Cloudflare dashboard)"
echo ""
echo "These credentials will be used for:"
echo "- Creating and configuring tunnels"
echo "- Managing DNS records"
echo "- Setting up access policies"
echo ""
}
# Function to update the tunnel configuration file with new domain
update_tunnel_config() {
local new_domain=$1
local tunnel_id=${2:-"(insert-tunnel-id)"}
local creds_path=${3:-"/path/to/credentials/file"}
# Create directory if it doesn't exist
mkdir -p "$TUNNEL_CONFIG_DIR"
echo "Updating tunnel configuration with new domain..."
# Create a backup if file exists
if [ -f "$TUNNEL_CONFIG_FILE" ]; then
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${TUNNEL_CONFIG_FILE}.backup_${timestamp}"
cp "$TUNNEL_CONFIG_FILE" "$backup_file"
echo "Created backup of tunnel-config.yml at $backup_file"
fi
# Load current environment variables to get port numbers
load_env_vars
# Create/update the tunnel configuration
cat > "$TUNNEL_CONFIG_FILE" << EOL
# filepath: /home/bunker-admin/changemaker.lite/configs/cloudflare/tunnel-config.yml
# Cloudflare Tunnel Configuration
# Auto-generated by Changemaker Configuration Wizard
tunnel: $tunnel_id # e.g. 1234567890abcdef
credentials-file: $creds_path # e.g. /home/bunker-admin/.cloudflared/[insert tunnel number].json
ingress:
- hostname: homepage.$new_domain
service: http://localhost:${HOMEPAGE_PORT:-3010}
- hostname: code.$new_domain
service: http://localhost:${CODE_SERVER_PORT:-8888}
- hostname: listmonk.$new_domain
service: http://localhost:${LISTMONK_PORT:-9001}
- hostname: docs.$new_domain
service: http://localhost:${MKDOCS_PORT:-4000}
- hostname: $new_domain
service: http://localhost:${MKDOCS_SITE_SERVER_PORT:-4002}
- hostname: n8n.$new_domain
service: http://localhost:${N8N_PORT:-5678}
- hostname: db.$new_domain
service: http://localhost:${NOCODB_PORT:-8090}
- hostname: git.$new_domain
service: http://localhost:${GITEA_WEB_PORT:-3030}
- hostname: map.$new_domain
service: http://localhost:${MAP_PORT:-3000}
- hostname: qr.$new_domain
service: http://localhost:${MINI_QR_PORT:-8089}
# Catch-all rule (required)
- service: http_status:404
EOL
echo "✅ Updated tunnel configuration for domain: $new_domain"
return 0
}
# Function to load environment variables from .env file
load_env_vars() {
if [ -f "$ENV_FILE" ]; then
# Load variables from .env file, ignoring comments and empty lines
while IFS= read -r line; do
if [[ "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
export "$line"
fi
done < <(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$ENV_FILE")
fi
}
# Function to update or create the map's .env file with domain settings
update_map_env() {
local new_domain=$1
# If the map .env file does not exist, create it with defaults
if [ ! -f "$MAP_ENV_FILE" ]; then
echo "Map .env file not found at $MAP_ENV_FILE, creating a new one with defaults."
cat > "$MAP_ENV_FILE" <<EOL
NOCODB_API_URL=https://db.$new_domain/api/v1
NOCODB_API_TOKEN=changeme
# NocoDB View URL is the URL to your NocoDB view where the map data is stored.
NOCODB_VIEW_URL=
# NOCODB_LOGIN_SHEET is the URL to your NocoDB login sheet.
NOCODB_LOGIN_SHEET=
# NOCODB_SETTINGS_SHEET is the URL to your NocoDB settings sheet.
NOCODB_SETTINGS_SHEET=
# NOCODB_SHIFTS_SHEET is the urls to your shifts sheets.
NOCODB_SHIFTS_SHEET=
# NOCODB_SHIFT_SIGNUPS_SHEET is the URL to your NocoDB shift signups sheet where users can add their own shifts.
NOCODB_SHIFT_SIGNUPS_SHEET=
# Server Configuration
PORT=3000
NODE_ENV=production
# Session Secret (IMPORTANT: Generate a secure random string for production)
# You can generate one with: openssl rand -hex 32
SESSION_SECRET=$(openssl rand -hex 32 2>/dev/null || echo "changeme")
# Map Defaults (Edmonton, Alberta, Canada)
DEFAULT_LAT=53.5461
DEFAULT_LNG=-113.4938
DEFAULT_ZOOM=11
# Optional: Map Boundaries (prevents users from adding points outside area)
# BOUND_NORTH=53.7
# BOUND_SOUTH=53.4
# BOUND_EAST=-113.3
# BOUND_WEST=-113.7
# Cloudflare Settings
TRUST_PROXY=true
COOKIE_DOMAIN=.$new_domain
# Update NODE_ENV to production for HTTPS
NODE_ENV=production
# Add allowed origin
ALLOWED_ORIGINS=https://map.$new_domain,http://localhost:3000
EOL
echo "✅ Created new map .env file at $MAP_ENV_FILE"
return 0
fi
echo "Updating map .env file with new domain settings..."
# Create a backup of the map's .env file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${MAP_ENV_FILE}.backup_${timestamp}"
cp "$MAP_ENV_FILE" "$backup_file"
echo "Created backup of map .env at $backup_file"
# Update NOCODB_API_URL to use new domain
if grep -q "^NOCODB_API_URL=" "$MAP_ENV_FILE"; then
sed -i "s|^NOCODB_API_URL=.*|NOCODB_API_URL=https://db.$new_domain/api/v1|" "$MAP_ENV_FILE"
else
echo "NOCODB_API_URL=https://db.$new_domain/api/v1" >> "$MAP_ENV_FILE"
fi
# Update COOKIE_DOMAIN
if grep -q "^COOKIE_DOMAIN=" "$MAP_ENV_FILE"; then
sed -i "s|^COOKIE_DOMAIN=.*|COOKIE_DOMAIN=.$new_domain|" "$MAP_ENV_FILE"
else
echo "COOKIE_DOMAIN=.$new_domain" >> "$MAP_ENV_FILE"
fi
# Update ALLOWED_ORIGINS
local allowed_origins="https://map.$new_domain,http://localhost:3000"
if grep -q "^ALLOWED_ORIGINS=" "$MAP_ENV_FILE"; then
sed -i "s|^ALLOWED_ORIGINS=.*|ALLOWED_ORIGINS=$allowed_origins|" "$MAP_ENV_FILE"
else
echo "ALLOWED_ORIGINS=$allowed_origins" >> "$MAP_ENV_FILE"
fi
# Update domain references in NocoDB URLs while preserving the sheet IDs and paths
# This will update domains like cmlite.org, changeme.org, etc. to the new domain
sed -i "s|://db\.cmlite\.org/|://db.$new_domain/|g" "$MAP_ENV_FILE"
sed -i "s|://map\.cmlite\.org|://map.$new_domain|g" "$MAP_ENV_FILE"
echo "✅ Updated map .env file with:"
echo " - NOCODB_API_URL=https://db.$new_domain/api/v1"
echo " - COOKIE_DOMAIN=.$new_domain"
echo " - ALLOWED_ORIGINS=$allowed_origins"
echo " - Updated all NocoDB URLs to use $new_domain domain"
return 0
}
# Function to get instance identifier
get_instance_identifier() {
# Try to get from directory name first
local dir_name=$(basename "$SCRIPT_DIR")
local default_instance=""
# Extract potential instance name from directory
if [[ "$dir_name" =~ changemaker\.lite\.(.+)$ ]]; then
default_instance="${BASH_REMATCH[1]}"
elif [[ "$dir_name" != "changemaker.lite" ]]; then
default_instance="$dir_name"
fi
echo ""
echo "=== Instance Configuration ==="
echo "To run multiple Changemaker instances on the same machine,"
echo "each instance needs a unique identifier for containers and networks."
echo ""
if [ -n "$default_instance" ]; then
echo "Detected potential instance name from directory: $default_instance"
read -p "Use this instance identifier? [Y/n]: " use_detected
if [[ ! "$use_detected" =~ ^[Nn]$ ]]; then
echo "$default_instance"
return 0
fi
fi
read -p "Enter instance identifier (letters, numbers, hyphens only) [default: main]: " instance_id
# Validate and sanitize instance identifier
if [ -z "$instance_id" ]; then
instance_id="main"
fi
# Remove invalid characters and convert to lowercase
instance_id=$(echo "$instance_id" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g')
if [ -z "$instance_id" ]; then
instance_id="main"
fi
echo "$instance_id"
}
# Function to update docker-compose.yml with unique names
update_docker_compose_names() {
local instance_id=$1
if [ -z "$instance_id" ] || [ "$instance_id" = "main" ]; then
echo "Using default container names (no instance suffix)"
return 0
fi
echo "Updating docker-compose.yml with instance identifier: $instance_id"
# Create a backup of the docker-compose.yml file
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${DOCKER_COMPOSE_FILE}.backup_${timestamp}"
cp "$DOCKER_COMPOSE_FILE" "$backup_file"
echo "Created backup of docker-compose.yml at $backup_file"
# Create temporary file for modifications
local temp_file=$(mktemp)
# Update container names, network names, and volume names
sed -e "s/container_name: \([^-]*\)$/container_name: \1-${instance_id}/g" \
-e "s/container_name: \([^-]*\)-changemaker$/container_name: \1-changemaker-${instance_id}/g" \
-e "s/container_name: \([^-]*\)_\([^-]*\)$/container_name: \1_\2_${instance_id}/g" \
-e "s/container_name: \([^-]*\)_\([^-]*\)_changemaker$/container_name: \1_\2_changemaker_${instance_id}/g" \
-e "s/networks:/networks:/g" \
-e "s/changemaker-lite:/changemaker-lite-${instance_id}:/g" \
-e "s/- changemaker-lite$/- changemaker-lite-${instance_id}/g" \
-e "s/driver: bridge$/driver: bridge/g" \
-e "s/volumes:/volumes:/g" \
-e "s/listmonk-data:/listmonk-data-${instance_id}:/g" \
-e "s/n8n_data:/n8n_data_${instance_id}:/g" \
-e "s/nc_data:/nc_data_${instance_id}:/g" \
-e "s/db_data:/db_data_${instance_id}:/g" \
-e "s/gitea_data:/gitea_data_${instance_id}:/g" \
-e "s/mysql_data:/mysql_data_${instance_id}:/g" \
"$DOCKER_COMPOSE_FILE" > "$temp_file"
# Replace the original file
mv "$temp_file" "$DOCKER_COMPOSE_FILE"
echo "✅ Updated docker-compose.yml with instance-specific names:"
echo " - Container names: *-${instance_id}"
echo " - Network name: changemaker-lite-${instance_id}"
echo " - Volume names: *-${instance_id}"
return 0
}
# Function to update .env file with instance identifier
update_env_instance_config() {
local instance_id=$1
if [ -n "$instance_id" ] && [ "$instance_id" != "main" ]; then
update_env_var "INSTANCE_ID" "$instance_id"
update_env_var "COMPOSE_PROJECT_NAME" "changemaker-lite-${instance_id}"
echo "Updated .env with instance configuration"
fi
}
# Initialize a new .env file if it doesn't exist
if [ ! -f "$ENV_FILE" ]; then
echo "No .env file found. Creating a new one from scratch."
touch "$ENV_FILE"
initialize_env_file
else
echo "Found existing .env file. Will update values."
backup_env_file
# For existing .env files, also scan ports and suggest alternatives if conflicts exist
echo ""
echo "Checking existing port assignments for conflicts..."
load_env_vars
check_port_conflicts
fi
# Load existing environment variables
load_env_vars
echo -e "\n\nWelcome to Changemaker Config!\n"
echo "This script will help you configure your .env file for Changemaker."
echo "Please provide the following information:"
# Get instance identifier and update docker-compose.yml
echo -e "\n---- Instance Configuration ----"
instance_identifier=$(get_instance_identifier)
update_docker_compose_names "$instance_identifier"
update_env_instance_config "$instance_identifier"
# Domain configuration
read -p "Enter your domain name (without protocol, e.g., example.com): " domain_name
if [ -z "$domain_name" ]; then
echo "Domain name cannot be empty. Using default: changeme.org"
domain_name="changeme.org"
fi
echo -e "\nUpdating domain settings in .env file..."
# Update main domain settings
update_env_var "DOMAIN" "$domain_name"
update_env_var "BASE_DOMAIN" "https://$domain_name"
update_env_var "HOMEPAGE_VAR_BASE_URL" "https://$domain_name"
update_env_var "LISTMONK_HOSTNAME" "listmonk.$domain_name"
update_env_var "N8N_HOST" "n8n.$domain_name"
update_env_var "CF_DOMAIN" "$domain_name"
update_env_var "GITEA_DOMAIN" "git.$domain_name"
update_env_var "GITEA_ROOT_URL" "https://git.$domain_name"
# Update cookie and CORS settings
update_env_var "COOKIE_DOMAIN" ".$domain_name"
update_env_var "ALLOWED_ORIGINS" "https://map.$domain_name,http://localhost:3000"
echo "Domain settings updated successfully!"
# Update the map's .env file
echo -e "\nUpdating map configuration..."
update_map_env "$domain_name"
# Cloudflare Configuration
echo -e "\n---- Cloudflare Configuration ----"
get_cloudflare_credentials
read -p "Do you want to configure Cloudflare settings now? [Y/n]: " configure_cf
if [[ ! "$configure_cf" =~ ^[Nn]$ ]]; then
# Get Cloudflare API token
read -p "Enter your Cloudflare API Token: " cf_api_token
if [ -z "$cf_api_token" ] || [ "$cf_api_token" = "your_cloudflare_api_token" ]; then
echo "Warning: API token is required for Cloudflare integration"
cf_api_token="your_cloudflare_api_token"
fi
update_env_var "CF_API_TOKEN" "$cf_api_token"
# Get Cloudflare Zone ID
read -p "Enter your Cloudflare Zone ID: " cf_zone_id
if [ -z "$cf_zone_id" ] || [ "$cf_zone_id" = "your_cloudflare_zone_id" ]; then
echo "Warning: Zone ID is required for DNS configuration"
cf_zone_id="your_cloudflare_zone_id"
fi
update_env_var "CF_ZONE_ID" "$cf_zone_id"
# Get Cloudflare Account ID
read -p "Enter your Cloudflare Account ID: " cf_account_id
if [ -z "$cf_account_id" ] || [ "$cf_account_id" = "your_cloudflare_account_id" ]; then
echo "Warning: Account ID is required for tunnel operations"
cf_account_id="your_cloudflare_account_id"
fi
update_env_var "CF_ACCOUNT_ID" "$cf_account_id"
# Note: CF_TUNNEL_ID will be set by start-production.sh when the tunnel is created
update_env_var "CF_TUNNEL_ID" "will_be_set_by_start_production"
echo ""
echo "Cloudflare configuration saved to .env file."
echo ""
echo "To complete the setup and deploy your services:"
echo "1. Start your services with: docker compose up -d"
echo "2. Run ./start-production.sh to configure the Cloudflare tunnel"
echo ""
else
echo "Skipping Cloudflare configuration."
echo "You'll need to configure Cloudflare settings manually before running start-production.sh."
fi
# Update the site_url in mkdocs.yml
echo -e "\nUpdating site_url in mkdocs.yml..."
update_mkdocs_yml "$domain_name"
# Update service URLs in services.yaml
echo -e "\nUpdating service URLs in services.yaml..."
update_services_yaml "$domain_name"
# Update the login URL in main.html
echo -e "\nUpdating login URL in main.html..."
update_main_html "$domain_name"
# Listmonk Admin Credentials configuration
echo -e "\n---- Listmonk Admin Credentials ----"
read -p "Enter Listmonk admin email/username [default: admin@example.com]: " listmonk_user
read -sp "Enter Listmonk admin password [default: changeMe]: " listmonk_password
echo # Add new line after password input
if [ -z "$listmonk_user" ]; then
listmonk_user="admin@example.com"
fi
if [ -z "$listmonk_password" ]; then
listmonk_password="changeMe"
fi
update_env_var "LISTMONK_ADMIN_USER" "$listmonk_user"
update_env_var "LISTMONK_ADMIN_PASSWORD" "$listmonk_password"
echo "Listmonk admin credentials updated."
# N8N User Credentials configuration
echo -e "\n---- N8N Admin Credentials ----"
read -p "Enter N8N admin email [default: admin@example.com]: " n8n_email
read -sp "Enter N8N admin password [default: changeMe]: " n8n_password
echo # Add new line after password input
if [ -z "$n8n_email" ]; then
n8n_email="admin@example.com"
fi
if [ -z "$n8n_password" ]; then
n8n_password="changeMe"
fi
update_env_var "N8N_USER_EMAIL" "$n8n_email"
update_env_var "N8N_USER_PASSWORD" "$n8n_password"
echo "N8N admin credentials updated."
# Generate secure passwords for database and encryption
echo -e "\n---- Generating Secure Passwords ----"
echo "Generating secure passwords for database and encryption keys..."
# Generate and update database password
postgres_password=$(generate_password 20)
update_env_var "POSTGRES_PASSWORD" "$postgres_password"
# Generate and update N8N encryption key
n8n_encryption_key=$(generate_password 32)
update_env_var "N8N_ENCRYPTION_KEY" "$n8n_encryption_key"
# Generate and update NocoDB passwords
nocodb_jwt_secret=$(generate_password 32)
update_env_var "NOCODB_JWT_SECRET" "$nocodb_jwt_secret"
nocodb_db_password=$(generate_password 20)
update_env_var "NOCODB_DB_PASSWORD" "$nocodb_db_password"
# Generate and update Gitea passwords (these should already exist in the file)
gitea_db_password=$(generate_password 20)
update_env_var "GITEA_DB_PASSWD" "$gitea_db_password"
gitea_db_root_password=$(generate_password 20)
update_env_var "GITEA_DB_ROOT_PASSWORD" "$gitea_db_root_password"
echo "Secure passwords generated and updated."
echo -e "\n✅ Configuration completed successfully!"
echo "Your .env file has been configured with:"
echo "- Instance ID: $instance_identifier"
echo "- Domain: $domain_name"
echo "- Cookie Domain: .$domain_name"
echo "- Allowed Origins: https://map.$domain_name,http://localhost:3000"
echo "- Map .env updated with domain settings"
echo "- Listmonk Admin: $listmonk_user"
echo "- N8N Admin Email: $n8n_email"
echo "- Secure random passwords for database, encryption, and NocoDB"
echo "- Tunnel configuration updated at: $TUNNEL_CONFIG_FILE"
echo -e "\nYour .env file is located at: $ENV_FILE"
echo "A backup of your original .env file was created before modifications."
echo ""
echo "======================================"
echo "Next Steps:"
echo "======================================"
echo ""
if [ -n "$instance_identifier" ] && [ "$instance_identifier" != "main" ]; then
echo "Instance: $instance_identifier"
echo ""
fi
echo "1. Start services locally:"
echo " docker compose up -d"
echo ""
echo "2. Test all services at:"
echo " - Homepage: http://localhost:${HOMEPAGE_PORT:-3010}"
echo " - Code Server: http://localhost:${CODE_SERVER_PORT:-8888}"
echo " - Listmonk: http://localhost:${LISTMONK_PORT:-9000}"
echo " - Documentation: http://localhost:${MKDOCS_PORT:-4000}"
echo " - n8n: http://localhost:${N8N_PORT:-5678}"
echo " - NocoDB: http://localhost:${NOCODB_PORT:-8090}"
echo " - Gitea: http://localhost:${GITEA_WEB_PORT:-3030}"
echo " - Map: http://localhost:${MAP_PORT:-3000}"
echo " - Mini QR: http://localhost:${MINI_QR_PORT:-8089}"
echo ""
echo "3. When ready for production:"
echo " ./start-production.sh"
echo ""
echo "======================================"