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

Fixed filesystem access issues and network binding for dual-instance Ente setup

parent 42c13746
Loading
Loading
Loading
Loading
+322 −110
Original line number Diff line number Diff line
#!/bin/bash

# Better signal handling - forward signals to child processes
trap 'kill -TERM $SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT
trap 'kill -TERM $SERVER_PID; kill -TERM $PUBLIC_SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT

set -eu

@@ -231,6 +231,7 @@ mkdir -p /app/data/caddy
# Define ports
CADDY_PORT=3080
API_PORT=8080
PUBLIC_ALBUMS_PORT=8081  # New port for public albums service

# Check if ports are available
echo "==> Checking port availability"
@@ -246,6 +247,12 @@ else
    echo "==> Port $API_PORT is available for API server"
fi

if lsof -i:$PUBLIC_ALBUMS_PORT > /dev/null 2>&1; then
    echo "==> WARNING: Port $PUBLIC_ALBUMS_PORT is already in use"
else
    echo "==> Port $PUBLIC_ALBUMS_PORT is available for Public Albums server"
fi

# 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)
@@ -343,11 +350,15 @@ if [ -d "$SERVER_DIR/cmd/museum" ]; then
    if [ -f "$MAIN_GO" ]; then
        echo "==> Patching main.go to force correct database host"
        
        # Create a backup of the original file
        cp "$MAIN_GO" "${MAIN_GO}.orig"
        # Create writable directory for patched files
        mkdir -p /app/data/patched
        
        # Copy the file to a writable location before patching
        cp "$MAIN_GO" "/app/data/patched/main.go"
        WRITABLE_MAIN_GO="/app/data/patched/main.go"
        
        # Look for setupDatabase function and patch it
        DB_SETUP_LINE=$(grep -n "func setupDatabase" "$MAIN_GO" | cut -d: -f1)
        DB_SETUP_LINE=$(grep -n "func setupDatabase" "$WRITABLE_MAIN_GO" | cut -d: -f1)
        
        if [ -n "$DB_SETUP_LINE" ]; then
            echo "==> Found setupDatabase function at line $DB_SETUP_LINE"
@@ -356,25 +367,24 @@ if [ -d "$SERVER_DIR/cmd/museum" ]; then
            sed -i "${DB_SETUP_LINE}a\\
\\tlog.Printf(\"Forcing database host to %s\", \"${CLOUDRON_POSTGRESQL_HOST}\")\\
\\tos.Setenv(\"PGHOST\", \"${CLOUDRON_POSTGRESQL_HOST}\")\\
\\tos.Setenv(\"PGHOSTADDR\", \"${CLOUDRON_POSTGRESQL_HOST}\")" "$MAIN_GO"
\\tos.Setenv(\"PGHOSTADDR\", \"${CLOUDRON_POSTGRESQL_HOST}\")" "$WRITABLE_MAIN_GO"
            
            echo "==> Patched setupDatabase function"
        fi
        
        # If there's a connection string being built, patch that too
        CONN_STR_LINE=$(grep -n "postgres://" "$MAIN_GO" | head -1 | cut -d: -f1)
        CONN_STR_LINE=$(grep -n "postgres://" "$WRITABLE_MAIN_GO" | head -1 | cut -d: -f1)
        if [ -n "$CONN_STR_LINE" ]; then
            echo "==> Found connection string at line $CONN_STR_LINE"
            
            # Backup again just to be safe
            cp "$MAIN_GO" "${MAIN_GO}.conn_patch"
            
            # Replace localhost or [::1] with the actual host
            sed -i "s/localhost/${CLOUDRON_POSTGRESQL_HOST}/g" "$MAIN_GO"
            sed -i "s/\[::1\]/${CLOUDRON_POSTGRESQL_HOST}/g" "$MAIN_GO"
            sed -i "s/localhost/${CLOUDRON_POSTGRESQL_HOST}/g" "$WRITABLE_MAIN_GO"
            sed -i "s/\[::1\]/${CLOUDRON_POSTGRESQL_HOST}/g" "$WRITABLE_MAIN_GO"
            
            echo "==> Patched connection string"
        fi
        
        echo "==> Will use patched version at runtime"
    fi
fi

@@ -423,103 +433,8 @@ else
    echo "==> Skipping migration state check: cmd/museum not found"
fi

# Set up Caddy web server
echo "==> Setting up Caddy web server"

# Create a Caddyfile for serving web apps and reverse proxy to API
cat > /app/data/caddy/Caddyfile <<EOT
# 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
    }

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

    # Accounts app
    handle /accounts/* {
        root * /app/web/accounts
        uri strip_prefix /accounts
        try_files {path} /index.html
        file_server
    }

    # Auth app
    handle /auth/* {
        root * /app/web/auth
        uri strip_prefix /auth
        try_files {path} /index.html
        file_server
    }

    # Cast app
    handle /cast/* {
        root * /app/web/cast
        uri strip_prefix /cast
        try_files {path} /index.html
        file_server
    }

    # API proxy
    handle /api/* {
        uri strip_prefix /api
        reverse_proxy localhost:$API_PORT
    }

    # Health check endpoints
    handle /health {
        respond "OK"
    }

    handle /healthcheck {
        respond "OK"
    }

    handle /api/health {
        uri strip_prefix /api
        reverse_proxy localhost:$API_PORT
    }

    # Configuration scripts
    handle /config.js {
        respond "
            // Direct configuration for Ente
            window.ENTE_CONFIG = {
                API_URL: '${API_ENDPOINT}'
            };
            
            // Next.js environment variables
            window.process = window.process || {};
            window.process.env = window.process.env || {};
            window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = '${API_ENDPOINT}';
            
            console.log('Ente config loaded - API_URL:', window.ENTE_CONFIG.API_URL);
        " 200 {
            Content-Type "application/javascript"
        }
    }
}
EOT

echo "==> Created Caddy config at /app/data/caddy/Caddyfile"

# Start the Museum server with proper environment variables
echo "==> Starting Museum server"
echo "==> Starting main Museum server"
cd "$SERVER_DIR"

# Check if there's a pre-built binary
@@ -560,8 +475,17 @@ CLOUDRON_POSTGRESQL_PORT="${CLOUDRON_POSTGRESQL_PORT}" \
CLOUDRON_POSTGRESQL_USERNAME="${CLOUDRON_POSTGRESQL_USERNAME}" \
CLOUDRON_POSTGRESQL_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" \
CLOUDRON_POSTGRESQL_DATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" \
go run -ldflags "-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'" overrides/db_override.go cmd/museum/main.go serve
EOF

    # Check if we have a patched main.go to use
    if [ -f "/app/data/patched/main.go" ]; then
        echo "Using patched main.go from writable location"
        echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go /app/data/patched/main.go serve" >> /tmp/run_server.sh
    else
        echo "Using original main.go from read-only location"
        echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go cmd/museum/main.go serve" >> /tmp/run_server.sh
    fi
    
    chmod +x /tmp/run_server.sh
    
    /usr/local/bin/gosu cloudron:cloudron env \
@@ -672,7 +596,7 @@ 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
    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
@@ -687,6 +611,264 @@ for i in {1..5}; do
    fi
done

# Start the Public Albums Museum server (second instance)
echo "==> Starting Public Albums Museum server"

# Create configuration for public albums instance
mkdir -p /app/data/config/public
cp /app/data/config/museum.yaml /app/data/config/public/museum.yaml

if [ -n "$MUSEUM_BIN" ]; then
    echo "==> Starting Public Albums Museum from binary: $MUSEUM_BIN"
    MUSEUM_CONFIG="/app/data/config/public/museum.yaml" $MUSEUM_BIN serve --port $PUBLIC_ALBUMS_PORT > /app/data/logs/public_museum.log 2>&1 &
    PUBLIC_SERVER_PID=$!
elif [ -d "$SERVER_DIR/cmd/museum" ]; then
    echo "==> Starting Public Albums Museum from source"
    # Create a startup script
    cat > /tmp/run_public_server.sh <<EOF
#!/bin/bash
cd "$SERVER_DIR" && \
PGHOST="${CLOUDRON_POSTGRESQL_HOST}" \
PGPORT="${CLOUDRON_POSTGRESQL_PORT}" \
PGUSER="${CLOUDRON_POSTGRESQL_USERNAME}" \
PGPASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" \
PGDATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" \
PGSSLMODE="disable" \
ENTE_PG_HOST="${MUSEUM_DB_HOST}" \
ENTE_PG_PORT="${MUSEUM_DB_PORT}" \
ENTE_PG_USER="${MUSEUM_DB_USER}" \
ENTE_PG_PASSWORD="${MUSEUM_DB_PASSWORD}" \
ENTE_PG_DATABASE="${MUSEUM_DB_NAME}" \
ENTE_PG_DSN="postgres://${MUSEUM_DB_USER}:${MUSEUM_DB_PASSWORD}@${MUSEUM_DB_HOST}:${MUSEUM_DB_PORT}/${MUSEUM_DB_NAME}?sslmode=disable&host=${MUSEUM_DB_HOST}" \
CLOUDRON_POSTGRESQL_HOST="${CLOUDRON_POSTGRESQL_HOST}" \
CLOUDRON_POSTGRESQL_PORT="${CLOUDRON_POSTGRESQL_PORT}" \
CLOUDRON_POSTGRESQL_USERNAME="${CLOUDRON_POSTGRESQL_USERNAME}" \
CLOUDRON_POSTGRESQL_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" \
CLOUDRON_POSTGRESQL_DATABASE="${CLOUDRON_POSTGRESQL_DATABASE}" \
EOF

    # Check if we have a patched main.go to use
    if [ -f "/app/data/patched/main.go" ]; then
        echo "Using patched main.go from writable location"
        echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go /app/data/patched/main.go serve --port $PUBLIC_ALBUMS_PORT" >> /tmp/run_public_server.sh
    else
        echo "Using original main.go from read-only location"
        echo "go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" overrides/db_override.go cmd/museum/main.go serve --port $PUBLIC_ALBUMS_PORT" >> /tmp/run_public_server.sh
    fi
    
    chmod +x /tmp/run_public_server.sh
    
    /usr/local/bin/gosu cloudron:cloudron env \
        GOCACHE="$GOCACHE" \
        GOMODCACHE="$GOMODCACHE" \
        GO111MODULE=on \
        GOFLAGS="$GOFLAGS" \
        MUSEUM_CONFIG="/app/data/config/public/museum.yaml" \
        MUSEUM_DB_HOST="$MUSEUM_DB_HOST" \
        MUSEUM_DB_PORT="$MUSEUM_DB_PORT" \
        MUSEUM_DB_USER="$MUSEUM_DB_USER" \
        MUSEUM_DB_PASSWORD="$MUSEUM_DB_PASSWORD" \
        MUSEUM_DB_NAME="$MUSEUM_DB_NAME" \
        ENTE_PG_DSN="postgres://${MUSEUM_DB_USER}:${MUSEUM_DB_PASSWORD}@${MUSEUM_DB_HOST}:${MUSEUM_DB_PORT}/${MUSEUM_DB_NAME}?sslmode=disable" \
        ENTE_PG_HOST="$MUSEUM_DB_HOST" \
        ENTE_PG_PORT="$MUSEUM_DB_PORT" \
        ENTE_PG_USER="$MUSEUM_DB_USER" \
        ENTE_PG_PASSWORD="$MUSEUM_DB_PASSWORD" \
        ENTE_PG_DATABASE="$MUSEUM_DB_NAME" \
        PGHOST="$PGHOST" \
        PGPORT="$PGPORT" \
        PGUSER="$PGUSER" \
        PGPASSWORD="$PGPASSWORD" \
        PGDATABASE="$PGDATABASE" \
        PGSSLMODE="$PGSSLMODE" \
        ENTE_LOG_LEVEL=debug \
        bash /tmp/run_public_server.sh > /app/data/logs/public_museum.log 2>&1 &
    PUBLIC_SERVER_PID=$!
else
    echo "==> ERROR: Museum server not found for public albums"
    echo "==> Starting a mock public albums server"
    
    # Create a temporary directory for a simple Go server
    mkdir -p /tmp/mock-public-server
    cat > /tmp/mock-public-server/main.go.template <<EOT
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
)

func main() {
    // Log environment variables
    log.Println("Starting mock Public Albums API server")
    log.Println("Running on port: PORT_NUMBER")
    
    // Add a health endpoint
    http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprint(w, `{"status":"ok","message":"Mock Public Albums server running","time":"` + time.Now().String() + `"}`)
    })
    
    // Handle all other requests
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Public Albums: Received request for %s via %s", r.URL.Path, r.Method)
        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().String())
    })
    
    // Start the server
    log.Printf("Starting mock Public Albums server on port PORT_NUMBER\n")
    
    if err := http.ListenAndServe(":PORT_NUMBER", nil); err != nil {
        log.Fatalf("Failed to start Public Albums server: %v", err)
    }
}
EOT

    # Replace the port placeholder with the actual port number
    sed "s/PORT_NUMBER/$PUBLIC_ALBUMS_PORT/g" /tmp/mock-public-server/main.go.template > /tmp/mock-public-server/main.go
    
    # Run the mock server with environment variables
    cd /tmp/mock-public-server
    go run main.go > /app/data/logs/public_museum.log 2>&1 &
    PUBLIC_SERVER_PID=$!
    
    echo "==> Mock Public Albums server started with PID $PUBLIC_SERVER_PID"
fi

echo "==> Public Albums server started with PID $PUBLIC_SERVER_PID"

# Test if Public Albums API is responding
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 /app/data/logs/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

# Set up Caddy web server
echo "==> Setting up Caddy web server"

# Create a Caddyfile for serving web apps and reverse proxy to API
cat > /app/data/caddy/Caddyfile <<EOT
# 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
    }

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

    # Accounts app
    handle /accounts/* {
        root * /app/web/accounts
        uri strip_prefix /accounts
        try_files {path} /index.html
        file_server
    }

    # Auth app
    handle /auth/* {
        root * /app/web/auth
        uri strip_prefix /auth
        try_files {path} /index.html
        file_server
    }

    # Cast app
    handle /cast/* {
        root * /app/web/cast
        uri strip_prefix /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
    }

    # Configuration scripts
    handle /config.js {
        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);
        " 200 {
            Content-Type "application/javascript"
        }
    }
}
EOT

echo "==> Created Caddy config at /app/data/caddy/Caddyfile"

# Start Caddy server
echo "==> Starting Caddy server"
caddy start --config /app/data/caddy/Caddyfile --adapter caddyfile &
@@ -699,7 +881,7 @@ sleep 2
# Test Caddy connectivity
echo "==> Testing Caddy connectivity"
for i in {1..5}; do
    if curl -s --max-time 2 --fail http://localhost:$CADDY_PORT/health > /dev/null; then
    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"
        break
    else
@@ -716,9 +898,39 @@ done
echo "==> Application is now running"
echo "==> Access your Ente instance at: $CLOUDRON_APP_ORIGIN"

# Check communication between frontend and backend services
echo "==> Checking communication between frontend and backend services"
echo "==> Testing main API communication"
MAIN_API_TEST=$(curl -s --max-time 2 http://0.0.0.0:$CADDY_PORT/api/health)
if [ $? -eq 0 ]; then
    echo "==> Main API communication via frontend is working"
else
    echo "==> WARNING: Main API communication via frontend is NOT working"
    echo "==> Response: $MAIN_API_TEST"
fi

echo "==> Testing public albums API communication"
PUBLIC_API_TEST=$(curl -s --max-time 2 http://0.0.0.0:$CADDY_PORT/public/health)
if [ $? -eq 0 ]; then
    echo "==> Public Albums API communication via frontend is working"
else
    echo "==> WARNING: Public Albums API communication via frontend is NOT working"
    echo "==> Response: $PUBLIC_API_TEST"
fi

echo "==> Testing frontend config.js"
CONFIG_JS_TEST=$(curl -s --max-time 2 http://0.0.0.0:$CADDY_PORT/config.js)
if [[ "$CONFIG_JS_TEST" == *"API_URL"* && "$CONFIG_JS_TEST" == *"PUBLIC_ALBUMS_URL"* ]]; then
    echo "==> Frontend configuration is properly loaded"
else
    echo "==> WARNING: Frontend configuration is not properly loaded"
    echo "==> Response: $CONFIG_JS_TEST"
fi

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 $PUBLIC_SERVER_PID
wait $CADDY_PID

# Create a new go file to inject into the build that overrides the database connection