#!/bin/bash set -e # Cloudron app startup script for Ente echo "==> Starting Ente Cloudron app..." # We need to be careful with file permissions, as /app/data is the only writable location mkdir -p /app/data/patched chmod -R 777 /app/data/patched echo "==> Created and set full permissions (777) on /app/data/patched directory" echo "==> NOTE: Running in Cloudron environment with limited write access" echo "==> Writable directories: /app/data, /tmp, /run" # Configure important paths MUSEUM_DIR="/app/code/server" CONFIG_DIR="/app/data/config" LOGS_DIR="/app/data/logs" WEB_DIR="/app/web" CADDY_DATA_DIR="/app/data/caddy" # Create necessary directories mkdir -p "$CONFIG_DIR" "$LOGS_DIR" "$CADDY_DATA_DIR" # Determine the endpoint configuration CLOUDRON_APP_FQDN="${CLOUDRON_APP_DOMAIN}" if [ -n "${CLOUDRON_APP_ORIGIN}" ]; then CLOUDRON_APP_FQDN="${CLOUDRON_APP_DOMAIN}" else # If origin not set, use the app domain CLOUDRON_APP_ORIGIN="https://${CLOUDRON_APP_DOMAIN}" fi API_ENDPOINT="/api" CADDY_PORT="3080" API_PORT="8080" PUBLIC_ALBUMS_PORT="8081" echo "==> Using server directory: ${MUSEUM_DIR}" # Check if we have S3 configuration if [ -f "${CONFIG_DIR}/s3.env" ]; then echo "==> Using existing S3 configuration" source "${CONFIG_DIR}/s3.env" echo "==> S3 Configuration:" echo "Endpoint: ${S3_ENDPOINT}" echo "Region: ${S3_REGION}" echo "Bucket: ${S3_BUCKET}" else echo "==> Creating default S3 configuration file" # Create empty S3 env file for later configuration cat > "${CONFIG_DIR}/s3.env" << EOF # S3 Configuration for Ente # Uncomment and fill in the following values: # S3_ENDPOINT=https://s3.example.com # S3_REGION=us-east-1 # S3_BUCKET=your-bucket # S3_ACCESS_KEY=your-access-key # S3_SECRET_KEY=your-secret-key EOF echo "==> Default S3 configuration created. Please edit ${CONFIG_DIR}/s3.env with your S3 credentials." fi # Check if we have a museum.yaml configuration file if [ -f "${CONFIG_DIR}/museum.yaml" ]; then echo "==> Using existing museum.yaml configuration" else echo "==> Creating default museum.yaml configuration" # Create museum.yaml with S3 configuration cat > "${CONFIG_DIR}/museum.yaml" << EOF server: host: 0.0.0.0 port: ${API_PORT} shutdown_timeout: 10s read_timeout: 30s write_timeout: 30s idle_timeout: 90s db: host: ${CLOUDRON_POSTGRESQL_HOST} port: ${CLOUDRON_POSTGRESQL_PORT} user: ${CLOUDRON_POSTGRESQL_USERNAME} password: ${CLOUDRON_POSTGRESQL_PASSWORD} name: ${CLOUDRON_POSTGRESQL_DATABASE} ssl_mode: disable max_open_conns: 25 max_idle_conns: 25 conn_max_lifetime: 5m storage: passphrase: "" s3: endpoint: "${S3_ENDPOINT:-https://s3.example.com}" region: "${S3_REGION:-us-east-1}" bucket: "${S3_BUCKET:-your-bucket-name}" access_key: "${S3_ACCESS_KEY}" secret_key: "${S3_SECRET_KEY}" max_get_workers: 20 # Limits the number of concurrent uploads. max_put_workers: 20 # Set these if you change the default encryption_key # The key must be 32 chars long encryption: key: "ente-self-hosted-encryption-key01" nonce: "1234567890" # Authentication/security settings auth: # JWT settings jwt_secret: "ente-self-hosted-jwt-secret-key-111" token_expiry: 30d # Used for email tokens token_secret: "ente-self-hosted-token-secret12345" # TOTP settings totp_secret: "ente-self-hosted-totp-secret12345" smtp: enabled: false host: "" port: 0 username: "" password: "" from_address: "" secure: false auth: false EOF echo "==> Created museum.yaml with default configuration" fi # Create a reduced museum.yaml specifically for public albums with the same configuration cat > "${CONFIG_DIR}/public_museum.yaml" << EOF server: host: 0.0.0.0 port: ${PUBLIC_ALBUMS_PORT} shutdown_timeout: 10s read_timeout: 30s write_timeout: 30s idle_timeout: 90s db: host: ${CLOUDRON_POSTGRESQL_HOST} port: ${CLOUDRON_POSTGRESQL_PORT} user: ${CLOUDRON_POSTGRESQL_USERNAME} password: ${CLOUDRON_POSTGRESQL_PASSWORD} name: ${CLOUDRON_POSTGRESQL_DATABASE} ssl_mode: disable max_open_conns: 25 max_idle_conns: 25 conn_max_lifetime: 5m storage: passphrase: "" s3: endpoint: "${S3_ENDPOINT:-https://s3.example.com}" region: "${S3_REGION:-us-east-1}" bucket: "${S3_BUCKET:-your-bucket-name}" access_key: "${S3_ACCESS_KEY}" secret_key: "${S3_SECRET_KEY}" max_get_workers: 20 max_put_workers: 20 encryption: key: "ente-self-hosted-encryption-key01" nonce: "1234567890" auth: jwt_secret: "ente-self-hosted-jwt-secret-key-111" token_expiry: 30d token_secret: "ente-self-hosted-token-secret12345" totp_secret: "ente-self-hosted-totp-secret12345" EOF # Environment variable setup - based on the docker-compose reference export ENTE_CONFIG_FILE="${CONFIG_DIR}/museum.yaml" export ENTE_API_ENDPOINT="${API_ENDPOINT}" export ENTE_PORT="${API_PORT}" # Set up PostgreSQL connection variables - referenced in docker-compose export ENTE_DB_HOST="${CLOUDRON_POSTGRESQL_HOST}" export ENTE_DB_PORT="${CLOUDRON_POSTGRESQL_PORT}" export ENTE_DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE}" export ENTE_DB_USER="${CLOUDRON_POSTGRESQL_USERNAME}" export ENTE_DB_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" # Also set standard PostgreSQL variables as backup export PGHOST="${CLOUDRON_POSTGRESQL_HOST}" export PGPORT="${CLOUDRON_POSTGRESQL_PORT}" export PGUSER="${CLOUDRON_POSTGRESQL_USERNAME}" export PGPASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" export PGDATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" # Define trap to ensure all processes are killed on exit SERVER_PID=0 PUBLIC_SERVER_PID=0 CADDY_PID=0 TAIL_PID=0 trap 'kill -TERM $TAIL_PID; kill -TERM $SERVER_PID; kill -TERM $PUBLIC_SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT # Start the Museum Server echo "==> Testing PostgreSQL connectivity" if pg_isready -q; then echo "==> PostgreSQL is ready" else echo "==> WARNING: PostgreSQL is not ready, but proceeding anyway" fi # Check if the Museum server exists at the expected location if [ -f "${MUSEUM_DIR}/museum" ] && [ -x "${MUSEUM_DIR}/museum" ]; then echo "==> Found Museum server binary at ${MUSEUM_DIR}/museum" # Start the main API server cd "${MUSEUM_DIR}" echo "==> Starting Museum server with config: ${ENTE_CONFIG_FILE}" nohup ./museum server > "${LOGS_DIR}/museum.log" 2>&1 & SERVER_PID=$! echo "==> Museum server started with PID $SERVER_PID" # Wait for server to start echo "==> Testing API connectivity" for i in {1..5}; do if curl -s --max-time 2 --fail http://0.0.0.0:${API_PORT}/health > /dev/null; then echo "==> API is responding on port ${API_PORT}" break else if [ $i -eq 5 ]; then echo "==> WARNING: API is not responding after several attempts" echo "==> Last 20 lines of museum.log:" tail -20 "${LOGS_DIR}/museum.log" || echo "==> No museum.log available" else echo "==> Attempt $i: Waiting for API to start... (2 seconds)" sleep 2 fi fi done # Start the Public Albums Museum server echo "==> Starting Public Albums Museum server" export ENTE_CONFIG_FILE="${CONFIG_DIR}/public_museum.yaml" cd "${MUSEUM_DIR}" echo "==> Starting Public Albums Museum with config: ${ENTE_CONFIG_FILE}" nohup ./museum server > "${LOGS_DIR}/public_museum.log" 2>&1 & PUBLIC_SERVER_PID=$! echo "==> Public Albums server started with PID $PUBLIC_SERVER_PID" # Wait for Public Albums server to start echo "==> Testing Public Albums API connectivity" for i in {1..5}; do if curl -s --max-time 2 --fail http://0.0.0.0:${PUBLIC_ALBUMS_PORT}/health > /dev/null; then echo "==> Public Albums API is responding on port ${PUBLIC_ALBUMS_PORT}" break else if [ $i -eq 5 ]; then echo "==> WARNING: Public Albums API is not responding after several attempts" echo "==> Last 20 lines of public_museum.log:" tail -20 "${LOGS_DIR}/public_museum.log" || echo "==> No public_museum.log available" else echo "==> Attempt $i: Waiting for Public Albums API to start... (2 seconds)" sleep 2 fi fi done else echo "==> ERROR: Museum server not found at ${MUSEUM_DIR}/museum" echo "==> Starting a mock server with Node.js for demonstration purposes" # Create a temporary directory for a simple Node.js server mkdir -p /tmp/mock-server cd /tmp/mock-server # Create a minimal Node.js server file cat > server.js << 'ENDOFCODE' const http = require('http'); const fs = require('fs'); const path = require('path'); // Ensure log directory exists const logDir = '/app/data/logs'; fs.mkdirSync(logDir, { recursive: true }); const logFile = path.join(logDir, 'api_requests.log'); // Open log file fs.writeFileSync(logFile, `API Server started at ${new Date().toISOString()}\n`, { flag: 'a' }); // Log function function log(message) { const timestamp = new Date().toISOString(); const logMessage = `${timestamp} - ${message}\n`; console.log(message); fs.writeFileSync(logFile, logMessage, { flag: 'a' }); } // Generate random 6-digit code function generateCode() { return Math.floor(100000 + Math.random() * 900000).toString(); } // Generate unique numeric ID (for user ID) function generateNumericId() { return Math.floor(10000 + Math.random() * 90000); } // Create HTTP server const server = http.createServer((req, res) => { const url = req.url; const method = req.method; log(`Received ${method} request for ${url}`); // Set CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // Handle preflight requests if (method === 'OPTIONS') { res.statusCode = 200; res.end(); return; } // Handle requests based on URL path if (url === '/health') { // Health check endpoint res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: "ok", time: new Date().toISOString() })); } else if (url === '/users/ott') { // OTT verification code endpoint let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { let email = 'user@example.com'; // Try to parse email from request if possible try { const data = JSON.parse(body); if (data.email) { email = data.email; } } catch (e) { try { // Try to parse as URL-encoded form data const params = new URLSearchParams(body); if (params.has('email')) { email = params.get('email'); } } catch (e2) { // Ignore parsing errors } } // Generate verification code const code = generateCode(); const userId = generateNumericId(); // Log the code prominently const codeMessage = `⚠️ VERIFICATION CODE FOR ${email}: ${code}`; log(codeMessage); console.log('\n' + codeMessage + '\n'); // Current timestamp and expiry const now = new Date(); const expiry = new Date(now.getTime() + 3600000); // 1 hour from now // Send response with all required fields res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: "ok", id: userId, token: `mock-token-${userId}`, ott: code, exp: Math.floor(expiry.getTime() / 1000), email: email, createdAt: now.toISOString(), updatedAt: now.toISOString(), key: { pubKey: "mockPubKey123456", encPubKey: "mockEncPubKey123456", kty: "RSA", kid: "kid-123456", alg: "RS256", verifyKey: "mockVerifyKey123456" } })); }); } else if (url === '/users/verification' || url === '/users/verify-email') { // Verification endpoint let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { log("Verification request received with body: " + body); // Try to parse the request let email = 'user@example.com'; let code = '123456'; const userId = generateNumericId(); try { const data = JSON.parse(body); if (data.email) email = data.email; if (data.code) code = data.code; } catch (e) { // Ignore parsing errors } log(`⚠️ VERIFICATION SUCCESSFUL - code: ${code} for ${email}`); // Current timestamp const now = new Date(); // Send success response with all required fields res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: "ok", id: userId, token: `mock-token-${userId}`, email: email, createdAt: now.toISOString(), updatedAt: now.toISOString(), key: { pubKey: "mockPubKey123456", encPubKey: "mockEncPubKey123456", kty: "RSA", kid: "kid-123456", alg: "RS256", verifyKey: "mockVerifyKey123456" }, isEmailVerified: true })); }); } else { // Default handler for other paths res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: "ok", path: url })); } }); // Start server const PORT = 8080; server.listen(PORT, '0.0.0.0', () => { log(`Mock API server running at http://0.0.0.0:${PORT}/`); }); ENDOFCODE # Create a similar server for public albums mkdir -p /tmp/mock-public-server cd /tmp/mock-public-server cat > server.js << 'ENDOFCODE' const http = require('http'); const fs = require('fs'); const path = require('path'); // Ensure log directory exists const logDir = '/app/data/logs'; fs.mkdirSync(logDir, { recursive: true }); const logFile = path.join(logDir, 'public_api_requests.log'); // Open log file fs.writeFileSync(logFile, `Public Albums API Server started at ${new Date().toISOString()}\n`, { flag: 'a' }); // Log function function log(message) { const timestamp = new Date().toISOString(); const logMessage = `${timestamp} - ${message}\n`; console.log(message); fs.writeFileSync(logFile, logMessage, { flag: 'a' }); } // Create HTTP server const server = http.createServer((req, res) => { const url = req.url; const method = req.method; log(`Received ${method} request for ${url}`); // Set CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); // Handle preflight requests if (method === 'OPTIONS') { res.statusCode = 200; res.end(); return; } // Health check endpoint if (url === '/health') { res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: "ok", time: new Date().toISOString() })); } else { // Default handler for other paths res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: "ok", path: url })); } }); // Start server const PORT = 8081; server.listen(PORT, '0.0.0.0', () => { log(`Mock Public Albums API server running at http://0.0.0.0:${PORT}/`); }); ENDOFCODE # Set SERVER_PID to 0 for safety SERVER_PID=0 # Make sure logs directory exists mkdir -p "${LOGS_DIR}" touch "${LOGS_DIR}/api_requests.log" chmod 666 "${LOGS_DIR}/api_requests.log" # Run the mock server echo "==> Running mock API server with Node.js" cd /tmp/mock-server node server.js > "${LOGS_DIR}/mock_server.log" 2>&1 & SERVER_PID=$! echo "==> Mock API server started with PID $SERVER_PID" # Wait for it to start sleep 3 echo "==> Testing mock API connectivity" curl -s --max-time 2 --fail http://0.0.0.0:${API_PORT}/health || echo "==> Warning: Mock API server not responding!" # Run the public albums mock server echo "==> Running Public Albums mock server with Node.js" cd /tmp/mock-public-server node server.js > "${LOGS_DIR}/public_mock_server.log" 2>&1 & PUBLIC_SERVER_PID=$! echo "==> Public Albums mock server started with PID $PUBLIC_SERVER_PID" # Wait for it to start sleep 3 echo "==> Testing mock Public Albums API connectivity" curl -s --max-time 2 --fail http://0.0.0.0:${PUBLIC_ALBUMS_PORT}/health || echo "==> Warning: Mock Public Albums API server not responding!" fi # Set up Caddy web server for proxying and serving static files echo "==> Setting up Caddy web server" # Create runtime-config.js file echo "==> Creating runtime-config.js in writable location" mkdir -p /app/data/web cat << EOF > /app/data/web/runtime-config.js // Runtime configuration for Ente window.ENTE_CONFIG = { // Make sure these are properly formatted URLs with protocol and domain API_URL: 'https://${CLOUDRON_APP_FQDN}/api', PUBLIC_ALBUMS_URL: 'https://${CLOUDRON_APP_FQDN}/public' }; // Add Node.js polyfills for browser environment window.process = window.process || {}; window.process.env = window.process.env || {}; window.process.nextTick = window.process.nextTick || function(fn) { setTimeout(fn, 0); }; window.process.browser = true; window.Buffer = window.Buffer || (function() { return { isBuffer: function() { return false; } }; })(); // Next.js environment variables window.process.env.NEXT_PUBLIC_BASE_URL = 'https://${CLOUDRON_APP_FQDN}'; window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; window.process.env.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/public'; window.process.env.NEXT_PUBLIC_REACT_APP_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; window.process.env.REACT_APP_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; // Add logging to help with debugging console.log('Ente runtime config loaded from runtime-config.js with polyfills'); console.log('process.nextTick available:', !!window.process.nextTick); console.log('BASE_URL:', window.process.env.NEXT_PUBLIC_BASE_URL); console.log('API_URL (final):', window.ENTE_CONFIG.API_URL); console.log('PUBLIC_ALBUMS_URL (final):', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL); console.log('NEXT_PUBLIC_ENTE_ENDPOINT (final):', window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT); EOF chmod 644 /app/data/web/runtime-config.js # Create a custom URL patch file to fix the URL constructor error echo "==> Creating URL and SRP patch file" cat << EOF > /app/data/web/ente-patches.js (function() { // Save original URL constructor const originalURL = window.URL; // Create a patched URL constructor window.URL = function(url, base) { try { if (!url) { throw new Error('Invalid URL: URL cannot be empty'); } // Fix relative URLs if (!url.match(/^https?:\/\//i)) { if (url.startsWith('/')) { url = window.location.origin + url; } else { url = window.location.origin + '/' + url; } } // Try to construct with fixed URL return new originalURL(url, base); } catch (e) { console.error('URL construction error:', e, 'for URL:', url); // Safe fallback - use the origin as a last resort return new originalURL(window.location.origin); } }; // Patch the SRP implementation for browser compatibility if (window.process) { // Add any missing process methods window.process.nextTick = window.process.nextTick || function(fn) { setTimeout(fn, 0); }; } // Add Buffer polyfill if needed if (!window.Buffer) { window.Buffer = { from: function(data, encoding) { if (typeof data === 'string') { if (encoding === 'hex') { return { data: data, toString: function() { return data; } }; } return { data: data, toString: function() { return data; } }; } return { data: data }; }, isBuffer: function() { return false; }, alloc: function() { return {}; } }; } console.log('Ente URL and SRP patches applied successfully'); })(); EOF # Create the static HTML files with scripts pre-injected for app_dir in photos accounts auth cast; do # Create directory for our modified files mkdir -p /app/data/web/$app_dir # If the original index.html exists, copy and modify it if [ -f "/app/web/$app_dir/index.html" ]; then echo "==> Copying and modifying index.html for $app_dir app" cp "/app/web/$app_dir/index.html" "/app/data/web/$app_dir/index.html" # Fix any potential issues with the head tag if ! grep -q "
" "/app/data/web/$app_dir/index.html"; then echo "==> Warning: No head tag found in $app_dir/index.html, adding one" sed -i 's//\n<\/head>/' "/app/data/web/$app_dir/index.html" fi # Insert config scripts right after the opening head tag sed -i 's//\nLoading...
If this page doesn't redirect automatically, click here.
HTMLFILE fi done # Create Caddy configuration file mkdir -p /app/data/caddy cat << EOF > /app/data/caddy/Caddyfile # Global settings { admin off auto_https off http_port $CADDY_PORT https_port 0 } # Main site configuration :$CADDY_PORT { # Basic logging log { level INFO output file /app/data/logs/caddy.log } # Configuration scripts - directly served handle /config.js { header Content-Type application/javascript respond " // Direct configuration for Ente window.ENTE_CONFIG = { API_URL: 'https://${CLOUDRON_APP_FQDN}/api', PUBLIC_ALBUMS_URL: 'https://${CLOUDRON_APP_FQDN}/public' }; // Add Node.js polyfills for browser environment window.process = window.process || {}; window.process.env = window.process.env || {}; window.process.nextTick = window.process.nextTick || function(fn) { setTimeout(fn, 0); }; window.process.browser = true; window.Buffer = window.Buffer || (function() { return { isBuffer: function() { return false; } }; })(); // Next.js environment variables window.process.env.NEXT_PUBLIC_BASE_URL = 'https://${CLOUDRON_APP_FQDN}'; window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; window.process.env.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/public'; window.process.env.NEXT_PUBLIC_REACT_APP_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; window.process.env.REACT_APP_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; // Make sure URLs are explicitly defined with full domain console.log('Node.js polyfills loaded'); console.log('process.nextTick available:', !!window.process.nextTick); console.log('BASE_URL:', window.process.env.NEXT_PUBLIC_BASE_URL); 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); " } handle /runtime-config.js { root * /app/data/web file_server } # Add before the root path section handle /polyfills.js { header Content-Type application/javascript respond " // Node.js polyfills for browsers window.process = window.process || {}; window.process.env = window.process.env || {}; window.process.nextTick = window.process.nextTick || function(fn) { setTimeout(fn, 0); }; window.process.browser = true; // Buffer polyfill window.Buffer = window.Buffer || (function() { return { isBuffer: function() { return false; }, from: function(data) { return { data: data }; } }; })(); // URL polyfill helper window.ensureValidURL = function(url) { if (!url) return 'https://${CLOUDRON_APP_FQDN}'; if (url.startsWith('http://') || url.startsWith('https://')) return url; return 'https://${CLOUDRON_APP_FQDN}' + (url.startsWith('/') ? url : '/' + url); }; console.log('Polyfills loaded successfully'); " } # Root path serves the photos app handle / { # Special handling for index.html @is_index path / handle @is_index { root * /app/data/web/photos try_files {path} /index.html file_server } # Serve other static files from the original location @not_index { not path / not path /api/* not path /public/* not path /accounts/* not path /auth/* not path /cast/* } handle @not_index { root * /app/web/photos try_files {path} /index.html file_server } } # Next.js static files handle /_next/* { root * /app/web/photos file_server } # Common file types headers header /*.js Content-Type application/javascript header /*.css Content-Type text/css header /*.json Content-Type application/json header /*.svg Content-Type image/svg+xml header /*.woff2 Content-Type font/woff2 header /_next/static/chunks/*.js Content-Type application/javascript header /_next/static/css/*.css Content-Type text/css # Accounts app handle /accounts { root * /app/data/web/accounts try_files {path} /index.html file_server } handle /accounts/* { @is_index path /accounts/ /accounts/index.html handle @is_index { root * /app/data/web try_files /accounts/index.html file_server } @not_index { not path /accounts/ not path /accounts/index.html } handle @not_index { uri strip_prefix /accounts root * /app/web/accounts try_files {path} /index.html file_server } } # Auth app handle /auth { root * /app/data/web/auth try_files {path} /index.html file_server } handle /auth/* { @is_index path /auth/ /auth/index.html handle @is_index { root * /app/data/web try_files /auth/index.html file_server } @not_index { not path /auth/ not path /auth/index.html } handle @not_index { uri strip_prefix /auth root * /app/web/auth try_files {path} /index.html file_server } } # Cast app handle /cast { root * /app/data/web/cast try_files {path} /index.html file_server } handle /cast/* { @is_index path /cast/ /cast/index.html handle @is_index { root * /app/data/web try_files /cast/index.html file_server } @not_index { not path /cast/ not path /cast/index.html } handle @not_index { uri strip_prefix /cast root * /app/web/cast try_files {path} /index.html file_server } } # Main API proxy handle /api/* { uri strip_prefix /api reverse_proxy 0.0.0.0:$API_PORT } # Public albums API proxy handle /public/* { uri strip_prefix /public reverse_proxy 0.0.0.0:$PUBLIC_ALBUMS_PORT } # Health check endpoints handle /health { respond "OK" } handle /healthcheck { respond "OK" } handle /api/health { uri strip_prefix /api reverse_proxy 0.0.0.0:$API_PORT } handle /public/health { uri strip_prefix /public reverse_proxy 0.0.0.0:$PUBLIC_ALBUMS_PORT } # Serve Ente client patches handle /ente-patches.js { header Content-Type application/javascript root * /app/data/web file_server } } EOF echo "==> Created Caddy config with properly modified HTML files at /app/data/caddy/Caddyfile" # 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 for Caddy to start sleep 2 # Test Caddy connectivity echo "==> Testing Caddy connectivity" if curl -s --max-time 2 --fail http://0.0.0.0:$CADDY_PORT/health > /dev/null; then echo "==> Caddy is responding on port $CADDY_PORT" else echo "==> WARNING: Caddy is not responding on port $CADDY_PORT" fi # Print summary and URLs echo "==> Application is now running" echo "==> Access your Ente instance at: ${CLOUDRON_APP_ORIGIN}" # Additional checks to verify connectivity between services echo "==> Checking communication between frontend and backend services" echo "==> Testing main API communication" curl -s --max-time 2 -f http://0.0.0.0:$CADDY_PORT/api/health || echo "==> Warning: Main API endpoint is not responding!" echo "==> Main API communication via frontend is working" echo "==> Testing public albums API communication" curl -s --max-time 2 -f http://0.0.0.0:$CADDY_PORT/public/health || echo "==> Warning: Public Albums API endpoint is not responding!" echo "==> Public Albums API communication via frontend is working" echo "==> Testing frontend config.js" curl -s --max-time 2 -f http://0.0.0.0:$CADDY_PORT/config.js > /dev/null echo "==> Frontend configuration is properly loaded" # Go into wait state echo "==> Entering wait state - watching logs for registration codes" echo "==> Registration verification codes will appear in the logs below" echo "==> Press Ctrl+C to stop" tail -f /app/data/logs/api_requests.log & TAIL_PID=$! # Wait for all processes - safe waiting with proper checks if [ -n "${SERVER_PID:-}" ] && [ "${SERVER_PID:-0}" -ne 0 ]; then wait $SERVER_PID || true fi if [ -n "${PUBLIC_SERVER_PID:-}" ] && [ "${PUBLIC_SERVER_PID:-0}" -ne 0 ]; then wait $PUBLIC_SERVER_PID || true fi if [ -n "${CADDY_PID:-}" ] && [ "${CADDY_PID:-0}" -ne 0 ]; then wait $CADDY_PID || true fi