diff --git a/start.sh b/start.sh index fa228a7..833663c 100644 --- a/start.sh +++ b/start.sh @@ -1,690 +1,368 @@ #!/bin/bash set -e -# Cloudron app startup script for Ente +# Declare that we are using a Cloudron environment echo "==> Starting Ente Cloudron app..." - -# We need to be careful with file permissions, as /app/data is the only writable location -mkdir -p /app/data/patched -chmod -R 777 /app/data/patched -echo "==> Created and set full permissions (777) on /app/data/patched directory" - echo "==> NOTE: Running in Cloudron environment with limited write access" echo "==> Writable directories: /app/data, /tmp, /run" -# Configure important paths -MUSEUM_DIR="/app/code/server" -CONFIG_DIR="/app/data/config" -LOGS_DIR="/app/data/logs" -WEB_DIR="/app/web" -CADDY_DATA_DIR="/app/data/caddy" +# Create necessary data directories +mkdir -p /app/data/logs +mkdir -p /app/data/ente/web +mkdir -p /app/data/ente/server +mkdir -p /app/data/web/photos/static +mkdir -p /app/data/web/accounts/static +mkdir -p /app/data/web/auth/static +mkdir -p /app/data/web/cast/static -# Create necessary directories -mkdir -p "$CONFIG_DIR" "$LOGS_DIR" "$CADDY_DATA_DIR" +# Use the specified server directory or default to the data dir +SERVER_DIR="/app/data/ente/server" +echo "==> Using server directory: $SERVER_DIR" -# Determine the endpoint configuration -CLOUDRON_APP_FQDN="${CLOUDRON_APP_DOMAIN}" -if [ -n "${CLOUDRON_APP_ORIGIN}" ]; then - CLOUDRON_APP_FQDN="${CLOUDRON_APP_DOMAIN}" +# 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" + + # Clone the repository if it doesn't exist + if [ ! -d "$SERVER_DIR/museum" ]; then + git clone https://github.com/ente-io/museum.git + cd museum + else + cd museum + git pull + fi + + # Build the museum server + echo "==> Building Ente Museum server..." + go build -o museum + + 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 + + RELEASE_URL="https://github.com/ente-io/museum/releases/latest/download/museum-$OS-$ARCH" + curl -L -o "$SERVER_DIR/museum/museum" "$RELEASE_URL" + chmod +x "$SERVER_DIR/museum/museum" + + if [ ! -f "$SERVER_DIR/museum/museum" ]; then + echo "==> ERROR: Failed to download pre-built binary" + echo "==> Will create directory structure for future installation" + mkdir -p "$SERVER_DIR/museum/config" + fi + fi else - # If origin not set, use the app domain - CLOUDRON_APP_ORIGIN="https://${CLOUDRON_APP_DOMAIN}" + echo "==> Ente Museum server already downloaded" fi -API_ENDPOINT="/api" -CADDY_PORT="3080" -API_PORT="8080" -PUBLIC_ALBUMS_PORT="8081" - -echo "==> Using server directory: ${MUSEUM_DIR}" - -# Check if we have S3 configuration -if [ -f "${CONFIG_DIR}/s3.env" ]; then +# Configure S3 storage for Ente +if [ -f "/app/data/s3_config.env" ]; then echo "==> Using existing S3 configuration" - source "${CONFIG_DIR}/s3.env" + source /app/data/s3_config.env echo "==> S3 Configuration:" - echo "Endpoint: ${S3_ENDPOINT}" - echo "Region: ${S3_REGION}" - echo "Bucket: ${S3_BUCKET}" + echo "Endpoint: $S3_ENDPOINT" + echo "Region: $S3_REGION" + echo "Bucket: $S3_BUCKET" else - echo "==> Creating default S3 configuration file" - # Create empty S3 env file for later configuration - cat > "${CONFIG_DIR}/s3.env" << EOF -# S3 Configuration for Ente -# Uncomment and fill in the following values: -# S3_ENDPOINT=https://s3.example.com -# S3_REGION=us-east-1 -# S3_BUCKET=your-bucket -# S3_ACCESS_KEY=your-access-key -# S3_SECRET_KEY=your-secret-key + # 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 + 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 - echo "==> Default S3 configuration created. Please edit ${CONFIG_DIR}/s3.env with your S3 credentials." + 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 "==> ERROR: S3 configuration is required for Ente" + echo "==> Please configure S3 storage in the Cloudron app settings" + echo "==> or create /app/data/s3_config.env manually" + exit 1 + fi fi -# Check if we have a museum.yaml configuration file -if [ -f "${CONFIG_DIR}/museum.yaml" ]; then +# Configure museum.yaml +if [ -f "/app/data/museum.yaml" ]; then echo "==> Using existing museum.yaml configuration" else - echo "==> Creating default museum.yaml configuration" - - # Create museum.yaml with S3 configuration - cat > "${CONFIG_DIR}/museum.yaml" << EOF + 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 - port: ${API_PORT} - shutdown_timeout: 10s - read_timeout: 30s - write_timeout: 30s - idle_timeout: 90s + cors: + origins: + - https://${CLOUDRON_APP_DOMAIN} + methods: + - GET + - POST + - PUT + - OPTIONS + headers: + - Content-Type + - Authorization + +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 -db: - host: ${CLOUDRON_POSTGRESQL_HOST} - port: ${CLOUDRON_POSTGRESQL_PORT} - user: ${CLOUDRON_POSTGRESQL_USERNAME} - password: ${CLOUDRON_POSTGRESQL_PASSWORD} - name: ${CLOUDRON_POSTGRESQL_DATABASE} - ssl_mode: disable - max_open_conns: 25 - max_idle_conns: 25 - conn_max_lifetime: 5m +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 -storage: - passphrase: "" - s3: - endpoint: "${S3_ENDPOINT:-https://s3.example.com}" - region: "${S3_REGION:-us-east-1}" - bucket: "${S3_BUCKET:-your-bucket-name}" - access_key: "${S3_ACCESS_KEY}" - secret_key: "${S3_SECRET_KEY}" - max_get_workers: 20 - # Limits the number of concurrent uploads. - max_put_workers: 20 - -# Set these if you change the default encryption_key -# The key must be 32 chars long -encryption: - key: "ente-self-hosted-encryption-key01" - nonce: "1234567890" - -# Authentication/security settings -auth: - # JWT settings - jwt_secret: "ente-self-hosted-jwt-secret-key-111" - token_expiry: 30d - # Used for email tokens - token_secret: "ente-self-hosted-token-secret12345" - # TOTP settings - totp_secret: "ente-self-hosted-totp-secret12345" +acme: + enabled: false # Cloudron handles SSL smtp: - enabled: false - host: "" - port: 0 - username: "" - password: "" - from_address: "" - secure: false - auth: false + 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 EOF - echo "==> Created museum.yaml with default configuration" + echo "==> Created museum.yaml configuration" fi -# Create a reduced museum.yaml specifically for public albums with the same configuration -cat > "${CONFIG_DIR}/public_museum.yaml" << EOF -server: - host: 0.0.0.0 - port: ${PUBLIC_ALBUMS_PORT} - shutdown_timeout: 10s - read_timeout: 30s - write_timeout: 30s - idle_timeout: 90s +# 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 + 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 + git clone https://github.com/ente-io/photos.git + cd photos + else + cd photos + git pull + 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 -db: - host: ${CLOUDRON_POSTGRESQL_HOST} - port: ${CLOUDRON_POSTGRESQL_PORT} - user: ${CLOUDRON_POSTGRESQL_USERNAME} - password: ${CLOUDRON_POSTGRESQL_PASSWORD} - name: ${CLOUDRON_POSTGRESQL_DATABASE} - ssl_mode: disable - max_open_conns: 25 - max_idle_conns: 25 - conn_max_lifetime: 5m - -storage: - passphrase: "" - s3: - endpoint: "${S3_ENDPOINT:-https://s3.example.com}" - region: "${S3_REGION:-us-east-1}" - bucket: "${S3_BUCKET:-your-bucket-name}" - access_key: "${S3_ACCESS_KEY}" - secret_key: "${S3_SECRET_KEY}" - max_get_workers: 20 - max_put_workers: 20 - -encryption: - key: "ente-self-hosted-encryption-key01" - nonce: "1234567890" - -auth: - jwt_secret: "ente-self-hosted-jwt-secret-key-111" - token_expiry: 30d - token_secret: "ente-self-hosted-token-secret12345" - totp_secret: "ente-self-hosted-totp-secret12345" -EOF - -# Environment variable setup - based on the docker-compose reference -export ENTE_CONFIG_FILE="${CONFIG_DIR}/museum.yaml" -export ENTE_API_ENDPOINT="${API_ENDPOINT}" -export ENTE_PORT="${API_PORT}" - -# Set up PostgreSQL connection variables - referenced in docker-compose -export ENTE_DB_HOST="${CLOUDRON_POSTGRESQL_HOST}" -export ENTE_DB_PORT="${CLOUDRON_POSTGRESQL_PORT}" -export ENTE_DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE}" -export ENTE_DB_USER="${CLOUDRON_POSTGRESQL_USERNAME}" -export ENTE_DB_PASSWORD="${CLOUDRON_POSTGRESQL_PASSWORD}" - -# Also set standard PostgreSQL variables as backup -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}" - -# Define trap to ensure all processes are killed on exit -SERVER_PID=0 -PUBLIC_SERVER_PID=0 -CADDY_PID=0 -TAIL_PID=0 - -trap 'kill -TERM $TAIL_PID; kill -TERM $SERVER_PID; kill -TERM $PUBLIC_SERVER_PID; kill -TERM $CADDY_PID; exit' TERM INT - -# Start the Museum Server +# Test PostgreSQL connectivity echo "==> Testing PostgreSQL connectivity" -if pg_isready -q; then +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" else - echo "==> WARNING: PostgreSQL is not ready, but proceeding anyway" + echo "==> ERROR: Could not connect to PostgreSQL" + echo "==> Please check your PostgreSQL configuration" + exit 1 fi -# Check if the Museum server exists at the expected location -if [ -f "${MUSEUM_DIR}/museum" ] && [ -x "${MUSEUM_DIR}/museum" ]; then - echo "==> Found Museum server binary at ${MUSEUM_DIR}/museum" +# Start the real Museum server +mkdir -p "${SERVER_DIR}/museum/config" +cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml" + +if [ -f "${SERVER_DIR}/museum/museum" ]; then + echo "==> Found Museum server at ${SERVER_DIR}/museum" - # Start the main API server - cd "${MUSEUM_DIR}" - echo "==> Starting Museum server with config: ${ENTE_CONFIG_FILE}" - nohup ./museum server > "${LOGS_DIR}/museum.log" 2>&1 & - SERVER_PID=$! - echo "==> Museum server started with PID $SERVER_PID" + # Make sure the museum binary is executable + chmod +x "${SERVER_DIR}/museum/museum" - # Wait for server to start - 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}" + # 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 - else - if [ $i -eq 5 ]; then - echo "==> WARNING: API is not responding after several attempts" - echo "==> Last 20 lines of museum.log:" - tail -20 "${LOGS_DIR}/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 - echo "==> Starting Public Albums Museum server" - export ENTE_CONFIG_FILE="${CONFIG_DIR}/public_museum.yaml" - cd "${MUSEUM_DIR}" - echo "==> Starting Public Albums Museum with config: ${ENTE_CONFIG_FILE}" - nohup ./museum server > "${LOGS_DIR}/public_museum.log" 2>&1 & - PUBLIC_SERVER_PID=$! - echo "==> Public Albums server started with PID $PUBLIC_SERVER_PID" - - # Wait for Public Albums server to start - 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 "${LOGS_DIR}/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 + if [ $i -eq 30 ]; then + echo "==> ERROR: Museum server failed to start" + echo "==> Please check logs at /app/data/logs/museum.log" + exit 1 fi done else - echo "==> ERROR: Museum server not found at ${MUSEUM_DIR}/museum" - echo "==> Starting a mock server with Node.js for demonstration purposes" - - # Create a temporary directory for a simple Node.js server - mkdir -p /tmp/mock-server - cd /tmp/mock-server - - # Create a minimal Node.js server file - cat > server.js << 'ENDOFCODE' -const http = require('http'); -const fs = require('fs'); -const path = require('path'); - -// Ensure log directory exists -const logDir = '/app/data/logs'; -fs.mkdirSync(logDir, { recursive: true }); -const logFile = path.join(logDir, 'api_requests.log'); - -// Open log file -fs.writeFileSync(logFile, `API Server started at ${new Date().toISOString()}\n`, { flag: 'a' }); - -// Log function -function log(message) { - const timestamp = new Date().toISOString(); - const logMessage = `${timestamp} - ${message}\n`; - console.log(message); - fs.writeFileSync(logFile, logMessage, { flag: 'a' }); -} - -// Generate random 6-digit code -function generateCode() { - return Math.floor(100000 + Math.random() * 900000).toString(); -} - -// Generate unique numeric ID (for user ID) -function generateNumericId() { - return Math.floor(10000 + Math.random() * 90000); -} - -// Store codes for verification (simple in-memory cache) -const verificationCodes = {}; - -// Create HTTP server -const server = http.createServer((req, res) => { - const url = req.url; - const method = req.method; - - log(`Received ${method} request for ${url}`); - - // Set CORS headers - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); - - // Handle preflight requests - if (method === 'OPTIONS') { - res.statusCode = 200; - res.end(); - return; - } - - // Handle requests based on URL path - if (url === '/health') { - // Health check endpoint - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - time: new Date().toISOString() - })); - } - else if (url.startsWith('/users/srp')) { - // SRP endpoints - just return success for all SRP requests - let body = ''; - - req.on('data', chunk => { - body += chunk.toString(); - }); - - req.on('end', () => { - log(`SRP request received: ${url} with body: ${body}`); - - // Return a standard response for any SRP request - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - id: 12345, - token: "mock-token-12345", - key: { - pubKey: "mockPubKey123456", - encPubKey: "mockEncPubKey123456", - kty: "RSA", - kid: "kid-123456", - alg: "RS256", - verifyKey: "mockVerifyKey123456" - } - })); - }); - } - else if (url === '/users/ott') { - // OTT verification code endpoint - let body = ''; - - req.on('data', chunk => { - body += chunk.toString(); - }); - - req.on('end', () => { - let email = 'user@example.com'; - - // Try to parse email from request if possible - try { - const data = JSON.parse(body); - if (data.email) { - email = data.email; - } - } catch (e) { - try { - // Try to parse as URL-encoded form data - const params = new URLSearchParams(body); - if (params.has('email')) { - email = params.get('email'); - } - } catch (e2) { - // Ignore parsing errors - } - } - - // Generate verification code - const code = generateCode(); - const userId = generateNumericId(); - - // Store the code for this email - verificationCodes[email] = code; - - // Log the code prominently - const codeMessage = `⚠️ VERIFICATION CODE FOR ${email}: ${code}`; - log(codeMessage); - console.log('\n' + codeMessage + '\n'); - - // Current timestamp and expiry - const now = new Date(); - const expiry = new Date(now.getTime() + 3600000); // 1 hour from now - - // Send response with all required fields - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - id: userId, - token: `mock-token-${userId}`, - ott: code, - exp: Math.floor(expiry.getTime() / 1000), - email: email, - createdAt: now.toISOString(), - updatedAt: now.toISOString(), - key: { - pubKey: "mockPubKey123456", - encPubKey: "mockEncPubKey123456", - kty: "RSA", - kid: "kid-123456", - alg: "RS256", - verifyKey: "mockVerifyKey123456" - } - })); - }); - } - else if (url === '/users/verification' || url === '/users/verify-email') { - // Verification endpoint - let body = ''; - - req.on('data', chunk => { - body += chunk.toString(); - }); - - req.on('end', () => { - log("Verification request received with body: " + body); - - // Try to parse the request - let email = 'user@example.com'; - let code = ''; - let isValid = false; - const userId = generateNumericId(); - - try { - const data = JSON.parse(body); - if (data.email) email = data.email; - - // Try to get the verification code from different possible fields - if (data.code) code = data.code; - else if (data.ott) code = data.ott; - - // Check if code matches the stored code or is a test code - if (code && (code === verificationCodes[email] || code === '123456' || code === '261419')) { - isValid = true; - } - } catch (e) { - log(`Error parsing verification request: ${e.message}`); - // For testing, treat as valid - isValid = true; - } - - if (isValid) { - log(`⚠️ VERIFICATION SUCCESSFUL - code: ${code} for ${email}`); - - // Current timestamp - const now = new Date(); - - // Send success response with all required fields - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - id: userId, - token: `mock-token-${userId}`, - email: email, - createdAt: now.toISOString(), - updatedAt: now.toISOString(), - key: { - pubKey: "mockPubKey123456", - encPubKey: "mockEncPubKey123456", - kty: "RSA", - kid: "kid-123456", - alg: "RS256", - verifyKey: "mockVerifyKey123456" - }, - isEmailVerified: true - })); - } else { - log(`⚠️ VERIFICATION FAILED - code: ${code} for ${email}`); - - // Send failure response - res.statusCode = 400; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "error", - message: "Invalid verification code" - })); - } - }); - } - else if (url === '/users/attributes' && method === 'PUT') { - // Handle user attributes update - let body = ''; - - req.on('data', chunk => { - body += chunk.toString(); - }); - - req.on('end', () => { - log(`User attributes update: ${body}`); - - // Send success response - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok" - })); - }); - } - else { - // Default handler for other paths - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - path: url - })); - } -}); - -// Start server -const PORT = 8080; -server.listen(PORT, '0.0.0.0', () => { - log(`Mock API server running at http://0.0.0.0:${PORT}/`); -}); -ENDOFCODE - - # Create a similar server for public albums - mkdir -p /tmp/mock-public-server - cd /tmp/mock-public-server - - cat > server.js << 'ENDOFCODE' -const http = require('http'); -const fs = require('fs'); -const path = require('path'); - -// Ensure log directory exists -const logDir = '/app/data/logs'; -fs.mkdirSync(logDir, { recursive: true }); -const logFile = path.join(logDir, 'public_api_requests.log'); - -// Open log file -fs.writeFileSync(logFile, `Public Albums API Server started at ${new Date().toISOString()}\n`, { flag: 'a' }); - -// Log function -function log(message) { - const timestamp = new Date().toISOString(); - const logMessage = `${timestamp} - ${message}\n`; - console.log(message); - fs.writeFileSync(logFile, logMessage, { flag: 'a' }); -} - -// Create HTTP server -const server = http.createServer((req, res) => { - const url = req.url; - const method = req.method; - - log(`Received ${method} request for ${url}`); - - // Set CORS headers - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - - // Handle preflight requests - if (method === 'OPTIONS') { - res.statusCode = 200; - res.end(); - return; - } - - // Health check endpoint - if (url === '/health') { - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - time: new Date().toISOString() - })); - } - else { - // Default handler for other paths - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ - status: "ok", - path: url - })); - } -}); - -// Start server -const PORT = 8081; -server.listen(PORT, '0.0.0.0', () => { - log(`Mock Public Albums API server running at http://0.0.0.0:${PORT}/`); -}); -ENDOFCODE - - # Set SERVER_PID to 0 for safety - SERVER_PID=0 - - # Make sure logs directory exists - mkdir -p "${LOGS_DIR}" - touch "${LOGS_DIR}/api_requests.log" - chmod 666 "${LOGS_DIR}/api_requests.log" - - # Run the mock server - echo "==> Running mock API server with Node.js" - cd /tmp/mock-server - node server.js > "${LOGS_DIR}/mock_server.log" 2>&1 & - SERVER_PID=$! - echo "==> Mock API server started with PID $SERVER_PID" - - # Wait for it to start - sleep 3 - echo "==> Testing mock API connectivity" - curl -s --max-time 2 --fail http://0.0.0.0:${API_PORT}/health || echo "==> Warning: Mock API server not responding!" - - # Run the public albums mock server - echo "==> Running Public Albums mock server with Node.js" - cd /tmp/mock-public-server - node server.js > "${LOGS_DIR}/public_mock_server.log" 2>&1 & - PUBLIC_SERVER_PID=$! - echo "==> Public Albums mock server started with PID $PUBLIC_SERVER_PID" - - # Wait for it to start - sleep 3 - echo "==> Testing mock Public Albums API connectivity" - curl -s --max-time 2 --fail http://0.0.0.0:${PUBLIC_ALBUMS_PORT}/health || echo "==> Warning: Mock Public Albums API server not responding!" + echo "==> ERROR: Museum server not found at ${SERVER_DIR}/museum" + echo "==> Please install the Museum server manually" + exit 1 fi -# Set up Caddy web server for proxying and serving static files +# Set up Caddy web server echo "==> Setting up Caddy web server" -# Create runtime-config.js file -echo "==> Creating runtime-config.js in writable location" -mkdir -p /app/data/web -cat << EOF > /app/data/web/runtime-config.js -// Runtime configuration for Ente -window.ENTE_CONFIG = { - // Make sure these are properly formatted URLs with protocol and domain - API_URL: 'https://${CLOUDRON_APP_FQDN}/api', - PUBLIC_ALBUMS_URL: 'https://${CLOUDRON_APP_FQDN}/public' -}; +# Create Caddy configuration file +cat > /app/data/Caddyfile << 'EOF' +{ + admin off +} -// Add Node.js polyfills for browser environment -window.process = window.process || {}; -window.process.env = window.process.env || {}; -window.process.nextTick = window.process.nextTick || function(fn) { setTimeout(fn, 0); }; -window.process.browser = true; -window.Buffer = window.Buffer || (function() { return { isBuffer: function() { return false; } }; })(); - -// Next.js environment variables -window.process.env.NEXT_PUBLIC_BASE_URL = 'https://${CLOUDRON_APP_FQDN}'; -window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; -window.process.env.NEXT_PUBLIC_ENTE_PUBLIC_ALBUMS_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/public'; -window.process.env.NEXT_PUBLIC_REACT_APP_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; -window.process.env.REACT_APP_ENTE_ENDPOINT = 'https://${CLOUDRON_APP_FQDN}/api'; - -// Add logging to help with debugging -console.log('Ente runtime config loaded from runtime-config.js with polyfills'); -console.log('process.nextTick available:', !!window.process.nextTick); -console.log('BASE_URL:', window.process.env.NEXT_PUBLIC_BASE_URL); -console.log('API_URL (final):', window.ENTE_CONFIG.API_URL); -console.log('PUBLIC_ALBUMS_URL (final):', window.ENTE_CONFIG.PUBLIC_ALBUMS_URL); -console.log('NEXT_PUBLIC_ENTE_ENDPOINT (final):', window.process.env.NEXT_PUBLIC_ENTE_ENDPOINT); +:80 { + # 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 -chmod 644 /app/data/web/runtime-config.js +# 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 -# Create necessary directories -mkdir -p /app/data/web/photos/static -mkdir -p /app/data/web/photos/_next/static/runtime -mkdir -p /app/data/web/accounts -mkdir -p /app/data/web/auth -mkdir -p /app/data/web/cast +# 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 -# Now create the ente-patches.js file in the properly created directory +# 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...'); @@ -1007,58 +685,6 @@ cat > /app/data/web/photos/static/ente-patches.js << 'ENDPATCHES' } }; - // Add missing crypto methods that SRP might need - if (window.crypto) { - if (!window.crypto.randomBytes) { - window.crypto.randomBytes = function(size) { - const array = new Uint8Array(size); - window.crypto.getRandomValues(array); - return Buffer.from(array); - }; - } - - // Add cryptographic hash functions if needed - if (!window.crypto.createHash) { - window.crypto.createHash = function(algorithm) { - return { - update: function(data) { - this.data = data; - return this; - }, - digest: async function(encoding) { - // Use the SubtleCrypto API for actual hashing - const dataBuffer = typeof this.data === 'string' ? - new TextEncoder().encode(this.data) : - this.data; - - let hashBuffer; - try { - if (algorithm === 'sha256') { - hashBuffer = await window.crypto.subtle.digest('SHA-256', dataBuffer); - } else if (algorithm === 'sha1') { - hashBuffer = await window.crypto.subtle.digest('SHA-1', dataBuffer); - } else { - console.error('Unsupported hash algorithm:', algorithm); - return Buffer.alloc(32); // Return empty buffer as fallback - } - - const hashArray = Array.from(new Uint8Array(hashBuffer)); - - if (encoding === 'hex') { - return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - } - - return Buffer.from(hashArray); - } catch (e) { - console.error('Hash calculation failed:', e); - return Buffer.alloc(32); // Return empty buffer as fallback - } - } - }; - }; - } - } - // Patch the SRP implementation for browser compatibility if (!window.process) { window.process = { @@ -1077,46 +703,22 @@ cat > /app/data/web/photos/static/ente-patches.js << 'ENDPATCHES' })(); ENDPATCHES -# Create a patched runtime configuration for the Ente web app -cat > /app/data/web/photos/static/runtime-config.js << 'ENDCONFIG' -// 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; - - 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); - } -})(); -ENDCONFIG +# 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 basic sample index.html for testing -cat > /app/data/web/photos/index.html << 'EOT' +# 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 - Ente Photos - + Ente $app