Files
n8n-install/scripts/generate_welcome_page.sh

593 lines
17 KiB
Bash
Executable File

#!/bin/bash
# Generate data.json for the welcome page with active services and credentials
set -e
# Source the utilities file and initialize paths
source "$(dirname "$0")/utils.sh"
init_paths
# Source local mode utilities
source "$SCRIPT_DIR/local.sh"
OUTPUT_FILE="$PROJECT_ROOT/welcome/data.json"
# Load environment variables from .env file
load_env || exit 1
# Get protocol using local.sh helper (based on INSTALL_MODE)
PROTOCOL="$(get_protocol)"
# Ensure welcome directory exists
mkdir -p "$PROJECT_ROOT/welcome"
# Remove existing data.json if it exists (always regenerate)
if [ -f "$OUTPUT_FILE" ]; then
rm -f "$OUTPUT_FILE"
fi
# Start building JSON
GENERATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Build services array - each entry is a formatted JSON block
declare -a SERVICES_ARRAY
# n8n
if is_profile_active "n8n"; then
N8N_WORKER_COUNT_VAL="${N8N_WORKER_COUNT:-1}"
SERVICES_ARRAY+=(" \"n8n\": {
\"hostname\": \"$(json_escape "$N8N_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create your account on first login\"
},
\"extra\": {
\"workers\": \"$N8N_WORKER_COUNT_VAL\"
}
}")
fi
# Flowise
if is_profile_active "flowise"; then
SERVICES_ARRAY+=(" \"flowise\": {
\"hostname\": \"$(json_escape "$FLOWISE_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create your account on first login\"
}
}")
fi
# Open WebUI
if is_profile_active "open-webui"; then
SERVICES_ARRAY+=(" \"open-webui\": {
\"hostname\": \"$(json_escape "$WEBUI_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create account on first login\"
}
}")
fi
# Grafana (monitoring)
if is_profile_active "monitoring"; then
SERVICES_ARRAY+=(" \"grafana\": {
\"hostname\": \"$(json_escape "$GRAFANA_HOSTNAME")\",
\"credentials\": {
\"username\": \"admin\",
\"password\": \"$(json_escape "$GRAFANA_ADMIN_PASSWORD")\"
}
}")
SERVICES_ARRAY+=(" \"prometheus\": {
\"hostname\": \"$(json_escape "$PROMETHEUS_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$PROMETHEUS_USERNAME")\",
\"password\": \"$(json_escape "$PROMETHEUS_PASSWORD")\"
}
}")
fi
# Portainer
if is_profile_active "portainer"; then
SERVICES_ARRAY+=(" \"portainer\": {
\"hostname\": \"$(json_escape "$PORTAINER_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create admin account on first login\"
}
}")
fi
# Databasus
if is_profile_active "databasus"; then
SERVICES_ARRAY+=(" \"databasus\": {
\"hostname\": \"$(json_escape "$DATABASUS_HOSTNAME")\",
\"credentials\": {
\"note\": \"PostgreSQL credentials are shown in the PostgreSQL card\"
}
}")
fi
# Langfuse
if is_profile_active "langfuse"; then
SERVICES_ARRAY+=(" \"langfuse\": {
\"hostname\": \"$(json_escape "$LANGFUSE_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$LANGFUSE_INIT_USER_EMAIL")\",
\"password\": \"$(json_escape "$LANGFUSE_INIT_USER_PASSWORD")\"
}
}")
fi
# Supabase
if is_profile_active "supabase"; then
SERVICES_ARRAY+=(" \"supabase\": {
\"hostname\": \"$(json_escape "$SUPABASE_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$DASHBOARD_USERNAME")\",
\"password\": \"$(json_escape "$DASHBOARD_PASSWORD")\"
},
\"extra\": {
\"internal_api\": \"http://kong:8000\",
\"service_role_key\": \"$(json_escape "$SERVICE_ROLE_KEY")\"
}
}")
fi
# Dify
if is_profile_active "dify"; then
SERVICES_ARRAY+=(" \"dify\": {
\"hostname\": \"$(json_escape "$DIFY_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create account on first login\"
},
\"extra\": {
\"api_endpoint\": \"${PROTOCOL}://$(json_escape "$DIFY_HOSTNAME")/v1\",
\"internal_api\": \"http://dify-api:5001\"
}
}")
fi
# Qdrant
if is_profile_active "qdrant"; then
SERVICES_ARRAY+=(" \"qdrant\": {
\"hostname\": \"$(json_escape "$QDRANT_HOSTNAME")\",
\"credentials\": {
\"api_key\": \"$(json_escape "$QDRANT_API_KEY")\"
},
\"extra\": {
\"dashboard\": \"${PROTOCOL}://$(json_escape "$QDRANT_HOSTNAME")/dashboard\",
\"internal_api\": \"http://qdrant:6333\"
}
}")
fi
# Weaviate
if is_profile_active "weaviate"; then
SERVICES_ARRAY+=(" \"weaviate\": {
\"hostname\": \"$(json_escape "$WEAVIATE_HOSTNAME")\",
\"credentials\": {
\"api_key\": \"$(json_escape "$WEAVIATE_API_KEY")\",
\"username\": \"$(json_escape "$WEAVIATE_USERNAME")\"
}
}")
fi
# Neo4j
if is_profile_active "neo4j"; then
SERVICES_ARRAY+=(" \"neo4j\": {
\"hostname\": \"$(json_escape "$NEO4J_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$NEO4J_AUTH_USERNAME")\",
\"password\": \"$(json_escape "$NEO4J_AUTH_PASSWORD")\"
},
\"extra\": {
\"bolt_port\": \"7687\"
}
}")
fi
# NocoDB
if is_profile_active "nocodb"; then
SERVICES_ARRAY+=(" \"nocodb\": {
\"hostname\": \"$(json_escape "$NOCODB_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create your account on first login\",
\"user_token\": \"$(json_escape "$NOCODB_JWT_SECRET")\"
},
\"extra\": {
\"internal_api\": \"http://nocodb:8080\",
\"docs\": \"https://docs.nocodb.com\"
}
}")
fi
# SearXNG
if is_profile_active "searxng"; then
SERVICES_ARRAY+=(" \"searxng\": {
\"hostname\": \"$(json_escape "$SEARXNG_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$SEARXNG_USERNAME")\",
\"password\": \"$(json_escape "$SEARXNG_PASSWORD")\"
}
}")
fi
# RAGApp
if is_profile_active "ragapp"; then
SERVICES_ARRAY+=(" \"ragapp\": {
\"hostname\": \"$(json_escape "$RAGAPP_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$RAGAPP_USERNAME")\",
\"password\": \"$(json_escape "$RAGAPP_PASSWORD")\"
},
\"extra\": {
\"admin\": \"${PROTOCOL}://$(json_escape "$RAGAPP_HOSTNAME")/admin\",
\"docs\": \"${PROTOCOL}://$(json_escape "$RAGAPP_HOSTNAME")/docs\",
\"internal_api\": \"http://ragapp:8000\"
}
}")
fi
# RAGFlow
if is_profile_active "ragflow"; then
SERVICES_ARRAY+=(" \"ragflow\": {
\"hostname\": \"$(json_escape "$RAGFLOW_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create account on first login\"
},
\"extra\": {
\"internal_api\": \"http://ragflow:80\"
}
}")
fi
# LightRAG
if is_profile_active "lightrag"; then
SERVICES_ARRAY+=(" \"lightrag\": {
\"hostname\": \"$(json_escape "$LIGHTRAG_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$LIGHTRAG_USERNAME")\",
\"password\": \"$(json_escape "$LIGHTRAG_PASSWORD")\",
\"api_key\": \"$(json_escape "$LIGHTRAG_API_KEY")\"
},
\"extra\": {
\"docs\": \"${PROTOCOL}://$(json_escape "$LIGHTRAG_HOSTNAME")/docs\",
\"internal_api\": \"http://lightrag:9621\"
}
}")
fi
# Letta
if is_profile_active "letta"; then
SERVICES_ARRAY+=(" \"letta\": {
\"hostname\": \"$(json_escape "$LETTA_HOSTNAME")\",
\"credentials\": {
\"api_key\": \"$(json_escape "$LETTA_SERVER_PASSWORD")\"
}
}")
fi
# ComfyUI
if is_profile_active "comfyui"; then
SERVICES_ARRAY+=(" \"comfyui\": {
\"hostname\": \"$(json_escape "$COMFYUI_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$COMFYUI_USERNAME")\",
\"password\": \"$(json_escape "$COMFYUI_PASSWORD")\"
}
}")
fi
# LibreTranslate
if is_profile_active "libretranslate"; then
SERVICES_ARRAY+=(" \"libretranslate\": {
\"hostname\": \"$(json_escape "$LT_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$LT_USERNAME")\",
\"password\": \"$(json_escape "$LT_PASSWORD")\"
},
\"extra\": {
\"internal_api\": \"http://libretranslate:5000\"
}
}")
fi
# Docling
if is_profile_active "docling"; then
SERVICES_ARRAY+=(" \"docling\": {
\"hostname\": \"$(json_escape "$DOCLING_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$DOCLING_USERNAME")\",
\"password\": \"$(json_escape "$DOCLING_PASSWORD")\"
},
\"extra\": {
\"ui\": \"${PROTOCOL}://$(json_escape "$DOCLING_HOSTNAME")/ui\",
\"docs\": \"${PROTOCOL}://$(json_escape "$DOCLING_HOSTNAME")/docs\",
\"internal_api\": \"http://docling:5001\"
}
}")
fi
# PaddleOCR
if is_profile_active "paddleocr"; then
SERVICES_ARRAY+=(" \"paddleocr\": {
\"hostname\": \"$(json_escape "$PADDLEOCR_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$PADDLEOCR_USERNAME")\",
\"password\": \"$(json_escape "$PADDLEOCR_PASSWORD")\"
},
\"extra\": {
\"internal_api\": \"http://paddleocr:8080\"
}
}")
fi
# Postiz
if is_profile_active "postiz"; then
SERVICES_ARRAY+=(" \"postiz\": {
\"hostname\": \"$(json_escape "$POSTIZ_HOSTNAME")\",
\"credentials\": {
\"note\": \"Create account on first login\"
},
\"extra\": {
\"internal_api\": \"http://postiz:5000\"
}
}")
fi
# Temporal UI
if is_profile_active "postiz"; then
SERVICES_ARRAY+=(" \"temporal-ui\": {
\"hostname\": \"$(json_escape "$TEMPORAL_UI_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$TEMPORAL_UI_USERNAME")\",
\"password\": \"$(json_escape "$TEMPORAL_UI_PASSWORD")\"
},
\"extra\": {
\"note\": \"Workflow orchestration admin for Postiz\"
}
}")
fi
# WAHA
if is_profile_active "waha"; then
SERVICES_ARRAY+=(" \"waha\": {
\"hostname\": \"$(json_escape "$WAHA_HOSTNAME")\",
\"credentials\": {
\"username\": \"$(json_escape "$WAHA_DASHBOARD_USERNAME")\",
\"password\": \"$(json_escape "$WAHA_DASHBOARD_PASSWORD")\",
\"api_key\": \"$(json_escape "$WAHA_API_KEY_PLAIN")\"
},
\"extra\": {
\"dashboard\": \"${PROTOCOL}://$(json_escape "$WAHA_HOSTNAME")/dashboard\",
\"swagger_user\": \"$(json_escape "$WHATSAPP_SWAGGER_USERNAME")\",
\"swagger_pass\": \"$(json_escape "$WHATSAPP_SWAGGER_PASSWORD")\",
\"internal_api\": \"http://waha:3000\"
}
}")
fi
# Crawl4AI (internal only)
if is_profile_active "crawl4ai"; then
SERVICES_ARRAY+=(" \"crawl4ai\": {
\"hostname\": null,
\"credentials\": {
\"note\": \"Internal service only\"
},
\"extra\": {
\"internal_api\": \"http://crawl4ai:11235\"
}
}")
fi
# Gotenberg (internal only)
if is_profile_active "gotenberg"; then
SERVICES_ARRAY+=(" \"gotenberg\": {
\"hostname\": null,
\"credentials\": {
\"note\": \"Internal service only\"
},
\"extra\": {
\"internal_api\": \"http://gotenberg:3000\",
\"docs\": \"https://gotenberg.dev/docs\"
}
}")
fi
# Ollama (internal only)
if is_profile_active "cpu" || is_profile_active "gpu-nvidia" || is_profile_active "gpu-amd"; then
SERVICES_ARRAY+=(" \"ollama\": {
\"hostname\": null,
\"credentials\": {
\"note\": \"Internal service only\"
},
\"extra\": {
\"internal_api\": \"http://ollama:11434\"
}
}")
fi
# Redis/Valkey (internal only, shown if n8n or langfuse active)
if is_profile_active "n8n" || is_profile_active "langfuse"; then
SERVICES_ARRAY+=(" \"redis\": {
\"hostname\": null,
\"credentials\": {
\"password\": \"$(json_escape "$REDIS_AUTH")\"
},
\"extra\": {
\"internal_url\": \"${REDIS_HOST:-redis}:${REDIS_PORT:-6379}\"
}
}")
fi
# PostgreSQL (internal only, shown if n8n or langfuse active)
if is_profile_active "n8n" || is_profile_active "langfuse"; then
SERVICES_ARRAY+=(" \"postgres\": {
\"hostname\": null,
\"credentials\": {
\"username\": \"$(json_escape "${POSTGRES_USER:-postgres}")\",
\"password\": \"$(json_escape "$POSTGRES_PASSWORD")\"
},
\"extra\": {
\"internal_host\": \"postgres\",
\"internal_port\": \"${POSTGRES_PORT:-5432}\",
\"database\": \"$(json_escape "${POSTGRES_DB:-postgres}")\"
}
}")
fi
# Python Runner (internal only)
if is_profile_active "python-runner"; then
SERVICES_ARRAY+=(" \"python-runner\": {
\"hostname\": null,
\"credentials\": {
\"note\": \"Mount: ./python-runner → /app\\nEntry: /app/main.py\\nLogs: make logs s=python-runner\"
}
}")
fi
# Cloudflare Tunnel
if is_profile_active "cloudflare-tunnel"; then
SERVICES_ARRAY+=(" \"cloudflare-tunnel\": {
\"hostname\": null,
\"credentials\": {
\"note\": \"Zero-trust access via Cloudflare network\"
},
\"extra\": {
\"recommendation\": \"Close ports 80, 443, 7687 in your VPS firewall after confirming tunnel connectivity\"
}
}")
fi
# Gost Proxy (internal only)
if is_profile_active "gost"; then
SERVICES_ARRAY+=(" \"gost\": {
\"hostname\": null,
\"credentials\": {
\"username\": \"$(json_escape "$GOST_USERNAME")\",
\"password\": \"$(json_escape "$GOST_PASSWORD")\"
},
\"extra\": {
\"note\": \"Routes AI traffic through external proxy for geo-bypass\",
\"proxy_url\": \"$(json_escape "$GOST_PROXY_URL")\",
\"upstream_proxy\": \"$(json_escape "$GOST_UPSTREAM_PROXY")\",
\"internal_api\": \"http://gost:8080\"
}
}")
fi
# Join array with commas and newlines
SERVICES_JSON=""
for i in "${!SERVICES_ARRAY[@]}"; do
if [ $i -gt 0 ]; then
SERVICES_JSON+=",
"
fi
SERVICES_JSON+="${SERVICES_ARRAY[$i]}"
done
# Build quick_start array based on active profiles
declare -a QUICK_START_ARRAY
STEP_NUM=1
# Step 1: Log into primary service (n8n or Flowise)
if is_profile_active "n8n"; then
QUICK_START_ARRAY+=(" {
\"step\": $STEP_NUM,
\"title\": \"Log into n8n\",
\"description\": \"Create your account on first login\"
}")
((STEP_NUM++))
elif is_profile_active "flowise"; then
QUICK_START_ARRAY+=(" {
\"step\": $STEP_NUM,
\"title\": \"Log into Flowise\",
\"description\": \"Create your account on first login\"
}")
((STEP_NUM++))
fi
# Step 2: Create first workflow (if n8n active)
if is_profile_active "n8n"; then
QUICK_START_ARRAY+=(" {
\"step\": $STEP_NUM,
\"title\": \"Create your first workflow\",
\"description\": \"Start with Manual Trigger + HTTP Request nodes\"
}")
((STEP_NUM++))
fi
# Step 3: Configure database backups (if databasus active)
if is_profile_active "databasus"; then
QUICK_START_ARRAY+=(" {
\"step\": $STEP_NUM,
\"title\": \"Configure database backups\",
\"description\": \"Set up Databasus for automated database backups\"
}")
((STEP_NUM++))
fi
# Step 4: Monitor system (if monitoring active)
if is_profile_active "monitoring"; then
QUICK_START_ARRAY+=(" {
\"step\": $STEP_NUM,
\"title\": \"Monitor your system\",
\"description\": \"Use Grafana to track performance metrics\"
}")
((STEP_NUM++))
fi
# Join quick_start array
QUICK_START_JSON=""
for i in "${!QUICK_START_ARRAY[@]}"; do
if [ $i -gt 0 ]; then
QUICK_START_JSON+=",
"
fi
QUICK_START_JSON+="${QUICK_START_ARRAY[$i]}"
done
# Write final JSON with proper formatting
cat > "$OUTPUT_FILE" << EOF
{
"domain": "$(json_escape "$USER_DOMAIN_NAME")",
"protocol": "$PROTOCOL",
"generated_at": "$GENERATED_AT",
"services": {
$SERVICES_JSON
},
"quick_start": [
$QUICK_START_JSON
]
}
EOF
log_success "Welcome page data generated at: $OUTPUT_FILE"
log_info "Access it at: https://${WELCOME_HOSTNAME:-welcome.${USER_DOMAIN_NAME}}"
# Generate changelog.json with CHANGELOG.md content
CHANGELOG_JSON_FILE="$PROJECT_ROOT/welcome/changelog.json"
CHANGELOG_SOURCE="$PROJECT_ROOT/CHANGELOG.md"
if [ -f "$CHANGELOG_SOURCE" ]; then
# Read and escape content for JSON (preserve newlines as \n)
# Using awk for cross-platform compatibility (macOS + Linux)
CHANGELOG_CONTENT=$(awk '
BEGIN { ORS="" }
{
gsub(/\\/, "\\\\") # Escape backslashes first
gsub(/"/, "\\\"") # Escape double quotes
gsub(/\t/, "\\t") # Escape tabs
gsub(/\r/, "") # Remove carriage returns (CRLF → LF)
if (NR > 1) printf "\\n"
printf "%s", $0
}
' "$CHANGELOG_SOURCE")
# Write changelog.json file
printf '{\n "content": "%s"\n}\n' "$CHANGELOG_CONTENT" > "$CHANGELOG_JSON_FILE"
log_success "Changelog JSON generated at: $CHANGELOG_JSON_FILE"
else
log_warning "CHANGELOG.md not found, skipping changelog.json generation"
fi
log_info "Access it at: ${PROTOCOL}://${WELCOME_HOSTNAME:-welcome.${USER_DOMAIN_NAME}}"