From 23c9581f7bb741fd492fd4426353cc4a9888334c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andreas=20D=C3=BCren?=
 <andreasdueren@Andreass-Mac-mini.local>
Date: Sun, 16 Mar 2025 23:29:27 +0100
Subject: [PATCH] Switch from Caddy to NGINX and fix URL construction error

---
 start.sh | 463 ++++++++++++++++++++++---------------------------------
 1 file changed, 188 insertions(+), 275 deletions(-)

diff --git a/start.sh b/start.sh
index 3f8856b..84d7f72 100644
--- a/start.sh
+++ b/start.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 # Better signal handling - forward signals to child processes
-trap 'kill -TERM $SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT
+trap 'kill -TERM $SERVER_PID; kill -TERM $NGINX_PID; exit' TERM INT
 
 set -eu
 
@@ -172,7 +172,7 @@ fi
 # Source S3 configuration
 if [ -f /app/data/config/s3.env ]; then
     echo "==> Sourcing S3 configuration from /app/data/config/s3.env"
-    source /app/data/config/s3.env
+source /app/data/config/s3.env
 fi
 
 # Display S3 configuration (masking sensitive values)
@@ -213,12 +213,9 @@ sed -i 's|s3.are_local_buckets: true|s3.are_local_buckets: false|g' /app/data/co
 
 # Install or verify required packages
 echo "==> Checking for required packages"
-if ! command -v caddy &> /dev/null; then
-    echo "==> Installing Caddy"
-    apt-get update && apt-get install -y debian-keyring debian-archive-keyring apt-transport-https
-    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --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-get update && apt-get install -y caddy
+if ! command -v nginx &> /dev/null; then
+    echo "==> Installing NGINX"
+    apt-get update && apt-get install -y nginx
 fi
 
 # Set up the API endpoint for the web apps
@@ -235,6 +232,8 @@ echo "==> Set environment variables for web apps"
 # Create directory for configuration files
 mkdir -p /app/data/public
 mkdir -p /app/data/scripts
+mkdir -p /app/data/nginx
+mkdir -p /app/data/logs/nginx
 
 # Create a debugging script
 cat > /app/data/public/debug.js <<EOT
@@ -242,27 +241,6 @@ cat > /app/data/public/debug.js <<EOT
 (function() {
   console.log("Debug script loaded");
   
-  // Intercept URL constructor
-  const originalURL = window.URL;
-  window.URL = function(url, base) {
-    console.log("URL constructor called with:", url, base);
-    try {
-      return new originalURL(url, base);
-    } catch (e) {
-      console.error("URL construction failed:", e.message);
-      console.error("URL:", url);
-      console.error("Base:", base);
-      console.error("Stack:", e.stack);
-      
-      // Try to fix common issues
-      if (url && !url.startsWith("http") && !url.startsWith("/")) {
-        console.log("Attempting to fix relative URL by adding leading slash");
-        return new originalURL("/" + url, base);
-      }
-      throw e;
-    }
-  };
-  
   // Create debug overlay
   const debugDiv = document.createElement('div');
   debugDiv.style.position = 'fixed';
@@ -306,31 +284,6 @@ cat > /app/data/public/debug.js <<EOT
 })();
 EOT
 
-# Create a configuration script
-cat > /app/data/public/config.js <<EOT
-// Direct configuration for Ente
-window.ENTE_CONFIG = {
-  API_URL: "${API_ENDPOINT}"
-};
-
-// Next.js environment variables
-window.process = window.process || {};
-window.process.env = window.process.env || {};
-window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
-window.process.env.REACT_APP_ENTE_ENDPOINT = "${API_ENDPOINT}";
-window.process.env.VUE_APP_ENTE_ENDPOINT = "${API_ENDPOINT}";
-
-// Store in localStorage for persistence
-try {
-  localStorage.setItem('ENTE_CONFIG', JSON.stringify(window.ENTE_CONFIG));
-  localStorage.setItem('NEXT_PUBLIC_ENTE_ENDPOINT', "${API_ENDPOINT}");
-} catch (e) {
-  console.error("Failed to store config in localStorage:", e);
-}
-
-console.log("Ente config loaded - API_URL:", window.ENTE_CONFIG.API_URL);
-EOT
-
 # Create debug info HTML page
 cat > /app/data/public/debug.html <<EOT
 <!DOCTYPE html>
@@ -417,39 +370,73 @@ cat > /app/data/public/debug.html <<EOT
 </html>
 EOT
 
-# Create a simple app that loads the config and redirects to the main app
-cat > /app/data/public/index.html <<EOT
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Ente</title>
-  <script src="/config.js"></script>
-  <script src="/debug.js"></script>
-</head>
-<body>
-  <h1>Ente</h1>
-  <p>Welcome to Ente!</p>
-  <p><a href="/debug">Debug Information</a></p>
-</body>
-</html>
+# Create a configuration script with properly formatted URL
+cat > /app/data/public/config.js <<EOT
+// Direct configuration for Ente
+window.ENTE_CONFIG = {
+  API_URL: "${API_ENDPOINT}"
+};
+
+// Next.js environment variables
+window.process = window.process || {};
+window.process.env = window.process.env || {};
+window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = "${API_ENDPOINT}";
+window.process.env.REACT_APP_ENTE_ENDPOINT = "${API_ENDPOINT}";
+window.process.env.VUE_APP_ENTE_ENDPOINT = "${API_ENDPOINT}";
+
+// Create absolute URL helper function to prevent URL construction errors
+window.createApiUrl = function(path) {
+  // Handle paths with or without leading slash
+  if (path && path.startsWith('/')) {
+    return "${API_ENDPOINT}" + path;
+  } else if (path) {
+    return "${API_ENDPOINT}/" + path;
+  }
+  return "${API_ENDPOINT}";
+};
+
+// Store in localStorage for persistence
+try {
+  localStorage.setItem('ENTE_CONFIG', JSON.stringify(window.ENTE_CONFIG));
+  localStorage.setItem('NEXT_PUBLIC_ENTE_ENDPOINT', "${API_ENDPOINT}");
+} catch (e) {
+  console.error("Failed to store config in localStorage:", e);
+}
+
+console.log("Ente config loaded - API_URL:", window.ENTE_CONFIG.API_URL);
+// Override URL constructor to prevent errors
+const originalURL = window.URL;
+window.URL = function(url, base) {
+  try {
+    // Fix common URL construction issues
+    if (url && typeof url === 'string') {
+      // If URL doesn't have a protocol or start with /, add a /
+      if (!url.match(/^[a-z]+:\/\//) && !url.startsWith('/') && !base) {
+        url = '/' + url;
+      }
+    }
+    return new originalURL(url, base);
+  } catch (e) {
+    console.error("URL construction error:", e, "url:", url, "base:", base);
+    // Fallback - return a working URL
+    return new originalURL("${CLOUDRON_APP_ORIGIN}");
+  }
+};
 EOT
 
-# Set up Caddy
-echo "==> Setting up Caddy server"
-mkdir -p /app/data/caddy
-chmod -R 777 /app/data/caddy
+# Set up NGINX
+echo "==> Setting up NGINX server"
 
 # Define ports
-CADDY_PORT=3080
+NGINX_PORT=3080
 API_PORT=8080
 
 # Check if ports are available
 echo "==> Checking port availability"
-if lsof -i:$CADDY_PORT > /dev/null 2>&1; then
-    echo "==> WARNING: Port $CADDY_PORT is already in use"
+if lsof -i:$NGINX_PORT > /dev/null 2>&1; then
+    echo "==> WARNING: Port $NGINX_PORT is already in use"
 else
-    echo "==> Port $CADDY_PORT is available for Caddy"
+    echo "==> Port $NGINX_PORT is available for NGINX"
 fi
 
 if lsof -i:$API_PORT > /dev/null 2>&1; then
@@ -458,230 +445,156 @@ else
     echo "==> Port $API_PORT is available for API server"
 fi
 
-# Create the Caddyfile
-cat > /app/data/caddy/Caddyfile <<EOT
-{
-    admin off
-    auto_https off
-    log {
-        output file /app/data/caddy/caddy.log {
-            roll_size 10MB
-            roll_keep 10
-        }
-    }
+# Create the NGINX config
+cat > /app/data/nginx/nginx.conf <<EOT
+worker_processes 1;
+error_log /app/data/logs/nginx/error.log warn;
+pid /app/data/nginx/nginx.pid;
+
+events {
+    worker_connections 1024;
 }
 
-:$CADDY_PORT {
-    # Ensure we listen on all interfaces
-    bind 0.0.0.0
+http {
+    include /etc/nginx/mime.types;
+    default_type application/octet-stream;
     
-    log {
-        output file /app/data/caddy/access.log {
-            roll_size 10MB
-            roll_keep 10
-        }
+    log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
+                    '\$status \$body_bytes_sent "\$http_referer" '
+                    '"\$http_user_agent" "\$http_x_forwarded_for"';
+    
+    access_log /app/data/logs/nginx/access.log main;
+    
+    sendfile on;
+    tcp_nopush on;
+    tcp_nodelay on;
+    keepalive_timeout 65;
+    types_hash_max_size 2048;
+    
+    # Security headers
+    map \$sent_http_content_type \$cors_header {
+        default "";
+        "~*text/html" "credentialless";
     }
     
-    # Headers for WebAssembly and IndexedDB
-    header {
-        Cross-Origin-Embedder-Policy "credentialless"
-        Cross-Origin-Opener-Policy "same-origin"
-        Cross-Origin-Resource-Policy "cross-origin"
-    }
-    
-    # Serve configuration scripts
-    handle /config.js {
-        root * /app/data/public
-        file_server
-    }
-    
-    handle /debug.js {
-        root * /app/data/public
-        file_server
-    }
-    
-    # Serve debug page
-    handle /debug {
-        root * /app/data/public
-        rewrite * /debug.html
-        file_server
-    }
-    
-    # Health check endpoints
-    handle /health {
-        respond "OK" 200
-    }
-    
-    handle /healthcheck {
-        respond "OK" 200
-    }
-    
-    # API health check endpoint - direct proxy to API
-    handle /api/health {
-        reverse_proxy localhost:$API_PORT
-    }
-    
-    # Serve photos app at the root
-    handle /* {
-        root * /app/web/photos
+    server {
+        listen $NGINX_PORT default_server;
+        listen [::]:$NGINX_PORT default_server;
         
-        # Inject config script
-        @html {
-            path *.html index.html
-            not path */assets/* */static/* */img/* */css/* */js/*
+        root /app/web/photos;
+        index index.html;
+        
+        # Security headers
+        add_header Cross-Origin-Embedder-Policy \$cors_header always;
+        add_header Cross-Origin-Opener-Policy "same-origin" always;
+        add_header Cross-Origin-Resource-Policy "cross-origin" always;
+        
+        # Configuration scripts
+        location /config.js {
+            alias /app/data/public/config.js;
         }
         
-        header @html Content-Type "text/html; charset=utf-8"
-        
-        # Use rewrite to modify HTML content
-        @isIndex path /index.html
-        rewrite @isIndex /photos-injected.html
-        
-        file_server
-    }
-    
-    # Accounts app
-    handle /accounts* {
-        uri replace /accounts /
-        root * /app/web/accounts
-        
-        handle_path /* {
-            @html {
-                path *.html index.html
-                not path */assets/* */static/* */img/* */css/* */js/*
-            }
-            
-            header @html Content-Type "text/html; charset=utf-8"
-            
-            # Use rewrite to modify HTML content
-            @isIndex path /index.html
-            rewrite @isIndex /accounts-injected.html
-            
-            file_server
+        location /debug.js {
+            alias /app/data/public/debug.js;
         }
-    }
-    
-    # Auth app
-    handle /auth* {
-        uri replace /auth /
-        root * /app/web/auth
         
-        handle_path /* {
-            @html {
-                path *.html index.html
-                not path */assets/* */static/* */img/* */css/* */js/*
-            }
-            
-            header @html Content-Type "text/html; charset=utf-8"
-            
-            # Use rewrite to modify HTML content
-            @isIndex path /index.html
-            rewrite @isIndex /auth-injected.html
-            
-            file_server
+        # Debug page
+        location /debug {
+            alias /app/data/public/debug.html;
         }
-    }
-    
-    # Cast app
-    handle /cast* {
-        uri replace /cast /
-        root * /app/web/cast
         
-        handle_path /* {
-            @html {
-                path *.html index.html
-                not path */assets/* */static/* */img/* */css/* */js/*
-            }
-            
-            header @html Content-Type "text/html; charset=utf-8"
-            
-            # Use rewrite to modify HTML content
-            @isIndex path /index.html
-            rewrite @isIndex /cast-injected.html
-            
-            file_server
+        # Health check endpoints
+        location /health {
+            return 200 "OK";
+        }
+        
+        location /healthcheck {
+            return 200 "OK";
+        }
+        
+        # API health check and API endpoints
+        location /api/ {
+            proxy_pass http://localhost:$API_PORT/;
+            proxy_http_version 1.1;
+            proxy_set_header Connection "";
+            proxy_set_header Host \$host;
+            proxy_set_header X-Real-IP \$remote_addr;
+            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto \$scheme;
+        }
+        
+        # Root serves the photos app
+        location / {
+            try_files \$uri \$uri/ /index.html;
+            
+            # Insert config script for HTML files
+            sub_filter '</head>' '<script src="/config.js"></script><script src="/debug.js"></script></head>';
+            sub_filter_once on;
+            sub_filter_types text/html;
+        }
+        
+        # Accounts app
+        location /accounts/ {
+            alias /app/web/accounts/;
+            try_files \$uri \$uri/ /accounts/index.html;
+            
+            # Insert config script for HTML files
+            sub_filter '</head>' '<script src="/config.js"></script><script src="/debug.js"></script></head>';
+            sub_filter_once on;
+            sub_filter_types text/html;
+        }
+        
+        # Auth app
+        location /auth/ {
+            alias /app/web/auth/;
+            try_files \$uri \$uri/ /auth/index.html;
+            
+            # Insert config script for HTML files
+            sub_filter '</head>' '<script src="/config.js"></script><script src="/debug.js"></script></head>';
+            sub_filter_once on;
+            sub_filter_types text/html;
+        }
+        
+        # Cast app
+        location /cast/ {
+            alias /app/web/cast/;
+            try_files \$uri \$uri/ /cast/index.html;
+            
+            # Insert config script for HTML files
+            sub_filter '</head>' '<script src="/config.js"></script><script src="/debug.js"></script></head>';
+            sub_filter_once on;
+            sub_filter_types text/html;
         }
-    }
-    
-    # Proxy API calls to the backend server
-    handle /api* {
-        reverse_proxy localhost:$API_PORT
     }
 }
 EOT
 
-echo "==> Created Caddyfile at /app/data/caddy/Caddyfile"
+echo "==> Created NGINX config at /app/data/nginx/nginx.conf"
 
-# Create injected HTML files for each app
-for app_name in "photos" "accounts" "auth" "cast"; do
-    # Create an injected HTML file that loads the config
-    cat > "/app/data/public/${app_name}-injected.html" <<EOT
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Ente ${app_name^}</title>
-  <script src="/config.js"></script>
-  <script>
-    // Redirect to the actual app after loading config
-    window.onload = function() {
-      const iframe = document.createElement('iframe');
-      iframe.style.width = '100%';
-      iframe.style.height = '100%';
-      iframe.style.border = 'none';
-      iframe.style.position = 'absolute';
-      iframe.style.top = '0';
-      iframe.style.left = '0';
-      iframe.src = "/${app_name}/index.html";
-      document.body.appendChild(iframe);
-    };
-  </script>
-  <style>
-    body {
-      margin: 0;
-      padding: 0;
-      height: 100vh;
-      width: 100vw;
-      overflow: hidden;
-    }
-  </style>
-</head>
-<body>
-  <div id="loading" style="display: flex; justify-content: center; align-items: center; height: 100vh; flex-direction: column;">
-    <h1>Loading Ente ${app_name^}...</h1>
-    <p>Please wait while the application initializes.</p>
-  </div>
-</body>
-</html>
-EOT
-done
+# Start NGINX
+echo "==> Starting NGINX on port $NGINX_PORT"
+nginx -c /app/data/nginx/nginx.conf -p /app/data/nginx &
+NGINX_PID=$!
+echo "==> NGINX started with PID $NGINX_PID"
 
-echo "==> Created injected HTML files for all apps"
-
-# Start Caddy
-echo "==> Starting Caddy on port $CADDY_PORT"
-caddy run --config /app/data/caddy/Caddyfile --adapter caddyfile &
-CADDY_PID=$!
-echo "==> Caddy started with PID $CADDY_PID"
-
-# Wait for Caddy to start
+# Wait for NGINX to start
 sleep 2
 
-# Test Caddy connectivity
-echo "==> Testing Caddy connectivity"
+# Test NGINX connectivity
+echo "==> Testing NGINX connectivity"
 for i in {1..5}; do
-    if curl -s --max-time 2 --head --fail http://localhost:$CADDY_PORT/health > /dev/null; then
-        echo "==> Caddy is running properly on port $CADDY_PORT"
+    if curl -s --max-time 2 --head --fail http://localhost:$NGINX_PORT/health > /dev/null; then
+        echo "==> NGINX is running properly on port $NGINX_PORT"
         break
     else
         if [ $i -eq 5 ]; then
-            echo "==> Failed to connect to Caddy after multiple attempts"
-            echo "==> Last 20 lines of Caddy log:"
-            tail -20 /app/data/caddy/caddy.log || echo "==> No Caddy log available"
+            echo "==> Failed to connect to NGINX after multiple attempts"
+            echo "==> Last 20 lines of NGINX error log:"
+            tail -20 /app/data/logs/nginx/error.log || echo "==> No NGINX error log available"
             echo "==> Network ports in use:"
             netstat -tuln || echo "==> netstat command not available"
         else
-            echo "==> Attempt $i: Waiting for Caddy to start... (1 second)"
+            echo "==> Attempt $i: Waiting for NGINX to start... (1 second)"
             sleep 1
         fi
     fi
@@ -903,4 +816,4 @@ echo "==> To view debug information, visit: $CLOUDRON_APP_ORIGIN/debug"
 echo "==> Entering wait state - press Ctrl+C to stop"
 # Wait for all background processes to complete (or for user to interrupt)
 wait $SERVER_PID
-wait $CADDY_PID 
\ No newline at end of file
+wait $NGINX_PID 
\ No newline at end of file