diff --git a/scripts/07_final_report.sh b/scripts/07_final_report.sh index 073acfd..f875e80 100644 --- a/scripts/07_final_report.sh +++ b/scripts/07_final_report.sh @@ -46,24 +46,6 @@ echo " Installation Complete!" echo "=======================================================================" echo -# --- Make Commands --- -echo "================================= Make Commands =========================" -echo -echo " make logs View logs (all services)" -echo " make logs s= View logs for specific service" -echo " make status Show container status" -echo " make monitor Live CPU/memory monitoring" -echo " make restarts Show restart count per container" -echo -echo " make update Update system and services" -echo " make update-preview Preview available updates (dry-run)" -echo " make doctor Run system diagnostics" -echo " make clean Remove unused Docker resources" -echo -echo " make switch-beta Switch to beta (develop branch)" -echo " make switch-stable Switch to stable (main branch)" -echo - # --- Welcome Page --- echo "================================= Welcome Page ==========================" echo @@ -88,12 +70,20 @@ echo "1. Visit your Welcome Page to view all service credentials" echo " https://${WELCOME_HOSTNAME:-welcome.${USER_DOMAIN_NAME}}" echo echo "2. Store the Welcome Page credentials securely" -echo " (Username: ${WELCOME_USERNAME:-})" echo echo "3. Configure services as needed:" +if is_profile_active "n8n"; then echo " - n8n: Complete first-run setup with your email" +fi +if is_profile_active "portainer"; then echo " - Portainer: Create admin account on first login" +fi +if is_profile_active "flowise"; then +echo " - Flowise: Register and create your account" +fi +if is_profile_active "open-webui"; then echo " - Open WebUI: Register your account" +fi echo echo "4. Run 'make doctor' if you experience any issues" echo diff --git a/scripts/generate_welcome_page.sh b/scripts/generate_welcome_page.sh index e4d7406..3b13ac7 100755 --- a/scripts/generate_welcome_page.sh +++ b/scripts/generate_welcome_page.sh @@ -55,426 +55,433 @@ json_escape() { # Start building JSON GENERATED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -# Build services object (key-based) -SERVICES_JSON="" +# 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_JSON+='"n8n": { - "hostname": "'"$(json_escape "$N8N_HOSTNAME")"'", - "credentials": { - "note": "Use the email you provided during installation" + SERVICES_ARRAY+=(" \"n8n\": { + \"hostname\": \"$(json_escape "$N8N_HOSTNAME")\", + \"credentials\": { + \"note\": \"Use the email you provided during installation\" }, - "extra": { - "workers": "'"$N8N_WORKER_COUNT_VAL"'" + \"extra\": { + \"workers\": \"$N8N_WORKER_COUNT_VAL\" } - },' + }") fi # Flowise if is_profile_active "flowise"; then - SERVICES_JSON+='"flowise": { - "hostname": "'"$(json_escape "$FLOWISE_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$FLOWISE_USERNAME")"'", - "password": "'"$(json_escape "$FLOWISE_PASSWORD")"'" + SERVICES_ARRAY+=(" \"flowise\": { + \"hostname\": \"$(json_escape "$FLOWISE_HOSTNAME")\", + \"credentials\": { + \"username\": \"$(json_escape "$FLOWISE_USERNAME")\", + \"password\": \"$(json_escape "$FLOWISE_PASSWORD")\" } - },' + }") fi # Open WebUI if is_profile_active "open-webui"; then - SERVICES_JSON+='"open-webui": { - "hostname": "'"$(json_escape "$WEBUI_HOSTNAME")"'", - "credentials": { - "note": "Create account on first login" + 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_JSON+='"grafana": { - "hostname": "'"$(json_escape "$GRAFANA_HOSTNAME")"'", - "credentials": { - "username": "admin", - "password": "'"$(json_escape "$GRAFANA_ADMIN_PASSWORD")"'" + SERVICES_ARRAY+=(" \"grafana\": { + \"hostname\": \"$(json_escape "$GRAFANA_HOSTNAME")\", + \"credentials\": { + \"username\": \"admin\", + \"password\": \"$(json_escape "$GRAFANA_ADMIN_PASSWORD")\" } - },' - SERVICES_JSON+='"prometheus": { - "hostname": "'"$(json_escape "$PROMETHEUS_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$PROMETHEUS_USERNAME")"'", - "password": "'"$(json_escape "$PROMETHEUS_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_JSON+='"portainer": { - "hostname": "'"$(json_escape "$PORTAINER_HOSTNAME")"'", - "credentials": { - "note": "Create admin account on first login" + SERVICES_ARRAY+=(" \"portainer\": { + \"hostname\": \"$(json_escape "$PORTAINER_HOSTNAME")\", + \"credentials\": { + \"note\": \"Create admin account on first login\" } - },' + }") fi # Postgresus if is_profile_active "postgresus"; then - SERVICES_JSON+='"postgresus": { - "hostname": "'"$(json_escape "$POSTGRESUS_HOSTNAME")"'", - "credentials": { - "note": "Uses PostgreSQL credentials from .env" + SERVICES_ARRAY+=(" \"postgresus\": { + \"hostname\": \"$(json_escape "$POSTGRESUS_HOSTNAME")\", + \"credentials\": { + \"note\": \"Uses PostgreSQL credentials from .env\" }, - "extra": { - "pg_host": "postgres", - "pg_port": "'"${POSTGRES_PORT:-5432}"'", - "pg_user": "'"$(json_escape "${POSTGRES_USER:-postgres}")"'", - "pg_password": "'"$(json_escape "$POSTGRES_PASSWORD")"'", - "pg_db": "'"$(json_escape "${POSTGRES_DB:-postgres}")"'" + \"extra\": { + \"pg_host\": \"postgres\", + \"pg_port\": \"${POSTGRES_PORT:-5432}\", + \"pg_user\": \"$(json_escape "${POSTGRES_USER:-postgres}")\", + \"pg_password\": \"$(json_escape "$POSTGRES_PASSWORD")\", + \"pg_db\": \"$(json_escape "${POSTGRES_DB:-postgres}")\" } - },' + }") fi # Langfuse if is_profile_active "langfuse"; then - SERVICES_JSON+='"langfuse": { - "hostname": "'"$(json_escape "$LANGFUSE_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$LANGFUSE_INIT_USER_EMAIL")"'", - "password": "'"$(json_escape "$LANGFUSE_INIT_USER_PASSWORD")"'" + 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_JSON+='"supabase": { - "hostname": "'"$(json_escape "$SUPABASE_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$DASHBOARD_USERNAME")"'", - "password": "'"$(json_escape "$DASHBOARD_PASSWORD")"'" + 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")"'" + \"extra\": { + \"internal_api\": \"http://kong:8000\", + \"service_role_key\": \"$(json_escape "$SERVICE_ROLE_KEY")\" } - },' + }") fi # Dify if is_profile_active "dify"; then - SERVICES_JSON+='"dify": { - "hostname": "'"$(json_escape "$DIFY_HOSTNAME")"'", - "credentials": { - "note": "Create account on first login" + SERVICES_ARRAY+=(" \"dify\": { + \"hostname\": \"$(json_escape "$DIFY_HOSTNAME")\", + \"credentials\": { + \"note\": \"Create account on first login\" }, - "extra": { - "api_endpoint": "https://'"$(json_escape "$DIFY_HOSTNAME")"'/v1", - "internal_api": "http://dify-api:5001" + \"extra\": { + \"api_endpoint\": \"https://$(json_escape "$DIFY_HOSTNAME")/v1\", + \"internal_api\": \"http://dify-api:5001\" } - },' + }") fi # Qdrant if is_profile_active "qdrant"; then - SERVICES_JSON+='"qdrant": { - "hostname": "'"$(json_escape "$QDRANT_HOSTNAME")"'", - "credentials": { - "api_key": "'"$(json_escape "$QDRANT_API_KEY")"'" + SERVICES_ARRAY+=(" \"qdrant\": { + \"hostname\": \"$(json_escape "$QDRANT_HOSTNAME")\", + \"credentials\": { + \"api_key\": \"$(json_escape "$QDRANT_API_KEY")\" }, - "extra": { - "dashboard": "https://'"$(json_escape "$QDRANT_HOSTNAME")"'/dashboard", - "internal_api": "http://qdrant:6333" + \"extra\": { + \"dashboard\": \"https://$(json_escape "$QDRANT_HOSTNAME")/dashboard\", + \"internal_api\": \"http://qdrant:6333\" } - },' + }") fi # Weaviate if is_profile_active "weaviate"; then - SERVICES_JSON+='"weaviate": { - "hostname": "'"$(json_escape "$WEAVIATE_HOSTNAME")"'", - "credentials": { - "api_key": "'"$(json_escape "$WEAVIATE_API_KEY")"'", - "username": "'"$(json_escape "$WEAVIATE_USERNAME")"'" + 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_JSON+='"neo4j": { - "hostname": "'"$(json_escape "$NEO4J_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$NEO4J_AUTH_USERNAME")"'", - "password": "'"$(json_escape "$NEO4J_AUTH_PASSWORD")"'" + 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" + \"extra\": { + \"bolt_port\": \"7687\" } - },' + }") fi # SearXNG if is_profile_active "searxng"; then - SERVICES_JSON+='"searxng": { - "hostname": "'"$(json_escape "$SEARXNG_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$SEARXNG_USERNAME")"'", - "password": "'"$(json_escape "$SEARXNG_PASSWORD")"'" + 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_JSON+='"ragapp": { - "hostname": "'"$(json_escape "$RAGAPP_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$RAGAPP_USERNAME")"'", - "password": "'"$(json_escape "$RAGAPP_PASSWORD")"'" + SERVICES_ARRAY+=(" \"ragapp\": { + \"hostname\": \"$(json_escape "$RAGAPP_HOSTNAME")\", + \"credentials\": { + \"username\": \"$(json_escape "$RAGAPP_USERNAME")\", + \"password\": \"$(json_escape "$RAGAPP_PASSWORD")\" }, - "extra": { - "admin": "https://'"$(json_escape "$RAGAPP_HOSTNAME")"'/admin", - "docs": "https://'"$(json_escape "$RAGAPP_HOSTNAME")"'/docs", - "internal_api": "http://ragapp:8000" + \"extra\": { + \"admin\": \"https://$(json_escape "$RAGAPP_HOSTNAME")/admin\", + \"docs\": \"https://$(json_escape "$RAGAPP_HOSTNAME")/docs\", + \"internal_api\": \"http://ragapp:8000\" } - },' + }") fi # RAGFlow if is_profile_active "ragflow"; then - SERVICES_JSON+='"ragflow": { - "hostname": "'"$(json_escape "$RAGFLOW_HOSTNAME")"'", - "credentials": { - "note": "Create account on first login" + SERVICES_ARRAY+=(" \"ragflow\": { + \"hostname\": \"$(json_escape "$RAGFLOW_HOSTNAME")\", + \"credentials\": { + \"note\": \"Create account on first login\" }, - "extra": { - "internal_api": "http://ragflow:80" + \"extra\": { + \"internal_api\": \"http://ragflow:80\" } - },' + }") fi # LightRAG if is_profile_active "lightrag"; then - SERVICES_JSON+='"lightrag": { - "hostname": "'"$(json_escape "$LIGHTRAG_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$LIGHTRAG_USERNAME")"'", - "password": "'"$(json_escape "$LIGHTRAG_PASSWORD")"'", - "api_key": "'"$(json_escape "$LIGHTRAG_API_KEY")"'" + 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": "https://'"$(json_escape "$LIGHTRAG_HOSTNAME")"'/docs", - "internal_api": "http://lightrag:9621" + \"extra\": { + \"docs\": \"https://$(json_escape "$LIGHTRAG_HOSTNAME")/docs\", + \"internal_api\": \"http://lightrag:9621\" } - },' + }") fi # Letta if is_profile_active "letta"; then - SERVICES_JSON+='"letta": { - "hostname": "'"$(json_escape "$LETTA_HOSTNAME")"'", - "credentials": { - "api_key": "'"$(json_escape "$LETTA_SERVER_PASSWORD")"'" + 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_JSON+='"comfyui": { - "hostname": "'"$(json_escape "$COMFYUI_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$COMFYUI_USERNAME")"'", - "password": "'"$(json_escape "$COMFYUI_PASSWORD")"'" + 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_JSON+='"libretranslate": { - "hostname": "'"$(json_escape "$LT_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$LT_USERNAME")"'", - "password": "'"$(json_escape "$LT_PASSWORD")"'" + 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" + \"extra\": { + \"internal_api\": \"http://libretranslate:5000\" } - },' + }") fi # Docling if is_profile_active "docling"; then - SERVICES_JSON+='"docling": { - "hostname": "'"$(json_escape "$DOCLING_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$DOCLING_USERNAME")"'", - "password": "'"$(json_escape "$DOCLING_PASSWORD")"'" + SERVICES_ARRAY+=(" \"docling\": { + \"hostname\": \"$(json_escape "$DOCLING_HOSTNAME")\", + \"credentials\": { + \"username\": \"$(json_escape "$DOCLING_USERNAME")\", + \"password\": \"$(json_escape "$DOCLING_PASSWORD")\" }, - "extra": { - "ui": "https://'"$(json_escape "$DOCLING_HOSTNAME")"'/ui", - "docs": "https://'"$(json_escape "$DOCLING_HOSTNAME")"'/docs", - "internal_api": "http://docling:5001" + \"extra\": { + \"ui\": \"https://$(json_escape "$DOCLING_HOSTNAME")/ui\", + \"docs\": \"https://$(json_escape "$DOCLING_HOSTNAME")/docs\", + \"internal_api\": \"http://docling:5001\" } - },' + }") fi # PaddleOCR if is_profile_active "paddleocr"; then - SERVICES_JSON+='"paddleocr": { - "hostname": "'"$(json_escape "$PADDLEOCR_HOSTNAME")"'", - "credentials": { - "username": "'"$(json_escape "$PADDLEOCR_USERNAME")"'", - "password": "'"$(json_escape "$PADDLEOCR_PASSWORD")"'" + 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" + \"extra\": { + \"internal_api\": \"http://paddleocr:8080\" } - },' + }") fi # Postiz if is_profile_active "postiz"; then - SERVICES_JSON+='"postiz": { - "hostname": "'"$(json_escape "$POSTIZ_HOSTNAME")"'", - "credentials": { - "note": "Create account on first login" + SERVICES_ARRAY+=(" \"postiz\": { + \"hostname\": \"$(json_escape "$POSTIZ_HOSTNAME")\", + \"credentials\": { + \"note\": \"Create account on first login\" }, - "extra": { - "internal_api": "http://postiz:5000" + \"extra\": { + \"internal_api\": \"http://postiz:5000\" } - },' + }") fi # WAHA if is_profile_active "waha"; then - SERVICES_JSON+='"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")"'" + 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": "https://'"$(json_escape "$WAHA_HOSTNAME")"'/dashboard", - "swagger_user": "'"$(json_escape "$WHATSAPP_SWAGGER_USERNAME")"'", - "swagger_pass": "'"$(json_escape "$WHATSAPP_SWAGGER_PASSWORD")"'", - "internal_api": "http://waha:3000" + \"extra\": { + \"dashboard\": \"https://$(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_JSON+='"crawl4ai": { - "hostname": null, - "credentials": { - "note": "Internal service only" + SERVICES_ARRAY+=(" \"crawl4ai\": { + \"hostname\": null, + \"credentials\": { + \"note\": \"Internal service only\" }, - "extra": { - "internal_api": "http://crawl4ai:11235" + \"extra\": { + \"internal_api\": \"http://crawl4ai:11235\" } - },' + }") fi # Gotenberg (internal only) if is_profile_active "gotenberg"; then - SERVICES_JSON+='"gotenberg": { - "hostname": null, - "credentials": { - "note": "Internal service only" + SERVICES_ARRAY+=(" \"gotenberg\": { + \"hostname\": null, + \"credentials\": { + \"note\": \"Internal service only\" }, - "extra": { - "internal_api": "http://gotenberg:3000", - "docs": "https://gotenberg.dev/docs" + \"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_JSON+='"ollama": { - "hostname": null, - "credentials": { - "note": "Internal service only" + SERVICES_ARRAY+=(" \"ollama\": { + \"hostname\": null, + \"credentials\": { + \"note\": \"Internal service only\" }, - "extra": { - "internal_api": "http://ollama:11434" + \"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_JSON+='"redis": { - "hostname": null, - "credentials": { - "password": "'"$(json_escape "$REDIS_AUTH")"'" + SERVICES_ARRAY+=(" \"redis\": { + \"hostname\": null, + \"credentials\": { + \"password\": \"$(json_escape "$REDIS_AUTH")\" }, - "extra": { - "internal_host": "'"${REDIS_HOST:-redis}"'", - "internal_port": "'"${REDIS_PORT:-6379}"'" + \"extra\": { + \"internal_host\": \"${REDIS_HOST:-redis}\", + \"internal_port\": \"${REDIS_PORT:-6379}\" } - },' + }") fi # PostgreSQL (internal only, shown if n8n or langfuse active) if is_profile_active "n8n" || is_profile_active "langfuse"; then - SERVICES_JSON+='"postgres": { - "hostname": null, - "credentials": { - "username": "'"$(json_escape "${POSTGRES_USER:-postgres}")"'", - "password": "'"$(json_escape "$POSTGRES_PASSWORD")"'" + 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}")"'" + \"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_JSON+='"python-runner": { - "hostname": null, - "credentials": { - "note": "Internal service only" + SERVICES_ARRAY+=(" \"python-runner\": { + \"hostname\": null, + \"credentials\": { + \"note\": \"Internal service only\" }, - "extra": { - "logs_command": "docker compose -p localai logs -f python-runner" + \"extra\": { + \"logs_command\": \"docker compose -p localai logs -f python-runner\" } - },' + }") fi # Cloudflare Tunnel if is_profile_active "cloudflare-tunnel"; then - SERVICES_JSON+='"cloudflare-tunnel": { - "hostname": null, - "credentials": { - "note": "Zero-trust access via Cloudflare network" + 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" + \"extra\": { + \"recommendation\": \"Close ports 80, 443, 7687 in your VPS firewall after confirming tunnel connectivity\" } - },' + }") fi -# Remove trailing comma from services JSON -SERVICES_JSON=$(echo "$SERVICES_JSON" | sed 's/,$//') +# 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 -# Write final JSON +# Write final JSON with proper formatting cat > "$OUTPUT_FILE" << EOF { "domain": "$(json_escape "$USER_DOMAIN_NAME")", "generated_at": "$GENERATED_AT", "services": { - $SERVICES_JSON +$SERVICES_JSON } } EOF diff --git a/welcome/app.js b/welcome/app.js index a8ea342..6b84d93 100644 --- a/welcome/app.js +++ b/welcome/app.js @@ -1,6 +1,7 @@ /** * n8n-install Welcome Page * Dynamic rendering of services and credentials from data.json + * Supabase-inspired design */ (function() { @@ -26,7 +27,7 @@ name: 'Open WebUI', description: 'ChatGPT-like Interface', icon: 'AI', - color: 'bg-green-500', + color: 'bg-emerald-500', category: 'ai' }, 'grafana': { @@ -227,9 +228,25 @@ } }; + // Make commands data + const COMMANDS = [ + { cmd: 'make status', desc: 'Show container status' }, + { cmd: 'make logs', desc: 'View logs (all services)' }, + { cmd: 'make logs s=', desc: 'View logs for specific service' }, + { cmd: 'make monitor', desc: 'Live CPU/memory monitoring' }, + { cmd: 'make restarts', desc: 'Show restart count per container' }, + { cmd: 'make doctor', desc: 'Run system diagnostics' }, + { cmd: 'make update', desc: 'Update system and services' }, + { cmd: 'make update-preview', desc: 'Preview available updates' }, + { cmd: 'make clean', desc: 'Remove unused Docker resources' }, + { cmd: 'make switch-beta', desc: 'Switch to beta (develop branch)' }, + { cmd: 'make switch-stable', desc: 'Switch to stable (main branch)' } + ]; + // DOM Elements const servicesContainer = document.getElementById('services-container'); const quickstartContainer = document.getElementById('quickstart-container'); + const commandsContainer = document.getElementById('commands-container'); const domainInfo = document.getElementById('domain-info'); const errorToast = document.getElementById('error-toast'); const errorMessage = document.getElementById('error-message'); @@ -258,16 +275,16 @@ container.className = 'flex items-center gap-1'; const passwordSpan = document.createElement('span'); - passwordSpan.className = 'font-mono text-sm select-all'; + passwordSpan.className = 'font-mono text-sm select-all text-gray-300'; passwordSpan.textContent = '*'.repeat(Math.min(password.length, 12)); passwordSpan.dataset.password = password; passwordSpan.dataset.hidden = 'true'; // Toggle visibility button (eye icon) const toggleBtn = document.createElement('button'); - toggleBtn.className = 'p-1 rounded hover:bg-gray-200 dark:hover:bg-slate-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500'; + toggleBtn.className = 'p-1.5 rounded-lg hover:bg-surface-400 transition-colors focus:outline-none focus:ring-2 focus:ring-brand/50'; toggleBtn.innerHTML = ` - + @@ -292,12 +309,12 @@ // Copy button const copyBtn = document.createElement('button'); - copyBtn.className = 'p-1 rounded hover:bg-gray-200 dark:hover:bg-slate-600 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500'; + copyBtn.className = 'p-1.5 rounded-lg hover:bg-surface-400 transition-colors focus:outline-none focus:ring-2 focus:ring-brand/50'; copyBtn.innerHTML = ` - + -
-

${escapeHtml(creds.note)}

+
+

${escapeHtml(creds.note)}

`; } else { @@ -358,29 +375,29 @@ if (creds.username) { fields.push(`
- Username: - ${escapeHtml(creds.username)} + Username: + ${escapeHtml(creds.username)}
`); } if (creds.password) { fields.push(`
- Password: + Password:
`); } if (creds.api_key) { fields.push(`
- API Key: + API Key:
`); } if (fields.length > 0) { credentialsHtml = ` -
+
${fields.join('')}
`; @@ -395,13 +412,13 @@ const extra = serviceData.extra; if (extra.internal_api) { - extraItems.push(`Internal: ${escapeHtml(extra.internal_api)}`); + extraItems.push(`Internal: ${escapeHtml(extra.internal_api)}`); } if (extra.workers) { - extraItems.push(`Workers: ${escapeHtml(extra.workers)}`); + extraItems.push(`Workers: ${escapeHtml(extra.workers)}`); } if (extra.recommendation) { - extraItems.push(`${escapeHtml(extra.recommendation)}`); + extraItems.push(`${escapeHtml(extra.recommendation)}`); } if (extraItems.length > 0) { @@ -411,21 +428,21 @@ card.innerHTML = `
-
+
${metadata.icon}
-

${escapeHtml(metadata.name)}

-

${escapeHtml(metadata.description)}

+

${escapeHtml(metadata.name)}

+

${escapeHtml(metadata.description)}

${serviceData.hostname ? ` + class="text-brand hover:text-brand-400 text-sm font-medium inline-flex items-center gap-1 group transition-colors"> ${escapeHtml(serviceData.hostname)} - ` : 'Internal service'} + ` : 'Internal service'} ${extraHtml}
@@ -463,7 +480,7 @@ if (!services || Object.keys(services).length === 0) { servicesContainer.innerHTML = ` -
+

No services configured. Run the installer to set up services.

`; @@ -500,15 +517,15 @@ steps.forEach(item => { const stepEl = document.createElement('div'); - stepEl.className = 'flex items-start gap-4 p-4 bg-white dark:bg-slate-800 rounded-xl border border-gray-200 dark:border-slate-700'; + stepEl.className = 'flex items-start gap-4 p-4 bg-surface-100 rounded-xl border border-surface-400 hover:border-brand/30 transition-all'; stepEl.innerHTML = ` -
+
${item.step}
-

${escapeHtml(item.title)}

-

${escapeHtml(item.description)}

+

${escapeHtml(item.title)}

+

${escapeHtml(item.description)}

`; @@ -516,6 +533,30 @@ }); } + /** + * Render make commands + */ + function renderCommands() { + commandsContainer.innerHTML = ''; + + const grid = document.createElement('div'); + grid.className = 'grid gap-4 sm:grid-cols-2 lg:grid-cols-3'; + + COMMANDS.forEach(item => { + const cmdEl = document.createElement('div'); + cmdEl.className = 'flex flex-col gap-1 p-3 rounded-lg bg-surface-200/50 border border-surface-400 hover:border-brand/30 transition-all'; + + cmdEl.innerHTML = ` + ${escapeHtml(item.cmd)} + ${escapeHtml(item.desc)} + `; + + grid.appendChild(cmdEl); + }); + + commandsContainer.appendChild(grid); + } + /** * Escape HTML to prevent XSS */ @@ -530,6 +571,9 @@ * Load data and render page */ async function init() { + // Always render commands (static content) + renderCommands(); + try { const response = await fetch('data.json'); @@ -559,12 +603,12 @@ // Show error in UI servicesContainer.innerHTML = ` -
+
-

Unable to load service data

-

Make sure the installation completed successfully and data.json was generated.

+

Unable to load service data

+

Make sure the installation completed successfully and data.json was generated.

`; diff --git a/welcome/index.html b/welcome/index.html index 81334aa..839a32c 100644 --- a/welcome/index.html +++ b/welcome/index.html @@ -1,30 +1,38 @@ - + - Welcome to n8n-install + Welcome | n8n-install - -
+ +
-
-

- Welcome to n8n-install +
+
+ + System Online +
+

+ Welcome to n8n-install

-

- Your self-hosted automation platform is ready +

+ Your self-hosted automation platform is ready to use

-

+

-
-

- - - - Your Services -

+
+
+
+ + + +
+

Your Services

+
-
-
-
+
+
+
-
-

- - - - Quick Start -

- -