#!/bin/bash # Better signal handling - forward signals to child processes trap 'kill -TERM $SERVER_PID; kill -TERM $NGINX_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" # Define the server directory SERVER_DIR="/app/code/server" if [ ! -d "$SERVER_DIR" ]; then if [ -d "/app/code/museum" ]; then SERVER_DIR="/app/code/museum" else # Look for main.go in likely places SERVER_DIR=$(dirname $(find /app/code -name "main.go" -path "*/server*" -o -path "*/museum*" | head -1)) if [ ! -d "$SERVER_DIR" ]; then echo "==> WARNING: Could not find server directory, using /app/code as fallback" SERVER_DIR="/app/code" fi fi fi echo "==> Using server directory: $SERVER_DIR" # 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 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 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 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 <' + '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 HTML page cat > /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 # Create a configuration script with properly formatted URL cat > /app/data/public/config.js < Setting up NGINX server" # Define ports NGINX_PORT=3080 API_PORT=8080 # Check if ports are available echo "==> Checking port availability" if lsof -i:$NGINX_PORT > /dev/null 2>&1; then echo "==> WARNING: Port $NGINX_PORT is already in use" else echo "==> Port $NGINX_PORT is available for NGINX" fi if lsof -i:$API_PORT > /dev/null 2>&1; then echo "==> WARNING: Port $API_PORT is already in use" else echo "==> Port $API_PORT is available for API server" fi # Create necessary NGINX temp directories mkdir -p /app/data/nginx/client_body_temp mkdir -p /app/data/nginx/proxy_temp mkdir -p /app/data/nginx/fastcgi_temp mkdir -p /app/data/nginx/uwsgi_temp mkdir -p /app/data/nginx/scgi_temp mkdir -p /app/data/logs/nginx # Create the NGINX config cat > /app/data/nginx/nginx.conf <' ''; 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 '' ''; 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 '' ''; 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 '' ''; sub_filter_once on; sub_filter_types text/html; } } } EOT echo "==> Created NGINX config at /app/data/nginx/nginx.conf" # Start NGINX nginx -c /app/data/nginx/nginx.conf -p /app/data/nginx & NGINX_PID=$! echo "==> NGINX started with PID $NGINX_PID" # Wait for NGINX to start sleep 2 # Test NGINX connectivity echo "==> Testing NGINX connectivity" for i in {1..5}; do 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 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 NGINX 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) if [[ "$memory_limit" != "max" ]]; then MEMORY_MB=$((memory_limit / 1024 / 1024)) else MEMORY_MB=$(free -m | awk '/^Mem:/{print $2}') fi else # cgroup v1 if [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then memory_limit=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) MEMORY_MB=$((memory_limit / 1024 / 1024)) else MEMORY_MB=$(free -m | awk '/^Mem:/{print $2}') fi fi echo "==> Available memory: ${MEMORY_MB}MB" # Test database connectivity echo "==> Checking database connectivity" 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 if [ $? -ne 0 ]; then echo "==> ERROR: Failed to connect to database" echo "Host: ${CLOUDRON_POSTGRESQL_HOST}" echo "Port: ${CLOUDRON_POSTGRESQL_PORT}" echo "User: ${CLOUDRON_POSTGRESQL_USERNAME}" echo "Database: ${CLOUDRON_POSTGRESQL_DATABASE}" exit 1 fi echo "==> Successfully connected to database" # Create proper Go module environment echo "==> Setting up Go module environment" if [ -f "$SERVER_DIR/go.mod" ]; then echo "==> Found go.mod in $SERVER_DIR" mkdir -p /app/data/go cp "$SERVER_DIR/go.mod" "/app/data/go/go.mod" if [ -f "$SERVER_DIR/go.sum" ]; then cp "$SERVER_DIR/go.sum" "/app/data/go/go.sum" fi echo "==> Copied go.mod to /app/data/go/go.mod" else echo "==> WARNING: No go.mod found in $SERVER_DIR" # Create a minimal go.mod file mkdir -p /app/data/go cat > /app/data/go/go.mod < Created minimal go.mod in /app/data/go/go.mod" fi # Ensure the right permissions chmod 644 /app/data/go/go.mod # Setup Go directories with proper permissions mkdir -p /app/data/go/pkg/mod /app/data/go/cache chmod -R 777 /app/data/go chown -R cloudron:cloudron /app/data/go # Set necessary environment variables - MOVED EARLIER IN THE SCRIPT export MUSEUM_CONFIG="/app/data/config/museum.yaml" export MUSEUM_DB_HOST="${CLOUDRON_POSTGRESQL_HOST}" export MUSEUM_DB_PORT="${CLOUDRON_POSTGRESQL_PORT}" export MUSEUM_DB_USER="${CLOUDRON_POSTGRESQL_USERNAME}" export MUSEUM_DB_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" export MUSEUM_DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE}" export ENTE_LOG_LEVEL=debug export GOMODCACHE="/app/data/go/pkg/mod" export GOCACHE="/app/data/go/cache" export GO111MODULE=on export GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod" # Standard PostgreSQL environment variables (critical for Go's database/sql driver) 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}" export PGSSLMODE="disable" # Try to modify hosts file to block localhost PostgreSQL connections (may not work in containers) if [ -w /etc/hosts ]; then echo "==> Adding entry to /etc/hosts to redirect localhost PostgreSQL" echo "127.0.0.1 postgres-unavailable # Added by Ente startup script" >> /etc/hosts echo "::1 postgres-unavailable # Added by Ente startup script" >> /etc/hosts else echo "==> Cannot modify /etc/hosts (read-only filesystem)" 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" # Create migrations log directory mkdir -p /app/data/logs/migrations echo "==> Forcing migration version to 25" # Execute as the cloudron user but use a proper script instead of env cd cat > /tmp/run_migration.sh < /app/data/logs/migrations/force.log 2>&1; then echo "==> Successfully forced migration version" else echo "==> WARNING: Could not force migration version" echo "==> Migration force log:" cat /app/data/logs/migrations/force.log || echo "==> No migration log was created" fi else echo "==> Skipping migration state check: cmd/museum not found" fi # Start the Museum server with proper environment variables echo "==> Starting Museum server" cd "$SERVER_DIR" # Check if there's a pre-built binary MUSEUM_BIN="" if [ -f "$SERVER_DIR/bin/museum" ] && [ -x "$SERVER_DIR/bin/museum" ]; then echo "==> Found Museum binary at $SERVER_DIR/bin/museum" MUSEUM_BIN="$SERVER_DIR/bin/museum" elif [ -f "/app/data/go/bin/museum" ] && [ -x "/app/data/go/bin/museum" ]; then echo "==> Found Museum binary at /app/data/go/bin/museum" MUSEUM_BIN="/app/data/go/bin/museum" fi # Start server if [ -n "$MUSEUM_BIN" ]; then echo "==> Starting Museum from binary: $MUSEUM_BIN" $MUSEUM_BIN serve > /app/data/logs/museum.log 2>&1 & SERVER_PID=$! elif [ -d "$SERVER_DIR/cmd/museum" ]; then echo "==> Starting Museum from source" # Create a startup script cat > /tmp/run_server.sh < /app/data/logs/museum.log 2>&1 & SERVER_PID=$! else echo "==> ERROR: Museum server not found" echo "==> Starting a mock server" # Create a temporary directory for a simple Go server mkdir -p /tmp/mock-server cat > /tmp/mock-server/main.go < /app/data/logs/museum.log 2>&1 & SERVER_PID=$! echo "==> Mock server started with PID $SERVER_PID" fi echo "==> Server started with PID $SERVER_PID" # Test if API is responding echo "==> Testing API connectivity" for i in {1..5}; do if curl -s --max-time 2 --fail http://localhost:$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 /app/data/logs/museum.log || echo "==> No museum.log available" else echo "==> Attempt $i: Waiting for API to start... (2 seconds)" sleep 2 fi fi done echo "==> Application is now running" echo "==> Access your Ente instance at: $CLOUDRON_APP_ORIGIN" 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 $NGINX_PID