From 65e88f44084cb6d0a5440ee8d3da4816ee5b10bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20D=C3=BCren?= Date: Sun, 16 Mar 2025 22:17:41 +0100 Subject: [PATCH] Update Cloudron app configuration and setup --- CloudronManifest.json | 10 +- Dockerfile | 29 ++- README.md | 36 ++++ start.sh | 399 ++++++++++++++++++++++++++++++++---------- 4 files changed, 374 insertions(+), 100 deletions(-) diff --git a/CloudronManifest.json b/CloudronManifest.json index 6357658..f8be479 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -8,8 +8,8 @@ "tagline": "Open Source End-to-End Encrypted Photos & Authentication", "upstreamVersion": "1.0.0", "version": "1.0.0", - "healthCheckPath": "/", - "httpPort": 8080, + "healthCheckPath": "/healthcheck", + "httpPort": 3080, "memoryLimit": 1073741824, "addons": { "localstorage": {}, @@ -18,6 +18,12 @@ "supportsDisplayName": true } }, + "checklist": { + "create-permanent-admin": { + "message": "Required: S3 Storage Configuration!" + } + }, + "icon": "file://logo.png", "tags": [ "photos", diff --git a/Dockerfile b/Dockerfile index a7ff0d8..0a3cd38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,10 @@ RUN apt-get update && apt-get install -y git && \ RUN corepack enable # Set environment variables for web app build -# Will be overridden at runtime with the actual server endpoint -ENV NEXT_PUBLIC_ENTE_ENDPOINT="https://localhost:8080" +# Use "/api" as the endpoint which will be replaced at runtime with the full URL +ENV NEXT_PUBLIC_ENTE_ENDPOINT="/api" +# Add a note for clarity +RUN echo "Building with NEXT_PUBLIC_ENTE_ENDPOINT=/api, will be replaced at runtime with full URL" # Debugging the repository structure RUN find . -type d -maxdepth 3 | sort @@ -78,8 +80,14 @@ FROM cloudron/base:5.0.0@sha256:04fd70dbd8ad6149c19de39e35718e024417c3e01dc9c663 # Install necessary packages (excluding golang as we'll install it manually) RUN apt-get update && \ - apt-get install -y curl git nodejs npm libsodium23 libsodium-dev pkg-config nginx && \ + apt-get install -y curl git nodejs npm libsodium23 libsodium-dev pkg-config postgresql-client && \ npm install -g yarn serve && \ + # Install Caddy instead of NGINX + 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 && \ apt-get clean && apt-get autoremove && \ rm -rf /var/cache/apt /var/lib/apt/lists @@ -103,6 +111,17 @@ RUN git clone --depth=1 https://github.com/ente-io/ente.git . && \ cp -r server/go.mod server/go.sum /app/data/go/ && \ chmod 777 /app/data/go/go.mod /app/data/go/go.sum +# Pre-download Go dependencies +RUN cd server && \ + export GOMODCACHE="/app/data/go/pkg/mod" && \ + export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod" && \ + export GOTOOLCHAIN=local && \ + export GO111MODULE=on && \ + export GOSUMDB=off && \ + mkdir -p /app/data/go/pkg/mod && \ + chmod -R 777 /app/data/go && \ + go mod download + # Set Go environment variables ENV GOTOOLCHAIN=local ENV GO111MODULE=on @@ -127,8 +146,8 @@ RUN chmod +x /app/pkg/start.sh # Create NGINX directories RUN mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled -# Expose the web port (Cloudron expects port 8080) -EXPOSE 8080 +# Expose the web port (Cloudron expects port 3080 now) +EXPOSE 3080 # Start the application CMD ["/app/pkg/start.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 834eb4b..19354f4 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,28 @@ This repository contains the Cloudron packaging for [Ente](https://ente.io), an - Configured to use Cloudron's mail service for sending emails - Easy to deploy and manage through the Cloudron interface +## Requirements + +### Browser Compatibility + +Ente uses modern web technologies for its end-to-end encryption: + +- **WebAssembly**: Required for cryptographic operations +- **IndexedDB**: Required for client-side data storage + +Most modern browsers support these features, but they may be blocked by: +- Browser privacy settings +- Content Security Policies +- Certain browser extensions + +This package includes custom Caddy configuration with appropriate security headers to ensure these features work correctly. + +### S3-Compatible Storage + +Ente requires an S3-compatible object storage service. You can use: +- Cloudron's built-in object storage +- External services like AWS S3, Wasabi, or MinIO + ## Building and Installing ### Option 1: Build and Install Manually @@ -53,6 +75,14 @@ The app is configured automatically using Cloudron's environment variables for: - SMTP mail service - App origin URL +### Additional Configuration + +The package includes several enhancements to ensure proper functionality: + +1. **Security Headers**: Custom Content-Security-Policy headers that allow WebAssembly and IndexedDB +2. **API Configuration**: Dynamic runtime configuration to ensure the frontend connects to the correct API endpoint +3. **CORS Headers**: Proper CORS configuration for API access + ## Usage ### Web Client @@ -77,6 +107,12 @@ cloudron update --app ente.yourdomain.com ## Troubleshooting +### Common Issues + +1. **"Failed to fetch" errors**: Check if your browser is blocking API requests to your domain +2. **WebAssembly errors**: Ensure your browser supports and allows WebAssembly (try using Chrome or Firefox) +3. **IndexedDB errors**: Make sure your browser allows IndexedDB (not in private/incognito mode) + For issues specific to the Cloudron packaging, please open an issue in this repository. For issues with Ente itself, please refer to the [main Ente repository](https://github.com/ente-io/ente). diff --git a/start.sh b/start.sh index e3c46c7..78d511b 100644 --- a/start.sh +++ b/start.sh @@ -1,14 +1,14 @@ #!/bin/bash # Better signal handling - forward signals to child processes -trap 'kill -TERM $SERVER_PID; kill -TERM $NGINX_PID; exit' TERM INT +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/nginx/tmp /app/data/go /app/data/logs /run/nginx +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" @@ -164,82 +164,135 @@ 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 nginx &> /dev/null; then - echo "==> Installing NGINX" - apt-get update && apt-get install -y nginx +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 NGINX to serve the web apps and proxy to the Museum server -echo "==> Setting up NGINX for web apps and API" +# Set up Caddy to serve the web apps and proxy to the Museum server +echo "==> Setting up Caddy for web apps and API" -# Create a custom NGINX configuration in the writable data directory -cat > /app/data/nginx/ente.conf < /app/data/caddy/Caddyfile <; rel=preload; as=script" } - - # Auth app - location /auth/ { - alias /app/web/auth/; - try_files \$uri \$uri/ /auth/index.html; - } - - # Cast app - location /cast/ { - alias /app/web/cast/; - try_files \$uri \$uri/ /cast/index.html; - } - - # API endpoints - proxy to Museum server - location /api/ { - proxy_pass http://localhost:8000; - proxy_http_version 1.1; - proxy_set_header Upgrade \$http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host \$host; - proxy_cache_bypass \$http_upgrade; - access_log /dev/stdout; - error_log /dev/stderr debug; + header Content-Type "text/html; charset=utf-8" + replace_response_headers Content-Length "" + rewrite_early * { + # This injects the script into the head section of any index.html + r } } + + # Configuration available via JavaScript + handle /config.js { + header Content-Type "application/javascript" + respond "window.ENTE_CONFIG = { API_URL: '${CLOUDRON_APP_ORIGIN}/api' }; console.log('Ente config loaded with API_URL:', window.ENTE_CONFIG.API_URL);" + } + + # Photos app - root path + handle /photos* { + uri strip_prefix /photos + root * /app/web/photos + try_files {path} /index.html + file_server + } + + # Accounts app + handle /accounts/* { + uri strip_prefix /accounts + root * /app/web/accounts + try_files {path} /index.html + file_server + } + + # Auth app + handle /auth/* { + uri strip_prefix /auth + root * /app/web/auth + try_files {path} /index.html + file_server + } + + # Cast app + handle /cast/* { + uri strip_prefix /cast + root * /app/web/cast + try_files {path} /index.html + file_server + } + + # API endpoints - proxy to Museum server + handle /api/* { + reverse_proxy localhost:8080 + } + + # Health check endpoint for Cloudron + handle /healthcheck { + respond "OK" 200 + } + + # Default to photos app + handle { + root * /app/web/photos + try_files {path} /index.html + file_server + } } EOT - -echo "==> Custom NGINX configuration created at /app/data/nginx/ente.conf" +echo "==> Caddy configuration created at /app/data/caddy/Caddyfile" # Looking for Museum (Go server component) echo "==> Looking for Museum (Go server component)" @@ -363,21 +416,60 @@ if [ ! -f "/app/data/go/go.mod" ] && [ -f "$SERVER_DIR/go.mod" ]; then chmod 666 /app/data/go/go.mod /app/data/go/go.sum || echo "==> Warning: Could not set permissions" fi +# Create proper directories with correct permissions +mkdir -p /app/data/go/cache /app/data/go/pkg/mod /app/data/go/bin +chmod -R 777 /app/data/go +chown -R cloudron:cloudron /app/data/go + # Override version requirements and force module mode export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod -modcacherw" # Create a temporary directory for Go module cache and build in writable area export GOCACHE=/app/data/go/cache -export GOMODCACHE=/app/data/go/modcache +export GOMODCACHE=/app/data/go/pkg/mod mkdir -p $GOCACHE $GOMODCACHE echo "==> Set up Go environment in writable directories" # Set up more verbose logging for API debugging export ENTE_LOG_LEVEL=debug -# Test if API endpoint is reachable -echo "==> Testing API connectivity" -curl -v http://localhost:8000/api/health || echo "API not yet available, this is normal during startup" +# 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 3080" +echo "==> Testing if port 3080 is already in use" +if netstat -tuln | grep -q ":3080 "; then + echo "==> WARNING: Port 3080 is already in use, Caddy may fail to start" + netstat -tuln | grep ":3080 " +else + echo "==> Port 3080 is available" +fi + +# Only set permissions on the Caddy directory, not the web directory which is read-only +mkdir -p /app/data/caddy/logs +chmod -R 755 /app/data/caddy +chown -R cloudron:cloudron /app/data/caddy + +# Start Caddy +echo "==> Starting Caddy" +caddy run --config /app/data/caddy/Caddyfile --adapter caddyfile & +CADDY_PID=$! +echo "==> Caddy started with PID $CADDY_PID" + +# Check if Caddy started successfully +sleep 2 +if ! ps -p $CADDY_PID > /dev/null; then + echo "==> ERROR: Caddy failed to start" + echo "==> Any logs available:" + if [ -f "/app/data/caddy/caddy.log" ]; then + cat /app/data/caddy/caddy.log + else + echo "No Caddy logs available yet" + fi +else + echo "==> Caddy is running properly on port 3080" + echo "==> Testing Caddy connectivity" + curl -v http://localhost:3080/healthcheck || echo "==> Failed to connect to Caddy" +fi # Determine available memory and set limits accordingly if [[ -f /sys/fs/cgroup/cgroup.controllers ]]; then # cgroup v2 @@ -395,9 +487,66 @@ 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="require" +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}" @@ -408,21 +557,61 @@ export REMOTE_STORAGE_PREFIX="${S3_PREFIX:-ente/}" # Change ownership to cloudron user chown -R cloudron:cloudron /app/data -chown -R cloudron:cloudron /run/nginx -# Start Museum server on port 8000 (different from the NGINX port 8080) +# 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" -# For web frontend, these aren't changing -export NEXT_PUBLIC_ENTE_ENDPOINT="${CLOUDRON_APP_ORIGIN}" +# 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 8000 \ + /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}" \ @@ -433,7 +622,7 @@ if find "$SERVER_DIR" -name "museum" -type f -executable | grep -q .; then --storage.s3.areLocalBuckets=false \ --storage.type="s3" \ --config.path="/app/data/config/museum.yaml" \ - --database.sslmode="require" > /app/data/logs/museum-server.log 2>&1 & + --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 @@ -451,7 +640,8 @@ elif [ -d "$SERVER_DIR/cmd/museum" ]; then if [[ "${S3_ENDPOINT}" == *"wasabi"* ]]; then echo "==> Detected Wasabi S3 endpoint, adjusting settings" echo "==> Adding -mod=mod to go run to ignore version mismatch" - /usr/local/bin/gosu cloudron:cloudron go run -mod=mod cmd/museum/main.go --port 8000 \ + 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}" \ @@ -462,10 +652,11 @@ elif [ -d "$SERVER_DIR/cmd/museum" ]; then --storage.s3.areLocalBuckets=false \ --storage.type="s3" \ --config.path="/app/data/config/museum.yaml" \ - --database.sslmode="require" \ + --database.sslmode="disable" \ --log.level=debug > /app/data/logs/museum-server.log 2>&1 & else - /usr/local/bin/gosu cloudron:cloudron go run -mod=mod cmd/museum/main.go --port 8000 \ + 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}" \ @@ -476,7 +667,7 @@ elif [ -d "$SERVER_DIR/cmd/museum" ]; then --storage.s3.areLocalBuckets=false \ --storage.type="s3" \ --config.path="/app/data/config/museum.yaml" \ - --database.sslmode="require" \ + --database.sslmode="disable" \ --log.level=debug > /app/data/logs/museum-server.log 2>&1 & fi SERVER_PID=$! @@ -491,7 +682,7 @@ elif [ -d "$SERVER_DIR/cmd/museum" ]; then 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:8000/api/health > /dev/null; then + if curl -s http://localhost:8080/api/health > /dev/null; then echo "==> API is now responding" break else @@ -527,7 +718,8 @@ else export GOTOOLCHAIN=local echo "==> Running main.go with Go" - /usr/local/bin/gosu cloudron:cloudron go run -mod=mod main.go --port 8000 \ + 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}" \ @@ -538,6 +730,7 @@ else --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" @@ -592,10 +785,10 @@ func main() { }) // Start the server - log.Println("Starting mock server on port 8000") + log.Println("Starting mock server on port 8080") // Start in a goroutine to prevent blocking - server := &http.Server{Addr: ":8000"} + server := &http.Server{Addr: ":8080"} go func() { if err := server.ListenAndServe(); err != nil { log.Fatalf("Server error: %v", err) @@ -612,15 +805,35 @@ EOT echo "==> Mock server started with PID $SERVER_PID to show error message" fi -# Serve the static web apps in the foreground using our custom nginx config -echo "==> Running NGINX in the foreground with custom configuration" -nginx -c /app/data/nginx/ente.conf & -NGINX_PID=$! -echo "==> NGINX started with PID $NGINX_PID" +echo "==> Testing health check endpoint directly" +curl -v http://localhost:3080/healthcheck echo "==> Ente is now running!" echo "==> Museum server: PID $SERVER_PID" -echo "==> NGINX: PID $NGINX_PID" +echo "==> Caddy: PID $CADDY_PID" -# Wait for the processes to finish (or be terminated) -wait \ No newline at end of file +# Function to inject the config.js script tag into index.html files +inject_config_script() { + echo "==> Attempting to inject config.js script tag into index.html files" + + # List of web app directories + 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" + + # Try to modify in place without creating a backup + sed -i 's|||' "$app_dir/index.html" || echo "==> Cannot modify $app_dir/index.html - read-only file system" + else + echo "==> Skipping $app_dir - index.html not found" + fi + done +} + +# Try to inject the config script but don't worry if it fails +inject_config_script || echo "==> NOTE: Could not inject configuration directly into HTML files" + +# Wait for child processes to exit +wait $SERVER_PID +wait $CADDY_PID \ No newline at end of file