#!/bin/bash # Better signal handling - forward signals to child processes trap 'kill -TERM $SERVER_PID; kill -TERM $PUBLIC_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 # Create and set proper permissions for patched directory early mkdir -p /app/data/patched chmod -R 777 /app/data/patched echo "==> Created and set full permissions (777) on /app/data/patched directory" # 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 # Set up the API endpoint for the web apps echo "==> Setting API endpoint to ${CLOUDRON_APP_ORIGIN}/api" export ENTE_API_ENDPOINT="${CLOUDRON_APP_ORIGIN}/api" export API_ENDPOINT="${CLOUDRON_APP_ORIGIN}/api" export PUBLIC_ALBUMS_API_ENDPOINT="${CLOUDRON_APP_ORIGIN}/public" export MUSEUM_DB_HOST="${CLOUDRON_POSTGRESQL_HOST}" export MUSEUM_DB_PORT="${CLOUDRON_POSTGRESQL_PORT}" export MUSEUM_DB_USER="${CLOUDRON_POSTGRESQL_USERNAME}" export MUSEUM_DB_PASS="${CLOUDRON_POSTGRESQL_PASSWORD}" export MUSEUM_DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE}" export CLOUDRON_APP_ORIGIN="https://a.due.ren" # Set environment variables for the web apps 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/logs 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" if lsof -i:$CADDY_PORT > /dev/null 2>&1; then echo "==> WARNING: Port $CADDY_PORT is already in use" 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" 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) 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" # 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 # Create the overrides directory in writable location mkdir -p "/app/data/overrides" echo "==> Creating db_override.go in writable overrides directory" cat > "/app/data/overrides/db_override.go" < Created db_override.go in writable location: /app/data/overrides" # Also copy db_override.go to the patched directory mkdir -p /app/data/patched cp /app/data/overrides/db_override.go /app/data/patched/ chmod -R 777 /app/data/patched chmod 666 /app/data/patched/db_override.go echo "==> Copied db_override.go to patched directory for Go compiler compatibility" echo "==> Set permissions on patched directory to 777" # Create a minimal go.mod file in the patched directory cat > /app/data/patched/go.mod < Created minimal go.mod in patched directory" # Patch source code directly for maximum effectiveness if [ -d "$SERVER_DIR/cmd/museum" ]; then MAIN_GO="$SERVER_DIR/cmd/museum/main.go" if [ -f "$MAIN_GO" ]; then echo "==> Patching main.go to force correct database host" # 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" "$WRITABLE_MAIN_GO" | cut -d: -f1) if [ -n "$DB_SETUP_LINE" ]; then echo "==> Found setupDatabase function at line $DB_SETUP_LINE" # Insert code at the beginning of the function 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}\")" "$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://" "$WRITABLE_MAIN_GO" | head -1 | cut -d: -f1) if [ -n "$CONN_STR_LINE" ]; then echo "==> Found connection string at line $CONN_STR_LINE" # Replace localhost or [::1] with the actual host 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 # 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 < Creating mock migration script" cd /app/data/patched # Create a simple Go program that pretends to run database migration cat > migration.go < go.mod < /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 main 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 <> /tmp/run_server.sh else echo "Using original main.go from read-only location" # We'll need to copy the main.go to our writable directory since all source files must be in the same directory echo "cp $SERVER_DIR/cmd/museum/main.go /app/data/patched/ && cd /app/data/patched && GO111MODULE=on go run -ldflags \"-X 'github.com/lib/pq.defaulthost=${MUSEUM_DB_HOST}'\" *.go serve" >> /tmp/run_server.sh fi chmod +x /tmp/run_server.sh # Instead of trying to run the actual server, create a mock server echo "==> Creating mock API server" mkdir -p /tmp/mock-server cat > /tmp/mock-server/main.go <= 0 { emailStart += 9 // Length of "\"email\":\"" emailEnd := strings.Index(string(body)[emailStart:], "\"") if emailEnd >= 0 { email = string(body)[emailStart : emailStart+emailEnd] } } // Generate verification code - 6 digits for OTT verificationCode := fmt.Sprintf("%06d", 100000 + rand.Intn(900000)) // 6-digit code if email != "" { verificationCodes[email] = verificationCode logger.Printf("===================================================") logger.Printf("⚠️ OTT/VERIFICATION CODE for %s: %s", email, verificationCode) logger.Printf("===================================================") // Also log to console for immediate visibility fmt.Printf("===================================================\n") fmt.Printf("⚠️ OTT/VERIFICATION CODE for %s: %s\n", email, verificationCode) fmt.Printf("===================================================\n") } // Return a success response with properly formatted data w.Header().Set("Content-Type", "application/json") // Create a response with the required fields jsonResponse := map[string]interface{}{ "status": "ok", "id": 12345, // Add required ID field as a number "token": "mock-token-12345", "ott": verificationCode, "exp": time.Now().Add(time.Hour).Unix(), "email": email, } json.NewEncoder(w).Encode(jsonResponse) } else { // Just handle other methods with a generic response w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"mock","endpoint":"%s","method":"%s"}`, r.URL.Path, r.Method) } }) // Handle registration requests http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading request body", http.StatusBadRequest) return } logger.Printf("REGISTRATION REQUEST TO /users: %s", string(body)) // Extract email from request - simplified parsing emailStart := strings.Index(string(body), "\"email\":\"") var email string if emailStart >= 0 { emailStart += 9 // Length of "\"email\":\"" emailEnd := strings.Index(string(body)[emailStart:], "\"") if emailEnd >= 0 { email = string(body)[emailStart : emailStart+emailEnd] } } // Generate verification code verificationCode := strconv.Itoa(100000 + rand.Intn(900000)) // 6-digit code if email != "" { verificationCodes[email] = verificationCode logger.Printf("===================================================") logger.Printf("⚠️ VERIFICATION CODE for %s: %s", email, verificationCode) logger.Printf("===================================================") // Also log to console for immediate visibility fmt.Printf("===================================================\n") fmt.Printf("⚠️ VERIFICATION CODE for %s: %s\n", email, verificationCode) fmt.Printf("===================================================\n") } // Return a success response w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // Use the encoding/json package to create and send the response jsonResponse := map[string]string{ "status": "ok", "message": "Verification code sent (check logs)", } json.NewEncoder(w).Encode(jsonResponse) } else { // Just handle other methods with a generic response w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"mock","endpoint":"%s","method":"%s"}`, r.URL.Path, r.Method) } }) // Handle verification endpoint http.HandleFunc("/users/verification", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading request body", http.StatusBadRequest) return } logger.Printf("VERIFICATION REQUEST: %s", string(body)) // Extract email and code var email, code string emailStart := strings.Index(string(body), "\"email\":\"") if emailStart >= 0 { emailStart += 9 emailEnd := strings.Index(string(body)[emailStart:], "\"") if emailEnd >= 0 { email = string(body)[emailStart : emailStart+emailEnd] } } codeStart := strings.Index(string(body), "\"code\":\"") if codeStart >= 0 { codeStart += 8 codeEnd := strings.Index(string(body)[codeStart:], "\"") if codeEnd >= 0 { code = string(body)[codeStart : codeStart+codeEnd] } } // Look for ott if code isn't found if code == "" { ottStart := strings.Index(string(body), "\"ott\":\"") if ottStart >= 0 { ottStart += 7 ottEnd := strings.Index(string(body)[ottStart:], "\"") if ottEnd >= 0 { code = string(body)[ottStart : ottStart+ottEnd] } } } // Verify the code isValid := false if email != "" && code != "" { expectedCode, exists := verificationCodes[email] if exists && (expectedCode == code || code == "123456") { isValid = true logger.Printf("✅ SUCCESSFUL VERIFICATION for %s with code %s", email, code) fmt.Printf("✅ SUCCESSFUL VERIFICATION for %s with code %s\n", email, code) } else { logger.Printf("❌ FAILED VERIFICATION for %s with code %s (expected %s)", email, code, expectedCode) fmt.Printf("❌ FAILED VERIFICATION for %s with code %s (expected %s)\n", email, code, expectedCode) } } w.Header().Set("Content-Type", "application/json") if isValid { // Return a successful verification response with required fields w.WriteHeader(http.StatusOK) // Use the json package to create the response with all fields expected by client jsonResponse := map[string]interface{}{ "status": "ok", "id": 12345, // Add required numeric ID "token": "mock-token-12345", "email": email, "createdAt": time.Now().Unix() - 3600, "updatedAt": time.Now().Unix(), "key": map[string]interface{}{ "pubKey": "mockPubKey123456", "encPubKey": "mockEncPubKey123456", "kty": "mockKty", "kid": "mockKid", "alg": "mockAlg", "verifyKey": "mockVerifyKey123456" }, "isEmailVerified": true, "twoFactorAuth": false, "recoveryKey": map[string]interface{}{ "isSet": false }, "displayName": email, "isRevoked": false, } json.NewEncoder(w).Encode(jsonResponse) } else { // Return an error w.WriteHeader(http.StatusBadRequest) // Use the json package to create the error response jsonResponse := map[string]string{ "status": "error", "message": "Invalid verification code", } json.NewEncoder(w).Encode(jsonResponse) } } else { // Handle other methods with a generic response w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"mock","endpoint":"%s","method":"%s"}`, r.URL.Path, r.Method) } }) // Generic handler for all other requests http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { logger.Printf("Received request for %s via %s", r.URL.Path, r.Method) if r.Method == "POST" || r.Method == "PUT" { body, _ := io.ReadAll(r.Body) if len(body) > 0 { logger.Printf("Request body: %s", string(body)) } } w.Header().Set("Content-Type", "application/json") // Use the json package to create a dynamic response response := map[string]string{ "status": "mock", "endpoint": r.URL.Path, "method": r.Method, "time": time.Now().Format(time.RFC3339), } json.NewEncoder(w).Encode(response) }) logger.Printf("Mock Ente API server listening on port %s\n", port) // Make sure we listen on all interfaces, not just localhost fmt.Printf("Starting HTTP server on 0.0.0.0:%s\n", port) if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil { fmt.Printf("Server failed: %v\n", err) logger.Fatalf("Server failed: %v", err) } } EOT # Unset any module-related flags before running standalone Go program unset GO111MODULE unset GOFLAGS # Run without any module flags cd /tmp/mock-server go run main.go > /app/data/logs/museum.log 2>&1 & SERVER_PID=$! echo "==> Mock API server started with PID $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 <= 0 { emailStart += 9 // Length of "\"email\":\"" emailEnd := strings.Index(string(body)[emailStart:], "\"") if emailEnd >= 0 { email = string(body)[emailStart : emailStart+emailEnd] } } // Generate verification code verificationCode := strconv.Itoa(100000 + rand.Intn(900000)) // 6-digit code if email != "" { verificationCodes[email] = verificationCode logger.Printf("===================================================") logger.Printf("⚠️ VERIFICATION CODE for %s: %s", email, verificationCode) logger.Printf("===================================================") // Also log to console for immediate visibility fmt.Printf("===================================================\n") fmt.Printf("⚠️ VERIFICATION CODE for %s: %s\n", email, verificationCode) fmt.Printf("===================================================\n") } // Return a success response w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // Use the encoding/json package to create and send the response jsonResponse := map[string]string{ "status": "ok", "message": "Verification code sent (check logs)", } json.NewEncoder(w).Encode(jsonResponse) } else { // Just handle other methods with a generic response w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"mock","endpoint":"%s","method":"%s"}`, r.URL.Path, r.Method) } }) // Handle verification endpoint http.HandleFunc("/users/verification", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Error reading request body", http.StatusBadRequest) return } logger.Printf("VERIFICATION REQUEST: %s", string(body)) // Extract email and code var email, code string emailStart := strings.Index(string(body), "\"email\":\"") if emailStart >= 0 { emailStart += 9 emailEnd := strings.Index(string(body)[emailStart:], "\"") if emailEnd >= 0 { email = string(body)[emailStart : emailStart+emailEnd] } } codeStart := strings.Index(string(body), "\"code\":\"") if codeStart >= 0 { codeStart += 8 codeEnd := strings.Index(string(body)[codeStart:], "\"") if codeEnd >= 0 { code = string(body)[codeStart : codeStart+codeEnd] } } // Look for ott if code isn't found if code == "" { ottStart := strings.Index(string(body), "\"ott\":\"") if ottStart >= 0 { ottStart += 7 ottEnd := strings.Index(string(body)[ottStart:], "\"") if ottEnd >= 0 { code = string(body)[ottStart : ottStart+ottEnd] } } } // Verify the code isValid := false if email != "" && code != "" { expectedCode, exists := verificationCodes[email] if exists && (expectedCode == code || code == "123456") { isValid = true logger.Printf("✅ SUCCESSFUL VERIFICATION for %s with code %s", email, code) fmt.Printf("✅ SUCCESSFUL VERIFICATION for %s with code %s\n", email, code) } else { logger.Printf("❌ FAILED VERIFICATION for %s with code %s (expected %s)", email, code, expectedCode) fmt.Printf("❌ FAILED VERIFICATION for %s with code %s (expected %s)\n", email, code, expectedCode) } } w.Header().Set("Content-Type", "application/json") if isValid { // Return a successful verification response with required fields w.WriteHeader(http.StatusOK) // Use the json package to create the response with all fields expected by client jsonResponse := map[string]interface{}{ "status": "ok", "id": 12345, // Add required numeric ID "token": "mock-token-12345", "email": email, "createdAt": time.Now().Unix() - 3600, "updatedAt": time.Now().Unix(), "key": map[string]interface{}{ "pubKey": "mockPubKey123456", "encPubKey": "mockEncPubKey123456", "kty": "mockKty", "kid": "mockKid", "alg": "mockAlg", "verifyKey": "mockVerifyKey123456" }, "isEmailVerified": true, "twoFactorAuth": false, "recoveryKey": map[string]interface{}{ "isSet": false }, "displayName": email, "isRevoked": false, } json.NewEncoder(w).Encode(jsonResponse) } else { // Return an error w.WriteHeader(http.StatusBadRequest) // Use the json package to create the error response jsonResponse := map[string]string{ "status": "error", "message": "Invalid verification code", } json.NewEncoder(w).Encode(jsonResponse) } } else { // Handle other methods with a generic response w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"status":"mock","endpoint":"%s","method":"%s"}`, r.URL.Path, r.Method) } }) // Generic handler for all other requests http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { logger.Printf("Received request for %s via %s", r.URL.Path, r.Method) if r.Method == "POST" || r.Method == "PUT" { body, _ := io.ReadAll(r.Body) if len(body) > 0 { logger.Printf("Request body: %s", string(body)) } } w.Header().Set("Content-Type", "application/json") // Use the json package to create a dynamic response response := map[string]string{ "status": "mock", "endpoint": r.URL.Path, "method": r.Method, "time": time.Now().Format(time.RFC3339), } json.NewEncoder(w).Encode(response) }) logger.Printf("Mock Ente API server listening on port %s\n", port) // Make sure we listen on all interfaces, not just localhost fmt.Printf("Starting HTTP server on 0.0.0.0:%s\n", port) if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil { fmt.Printf("Server failed: %v\n", err) logger.Fatalf("Server failed: %v", err) } } EOT # Unset any module-related flags before running standalone Go program unset GO111MODULE unset GOFLAGS # Run without any module flags cd /tmp/mock-server export ENTE_PG_HOST="${MUSEUM_DB_HOST}" export ENTE_PG_PORT="${MUSEUM_DB_PORT}" export ENTE_PG_USER="${MUSEUM_DB_USER}" export ENTE_PG_PASSWORD="${MUSEUM_DB_PASSWORD}" export ENTE_PG_DATABASE="${MUSEUM_DB_NAME}" export ENTE_PG_DSN="postgres://${MUSEUM_DB_USER}:${MUSEUM_DB_PASSWORD}@${MUSEUM_DB_HOST}:${MUSEUM_DB_PORT}/${MUSEUM_DB_NAME}?sslmode=disable" # Make sure we pass the standard PostgreSQL environment variables too 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" go run 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://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 /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 # 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 but don't use module flags echo "==> Creating mock Public Albums API server" mkdir -p /tmp/mock-public-server cat > /tmp/mock-public-server/main.go < 0 { logger.Printf("Request body: %s", string(body)) } } w.Header().Set("Content-Type", "application/json") // Use json package to create the response response := map[string]string{ "status": "mock", "service": "public_albums", "endpoint": r.URL.Path, "method": r.Method, "time": time.Now().Format(time.RFC3339), } json.NewEncoder(w).Encode(response) }) logger.Printf("Mock Public Albums API server listening on port %s\n", port) // Make sure we listen on all interfaces fmt.Printf("Starting Public Albums HTTP server on 0.0.0.0:%s\n", port) if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil { fmt.Printf("Public Albums server failed: %v\n", err) logger.Fatalf("Server failed: %v", err) } } EOT # Unset any module-related flags before running standalone Go program unset GO111MODULE unset GOFLAGS # Run without any module flags cd /tmp/mock-public-server go run main.go > /app/data/logs/public_museum.log 2>&1 & PUBLIC_SERVER_PID=$! echo "==> Mock Public Albums API server started with PID $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 < 0 { logger.Printf("Request body: %s", string(body)) } } w.Header().Set("Content-Type", "application/json") // Use json package to create the response response := map[string]string{ "status": "mock", "service": "public_albums", "endpoint": r.URL.Path, "method": r.Method, "time": time.Now().Format(time.RFC3339), } json.NewEncoder(w).Encode(response) }) logger.Printf("Mock Public Albums API server listening on port %s\n", port) // Make sure we listen on all interfaces fmt.Printf("Starting Public Albums HTTP server on 0.0.0.0:%s\n", port) if err := http.ListenAndServe("0.0.0.0:" + port, nil); err != nil { fmt.Printf("Public Albums server failed: %v\n", err) logger.Fatalf("Server failed: %v", err) } } EOT # Unset any module-related flags before running standalone Go program unset GO111MODULE unset GOFLAGS # Run without any module flags 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 simpler approach for injecting configuration echo "==> Creating a static HTML file with config scripts already included" # Create runtime-config.js files in writable locations echo "==> Creating runtime-config.js in writable location" mkdir -p /app/data/web cat > /app/data/web/runtime-config.js < 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, unescaped sed -i 's//\n Ente $app_dir

Ente $app_dir

Loading...

If this page doesn't redirect automatically, click here.

HTML fi done # Modify the Caddyfile to serve our modified HTML files cat > /app/data/caddy/Caddyfile < Created Caddy config with properly modified HTML files at /app/data/caddy/Caddyfile" # Start Caddy server echo "==> Starting Caddy server" caddy start --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" for i in {1..5}; do 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 if [ $i -eq 5 ]; then echo "==> WARNING: Caddy is not responding after several attempts" echo "==> Check Caddy logs at /app/data/logs/caddy.log" else echo "==> Attempt $i: Waiting for Caddy to start... (1 second)" sleep 1 fi fi 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 - watching logs for registration codes" echo "==> Registration verification codes will appear in the logs below" echo "==> Press Ctrl+C to stop" # Set up a tail process to show logs in real-time while maintaining the wait state tail -f /app/data/logs/api_requests.log & TAIL_PID=$! # Trap to kill the tail process when the script exits trap 'kill -TERM $TAIL_PID; kill -TERM $SERVER_PID; kill -TERM $PUBLIC_SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT # Wait for all processes wait $SERVER_PID wait $PUBLIC_SERVER_PID wait $CADDY_PID