#!/bin/bash set -e # Declare that we are using 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_FQDN=${CLOUDRON_APP_FQDN}" echo "==> Environment: Internal IP=$(hostname -i || echo 'unknown')" # 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/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 echo "==> Created all necessary directories" # Debugging information echo "==> Directory listing of /app/data:" ls -la /app/data echo "==> Directory listing of /app/data/web:" 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 SERVER_DIR="/app/data/ente/server" 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" # Clone the repository if it doesn't exist if [ ! -d "$SERVER_DIR/museum" ]; then # Use HTTPS instead of Git protocol to avoid authentication issues curl -L -o museum.zip https://github.com/ente-io/museum/archive/refs/heads/main.zip unzip -q museum.zip mv museum-main museum cd museum else cd museum # Use HTTPS instead of Git pull curl -L -o main.zip https://github.com/ente-io/museum/archive/refs/heads/main.zip unzip -q main.zip cp -R museum-main/* ./ rm -rf museum-main main.zip 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" echo "==> Downloading from: $RELEASE_URL" 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 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 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 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 acme: enabled: false # Cloudron handles SSL 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 EOF echo "==> Created museum.yaml configuration" cp /app/data/museum.yaml "${SERVER_DIR}/museum/config/museum.yaml" 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" # 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" else echo "==> ERROR: Could not connect to PostgreSQL" echo "==> Please check your PostgreSQL configuration" echo "==> Continuing anyway, but errors may occur later" 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 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 curl -L -o photos.zip https://github.com/ente-io/photos/archive/refs/heads/main.zip unzip -q photos.zip mv photos-main photos cd photos else cd photos # Use HTTPS instead of Git pull curl -L -o main.zip https://github.com/ente-io/photos/archive/refs/heads/main.zip unzip -q main.zip cp -R photos-main/* ./ rm -rf photos-main main.zip 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" ]; 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 "==> Continuing anyway, but errors may occur later" fi done else echo "==> ERROR: Museum server not found at ${SERVER_DIR}/museum" echo "==> Please install the Museum server manually" echo "==> Continuing anyway, but errors may occur later" 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
End-to-end encrypted photo storage and sharing platform.
This is a placeholder page until the proper Ente build is created.
Check API Status