Commit 43cb6858 authored by Andreas Düren's avatar Andreas Düren
Browse files

Fixed read-only filesystem issues by using Caddy's filter directives and improved mock servers

parent ded9e1d1
Loading
Loading
Loading
Loading
+222 −101
Original line number Diff line number Diff line
@@ -647,6 +647,7 @@ func main() {
    log.Println("API_ENDPOINT:", os.Getenv("ENTE_API_ENDPOINT"))
    
    // Create a logger that logs to both stdout and a file
    os.MkdirAll("/app/data/logs", 0755) // Ensure the logs directory exists
    logFile, err := os.OpenFile("/app/data/logs/api_requests.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        log.Printf("Error opening log file: %v", err)
@@ -656,6 +657,7 @@ func main() {
    multiWriter := io.MultiWriter(os.Stdout, logFile)
    logger := log.New(multiWriter, "", log.LstdFlags)
    
    // Initialize random seed
    rand.Seed(time.Now().UnixNano())
    
    // Map to store verification codes
@@ -699,8 +701,13 @@ func main() {
            
            // Return a success response
            w.Header().Set("Content-Type", "application/json")
            w.WriteHeader(http.StatusOK)
            fmt.Fprintf(w, \`{"status":"ok","message":"Verification code sent (check logs)"}\`)
            
            // Use the encoding/json package to create and send the response
            jsonResponse := map[string]string{
                "status":  "ok",
                "message": "Verification code sent (check logs)",
            }
            json.NewEncoder(w).Encode(jsonResponse)
        } else {
            // Just handle other methods with a generic response
            w.Header().Set("Content-Type", "application/json")
@@ -757,11 +764,23 @@ func main() {
            if isValid {
                // Return a successful verification response
                w.WriteHeader(http.StatusOK)
                fmt.Fprintf(w, \`{"status":"ok","token":"mock-token-12345"}\`)
                
                // Use the json package to create the response
                jsonResponse := map[string]string{
                    "status": "ok",
                    "token":  "mock-token-12345",
                }
                json.NewEncoder(w).Encode(jsonResponse)
            } else {
                // Return an error
                w.WriteHeader(http.StatusBadRequest)
                fmt.Fprintf(w, \`{"status":"error","message":"Invalid verification code"}\`)
                
                // Use the json package to create the error response
                jsonResponse := map[string]string{
                    "status":  "error",
                    "message": "Invalid verification code",
                }
                json.NewEncoder(w).Encode(jsonResponse)
            }
        } else {
            // Handle other methods with a generic response
@@ -782,11 +801,20 @@ func main() {
        }
        
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, \`{"status":"mock","endpoint":"%s","method":"%s","time":"%s"}\`, 
            r.URL.Path, r.Method, time.Now().Format(time.RFC3339))
        
        // Use the json package to create a dynamic response
        response := map[string]string{
            "status":   "mock",
            "endpoint": r.URL.Path,
            "method":   r.Method,
            "time":     time.Now().Format(time.RFC3339),
        }
        json.NewEncoder(w).Encode(response)
    })
    
    logger.Printf("Mock Ente API server listening on port %s\n", port)
    
    // Make sure we listen on all interfaces, not just localhost
    if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil {
        logger.Fatalf("Server failed: %v", err)
    }
@@ -918,7 +946,9 @@ elif [ -d "$SERVER_DIR/cmd/museum" ]; then
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
@@ -937,21 +967,59 @@ func main() {
    log.Println("PGPORT:", os.Getenv("PGPORT"))
    log.Println("API_ENDPOINT:", os.Getenv("ENTE_API_ENDPOINT"))
    
    // Create a logger that logs to both stdout and a file
    os.MkdirAll("/app/data/logs", 0755) // Ensure the logs directory exists
    logFile, err := os.OpenFile("/app/data/logs/public_api_requests.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        log.Printf("Error opening log file: %v", err)
    }
    defer logFile.Close()
    
    multiWriter := io.MultiWriter(os.Stdout, logFile)
    logger := log.New(multiWriter, "", log.LstdFlags)
    
    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, \`{"status":"ok","service":"public_albums","version":"mock-1.0.0","time":"%s"}\`, time.Now().Format(time.RFC3339))
        
        // Use json package to create the response
        response := map[string]string{
            "status":  "ok",
            "service": "public_albums",
            "version": "mock-1.0.0",
            "time":    time.Now().Format(time.RFC3339),
        }
        json.NewEncoder(w).Encode(response)
    })
    
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Public Albums: Received request for %s via %s", r.URL.Path, r.Method)
        logger.Printf("Public Albums: Received request for %s via %s", r.URL.Path, r.Method)
        
        // Log request body if it's a POST or PUT
        if r.Method == "POST" || r.Method == "PUT" {
            body, _ := io.ReadAll(r.Body)
            if len(body) > 0 {
                logger.Printf("Request body: %s", string(body))
            }
        }
        
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, \`{"status":"mock","service":"public_albums","endpoint":"%s","method":"%s","time":"%s"}\`, 
            r.URL.Path, r.Method, time.Now().Format(time.RFC3339))
        
        // Use json package to create the response
        response := map[string]string{
            "status":   "mock",
            "service":  "public_albums",
            "endpoint": r.URL.Path,
            "method":   r.Method,
            "time":     time.Now().Format(time.RFC3339),
        }
        json.NewEncoder(w).Encode(response)
    })
    
    log.Printf("Mock Public Albums API server listening on port %s\n", port)
    logger.Printf("Mock Public Albums API server listening on port %s\n", port)
    
    // Make sure we listen on all interfaces
    if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil {
        log.Fatalf("Server failed: %v", err)
        logger.Fatalf("Server failed: %v", err)
    }
}
EOT
@@ -974,7 +1042,9 @@ else
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
@@ -993,23 +1063,59 @@ func main() {
    log.Println("PGPORT:", os.Getenv("PGPORT"))
    log.Println("API_ENDPOINT:", os.Getenv("ENTE_API_ENDPOINT"))
    
    // Create a logger that logs to both stdout and a file
    os.MkdirAll("/app/data/logs", 0755) // Ensure the logs directory exists
    logFile, err := os.OpenFile("/app/data/logs/public_api_requests.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        log.Printf("Error opening log file: %v", err)
    }
    defer logFile.Close()
    
    multiWriter := io.MultiWriter(os.Stdout, logFile)
    logger := log.New(multiWriter, "", log.LstdFlags)
    
    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, \`{"status":"ok","service":"public_albums","version":"mock-1.0.0","time":"%s"}\`, time.Now().Format(time.RFC3339))
        
        // Use json package to create the response
        response := map[string]string{
            "status":  "ok",
            "service": "public_albums",
            "version": "mock-1.0.0",
            "time":    time.Now().Format(time.RFC3339),
        }
        json.NewEncoder(w).Encode(response)
    })
    
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Public Albums: Received request for %s via %s", r.URL.Path, r.Method)
        logger.Printf("Public Albums: Received request for %s via %s", r.URL.Path, r.Method)
        
        // Log request body if it's a POST or PUT
        if r.Method == "POST" || r.Method == "PUT" {
            body, _ := io.ReadAll(r.Body)
            if len(body) > 0 {
                logger.Printf("Request body: %s", string(body))
            }
        }
        
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, \`{"status":"mock","service":"public_albums","endpoint":"%s","method":"%s","time":"%s"}\`, 
            r.URL.Path, r.Method, time.Now().Format(time.RFC3339))
        
        // Use json package to create the response
        response := map[string]string{
            "status":   "mock",
            "service":  "public_albums",
            "endpoint": r.URL.Path,
            "method":   r.Method,
            "time":     time.Now().Format(time.RFC3339),
        }
        json.NewEncoder(w).Encode(response)
    })
    
    // Start the server
    log.Printf("Mock Public Albums server listening on port %s\n", port)
    logger.Printf("Mock Public Albums API server listening on port %s\n", port)
    
    // Make sure we listen on all interfaces
    if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil {
        log.Fatalf("Failed to start Public Albums server: %v", err)
        logger.Fatalf("Server failed: %v", err)
    }
}
EOT
@@ -1048,7 +1154,41 @@ done
# Set up Caddy web server
echo "==> Setting up Caddy web server"

# Create a Caddyfile for serving web apps and reverse proxy to API
# First inject the config.js script tags into all HTML files
echo "==> Injecting config.js into web application HTML files"

# Create writable data directories for web assets
mkdir -p /app/data/web/photos /app/data/web/accounts /app/data/web/auth /app/data/web/cast

# Create runtime-config.js files in writable locations
echo "==> Creating runtime-config.js in writable location"
cat > /app/data/web/runtime-config.js <<EOT
// Runtime configuration for Ente
window.ENTE_CONFIG = {
    API_URL: '${API_ENDPOINT}',
    PUBLIC_ALBUMS_URL: '${CLOUDRON_APP_ORIGIN}/public'
};

// 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.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = '${CLOUDRON_APP_ORIGIN}/public';

console.log('Ente runtime config loaded from runtime-config.js');
console.log('API_URL:', window.ENTE_CONFIG.API_URL);
console.log('PUBLIC_ALBUMS_URL:', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL);
EOT

# Copy the runtime-config.js to each app directory
for app_dir in /app/data/web/photos /app/data/web/accounts /app/data/web/auth /app/data/web/cast; do
    cp /app/data/web/runtime-config.js "$app_dir/"
done

# Ensure all files are readable
chmod -R 644 /app/data/web/runtime-config.js

# Update the Caddy configuration to serve config.js directly
cat > /app/data/caddy/Caddyfile <<EOT
# Global settings
{
@@ -1066,11 +1206,53 @@ cat > /app/data/caddy/Caddyfile <<EOT
        output file /app/data/logs/caddy.log
    }

    # Configuration scripts - This must come before the root handler
    handle /config.js {
        header Content-Type application/javascript
        respond "
            // Direct configuration for Ente
            window.ENTE_CONFIG = {
                API_URL: '${API_ENDPOINT}',
                PUBLIC_ALBUMS_URL: '${CLOUDRON_APP_ORIGIN}/public'
            };
            
            // 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.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = '${CLOUDRON_APP_ORIGIN}/public';
            
            console.log('Ente config loaded - API_URL:', window.ENTE_CONFIG.API_URL);
            console.log('Ente config loaded - PUBLIC_ALBUMS_URL:', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL);

            // Dynamically inject the config by creating script elements
            document.addEventListener('DOMContentLoaded', function() {
                console.log('DOM loaded, injecting runtime config as needed');
            });
        "
    }

    handle /runtime-config.js {
        root * /app/data/web
        file_server
    }

    # Root path serves the photos app
    handle / {
        root * /app/web/photos
        try_files {path} /index.html
        file_server

        # Dynamically inject our configuration script
        header * +Link "</config.js>; rel=preload; as=script"
        header * +Link "</runtime-config.js>; rel=preload; as=script"
        header ?index.html +Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src *"
        
        # Add script injection as HTML filter
        filter {
            search_pattern </head>
            replacement <script src="/config.js" type="text/javascript"></script><script src="/runtime-config.js" type="text/javascript"></script></head>
        }
    }

    # Next.js static files
@@ -1094,6 +1276,12 @@ cat > /app/data/caddy/Caddyfile <<EOT
        uri strip_prefix /accounts
        try_files {path} /index.html
        file_server
        
        # Dynamically inject our configuration script for accounts app
        filter ?index.html {
            search_pattern </head>
            replacement <script src="/config.js" type="text/javascript"></script><script src="/runtime-config.js" type="text/javascript"></script></head>
        }
    }

    # Auth app
@@ -1102,6 +1290,12 @@ cat > /app/data/caddy/Caddyfile <<EOT
        uri strip_prefix /auth
        try_files {path} /index.html
        file_server
        
        # Dynamically inject our configuration script for auth app
        filter ?index.html {
            search_pattern </head>
            replacement <script src="/config.js" type="text/javascript"></script><script src="/runtime-config.js" type="text/javascript"></script></head>
        }
    }

    # Cast app
@@ -1110,6 +1304,12 @@ cat > /app/data/caddy/Caddyfile <<EOT
        uri strip_prefix /cast
        try_files {path} /index.html
        file_server
        
        # Dynamically inject our configuration script for cast app
        filter ?index.html {
            search_pattern </head>
            replacement <script src="/config.js" type="text/javascript"></script><script src="/runtime-config.js" type="text/javascript"></script></head>
        }
    }

    # Main API proxy
@@ -1142,94 +1342,15 @@ cat > /app/data/caddy/Caddyfile <<EOT
        uri strip_prefix /public
        reverse_proxy 0.0.0.0:$PUBLIC_ALBUMS_PORT
    }

    # Configuration scripts
    handle /config.js {
        header Content-Type application/javascript
        respond "
            // Direct configuration for Ente
            window.ENTE_CONFIG = {
                API_URL: '${API_ENDPOINT}',
                PUBLIC_ALBUMS_URL: '${CLOUDRON_APP_ORIGIN}/public'
            };
            
            // 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.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = '${CLOUDRON_APP_ORIGIN}/public';
            
            console.log('Ente config loaded - API_URL:', window.ENTE_CONFIG.API_URL);
            console.log('Ente config loaded - PUBLIC_ALBUMS_URL:', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL);
        "
    }
}
EOT

echo "==> Created Caddy config at /app/data/caddy/Caddyfile"
echo "==> Created Caddy config with dynamic script injection at /app/data/caddy/Caddyfile"
echo "==> No longer trying to modify read-only HTML files"

# Start Caddy server
echo "==> Starting Caddy server"

# First inject the config.js script tags into all HTML files
echo "==> Injecting config.js into web application HTML files"

# Function to inject the script tag
inject_script_tag() {
    file="$1"
    if [ -f "$file" ]; then
        echo "==> Injecting config.js into $file"
        # Make a backup just in case
        cp "$file" "${file}.bak"
        # Insert the script tag right after the opening head tag
        sed -i 's/<head>/<head>\n    <script src="\/config.js" type="text\/javascript"><\/script>/' "$file"
    else
        echo "==> WARNING: Could not find $file to inject config script"
    fi
}

# Inject into all the web apps
inject_script_tag "/app/web/photos/index.html"
inject_script_tag "/app/web/accounts/index.html"
inject_script_tag "/app/web/auth/index.html"
inject_script_tag "/app/web/cast/index.html"

# Also create a runtime-config.js file with properly escaped content
cat > /app/web/photos/runtime-config.js <<EOT
// Runtime configuration for Ente
window.ENTE_CONFIG = {
    API_URL: '${API_ENDPOINT}',
    PUBLIC_ALBUMS_URL: '${CLOUDRON_APP_ORIGIN}/public'
};

// 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.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = '${CLOUDRON_APP_ORIGIN}/public';

console.log('Ente runtime config loaded from runtime-config.js');
console.log('API_URL:', window.ENTE_CONFIG.API_URL);
console.log('PUBLIC_ALBUMS_URL:', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL);
EOT

# Copy the runtime-config.js to each app directory
for app_dir in /app/web/photos /app/web/accounts /app/web/auth /app/web/cast; do
    cp /app/web/photos/runtime-config.js "$app_dir/"
    
    # Update the HTML to include this file too
    index_file="$app_dir/index.html"
    if [ -f "$index_file" ]; then
        echo "==> Adding runtime-config.js to $index_file"
        sed -i 's/<\/head>/<script src="runtime-config.js" type="text\/javascript"><\/script>\n    <\/head>/' "$index_file"
    fi
done

# Ensure all files are readable
chmod -R 644 /app/web/*/runtime-config.js

echo "==> Config injected into all web applications"

caddy start --config /app/data/caddy/Caddyfile --adapter caddyfile &
CADDY_PID=$!
echo "==> Caddy started with PID $CADDY_PID"