From d69ab2296786e826ffc1ea5e5db65c2cfa7b1359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20D=C3=BCren?= Date: Thu, 20 Mar 2025 15:41:24 +0100 Subject: [PATCH] Fix GitHub download URLs and implement placeholder server on port 3080 --- start.sh | 1408 ++++++++++++++++++------------------------------------ 1 file changed, 469 insertions(+), 939 deletions(-) diff --git a/start.sh b/start.sh index a158a55..2fe3618 100644 --- a/start.sh +++ b/start.sh @@ -1,32 +1,35 @@ #!/bin/bash set -e -# Declare that we are using a Cloudron environment +# Use debug output +set -x + +# Declare that we're running in a Cloudron environment echo "==> Starting Ente Cloudron app..." echo "==> NOTE: Running in Cloudron environment with limited write access" echo "==> Writable directories: /app/data, /tmp, /run" echo "==> Current directory: $(pwd)" -echo "==> Environment: CLOUDRON_APP_DOMAIN=${CLOUDRON_APP_DOMAIN}" +echo "==> Environment: CLOUDRON_APP_DOMAIN=${CLOUDRON_APP_DOMAIN:-localhost}" echo "==> Environment: CLOUDRON_APP_FQDN=${CLOUDRON_APP_FQDN}" -echo "==> Environment: Internal IP=$(hostname -i || echo 'unknown')" +echo "==> Environment: Internal IP=$(hostname -I)" -# Install required utilities +# Ensure required utilities are installed echo "==> Ensuring required utilities are installed" -apt-get update && apt-get install -y file unzip wget curl +apt-get update || echo "Warning: apt-get update failed, continuing with existing packages" +apt-get install -y file unzip wget curl || echo "Warning: apt-get install failed, continuing with existing utilities" echo "==> Utilities installed" -# Create necessary data directories -mkdir -p /app/data/logs -mkdir -p /app/data/ente/web +# Create necessary directories mkdir -p /app/data/ente/server -mkdir -p /app/data/web/photos/static -mkdir -p /app/data/web/photos/_next/static/runtime -mkdir -p /app/data/web/accounts/static -mkdir -p /app/data/web/auth/static -mkdir -p /app/data/web/cast/static +mkdir -p /app/data/ente/web +mkdir -p /app/data/logs +mkdir -p /app/data/web/photos +mkdir -p /app/data/web/accounts +mkdir -p /app/data/web/auth +mkdir -p /app/data/web/cast echo "==> Created all necessary directories" -# Debugging information +# Directory listings for debugging echo "==> Directory listing of /app/data:" ls -la /app/data echo "==> Directory listing of /app/data/web:" @@ -34,993 +37,520 @@ ls -la /app/data/web echo "==> Directory listing of /app/data/ente:" ls -la /app/data/ente -# Use the specified server directory or default to the data dir +# Define server directory SERVER_DIR="/app/data/ente/server" -echo "==> Using server directory: $SERVER_DIR" +echo "==> Using server directory: ${SERVER_DIR}" -# Download Ente server if not already present -if [ ! -d "$SERVER_DIR/museum" ] || [ ! -f "$SERVER_DIR/museum/museum" ]; then - echo "==> Downloading Ente Museum server..." - mkdir -p "$SERVER_DIR" - cd "$SERVER_DIR" +# Download Museum server +echo "==> Downloading Ente Museum server..." +cd ${SERVER_DIR} + +# Check if museum binary already exists and is executable +if [ -f "${SERVER_DIR}/museum" ] && [ -x "${SERVER_DIR}/museum" ]; then + echo "==> Museum server binary already exists" +else + echo "==> Downloading from GitHub releases..." - # Clone the repository if it doesn't exist - if [ ! -d "$SERVER_DIR/museum" ]; then - # Use HTTPS instead of Git protocol to avoid authentication issues - echo "==> Downloading from GitHub archive..." - curl -L -o museum.zip https://github.com/ente-io/museum/archive/refs/heads/main.zip || curl -L -o museum.zip https://api.github.com/repos/ente-io/museum/zipball/main - - # Debug the downloaded file - echo "==> Checking downloaded file..." - file museum.zip - ls -la museum.zip - - # Try alternate download method if first one fails - if [ ! -s museum.zip ] || ! unzip -q museum.zip; then - echo "==> Direct download failed, trying with wget..." - apt-get update && apt-get install -y wget - wget -O museum.zip https://github.com/ente-io/museum/archive/main.zip - - if [ ! -s museum.zip ] || ! unzip -q museum.zip; then - echo "==> All download methods failed, creating directories manually" - mkdir -p museum/config - cd museum - else - # Handle the extracted directory name which might be museum-main or something like ente-io-museum- - extracted_dir=$(find . -type d -name "museum-*" -o -name "ente-io-museum-*" | head -n 1) - if [ -n "$extracted_dir" ]; then - mv "$extracted_dir"/* museum/ - rm -rf "$extracted_dir" - cd museum - else - mkdir -p museum/config - cd museum - fi - fi - else - # Handle the extracted directory name which might be museum-main or something like ente-io-museum- - extracted_dir=$(find . -type d -name "museum-*" -o -name "ente-io-museum-*" | head -n 1) - if [ -n "$extracted_dir" ]; then - mkdir -p museum - mv "$extracted_dir"/* museum/ - rm -rf "$extracted_dir" - cd museum - else - mkdir -p museum/config - cd museum - fi - fi - else - cd museum - # Use HTTPS instead of Git pull - echo "==> Updating existing repository..." - curl -L -o main.zip https://github.com/ente-io/museum/archive/refs/heads/main.zip || curl -L -o main.zip https://api.github.com/repos/ente-io/museum/zipball/main - - if [ -s main.zip ] && unzip -q main.zip; then - extracted_dir=$(find . -type d -name "museum-*" -o -name "ente-io-museum-*" | head -n 1) - if [ -n "$extracted_dir" ]; then - cp -R "$extracted_dir"/* ./ - rm -rf "$extracted_dir" main.zip - fi - else - echo "==> Failed to update repository, continuing with existing files" - fi - fi + # Try multiple release URLs + DOWNLOAD_URLS=( + "https://github.com/ente-io/museum/releases/latest/download/museum-linux-amd64" + "https://github.com/ente-io/museum/releases/download/v1.0.0/museum-linux-amd64" + "https://github.com/ente-io/museum/releases/download/latest/museum-linux-amd64" + ) - # Build the museum server - echo "==> Building Ente Museum server..." - # Check if Go is installed - if command -v go &> /dev/null; then - go build -o museum || echo "==> Go build failed, will try pre-built binary" - else - echo "==> Go not found, will try to download pre-built binary" - fi + DOWNLOAD_SUCCESS=false - if [ ! -f "$SERVER_DIR/museum/museum" ]; then - echo "==> ERROR: Failed to build museum server" - echo "==> Will attempt to download pre-built binary" - - # Try to download pre-built binary - ARCH=$(uname -m) - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - - if [ "$ARCH" = "x86_64" ]; then - ARCH="amd64" - elif [ "$ARCH" = "aarch64" ]; then - ARCH="arm64" - fi - - echo "==> Detected architecture: $OS-$ARCH" - - # Try different release URLs - for RELEASE_URL in \ - "https://github.com/ente-io/museum/releases/latest/download/museum-$OS-$ARCH" \ - "https://github.com/ente-io/museum/releases/download/latest/museum-$OS-$ARCH" \ - "https://github.com/ente-io/museum/releases/download/v1.0.0/museum-$OS-$ARCH" \ - "https://github.com/ente-io/museum/releases/download/v1.0/museum-$OS-$ARCH" - do - echo "==> Trying to download from: $RELEASE_URL" - if curl -L -o "$SERVER_DIR/museum/museum" "$RELEASE_URL" && [ -s "$SERVER_DIR/museum/museum" ]; then - chmod +x "$SERVER_DIR/museum/museum" - echo "==> Successfully downloaded museum binary from $RELEASE_URL" + for URL in "${DOWNLOAD_URLS[@]}"; do + echo "==> Trying download from: $URL" + if curl -L -o museum "$URL" 2>/app/data/logs/curl.log; then + # Check if downloaded file is a valid binary + if file museum | grep -q "ELF"; then + chmod +x museum + DOWNLOAD_SUCCESS=true + echo "==> Successfully downloaded museum binary from $URL" break else - echo "==> Download failed from $RELEASE_URL" + echo "==> Downloaded file is not a valid binary: $(file museum)" + fi + else + echo "==> Download failed from $URL" + cat /app/data/logs/curl.log || echo "No curl logs available" + fi + done + + # If direct binary download fails, try downloading the source code and build + if [ "$DOWNLOAD_SUCCESS" = false ]; then + echo "==> Trying to download source code..." + + SOURCE_URLS=( + "https://github.com/ente-io/museum/archive/refs/heads/main.zip" + "https://github.com/ente-io/ente/archive/refs/heads/main.zip" + "https://api.github.com/repos/ente-io/museum/zipball/main" + ) + + for URL in "${SOURCE_URLS[@]}"; do + echo "==> Trying source download from: $URL" + if curl -L -o museum.zip "$URL" 2>/app/data/logs/curl.log; then + echo "==> Checking downloaded file..." + file museum.zip + + # Check if we have a valid zip file + if file museum.zip | grep -q "Zip archive data"; then + echo "==> Valid zip file downloaded" + unzip -o museum.zip + + # Find the extracted directory + EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "museum-*" -o -name "ente-io-museum-*" | head -1) + + if [ -n "$EXTRACTED_DIR" ]; then + echo "==> Found extracted directory: $EXTRACTED_DIR" + cd "$EXTRACTED_DIR" + + # Try to build if go is available + if command -v go &> /dev/null; then + echo "==> Building museum server with Go..." + go build -o museum cmd/museum/main.go && DOWNLOAD_SUCCESS=true + + if [ -f "museum" ]; then + chmod +x museum + mv museum ${SERVER_DIR}/ + echo "==> Successfully built museum binary" + cd ${SERVER_DIR} + DOWNLOAD_SUCCESS=true + break + fi + fi + else + echo "==> Could not find extracted directory" + fi + else + echo "==> Downloaded file is not a valid zip file: $(file museum.zip)" + cat museum.zip + fi + else + echo "==> Source download failed from $URL" fi done + fi + + # If all download attempts fail, create a placeholder server + if [ "$DOWNLOAD_SUCCESS" = false ]; then + echo "==> All download attempts failed, creating a placeholder server..." - if [ ! -f "$SERVER_DIR/museum/museum" ] || [ ! -s "$SERVER_DIR/museum/museum" ]; then - echo "==> ERROR: Failed to download pre-built binary" - echo "==> Will create a simple HTTP server as a placeholder" - - # Create a simple HTTP server in Go - mkdir -p "$SERVER_DIR/museum/config" - cat > "$SERVER_DIR/museum/museum.go" << 'EOF' -package main + # Creating a placeholder server with Node.js + cat > ${SERVER_DIR}/server.js << 'EOF' +const http = require('http'); +const fs = require('fs'); -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os" -) +const PORT = 3080; +const LOG_FILE = '/app/data/logs/museum.log'; -func main() { - port := "8080" - if len(os.Args) > 1 { - port = os.Args[1] - } - - http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{ - "status": "ok", - "message": "Ente Museum placeholder server", - }) - }) - - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - log.Printf("Request: %s %s", r.Method, r.URL.Path) - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{ - "status": "ok", - "message": "Ente Museum placeholder server", - "path": r.URL.Path, - "method": r.Method, - }) - }) - - fmt.Printf("Starting server on port %s...\n", port) - log.Fatal(http.ListenAndServe("0.0.0.0:"+port, nil)) +// Ensure log directory exists +if (!fs.existsSync('/app/data/logs')) { + fs.mkdirSync('/app/data/logs', { recursive: true }); } -EOF - # Try to compile the simple server - if command -v go &> /dev/null; then - cd "$SERVER_DIR/museum" - go build -o museum museum.go - if [ ! -f "$SERVER_DIR/museum/museum" ]; then - echo "==> ERROR: Failed to build placeholder server" - echo "==> Will use a Node.js server instead" - cat > "$SERVER_DIR/museum/server.js" << 'EOF' -const http = require('http'); -const port = 8080; +// Log function +function log(message) { + const timestamp = new Date().toISOString(); + const logMessage = `${timestamp} - ${message}\n`; + console.log(logMessage); + fs.appendFileSync(LOG_FILE, logMessage); +} +// Create server const server = http.createServer((req, res) => { - console.log(`Request: ${req.method} ${req.url}`); - - res.setHeader('Content-Type', 'application/json'); - - if (req.url === '/health') { - res.end(JSON.stringify({ - status: 'ok', - message: 'Ente Museum placeholder server (Node.js)' - })); - return; - } - - res.end(JSON.stringify({ - status: 'ok', - message: 'Ente Museum placeholder server (Node.js)', - path: req.url, - method: req.method - })); -}); - -server.listen(port, '0.0.0.0', () => { - console.log(`Placeholder server started on port ${port}`); -}); -EOF - chmod +x "$SERVER_DIR/museum/server.js" - echo "==> Created Node.js placeholder server" - else - echo "==> Successfully built placeholder Go server" - fi - else - echo "==> Go not available, creating Node.js server" - cat > "$SERVER_DIR/museum/server.js" << 'EOF' -const http = require('http'); -const port = 8080; - -const server = http.createServer((req, res) => { - console.log(`Request: ${req.method} ${req.url}`); - - res.setHeader('Content-Type', 'application/json'); - - if (req.url === '/health') { - res.end(JSON.stringify({ - status: 'ok', - message: 'Ente Museum placeholder server (Node.js)' - })); - return; - } - - res.end(JSON.stringify({ - status: 'ok', - message: 'Ente Museum placeholder server (Node.js)', - path: req.url, - method: req.method - })); -}); - -server.listen(port, '0.0.0.0', () => { - console.log(`Placeholder server started on port ${port}`); -}); -EOF - chmod +x "$SERVER_DIR/museum/server.js" - echo "==> Created Node.js placeholder server" - fi - fi - fi -else - echo "==> Ente Museum server already downloaded" -fi - -# Configure S3 storage for Ente -if [ -f "/app/data/s3_config.env" ]; then - echo "==> Using existing S3 configuration from s3_config.env" - source /app/data/s3_config.env - echo "==> S3 Configuration:" - echo "Endpoint: $S3_ENDPOINT" - echo "Region: $S3_REGION" - echo "Bucket: $S3_BUCKET" -elif [ -f "/app/data/s3.env" ]; then - echo "==> Using existing S3 configuration from s3.env" - source /app/data/s3.env - echo "==> S3 Configuration:" - echo "Endpoint: $S3_ENDPOINT" - echo "Region: $S3_REGION" - echo "Bucket: $S3_BUCKET" - - # Copy to expected location for consistency - cp /app/data/s3.env /app/data/s3_config.env -else - # Default to environment variables if they exist - if [ -n "$CLOUDRON_S3_ENDPOINT" ] && [ -n "$CLOUDRON_S3_KEY" ] && [ -n "$CLOUDRON_S3_SECRET" ]; then - echo "==> Using Cloudron S3 configuration" - S3_ENDPOINT="$CLOUDRON_S3_ENDPOINT" - S3_REGION="us-east-1" # Default region, can be overridden - S3_BUCKET="${CLOUDRON_APP_DOMAIN//./-}-ente" - S3_ACCESS_KEY="$CLOUDRON_S3_KEY" - S3_SECRET_KEY="$CLOUDRON_S3_SECRET" - - # Save for future runs - mkdir -p /app/data - cat > /app/data/s3_config.env << EOF -S3_ENDPOINT="$S3_ENDPOINT" -S3_REGION="$S3_REGION" -S3_BUCKET="$S3_BUCKET" -S3_ACCESS_KEY="$S3_ACCESS_KEY" -S3_SECRET_KEY="$S3_SECRET_KEY" -EOF - chmod 600 /app/data/s3_config.env - echo "==> Created S3 configuration file" - echo "==> S3 Configuration:" - echo "Endpoint: $S3_ENDPOINT" - echo "Region: $S3_REGION" - echo "Bucket: $S3_BUCKET" - else - echo "==> WARNING: S3 configuration is not found" - echo "==> Creating a template S3 configuration for you to fill in" - mkdir -p /app/data - cat > /app/data/s3.env.template << EOF -# Rename this file to s3.env and set the correct values -S3_ENDPOINT="your-s3-endpoint" -S3_REGION="your-s3-region" -S3_BUCKET="your-s3-bucket" -S3_ACCESS_KEY="your-s3-access-key" -S3_SECRET_KEY="your-s3-secret-key" -EOF - echo "==> Created S3 configuration template file at /app/data/s3.env.template" - echo "==> Please fill in the values and rename it to s3.env" - - # If we found s3.env in the logs but couldn't load it, there may be a permissions issue - if [ -f "/app/data/s3.env" ]; then - echo "==> NOTICE: s3.env file exists but could not be sourced" - echo "==> Check file permissions and format" - chmod 644 /app/data/s3.env - fi - fi -fi - -# Configure museum.yaml -mkdir -p "${SERVER_DIR}/museum/config" -if [ -f "/app/data/museum.yaml" ]; then - echo "==> Using existing museum.yaml configuration" - cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml" -else - echo "==> Creating museum.yaml configuration" - cat > /app/data/museum.yaml << EOF -database: - driver: postgres - source: postgres://${CLOUDRON_POSTGRESQL_USERNAME}:${CLOUDRON_POSTGRESQL_PASSWORD}@${CLOUDRON_POSTGRESQL_HOST}:${CLOUDRON_POSTGRESQL_PORT}/${CLOUDRON_POSTGRESQL_DATABASE} - auto-migrate: true - -server: - port: 8080 - host: 0.0.0.0 - cors: - origins: - - https://${CLOUDRON_APP_DOMAIN} - methods: - - GET - - POST - - PUT - - OPTIONS - headers: - - Content-Type - - Authorization + log(`Request received: ${req.method} ${req.url}`); -endpoints: - photos: https://${CLOUDRON_APP_DOMAIN}/photos - accounts: https://${CLOUDRON_APP_DOMAIN}/accounts - auth: https://${CLOUDRON_APP_DOMAIN}/auth - cast: https://${CLOUDRON_APP_DOMAIN}/cast - "public-albums": https://${CLOUDRON_APP_DOMAIN}/public + // Health check endpoint + if (req.url === '/health' || req.url === '/api/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'OK', server: 'Museum Placeholder' })); + return; + } + + // Authentication endpoints + if (req.url === '/api/users/verify') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + isValidEmail: true, + isAvailable: true, + isVerified: true, + canCreateAccount: true + })); + return; + } + + // Default response for any other endpoint + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ message: 'Placeholder Museum Server' })); +}); -s3: - endpoint: ${S3_ENDPOINT} - region: ${S3_REGION} - bucket: ${S3_BUCKET} - access-key-id: ${S3_ACCESS_KEY} - secret-access-key: ${S3_SECRET_KEY} - cache-control: public, max-age=31536000 +// Start server +server.listen(PORT, '0.0.0.0', () => { + log(`Museum placeholder server running on port ${PORT}`); +}); -acme: - enabled: false # Cloudron handles SSL +// Handle errors +server.on('error', (error) => { + log(`Server error: ${error.message}`); +}); -smtp: - enabled: true - host: ${CLOUDRON_SMTP_SERVER:-localhost} - port: ${CLOUDRON_SMTP_PORT:-25} - username: ${CLOUDRON_SMTP_USERNAME:-""} - password: ${CLOUDRON_SMTP_PASSWORD:-""} - from: "Ente <${CLOUDRON_MAIL_FROM:-no-reply@${CLOUDRON_APP_DOMAIN}}>" - reply-to: "Ente <${CLOUDRON_MAIL_FROM:-no-reply@${CLOUDRON_APP_DOMAIN}}>" - -logging: - level: info - file: /app/data/logs/museum.log +// Log startup +log('Museum placeholder server starting up'); EOF - echo "==> Created museum.yaml configuration" - cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml" + + # Mark the failure but continue with the placeholder + echo "==> Created Node.js placeholder server at ${SERVER_DIR}/server.js" + fi fi -# Debug PostgreSQL connection information -echo "==> PostgreSQL information:" -echo "CLOUDRON_POSTGRESQL_HOST: $CLOUDRON_POSTGRESQL_HOST" -echo "CLOUDRON_POSTGRESQL_PORT: $CLOUDRON_POSTGRESQL_PORT" -echo "CLOUDRON_POSTGRESQL_DATABASE: $CLOUDRON_POSTGRESQL_DATABASE" -echo "CLOUDRON_POSTGRESQL_USERNAME: $CLOUDRON_POSTGRESQL_USERNAME" +# Set up S3 config +if [ ! -f "/app/data/s3.env" ]; then + echo "==> Creating default S3 environment file" + cat > /app/data/s3.env << 'EOF' +S3_ACCESS_KEY=your-access-key +S3_SECRET_KEY=your-secret-key +S3_ENDPOINT=your-s3-endpoint +S3_REGION=your-region +S3_BUCKET=your-bucket +EOF + chmod 600 /app/data/s3.env + echo "==> Created S3 environment file at /app/data/s3.env" + echo "==> IMPORTANT: Please edit this file with your actual S3 credentials" +else + echo "==> S3 environment file already exists" +fi + +# Load S3 config +if [ -f "/app/data/s3.env" ]; then + source /app/data/s3.env + echo "==> Loaded S3 configuration" +fi + +# Create museum.yaml config +if [ ! -f "${SERVER_DIR}/museum.yaml" ]; then + echo "==> Creating museum.yaml configuration" + cat > ${SERVER_DIR}/museum.yaml << EOF +port: 3080 +host: 0.0.0.0 +db: + driver: postgres + source: "postgres://${CLOUDRON_POSTGRESQL_USERNAME}:${CLOUDRON_POSTGRESQL_PASSWORD}@${CLOUDRON_POSTGRESQL_HOST}:${CLOUDRON_POSTGRESQL_PORT}/${CLOUDRON_POSTGRESQL_DATABASE}?sslmode=disable" + max_conns: 10 + max_idle: 5 +log_level: info +cors: + allow_origins: + - "*" +s3: + endpoint: "${S3_ENDPOINT:-s3.amazonaws.com}" + region: "${S3_REGION:-us-east-1}" + access_key: "${S3_ACCESS_KEY}" + secret_key: "${S3_SECRET_KEY}" + bucket: "${S3_BUCKET}" + public_url: "https://${CLOUDRON_APP_FQDN}/photos" +EOF + chmod 600 ${SERVER_DIR}/museum.yaml + echo "==> Created museum.yaml configuration" +else + echo "==> museum.yaml configuration already exists" +fi # Test PostgreSQL connectivity -echo "==> Testing PostgreSQL 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 2>&1 -if [ $? -eq 0 ]; then - echo "==> PostgreSQL is ready" +echo "==> Testing PostgreSQL connectivity..." +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 "==> ERROR: Failed to connect to PostgreSQL" + echo "==> Connection details:" + echo "==> Host: $CLOUDRON_POSTGRESQL_HOST" + echo "==> Port: $CLOUDRON_POSTGRESQL_PORT" + echo "==> User: $CLOUDRON_POSTGRESQL_USERNAME" + echo "==> Database: $CLOUDRON_POSTGRESQL_DATABASE" else - echo "==> ERROR: Could not connect to PostgreSQL" - echo "==> Please check your PostgreSQL configuration" - echo "==> Continuing anyway, but errors may occur later" + echo "==> PostgreSQL connection successful" fi -# Download and install Ente web app if not already present -if [ ! -d "/app/data/ente/web" ] || [ ! -f "/app/data/ente/web/photos/index.html" ]; then +# Download and set up web app +echo "==> Setting up Ente web app..." +WEB_DIR="/app/data/ente/web" +if [ ! -d "${WEB_DIR}/photos" ] || [ ! -f "${WEB_DIR}/photos/index.html" ]; then + # Create placeholder HTML echo "==> Downloading Ente web app..." - mkdir -p "/app/data/ente/web" - cd "/app/data/ente/web" - # Clone the repository if it doesn't exist - if [ ! -d "/app/data/ente/web/photos" ]; then - # Use HTTPS download instead of git clone - echo "==> Downloading photos web app from GitHub archive..." - curl -L -o photos.zip https://github.com/ente-io/photos/archive/refs/heads/main.zip || curl -L -o photos.zip https://api.github.com/repos/ente-io/photos/zipball/main - - # Debug the downloaded file - echo "==> Checking downloaded file..." - file photos.zip - ls -la photos.zip - - # Try alternate download method if first one fails - if [ ! -s photos.zip ] || ! unzip -q photos.zip; then - echo "==> Direct download failed, trying with wget..." - if ! command -v wget &> /dev/null; then - apt-get update && apt-get install -y wget - fi - wget -O photos.zip https://github.com/ente-io/photos/archive/main.zip - - if [ ! -s photos.zip ] || ! unzip -q photos.zip; then - echo "==> All download methods failed, creating directories manually" - mkdir -p photos - cd photos + # Try to download web app + if curl -L -o /tmp/web.zip "https://github.com/ente-io/ente/releases/latest/download/web.zip" 2>/app/data/logs/curl_web.log; then + echo "==> Web app download successful, extracting..." + # Check if it's a valid zip file + if file /tmp/web.zip | grep -q "Zip archive data"; then + mkdir -p /tmp/web + unzip -o /tmp/web.zip -d /tmp/web + # Check if extraction was successful + if [ -d "/tmp/web" ]; then + # Copy contents to the web directory + cp -r /tmp/web/* ${WEB_DIR}/ + echo "==> Web app extracted and installed" else - # Handle the extracted directory name which might be photos-main or something like ente-io-photos- - extracted_dir=$(find . -type d -name "photos-*" -o -name "ente-io-photos-*" | head -n 1) - if [ -n "$extracted_dir" ]; then - mkdir -p photos - mv "$extracted_dir"/* photos/ - rm -rf "$extracted_dir" - cd photos - else - mkdir -p photos - cd photos - fi + echo "==> Failed to extract web app" fi else - # Handle the extracted directory name which might be photos-main or something like ente-io-photos- - extracted_dir=$(find . -type d -name "photos-*" -o -name "ente-io-photos-*" | head -n 1) - if [ -n "$extracted_dir" ]; then - mkdir -p photos - mv "$extracted_dir"/* photos/ - rm -rf "$extracted_dir" - cd photos - else - mkdir -p photos - cd photos - fi + echo "==> Downloaded file is not a valid zip file: $(file /tmp/web.zip)" fi else - cd photos - # Use HTTPS instead of Git pull - echo "==> Updating existing web app repository..." - curl -L -o main.zip https://github.com/ente-io/photos/archive/refs/heads/main.zip || curl -L -o main.zip https://api.github.com/repos/ente-io/photos/zipball/main - - if [ -s main.zip ] && unzip -q main.zip; then - extracted_dir=$(find . -type d -name "photos-*" -o -name "ente-io-photos-*" | head -n 1) - if [ -n "$extracted_dir" ]; then - cp -R "$extracted_dir"/* ./ - rm -rf "$extracted_dir" main.zip + echo "==> Web app download failed, trying with wget..." + if wget -O /tmp/web.zip "https://github.com/ente-io/ente/releases/latest/download/web.zip" 2>/app/data/logs/wget_web.log; then + echo "==> Web app download with wget successful, extracting..." + # Check if it's a valid zip file + if file /tmp/web.zip | grep -q "Zip archive data"; then + mkdir -p /tmp/web + unzip -o /tmp/web.zip -d /tmp/web + # Copy contents to the web directory + cp -r /tmp/web/* ${WEB_DIR}/ + echo "==> Web app extracted and installed" + else + echo "==> Downloaded file is not a valid zip file: $(file /tmp/web.zip)" fi else - echo "==> Failed to update web app repository, continuing with existing files" + echo "==> Web app download with wget also failed, creating placeholders" fi fi - # Try to build the web app - echo "==> Building Ente web app (this may take a while)..." - if command -v npm &> /dev/null; then - npm install - npm run build - else - echo "==> WARNING: npm not found, cannot build web app" - echo "==> Will continue with placeholder pages" - fi -else - echo "==> Ente web app already downloaded" -fi - -# Start the real Museum server -if [ -f "${SERVER_DIR}/museum/museum" ] && [ -s "${SERVER_DIR}/museum/museum" ]; then - echo "==> Found Museum server at ${SERVER_DIR}/museum" - - # Make sure the museum binary is executable - chmod +x "${SERVER_DIR}/museum/museum" - - # Start the real Museum server - cd "${SERVER_DIR}/museum" - ./museum --config "${SERVER_DIR}/museum/config/museum.yaml" > /app/data/logs/museum.log 2>&1 & - MUSEUM_PID=$! - echo "==> Started Museum server with PID: $MUSEUM_PID" - - # Wait for Museum server to start - echo "==> Waiting for Museum server to start..." - for i in {1..30}; do - sleep 1 - if curl -s http://localhost:8080/health > /dev/null; then - echo "==> Museum server started successfully" - break - fi - if [ $i -eq 30 ]; then - echo "==> ERROR: Museum server failed to start" - echo "==> Please check logs at /app/data/logs/museum.log" - tail -n 50 /app/data/logs/museum.log - echo "==> Falling back to placeholder server" - # Kill the failed process if it's still running - kill $MUSEUM_PID 2>/dev/null || true - MUSEUM_PID="" - fi - done -elif [ -f "${SERVER_DIR}/museum/server.js" ]; then - echo "==> Found Node.js placeholder server at ${SERVER_DIR}/museum/server.js" - cd "${SERVER_DIR}/museum" - node server.js > /app/data/logs/museum.log 2>&1 & - MUSEUM_PID=$! - echo "==> Started Node.js placeholder server with PID: $MUSEUM_PID" - - # Wait for Museum server to start - echo "==> Waiting for Museum server to start..." - for i in {1..10}; do - sleep 1 - if curl -s http://localhost:8080/health > /dev/null; then - echo "==> Node.js placeholder server started successfully" - break - fi - if [ $i -eq 10 ]; then - echo "==> ERROR: Node.js placeholder server failed to start" - echo "==> Please check logs at /app/data/logs/museum.log" - tail -n 50 /app/data/logs/museum.log - echo "==> Will continue but API functionality will be limited" - MUSEUM_PID="" - fi - done -else - echo "==> ERROR: No server executable found at ${SERVER_DIR}/museum" - echo "==> Creating a minimal Node.js server on the fly" - - mkdir -p "${SERVER_DIR}/museum" - cd "${SERVER_DIR}/museum" - - # Create a simple HTTP server with Node.js - cat > server.js << 'EOF' -const http = require('http'); -const port = 8080; - -const server = http.createServer((req, res) => { - console.log(`Request: ${req.method} ${req.url}`); - - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); - - if (req.method === 'OPTIONS') { - res.statusCode = 200; - res.end(); - return; - } - - if (req.url === '/health') { - res.end(JSON.stringify({ - status: 'ok', - message: 'Ente Museum minimal placeholder server' - })); - return; - } - - res.end(JSON.stringify({ - status: 'ok', - message: 'Ente Museum minimal placeholder server', - path: req.url, - method: req.method - })); -}); - -server.listen(port, '0.0.0.0', () => { - console.log(`Minimal placeholder server started on port ${port}`); -}); -EOF - - node server.js > /app/data/logs/museum.log 2>&1 & - MUSEUM_PID=$! - echo "==> Started minimal Node.js server with PID: $MUSEUM_PID" - - # Wait for minimal server to start - sleep 3 - if curl -s http://localhost:8080/health > /dev/null; then - echo "==> Minimal Node.js server started successfully" - else - echo "==> WARNING: Minimal Node.js server may not have started properly" - fi -fi - -# Set up Caddy web server -echo "==> Setting up Caddy web server" - -# Get local IP address -LOCAL_IP=$(hostname -i 2>/dev/null || echo "0.0.0.0") -echo "==> Local IP address: $LOCAL_IP" - -# Create Caddy configuration file -cat > /app/data/Caddyfile << EOF -{ - admin off -} - -:3080 { - # API endpoints - proxy to Museum server - handle /api/* { - uri strip_prefix /api - reverse_proxy localhost:8080 - } - - # Web applications static content - handle /photos/* { - uri strip_prefix /photos - root * /app/data/web/photos - try_files {path} {path}/ /index.html - file_server - } - - handle /accounts/* { - uri strip_prefix /accounts - root * /app/data/web/accounts - try_files {path} {path}/ /index.html - file_server - } - - handle /auth/* { - uri strip_prefix /auth - root * /app/data/web/auth - try_files {path} {path}/ /index.html - file_server - } - - handle /cast/* { - uri strip_prefix /cast - root * /app/data/web/cast - try_files {path} {path}/ /index.html - file_server - } - - # Public albums handler - handle /public/* { - uri strip_prefix /public - reverse_proxy localhost:8080/public - } - - # Redirect root to photos - handle / { - redir /photos permanent - } - - # Serve static files from photos by default - handle { - root * /app/data/web/photos - try_files {path} {path}/ /index.html - file_server - } - - # Error handling - handle_errors { - respond "{http.error.status_code} {http.error.status_text}" - } - - # Logging - log { - output file /app/data/logs/access.log - format console - level info - } -} -EOF - -echo "==> Caddy configuration created" -cat /app/data/Caddyfile - -# Create runtime-config.js in writable location -echo "==> Creating runtime-config.js in writable location" -cat > /app/data/web/photos/static/runtime-config.js << 'EOF' -// Runtime configuration for Ente web app -(function() { - if (typeof window !== 'undefined') { - // Polyfill process for browser environment - if (!window.process) { - window.process = { - env: {}, - nextTick: function(cb) { setTimeout(cb, 0); } - }; - } - - const BASE_URL = window.location.origin; - const API_URL = BASE_URL + '/api'; - const PUBLIC_ALBUMS_URL = BASE_URL + '/public'; - - // Make configuration available globally - window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = API_URL; - window.process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = PUBLIC_ALBUMS_URL; - - // Also maintain compatibility with older Ente code - window.ENTE_CONFIG = { - API_URL: API_URL, - PUBLIC_ALBUMS_URL: PUBLIC_ALBUMS_URL - }; - - console.log('Ente runtime config loaded from runtime-config.js with polyfills'); - console.log('process.nextTick available:', typeof window.process.nextTick === 'function'); - console.log('BASE_URL:', BASE_URL); - console.log('API_URL (final):', API_URL); - console.log('PUBLIC_ALBUMS_URL (final):', PUBLIC_ALBUMS_URL); - console.log('NEXT_PUBLIC_ENTE_ENDPOINT (final):', window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT); - } -})(); -EOF - -# Copy runtime-config.js to all app static directories -for app in accounts auth cast; do - cp /app/data/web/photos/static/runtime-config.js /app/data/web/$app/static/ -done - -# Create URL and SRP patch file -echo "==> Creating URL and SRP patch file" -cat > /app/data/web/photos/static/ente-patches.js << 'ENDPATCHES' -(function() { - console.log('Applying Ente URL and SRP patches...'); - - // Save original URL constructor - const originalURL = window.URL; - - // Create a patched URL constructor - window.URL = function(url, base) { - try { - if (!url) { - throw new Error('Invalid URL: URL cannot be empty'); - } - - // Fix relative URLs - if (!url.match(/^https?:\/\//i)) { - if (url.startsWith('/')) { - url = window.location.origin + url; - } else { - url = window.location.origin + '/' + url; - } - } - - // Try to construct with fixed URL - return new originalURL(url, base); - } catch (e) { - console.error('URL construction error:', e, 'for URL:', url); - - // Safe fallback - use the origin as a last resort - return new originalURL(window.location.origin); - } - }; - - console.log('Ente URL and SRP patches applied successfully'); -})(); -ENDPATCHES - -# Copy ente-patches.js to all app static directories -for app in accounts auth cast; do - cp /app/data/web/photos/static/ente-patches.js /app/data/web/$app/static/ -done - -# Create placeholder HTML files for each app if the actual builds don't exist -for app in photos accounts auth cast; do - if [ ! -f "/app/data/ente/web/$app/index.html" ]; then - echo "==> Creating placeholder HTML for $app" - cat > /app/data/web/$app/index.html << EOF + # Create placeholder HTML if download failed + if [ ! -f "${WEB_DIR}/photos/index.html" ]; then + echo "==> Creating placeholder HTML files..." + mkdir -p ${WEB_DIR}/photos + cat > ${WEB_DIR}/photos/index.html << 'EOF' - + + Ente Photos - Ente $app - -
- -

Ente $app

-

End-to-end encrypted photo storage and sharing platform.

-

This is a placeholder page until the proper Ente build is created.

- Check API Status +

Ente Photos

+

This is a placeholder for the Ente Photos application. The actual app is being set up.

- EOF - else - echo "==> Using existing $app web app" - cp -r "/app/data/ente/web/$app/"* "/app/data/web/$app/" + # Create similar placeholders for other apps + mkdir -p ${WEB_DIR}/accounts + cp ${WEB_DIR}/photos/index.html ${WEB_DIR}/accounts/index.html + sed -i 's/Photos/Accounts/g' ${WEB_DIR}/accounts/index.html + + mkdir -p ${WEB_DIR}/auth + cp ${WEB_DIR}/photos/index.html ${WEB_DIR}/auth/index.html + sed -i 's/Photos/Auth/g' ${WEB_DIR}/auth/index.html + + mkdir -p ${WEB_DIR}/cast + cp ${WEB_DIR}/photos/index.html ${WEB_DIR}/cast/index.html + sed -i 's/Photos/Cast/g' ${WEB_DIR}/cast/index.html + + echo "==> Created placeholder HTML files" fi +else + echo "==> Ente web app already set up" +fi + +# Copy web files to /app/data/web +echo "==> Copying web files to /app/data/web..." +if [ -d "${WEB_DIR}/photos" ]; then + cp -r ${WEB_DIR}/photos/* /app/data/web/photos/ || echo "==> Warning: Failed to copy photos files" +fi +if [ -d "${WEB_DIR}/accounts" ]; then + cp -r ${WEB_DIR}/accounts/* /app/data/web/accounts/ || echo "==> Warning: Failed to copy accounts files" +fi +if [ -d "${WEB_DIR}/auth" ]; then + cp -r ${WEB_DIR}/auth/* /app/data/web/auth/ || echo "==> Warning: Failed to copy auth files" +fi +if [ -d "${WEB_DIR}/cast" ]; then + cp -r ${WEB_DIR}/cast/* /app/data/web/cast/ || echo "==> Warning: Failed to copy cast files" +fi + +# Create runtime config for web app +echo "==> Creating runtime config for web app..." +for APP in photos accounts auth cast; do + CONFIG_DIR="/app/data/web/${APP}" + mkdir -p "${CONFIG_DIR}" + cat > "${CONFIG_DIR}/runtime-config.js" << EOF +window.RUNTIME_CONFIG = { + API_URL: "https://${CLOUDRON_APP_FQDN}/api", + PUBLIC_ALBUMS_URL: "https://${CLOUDRON_APP_FQDN}/public", + DEBUG: true +}; +console.log("Loaded runtime config:", window.RUNTIME_CONFIG); +EOF + echo "==> Created runtime config for ${APP}" done -# Install unzip if needed -if ! command -v unzip &> /dev/null; then - echo "==> Installing unzip (required for downloading repositories)" - apt-get update - apt-get install -y unzip -fi - -# Start Caddy -echo "==> Starting Caddy server" - -# Kill any existing Caddy process to avoid port conflicts -pkill -f "caddy run --config /app/data/Caddyfile" || true -sleep 1 - -# Start Caddy with proper configuration -caddy run --config /app/data/Caddyfile --adapter caddyfile > /app/data/logs/caddy.log 2>&1 & -CADDY_PID=$! -echo "==> Caddy server started with PID: $CADDY_PID" - -# Verify Caddy is listening -sleep 5 -echo "==> Checking Caddy status:" -if pgrep -f "caddy run --config /app/data/Caddyfile" > /dev/null; then - echo "==> Caddy is running" -else - echo "==> WARNING: Caddy does not appear to be running" -fi - -echo "==> Checking port 3080:" -if netstat -tln | grep ':3080' > /dev/null; then - echo "==> Port 3080 is open and listening" -else - echo "==> WARNING: Port 3080 is not listening" - netstat -tln +# Set up Caddy web server +echo "==> Setting up Caddy web server..." +cat > /app/data/Caddyfile << EOF +:3080 { + log { + output file /app/data/logs/caddy.log + } - # Try to start Caddy with explicit listen address - echo "==> Trying to start Caddy with explicit listen address" - cat > /app/data/Caddyfile << EOF -{ - admin off -} - -0.0.0.0:3080 { - # API endpoints - proxy to Museum server - handle /api/* { - uri strip_prefix /api - reverse_proxy localhost:8080 - } - - # Web applications static content - handle /photos/* { - uri strip_prefix /photos - root * /app/data/web/photos - try_files {path} {path}/ /index.html - file_server - } - - handle /accounts/* { - uri strip_prefix /accounts - root * /app/data/web/accounts - try_files {path} {path}/ /index.html - file_server - } - - handle /auth/* { - uri strip_prefix /auth - root * /app/data/web/auth - try_files {path} {path}/ /index.html - file_server - } - - handle /cast/* { - uri strip_prefix /cast - root * /app/data/web/cast - try_files {path} {path}/ /index.html - file_server - } - - # Public albums handler - handle /public/* { - uri strip_prefix /public - reverse_proxy localhost:8080/public - } - - # Redirect root to photos - handle / { - redir /photos permanent - } - - # Serve static files from photos by default - handle { - root * /app/data/web/photos - try_files {path} {path}/ /index.html - file_server - } - - # Error handling - handle_errors { - respond "{http.error.status_code} {http.error.status_text}" - } - - # Logging - log { - output file /app/data/logs/access.log - format console - level info - } + # API endpoints go to museum server + handle /api/* { + reverse_proxy localhost:3080 + } + + # Public albums endpoint + handle /public/* { + reverse_proxy localhost:3080 + } + + # Static web apps + handle /photos/* { + root * /app/data/web/photos + try_files {path} /index.html + file_server + } + + handle /accounts/* { + root * /app/data/web/accounts + try_files {path} /index.html + file_server + } + + handle /auth/* { + root * /app/data/web/auth + try_files {path} /index.html + file_server + } + + handle /cast/* { + root * /app/data/web/cast + try_files {path} /index.html + file_server + } + + # Redirect root to photos + handle { + redir / /photos/ + } } EOF +echo "==> Created Caddy configuration" + +# Start museum server or placeholder +cd ${SERVER_DIR} +if [ -f "${SERVER_DIR}/museum" ] && [ -x "${SERVER_DIR}/museum" ]; then + echo "==> Starting Museum server..." + mkdir -p /app/data/logs + ./museum serve --config museum.yaml > /app/data/logs/museum.log 2>&1 & - # Kill any existing Caddy process - pkill -f "caddy run --config /app/data/Caddyfile" || true - sleep 1 + # Wait for server to start + MAX_ATTEMPTS=30 + ATTEMPT=0 + while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + if curl -s http://localhost:3080/health > /dev/null; then + echo "==> Museum server started successfully" + break + fi + ATTEMPT=$((ATTEMPT+1)) + echo "==> Waiting for Museum server to start (attempt $ATTEMPT/$MAX_ATTEMPTS)..." + sleep 1 + done - # Start Caddy again with explicit configuration - caddy run --config /app/data/Caddyfile --adapter caddyfile > /app/data/logs/caddy.log 2>&1 & - CADDY_PID=$! - echo "==> Restarted Caddy server with PID: $CADDY_PID" - sleep 5 - - # Check again - if netstat -tln | grep ':3080' > /dev/null; then - echo "==> Port 3080 is now open and listening" - else - echo "==> WARNING: Port 3080 is still not listening" - netstat -tln + if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo "==> ERROR: Museum server failed to start within $MAX_ATTEMPTS seconds" + echo "==> Last few lines of museum.log:" + tail -n 20 /app/data/logs/museum.log || echo "==> No log file found" + + # Fall back to Node.js server if available + if [ -f "${SERVER_DIR}/server.js" ]; then + echo "==> Starting Node.js placeholder server..." + node ${SERVER_DIR}/server.js >> /app/data/logs/museum.log 2>&1 & + + # Wait for server to start + MAX_ATTEMPTS=10 + ATTEMPT=0 + while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + if curl -s http://localhost:3080/health > /dev/null; then + echo "==> Node.js placeholder server started successfully" + break + fi + ATTEMPT=$((ATTEMPT+1)) + echo "==> Waiting for Node.js server to start (attempt $ATTEMPT/$MAX_ATTEMPTS)..." + sleep 1 + done + + if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo "==> ERROR: Node.js server failed to start within $MAX_ATTEMPTS seconds" + echo "==> The application will have limited API functionality" + fi + else + echo "==> No fallback server available. Creating one on the fly..." + # Create a minimal Node.js server + cat > ${SERVER_DIR}/server.js << 'EOF' +const http = require('http'); +const fs = require('fs'); + +const PORT = 3080; +const LOG_FILE = '/app/data/logs/museum.log'; + +// Ensure log directory exists +if (!fs.existsSync('/app/data/logs')) { + fs.mkdirSync('/app/data/logs', { recursive: true }); +} + +// Log function +function log(message) { + const timestamp = new Date().toISOString(); + const logMessage = `${timestamp} - ${message}\n`; + console.log(logMessage); + fs.appendFileSync(LOG_FILE, logMessage); +} + +// Create server +const server = http.createServer((req, res) => { + log(`Request received: ${req.method} ${req.url}`); + + // Health check endpoint + if (req.url === '/health' || req.url === '/api/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'OK', server: 'Museum Placeholder' })); + return; + } + + // Default response for any other endpoint + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ message: 'Placeholder Museum Server' })); +}); + +// Start server +server.listen(PORT, '0.0.0.0', () => { + log(`Minimal museum placeholder server running on port ${PORT}`); +}); +EOF + node ${SERVER_DIR}/server.js >> /app/data/logs/museum.log 2>&1 & + echo "==> Started minimal Node.js placeholder server" + fi fi +elif [ -f "${SERVER_DIR}/server.js" ]; then + echo "==> Starting Node.js placeholder server..." + node ${SERVER_DIR}/server.js > /app/data/logs/museum.log 2>&1 & + echo "==> Node.js placeholder server started" +else + echo "==> ERROR: No server executable found" + exit 1 fi -# Check basic connectivity -curl -v http://localhost:3080/ || echo "==> WARNING: Cannot connect to localhost:3080" +# Start Caddy web server +echo "==> Starting Caddy web server..." +caddy run --config /app/data/Caddyfile > /app/data/logs/caddy.log 2>&1 & +echo "==> Caddy web server started" -# Enter a wait state to catch signals -echo "==> Entering wait state - watching logs for registration codes" -echo "==> Registration verification codes will appear in the logs below" -echo "==> Press Ctrl+C to stop" -tail -f /app/data/logs/*.log \ No newline at end of file +# Keep script running +echo "==> Setup complete, entering wait loop..." +tail -f /app/data/logs/museum.log \ No newline at end of file