#!/bin/bash # Better signal handling - forward signals to child processes trap 'kill -TERM $SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT set -eu echo "==> Starting Ente Cloudron app..." # Create necessary directories mkdir -p /app/data/config /app/data/storage /app/data/caddy /app/data/go /app/data/logs # Add comment about Cloudron filesystem limitations echo "==> NOTE: Running in Cloudron environment with limited write access" echo "==> Writable directories: /app/data, /tmp, /run" # One-time initialization tracking if [[ ! -f /app/data/.initialized ]]; then echo "==> Fresh installation, setting up data directory..." echo "==> DEBUG: Full repository structure at /app/code" find /app/code -type d -maxdepth 3 -not -path "*/node_modules/*" -not -path "*/\.*" | sort echo "==> DEBUG: Looking for Go files" find /app/code -name "*.go" | grep -v test | sort | head -10 echo "==> DEBUG: Looking for server-related directories" find /app/code -type d -path "*/server*" -o -path "*/museum*" | sort echo "==> DEBUG: All package.json files in repository" find /app/code -name "package.json" -not -path "*/node_modules/*" | sort echo "==> DEBUG: Looking for web app directories" find /app/code -type d -path "*/web*" | sort echo "==> DEBUG: Web app directories in /app/web (if they exist)" if [ -d "/app/web" ]; then ls -la /app/web else echo "Web app directory not yet copied to /app/web" fi # Create config template file on first run echo "==> First run - creating configuration template" # Generate random secrets JWT_SECRET=$(openssl rand -hex 32) SESSION_SECRET=$(openssl rand -hex 32) MASTER_KEY=$(openssl rand -hex 32) # Replace variables in template for things we know sed \ -e "s|%%POSTGRESQL_HOST%%|${CLOUDRON_POSTGRESQL_HOST}|g" \ -e "s|%%POSTGRESQL_PORT%%|${CLOUDRON_POSTGRESQL_PORT}|g" \ -e "s|%%POSTGRESQL_USERNAME%%|${CLOUDRON_POSTGRESQL_USERNAME}|g" \ -e "s|%%POSTGRESQL_PASSWORD%%|${CLOUDRON_POSTGRESQL_PASSWORD}|g" \ -e "s|%%POSTGRESQL_DATABASE%%|${CLOUDRON_POSTGRESQL_DATABASE}|g" \ -e "s|%%APP_ORIGIN%%|${CLOUDRON_APP_ORIGIN}|g" \ -e "s|%%MAIL_SMTP_SERVER%%|${CLOUDRON_MAIL_SMTP_SERVER}|g" \ -e "s|%%MAIL_SMTP_PORT%%|${CLOUDRON_MAIL_SMTP_PORT}|g" \ -e "s|%%MAIL_SMTP_USERNAME%%|${CLOUDRON_MAIL_SMTP_USERNAME}|g" \ -e "s|%%MAIL_SMTP_PASSWORD%%|${CLOUDRON_MAIL_SMTP_PASSWORD}|g" \ -e "s|%%MAIL_FROM%%|${CLOUDRON_MAIL_FROM}|g" \ -e "s|%%MAIL_FROM_DISPLAY_NAME%%|${CLOUDRON_MAIL_FROM_DISPLAY_NAME}|g" \ -e "s|%%JWT_SECRET%%|${JWT_SECRET}|g" \ -e "s|%%SESSION_SECRET%%|${SESSION_SECRET}|g" \ -e "s|%%MASTER_KEY%%|${MASTER_KEY}|g" \ /app/pkg/config.template.yaml > /app/data/config/config.yaml # Create an S3 configuration file template cat > /app/data/config/s3.env.template < IMPORTANT: S3 storage configuration required" echo "==> Please configure your S3 storage as follows:" echo "1. Log into your Cloudron dashboard" echo "2. Go to the app's configuration page" echo "3. Edit the file /app/data/config/s3.env" echo "4. Restart the app" # Mark initialization as complete touch /app/data/.initialized echo "==> Initialization complete" fi # Check if configuration exists if [ ! -f "/app/data/config/s3.env" ]; then echo "==> First run - creating configuration template" mkdir -p /app/data/config # Create a template S3 configuration file echo "==> Creating S3 configuration template" cat > /app/data/config/s3.env.template < IMPORTANT: S3 storage configuration required" echo "==> Please configure your S3 storage as follows:" echo "1. Log into your Cloudron dashboard" echo "2. Go to the app's configuration page" echo "3. Edit the file /app/data/config/s3.env" echo "4. Restart the app" else echo "==> Using existing S3 configuration" fi # Check if s3.env is empty if [ ! -s "/app/data/config/s3.env" ]; then echo "==> WARNING: S3 configuration file is empty. The app will not function correctly until configured." echo "==> Please refer to the template at /app/data/config/s3.env.template for instructions." 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 fi # Display S3 configuration (masking sensitive values) echo "==> S3 Configuration:" echo "Endpoint: ${S3_ENDPOINT}" echo "Region: ${S3_REGION}" echo "Bucket: ${S3_BUCKET}" echo "Prefix: ${S3_PREFIX:-}" # Create museum.yaml for proper S3 configuration echo "==> Creating museum.yaml configuration" cat > /app/data/config/museum.yaml < Created museum.yaml with S3 configuration" # Update the config file with S3 credentials sed -i \ -e "s|%%S3_ENDPOINT%%|${S3_ENDPOINT}|g" \ -e "s|%%S3_REGION%%|${S3_REGION}|g" \ -e "s|%%S3_BUCKET%%|${S3_BUCKET}|g" \ -e "s|%%S3_ACCESS_KEY%%|${S3_ACCESS_KEY}|g" \ -e "s|%%S3_SECRET_KEY%%|${S3_SECRET_KEY}|g" \ -e "s|%%S3_PREFIX%%|${S3_PREFIX:-}|g" \ /app/data/config/config.yaml # Set storage type to S3 in config sed -i 's|storage.type: "local"|storage.type: "s3"|g' /app/data/config/config.yaml sed -i 's|s3.are_local_buckets: true|s3.are_local_buckets: false|g' /app/data/config/config.yaml # 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 fi # Set up the API endpoint for the web apps API_ENDPOINT="${CLOUDRON_APP_ORIGIN}/api" echo "==> Setting API endpoint to $API_ENDPOINT" # Set environment variables for the web apps export ENTE_API_ENDPOINT=$API_ENDPOINT export NEXT_PUBLIC_ENTE_ENDPOINT=$API_ENDPOINT export REACT_APP_ENTE_ENDPOINT=$API_ENDPOINT export VUE_APP_ENTE_ENDPOINT=$API_ENDPOINT echo "==> Set environment variables for web apps" # Create directory for direct modifications mkdir -p /app/data/scripts # Create a script that will directly define the ENTE_CONFIG global in window cat > /app/data/scripts/ente-config.js < Setting up direct runtime configuration" # Global API endpoint API_ENDPOINT="${CLOUDRON_APP_ORIGIN}/api" echo "==> Setting API endpoint to $API_ENDPOINT" # Create runtime configuration files mkdir -p /app/data/runtime echo "==> Creating runtime configuration files" # Create a config script cat > /app/data/runtime/config.js < Attempting to directly modify web app HTML files" WEB_APPS=("/app/web/photos" "/app/web/accounts" "/app/web/auth" "/app/web/cast") for app_dir in "${WEB_APPS[@]}"; do if [ -f "$app_dir/index.html" ]; then echo "==> Processing $app_dir/index.html" # Create a backup copy in our data directory mkdir -p "/app/data/original/$(basename $app_dir)" cp "$app_dir/index.html" "/app/data/original/$(basename $app_dir)/index.html" # Create a modified version of the file in our data directory mkdir -p "/app/data/modified/$(basename $app_dir)" cp "$app_dir/index.html" "/app/data/modified/$(basename $app_dir)/index.html" # Insert our configuration script into the head section sed -i 's|||' "/app/data/modified/$(basename $app_dir)/index.html" # Try to replace the original file (may fail if read-only) if cp "/app/data/modified/$(basename $app_dir)/index.html" "$app_dir/index.html" 2>/dev/null; then echo "==> Successfully modified $app_dir/index.html" else echo "==> Could not modify $app_dir/index.html (read-only filesystem)" fi else echo "==> Skipping $app_dir - index.html not found" fi done # Second approach: Create a modified copy that Caddy will serve mkdir -p /app/data/public cp /app/data/runtime/config.js /app/data/public/ echo "==> Created public configuration script" # Create a loading page for each app with configuration for app_name in "photos" "accounts" "auth" "cast"; do cat > "/app/data/public/${app_name}-config.html" < Loading ${app_name}

Loading ${app_name}...

If you are not redirected automatically, click here.

Debug Information

EOT done # Create root index.html that loads config and redirects cat > /app/data/public/index.html < Ente

Loading Ente...

You will be redirected automatically.

Debug Information

EOT echo "==> Created special root index.html with configuration" # Check port availability before starting services echo "==> Checking port availability" CADDY_PORT=3080 API_PORT=8080 # Check if ports are already in use if lsof -i:$CADDY_PORT > /dev/null 2>&1; then echo "==> WARNING: Port $CADDY_PORT is already in use" echo "===> Process using port $CADDY_PORT:" lsof -i:$CADDY_PORT else echo "==> Port $CADDY_PORT is available for Caddy" fi if lsof -i:$API_PORT > /dev/null 2>&1; then echo "==> WARNING: Port $API_PORT is already in use" echo "===> Process using port $API_PORT:" lsof -i:$API_PORT else echo "==> Port $API_PORT is available for API server" fi # Set up Caddy echo "==> Setting up Caddy server for web apps" mkdir -p /app/data/caddy/public cat > /app/data/caddy/Caddyfile < Caddy configuration created at /app/data/caddy/Caddyfile" # Start Caddy first before the Museum server to ensure port 3080 is available for health checks echo "==> Setting up Caddy before starting the Museum server" echo "==> Caddy will be listening on port $CADDY_PORT" # Only set permissions on the Caddy directory, not the web directory which is read-only mkdir -p /app/data/caddy chmod -R 777 /app/data/caddy chown -R cloudron:cloudron /app/data/caddy # Start Caddy server echo "==> Starting Caddy server..." caddy run --config /app/data/caddy/Caddyfile --adapter caddyfile & CADDY_PID=$! echo "==> Caddy started with PID $CADDY_PID" # Wait a moment for Caddy to start sleep 2 # Test Caddy connectivity echo "==> Testing Caddy connectivity on port $CADDY_PORT" 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" break else if [ $i -eq 5 ]; then echo "==> Failed to connect to Caddy on port $CADDY_PORT after multiple attempts" echo "==> Last 20 lines of Caddy log:" tail -20 /app/data/caddy/caddy.log || echo "==> No Caddy log available" echo "==> Network ports in use:" netstat -tuln || echo "==> netstat command not available" echo "==> Processes listening on ports:" lsof -i -P -n | grep LISTEN || echo "==> lsof command not available" else echo "==> Attempt $i: Waiting for Caddy to start... (1 second)" sleep 1 fi fi done # Determine available memory and set limits accordingly if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then # cgroup v2 memory_limit=$(cat /sys/fs/cgroup/memory.max) [[ "${memory_limit}" == "max" ]] && memory_limit=$(( 2 * 1024 * 1024 * 1024 )) # "max" really means unlimited else memory_limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) # this is the RAM. we have equal amount of swap fi memory_mb=$((memory_limit/1024/1024)) echo "==> Available memory: ${memory_mb}MB" # Set up database environment variables and ensure proper SSL config export ENTE_DB_USER="${CLOUDRON_POSTGRESQL_USERNAME}" export ENTE_DB_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" export ENTE_DB_HOST="${CLOUDRON_POSTGRESQL_HOST}" export ENTE_DB_PORT="${CLOUDRON_POSTGRESQL_PORT}" export ENTE_DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE}" export ENTE_DB_SSLMODE="disable" export CONFIG_PATH="/app/data/config/config.yaml" # Check database connectivity echo "==> Checking database connectivity" MAX_RETRIES=5 RETRY_COUNT=0 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do if PGPASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" psql -h "${CLOUDRON_POSTGRESQL_HOST}" -p "${CLOUDRON_POSTGRESQL_PORT}" \ -U "${CLOUDRON_POSTGRESQL_USERNAME}" -d "${CLOUDRON_POSTGRESQL_DATABASE}" -c "SELECT 1" > /dev/null 2>&1; then echo "==> Successfully connected to database" break else echo "==> Failed to connect to database (attempt $((RETRY_COUNT+1))/$MAX_RETRIES)" RETRY_COUNT=$((RETRY_COUNT+1)) sleep 5 fi done if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then echo "==> ERROR: Could not connect to database after $MAX_RETRIES attempts" echo "==> Database connection details:" echo "Host: ${CLOUDRON_POSTGRESQL_HOST}" echo "Port: ${CLOUDRON_POSTGRESQL_PORT}" echo "User: ${CLOUDRON_POSTGRESQL_USERNAME}" echo "Database: ${CLOUDRON_POSTGRESQL_DATABASE}" exit 1 fi # Fix database migration state if needed echo "==> Checking database migration state" if [ -d "$SERVER_DIR/cmd/museum" ]; then echo "==> Attempting to fix dirty migration state" cd "$SERVER_DIR" # Create migrations log directory mkdir -p /app/data/logs/migrations echo "==> Forcing migration version to 25" if /usr/local/bin/gosu cloudron:cloudron env GOCACHE=/app/data/go/cache GOMODCACHE=/app/data/go/pkg/mod \ go run -modfile=/app/data/go/go.mod -mod=mod cmd/museum/main.go migrate force 25 \ > /app/data/logs/migrations/force.log 2>&1; then echo "==> Successfully forced migration version" else echo "==> Failed to force migration version. Check /app/data/logs/migrations/force.log for details" cat /app/data/logs/migrations/force.log fi echo "==> Running clean migrations" if /usr/local/bin/gosu cloudron:cloudron env GOCACHE=/app/data/go/cache GOMODCACHE=/app/data/go/pkg/mod \ go run -modfile=/app/data/go/go.mod -mod=mod cmd/museum/main.go migrate up \ > /app/data/logs/migrations/up.log 2>&1; then echo "==> Successfully ran migrations" else echo "==> Failed to run migrations. Check /app/data/logs/migrations/up.log for details" cat /app/data/logs/migrations/up.log echo "==> WARNING: Database migrations failed. The application may not work correctly." fi fi # Additional environment variables export REMOTE_STORAGE_ENDPOINT="${S3_ENDPOINT}" export REMOTE_STORAGE_REGION="${S3_REGION}" export REMOTE_STORAGE_BUCKET="${S3_BUCKET}" export REMOTE_STORAGE_ACCESS_KEY="${S3_ACCESS_KEY}" export REMOTE_STORAGE_SECRET_KEY="${S3_SECRET_KEY}" export REMOTE_STORAGE_PREFIX="${S3_PREFIX:-}" # Change ownership to cloudron user chown -R cloudron:cloudron /app/data # Start Museum server on port 8080 for compatibility with the Caddy configuration echo "==> Starting Museum server" # Modify environment variables for frontend echo "==> Setting up environment variables for frontend" # Set frontend endpoint to the proper origin export NEXT_PUBLIC_ENTE_ENDPOINT="${CLOUDRON_APP_ORIGIN}/api" echo "==> NEXT_PUBLIC_ENTE_ENDPOINT set to ${NEXT_PUBLIC_ENTE_ENDPOINT}" # Also create a direct config in multiple formats to ensure it's properly loaded echo "==> Creating runtime configuration for the web apps in multiple formats" mkdir -p /app/data/runtime # 1. Standard config.js cat > /app/data/runtime/config.js < /app/data/runtime/config.json < /app/data/runtime/env.js < Generated config.js content:" cat /app/data/runtime/config.js # First check for pre-built Museum binary if find "$SERVER_DIR" -name "museum" -type f -executable | grep -q .; then MUSEUM_BIN=$(find "$SERVER_DIR" -name "museum" -type f -executable | head -1) echo "==> Found Museum binary at $MUSEUM_BIN" /usr/local/bin/gosu cloudron:cloudron "$MUSEUM_BIN" --port 8080 \ --storage.s3.endpoint="${S3_ENDPOINT}" \ --storage.s3.region="${S3_REGION}" \ --storage.s3.bucket="${S3_BUCKET}" \ --storage.s3.accessKey="${S3_ACCESS_KEY}" \ --storage.s3.secretKey="${S3_SECRET_KEY}" \ --storage.s3.prefix="${S3_PREFIX:-}" \ --storage.s3.forcePathStyle=true \ --storage.s3.areLocalBuckets=false \ --storage.type="s3" \ --config.path="/app/data/config/museum.yaml" \ --database.sslmode="disable" > /app/data/logs/museum-server.log 2>&1 & SERVER_PID=$! echo "==> Museum server started with PID $SERVER_PID" # Next check for cmd/museum directory pattern elif [ -d "$SERVER_DIR/cmd/museum" ]; then echo "==> Found Museum source in cmd/museum, running with go run" cd "$SERVER_DIR" # Set environment variables for compatibility echo "==> Setting Go environment variables for compatibility" export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod -modcacherw" # Use local toolchain to avoid downloading required version export GOTOOLCHAIN=local # Launch the server with S3 configuration echo "==> Starting Museum server with S3 configuration" cd "$SERVER_DIR" && \ /usr/local/bin/gosu cloudron:cloudron env GOCACHE=/app/data/go/cache GOMODCACHE=/app/data/go/pkg/mod PORT=8080 GIN_MODE=release go run -modfile=/app/data/go/go.mod -mod=mod cmd/museum/main.go --port 8080 \ --storage.s3.endpoint="${S3_ENDPOINT}" \ --storage.s3.region="${S3_REGION}" \ --storage.s3.bucket="${S3_BUCKET}" \ --storage.s3.accessKey="${S3_ACCESS_KEY}" \ --storage.s3.secretKey="${S3_SECRET_KEY}" \ --storage.s3.prefix="${S3_PREFIX:-}" \ --storage.s3.forcePathStyle=true \ --storage.s3.areLocalBuckets=false \ --storage.type="s3" \ --config.path="/app/data/config/museum.yaml" \ --database.sslmode="disable" \ --log.level=debug > /app/data/logs/museum-server.log 2>&1 & SERVER_PID=$! echo "==> Museum server started with PID $SERVER_PID" # Wait for the server to start echo "==> Waiting for Museum server to start..." sleep 5 # Test if API is responding MAX_RETRIES=5 RETRY_COUNT=0 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do echo "==> Testing API connection (attempt $((RETRY_COUNT+1))/$MAX_RETRIES)" if curl -s http://localhost:8080/api/health > /dev/null; then echo "==> API is now responding" break else echo "==> API not responding yet, waiting..." RETRY_COUNT=$((RETRY_COUNT+1)) sleep 5 # Print recent log output echo "==> Recent server log output:" tail -n 20 /app/data/logs/museum-server.log || echo "==> No logs available yet" fi done if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then echo "==> WARNING: API server did not respond after multiple attempts" echo "==> Checking server logs:" ps -p $SERVER_PID >/dev/null || echo "==> ERROR: Server process is not running!" tail -n 50 /app/data/logs/museum-server.log || echo "==> No logs available" fi # Next try to find any main.go for the Museum else # Fallback approach - find main.go files MAIN_FILES=$(find "$SERVER_DIR" -name "main.go" | grep -v "test" || echo "") if [ -n "$MAIN_FILES" ]; then MAIN_FILE=$(echo "$MAIN_FILES" | head -1) MAIN_DIR=$(dirname "$MAIN_FILE") echo "==> Using main.go file at $MAIN_FILE" cd "$MAIN_DIR" # Instead of trying to modify go.mod, set environment variables for compatibility echo "==> Setting Go environment variables for compatibility" export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod -modcacherw" export GOTOOLCHAIN=local echo "==> Running main.go with Go" cd "$MAIN_DIR" && \ /usr/local/bin/gosu cloudron:cloudron env GOCACHE=/app/data/go/cache GOMODCACHE=/app/data/go/pkg/mod PORT=8080 GIN_MODE=release go run -modfile=/app/data/go/go.mod -mod=mod main.go --port 8080 \ --storage.s3.endpoint="${S3_ENDPOINT}" \ --storage.s3.region="${S3_REGION}" \ --storage.s3.bucket="${S3_BUCKET}" \ --storage.s3.accessKey="${S3_ACCESS_KEY}" \ --storage.s3.secretKey="${S3_SECRET_KEY}" \ --storage.s3.prefix="${S3_PREFIX:-}" \ --storage.s3.forcePathStyle=true \ --storage.s3.areLocalBuckets=false \ --storage.type="s3" \ --config.path="/app/data/config/museum.yaml" \ --database.sslmode="disable" \ --log.level=debug > /app/data/logs/museum-server.log 2>&1 & SERVER_PID=$! echo "==> Museum server started with PID $SERVER_PID" # Check if the server process is actually running after a brief pause sleep 2 if ! ps -p $SERVER_PID > /dev/null; then echo "==> WARNING: Server process exited immediately" echo "==> Recent server log output:" tail -n 50 /app/data/logs/museum-server.log || echo "==> No logs available yet" echo "==> Falling back to mock server" SERVER_PID="" fi else echo "==> ERROR: Could not find Museum binary or main.go source file" echo "==> Available Go files:" find "$SERVER_DIR" -name "*.go" | sort SERVER_PID="" fi fi # If server didn't start successfully, use mock server if [ -z "$SERVER_PID" ] || ! ps -p $SERVER_PID > /dev/null; then echo "==> Starting mock server as fallback" # Last resort - create a temporary Go HTTP server that returns a meaningful error mkdir -p /tmp/mock-server cat > /tmp/mock-server/main.go < Mock server started with PID $SERVER_PID to show error message" fi echo "==> Testing health check endpoint directly" curl -v http://localhost:3080/healthcheck echo "==> Ente is now running!" echo "==> Museum server: PID $SERVER_PID" echo "==> Caddy: PID $CADDY_PID" # Wait for child processes to exit wait $SERVER_PID wait $CADDY_PID # Create a special root index.html that loads the config and redirects cat > /app/data/public/index.html < Ente

Loading Ente...

You will be redirected automatically.

Debug Information

EOT echo "==> Created special root index.html with configuration" # Create debug script for frontend mkdir -p /app/data/public mkdir -p /app/data/debug echo "==> Creating debug scripts" cat > /app/data/public/debug.js <' + 'process.env.NEXT_PUBLIC_ENTE_ENDPOINT: ' + (window.process?.env?.NEXT_PUBLIC_ENTE_ENDPOINT || 'undefined') + '
' + 'localStorage ENTE_CONFIG: ' + localStorage.getItem('ENTE_CONFIG') + '
' + 'localStorage NEXT_PUBLIC_ENTE_ENDPOINT: ' + localStorage.getItem('NEXT_PUBLIC_ENTE_ENDPOINT'); debugDiv.appendChild(configInfo); // Add toggle button const toggleButton = document.createElement('button'); toggleButton.innerText = 'Toggle Debug Info'; toggleButton.style.marginTop = '10px'; toggleButton.onclick = function() { configInfo.style.display = configInfo.style.display === 'none' ? 'block' : 'none'; }; debugDiv.appendChild(toggleButton); // Add to document when it's ready if (document.body) { document.body.appendChild(debugDiv); } else { window.addEventListener('DOMContentLoaded', function() { document.body.appendChild(debugDiv); }); } })(); EOT # Create debug info script that prints environment info cat > /app/data/debug/debug_env.js < /app/data/public/debug.html < Ente Debug Info

Ente Debug Information

Frontend Configuration

Loading...

URL Test

Testing URL construction with API endpoint:

Running test...

API Health Check

Checking API health...
EOT