5 Commits

Author SHA1 Message Date
Your Name
b1e8df29e7 Allow runtime S3 configuration overrides 2025-09-29 20:59:57 -06:00
Your Name
176d23c086 Fix SPA asset routing for web apps 2025-09-29 20:47:07 -06:00
Andreas Dueren
aba8af9bb4 Force rebuild: Update asset routing with version bump 2025-08-01 14:02:07 -06:00
Andreas Dueren
7fc40ce970 Bump version to 0.1.81 for asset routing fix 2025-08-01 13:56:09 -06:00
Andreas Dueren
a0af6ec84c Fix static asset routing for all web apps
- Add specific _next asset routes for accounts, auth, cast apps
- Add image asset routes for each app
- Ensure each app's assets are served from correct directory
- Keep photos app routing unchanged

Should fix accounts/auth/cast apps loading issues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 13:55:53 -06:00
4 changed files with 405 additions and 126 deletions

View File

@@ -7,7 +7,7 @@
"contactEmail": "contact@ente.io",
"tagline": "Open Source End-to-End Encrypted Photos & Authentication",
"upstreamVersion": "1.0.0",
"version": "0.1.79",
"version": "0.1.119",
"healthCheckPath": "/ping",
"httpPort": 3080,
"memoryLimit": 1073741824,
@@ -35,4 +35,4 @@
"manifestVersion": 2,
"minBoxVersion": "8.1.0",
"website": "https://ente.io"
}
}

View File

@@ -120,7 +120,7 @@ WORKDIR /app/code
# Clone the ente repository during build (for the Museum server)
RUN git clone --depth=1 https://github.com/ente-io/ente.git . && \
sed -i 's/go 1.23/go 1.24.1/' server/go.mod && \
sed -i 's/go 1.23/go 1.24/' server/go.mod && \
mkdir -p /app/data/go && \
cp -r server/go.mod server/go.sum /app/data/go/ && \
chmod 777 /app/data/go/go.mod /app/data/go/go.sum
@@ -172,4 +172,4 @@ EXPOSE 3080
EXPOSE 8080
# Start the application
CMD ["/app/pkg/start.sh"]
CMD ["/app/pkg/start.sh"]

View File

@@ -7,19 +7,15 @@ Before you can use Ente, you need to configure an S3-compatible storage service:
1. Go to your Cloudron dashboard
2. Click on your Ente app
3. Click on "Terminal"
4. Edit the S3 configuration template:
4. Edit the S3 configuration file:
```
nano /app/data/config/s3.env.template
nano /app/data/config/s3.env
```
5. Fill in your S3 credentials (AWS S3, MinIO, DigitalOcean Spaces, etc.)
6. Save the file and rename it:
```
mv /app/data/config/s3.env.template /app/data/config/s3.env
```
7. Restart your Ente app from the Cloudron dashboard
5. Uncomment the variables you need and fill in your S3 credentials (AWS S3, MinIO, DigitalOcean Spaces, etc.)
6. Save the file and restart your Ente app from the Cloudron dashboard
## Next Steps
1. Once S3 is configured, visit your app URL to create an admin account
2. Configure your mobile apps to use your custom self-hosted server (Settings → Advanced → Custom Server)
3. Enjoy your private, end-to-end encrypted photo storage!
3. Enjoy your private, end-to-end encrypted photo storage!

509
start.sh
View File

@@ -77,32 +77,109 @@ fi
# ===============================================
log "INFO" "Setting up configuration"
# S3 configuration - HARDCODED VALUES
S3_ACCESS_KEY="QZ5M3VMBUHDTIFDFCD8E"
S3_SECRET_KEY="pz1eHYjU1NwAbbruedc7swzCuszd57p1rGSFVzjv"
S3_ENDPOINT="https://s3.eu-central-2.wasabisys.com"
S3_REGION="eu-central-2"
S3_BUCKET="ente-due-ren"
if [ -n "$CLOUDRON_APP_ORIGIN" ]; then
BASE_URL="$CLOUDRON_APP_ORIGIN"
else
BASE_URL="https://${CLOUDRON_APP_DOMAIN:-localhost}"
fi
RP_ID="${CLOUDRON_APP_FQDN:-${CLOUDRON_APP_DOMAIN:-localhost}}"
log "INFO" "Using hardcoded S3 configuration"
# S3 configuration (overridable post-install)
DEFAULT_S3_ACCESS_KEY="QZ5M3VMBUHDTIFDFCD8E"
DEFAULT_S3_SECRET_KEY="pz1eHYjU1NwAbbruedc7swzCuszd57p1rGSFVzjv"
DEFAULT_S3_ENDPOINT="https://s3.eu-central-2.wasabisys.com"
DEFAULT_S3_REGION="eu-central-2"
DEFAULT_S3_BUCKET="ente-due-ren"
S3_CONFIG_DIR="/app/data/config"
S3_CONFIG_FILE="$S3_CONFIG_DIR/s3.env"
mkdir -p "$S3_CONFIG_DIR"
if [ -f "$S3_CONFIG_FILE" ]; then
log "INFO" "Loading S3 configuration overrides from $S3_CONFIG_FILE"
# shellcheck disable=SC1090
set -a
. "$S3_CONFIG_FILE"
set +a
else
log "INFO" "S3 configuration file not found, writing template to $S3_CONFIG_FILE"
cat > "$S3_CONFIG_FILE" << 'EOF'
# S3 configuration overrides for Ente on Cloudron.
# Uncomment and set any of the variables below to override the packaged defaults.
# After editing this file, restart the Ente app from the Cloudron dashboard.
#
#S3_ACCESS_KEY=
#S3_SECRET_KEY=
#S3_ENDPOINT=
#S3_REGION=
#S3_BUCKET=
EOF
chown cloudron:cloudron "$S3_CONFIG_FILE" || true
fi
S3_ACCESS_KEY="${S3_ACCESS_KEY:-$DEFAULT_S3_ACCESS_KEY}"
S3_SECRET_KEY="${S3_SECRET_KEY:-$DEFAULT_S3_SECRET_KEY}"
S3_ENDPOINT="${S3_ENDPOINT:-$DEFAULT_S3_ENDPOINT}"
S3_REGION="${S3_REGION:-$DEFAULT_S3_REGION}"
S3_BUCKET="${S3_BUCKET:-$DEFAULT_S3_BUCKET}"
S3_ENDPOINT_HOST="${S3_ENDPOINT#https://}"
S3_ENDPOINT_HOST="${S3_ENDPOINT_HOST#http://}"
if [ -z "$S3_ACCESS_KEY" ] || [ -z "$S3_SECRET_KEY" ] || [ -z "$S3_ENDPOINT" ] || [ -z "$S3_REGION" ] || [ -z "$S3_BUCKET" ]; then
log "ERROR" "Incomplete S3 configuration detected. Please update $S3_CONFIG_FILE or set environment variables."
exit 1
fi
log "INFO" "Using S3 configuration"
log "INFO" "S3 Endpoint: $S3_ENDPOINT"
log "INFO" "S3 Region: $S3_REGION"
log "INFO" "S3 Bucket: $S3_BUCKET"
ENABLE_SMTP=${ENABLE_SMTP:-false}
SMTP_HOST=""
SMTP_PORT=""
SMTP_ENCRYPTION=""
if [ "$ENABLE_SMTP" = "true" ]; then
SMTP_HOST="${CLOUDRON_MAIL_SMTP_SERVER:-}"
SMTP_PORT="${CLOUDRON_MAIL_SMTP_PORT:-25}"
SMTP_ENCRYPTION="${CLOUDRON_MAIL_SMTP_ENCRYPTION:-}"
if [ -n "${CLOUDRON_MAIL_SMTPS_PORT:-}" ]; then
SMTP_PORT="${CLOUDRON_MAIL_SMTPS_PORT}"
SMTP_ENCRYPTION="tls"
fi
if [ "${SMTP_ENCRYPTION}" = "tls" ] && [ -n "${CLOUDRON_MAIL_DOMAIN:-}" ]; then
SMTP_HOST="mail.${CLOUDRON_MAIL_DOMAIN}"
fi
else
log "INFO" "EMAIL_DISABLED: Skipping SMTP configuration (ENABLE_SMTP=false)"
fi
SMTP_SENDER_NAME="${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Ente}"
# Museum server configuration - create configurations directory structure
MUSEUM_CONFIG_DIR="/app/data/ente/server/configurations"
MUSEUM_CONFIG="$MUSEUM_CONFIG_DIR/local.yaml"
mkdir -p "$MUSEUM_CONFIG_DIR"
if [ ! -f "$MUSEUM_CONFIG" ]; then
log "INFO" "Creating Museum server configuration"
cat > "$MUSEUM_CONFIG" << EOF
log "INFO" "Rendering Museum server configuration"
cat > "$MUSEUM_CONFIG" << EOF
# Museum server configuration
# Server settings
port: 8080
host: 0.0.0.0
log_level: info
log-file: ""
http:
port: 8080
use-tls: false
apps:
public-albums: "${BASE_URL}/photos"
public-locker: "${BASE_URL}/photos"
accounts: "${BASE_URL}/accounts"
cast: "${BASE_URL}/cast"
family: "${BASE_URL}/photos"
custom-domain:
cname: "${CLOUDRON_APP_DOMAIN:-localhost}"
# Database configuration
db:
@@ -113,48 +190,103 @@ db:
password: ${CLOUDRON_POSTGRESQL_PASSWORD}
sslmode: disable
# CORS settings
cors:
allow_origins:
- "*"
# S3 storage configuration
s3:
endpoint: "${S3_ENDPOINT}"
region: "${S3_REGION}"
access_key: "${S3_ACCESS_KEY}"
secret_key: "${S3_SECRET_KEY}"
bucket: "${S3_BUCKET}"
# For Wasabi, we need path style URLs
use_path_style_urls: true
are_local_buckets: false
use_path_style_urls: true
hot_storage:
primary: wasabi-eu-central-2-v3
secondary: wasabi-eu-central-2-v3
b2-eu-cen:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
wasabi-eu-central-2:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
wasabi-eu-central-2-v3:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
compliance: false
wasabi-eu-central-2-derived:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
scw-eu-fr:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
scw-eu-fr-locked:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
scw-eu-fr-v3:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
b5:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
b6:
key: "${S3_ACCESS_KEY}"
secret: "${S3_SECRET_KEY}"
endpoint: "${S3_ENDPOINT_HOST}"
region: "${S3_REGION}"
bucket: "${S3_BUCKET}"
derived-storage: wasabi-eu-central-2-v3
# Email settings
email:
enabled: true
host: "${CLOUDRON_MAIL_SMTP_SERVER:-localhost}"
port: ${CLOUDRON_MAIL_SMTP_PORT:-25}
smtp:
host: "${SMTP_HOST}"
port: "${SMTP_PORT}"
username: "${CLOUDRON_MAIL_SMTP_USERNAME:-}"
password: "${CLOUDRON_MAIL_SMTP_PASSWORD:-}"
from: "${CLOUDRON_MAIL_FROM:-no-reply@${CLOUDRON_APP_DOMAIN:-localhost}}"
email: "${CLOUDRON_MAIL_FROM:-no-reply@${CLOUDRON_APP_FQDN:-localhost}}"
sender-name: "${SMTP_SENDER_NAME}"
encryption: "${SMTP_ENCRYPTION}"
internal:
silent: true
disable-registration: false
# WebAuthn configuration for passkey support
webauthn:
rpid: "${CLOUDRON_APP_DOMAIN:-localhost}"
rpid: "${RP_ID}"
rporigins:
- "https://${CLOUDRON_APP_DOMAIN:-localhost}"
- "https://${RP_ID}"
# Additional Museum server configuration
http:
allowed_hosts:
- "${CLOUDRON_APP_DOMAIN:-localhost}"
base_url: "https://${CLOUDRON_APP_DOMAIN:-localhost}"
key:
encryption: yvmG/RnzKrbCb9L3mgsmoxXr9H7i2Z4qlbT0mL3ln4w=
hash: KXYiG07wC7GIgvCSdg+WmyWdXDAn6XKYJtp/wkEU7x573+byBRAYtpTP0wwvi8i/4l37uicX1dVTUzwH3sLZyw==
jwt:
secret: i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8=
jobs:
cron:
skip: true
EOF
chmod 600 "$MUSEUM_CONFIG"
log "INFO" "Created Museum configuration at ${MUSEUM_CONFIG}"
else
log "INFO" "Museum configuration already exists"
fi
chmod 600 "$MUSEUM_CONFIG"
log "INFO" "Wrote Museum configuration to ${MUSEUM_CONFIG}"
# ===============================================
# Database check
@@ -180,7 +312,7 @@ fi
# ===============================================
MUSEUM_BIN="/app/data/ente/server/museum"
MUSEUM_LOG="/app/data/logs/museum.log"
USE_PLACEHOLDER=false
USE_PLACEHOLDER=${FORCE_PLACEHOLDER:-false}
log "INFO" "Setting up Museum server binary"
@@ -207,7 +339,7 @@ else
log "INFO" "Migration files already exist or source not available"
fi
# Copy web templates to Museum working directory
# Copy web templates to Museum working directory
MUSEUM_WEB_TEMPLATES_DIR="/app/data/ente/server/web-templates"
REPO_WEB_TEMPLATES_DIR="/app/data/ente/repository/server/web-templates"
if [ ! -d "$MUSEUM_WEB_TEMPLATES_DIR" ] && [ -d "$REPO_WEB_TEMPLATES_DIR" ]; then
@@ -218,6 +350,17 @@ else
log "INFO" "Web templates already exist or source not available"
fi
# Copy mail templates for transactional emails
MUSEUM_MAIL_TEMPLATES_DIR="/app/data/ente/server/mail-templates"
REPO_MAIL_TEMPLATES_DIR="/app/data/ente/repository/server/mail-templates"
if [ ! -d "$MUSEUM_MAIL_TEMPLATES_DIR" ] && [ -d "$REPO_MAIL_TEMPLATES_DIR" ]; then
log "INFO" "Copying mail templates"
cp -r "$REPO_MAIL_TEMPLATES_DIR" "$MUSEUM_MAIL_TEMPLATES_DIR"
log "INFO" "Copied mail templates to $MUSEUM_MAIL_TEMPLATES_DIR"
else
log "INFO" "Mail templates already exist or source not available"
fi
# Check if Museum binary exists and is valid
log "INFO" "Checking for Museum binary at: $MUSEUM_BIN"
if [ -f "$MUSEUM_BIN" ]; then
@@ -247,7 +390,84 @@ fi
# ===============================================
# Web Application Setup
# ===============================================
log "INFO" "Web applications are pre-built with relative API endpoint /api"
log "INFO" "Setting up web applications with writable directory"
# Copy web apps to writable data directory first
WRITABLE_WEB_DIR="/app/data/web"
if [ ! -d "$WRITABLE_WEB_DIR" ]; then
log "INFO" "Copying web applications to writable directory"
mkdir -p "$WRITABLE_WEB_DIR"
cp -r /app/web/* "$WRITABLE_WEB_DIR/"
chown -R cloudron:cloudron "$WRITABLE_WEB_DIR"
log "INFO" "Web applications copied to $WRITABLE_WEB_DIR"
else
log "INFO" "Web applications already exist in writable directory"
fi
# Fix API endpoint configuration in built JavaScript files
log "INFO" "Updating API endpoint configuration in web apps"
ACTUAL_ENDPOINT="${BASE_URL}/api"
log "INFO" "Setting API endpoint to: $ACTUAL_ENDPOINT"
declare -a PLACEHOLDER_ENDPOINTS=(
"https://example.com/api"
"https://placeholder.invalid/api"
"https://api.ente.io"
"https://api.ente.io/api"
)
declare -A HOST_REWRITES=(
["https://accounts.ente.io"]="${BASE_URL}/accounts"
["https://auth.ente.io"]="${BASE_URL}/auth"
["https://cast.ente.io"]="${BASE_URL}/cast"
["https://photos.ente.io"]="${BASE_URL}/photos"
["https://web.ente.io"]="${BASE_URL}/photos"
)
for webapp in photos accounts auth cast; do
WEB_DIR="$WRITABLE_WEB_DIR/${webapp}"
if [ -d "$WEB_DIR" ]; then
log "INFO" "Processing ${webapp} app for endpoint rewrites"
for placeholder in "${PLACEHOLDER_ENDPOINTS[@]}"; do
find "$WEB_DIR" -name "*.js" -type f -exec sed -i "s|${placeholder}|${ACTUAL_ENDPOINT}|g" {} \;
done
for source in "${!HOST_REWRITES[@]}"; do
target="${HOST_REWRITES[$source]}"
find "$WEB_DIR" -name "*.js" -type f -exec sed -i "s|${source}|${target}|g" {} \;
done
log "INFO" "Endpoint rewrites complete for ${webapp}"
else
log "WARN" "Web directory not found for ${webapp}"
fi
done
# ===============================================
# Museum log highlighter (extract OTTs from logs)
# ===============================================
prepare_ott_highlighter() {
cat > "/app/data/ente/server/ott-log-highlight.js" << 'EOF'
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const shouldHighlight = (line) => {
if (!line) return false;
const lower = line.toLowerCase();
if (lower.includes('added ott')) return true;
if (lower.includes('ott"') || lower.includes(' ott ')) return true;
return false;
};
rl.on('line', (line) => {
process.stdout.write(line + '\n');
if (shouldHighlight(line)) {
const trimmed = line.trim();
process.stdout.write('============================================================\n');
process.stdout.write(`HIGHLIGHT: ${trimmed}\n`);
process.stdout.write('============================================================\n');
}
});
EOF
}
# ===============================================
# Node.js Placeholder Server
@@ -348,18 +568,55 @@ const apiHandlers = {
}));
log('Health check request - responded with status OK');
},
// User verification endpoint
'/api/users/verify': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log('User verify request - responding with success');
res.end(JSON.stringify({
success: true,
isValidEmail: true,
isAvailable: true,
isVerified: true,
canCreateAccount: true
}));
// User verification endpoint (returns minimal structure expected by UI)
'/api/users/verify-email': (req, res) => {
const buildResponse = (emailAddress) => {
const email = emailAddress || 'unknown@example.com';
const stableId = Math.abs(Buffer.from(email).reduce((acc, byte) => (acc * 31 + byte) % 100000, 17)) || 1;
return {
id: stableId,
token: `placeholder-token-${stableId}`,
encryptedToken: `placeholder-encrypted-token-${stableId}`,
accountsUrl: `${process.env.CLOUDRON_APP_ORIGIN || 'https://example.com'}/accounts`,
twoFactorSessionID: undefined,
twoFactorSessionIDV2: undefined,
passkeySessionID: undefined,
keyAttributes: undefined
};
};
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, message: 'Method not allowed' }));
return;
}
let rawBody = '';
req.on('data', chunk => { rawBody += chunk.toString(); });
req.on('end', () => {
let email = 'unknown@example.com';
let ott = 'unknown';
try {
const payload = JSON.parse(rawBody || '{}');
if (payload.email) {
email = payload.email;
}
if (payload.ott) {
ott = payload.ott;
}
} catch (err) {
log(`Failed to parse verify-email request body: ${err.message}`);
}
const responsePayload = buildResponse(email);
log('============================================================');
log(`HIGHLIGHT: Verifying OTT ${ott} for ${email}`);
log('============================================================');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(responsePayload));
});
},
// User login endpoint
@@ -377,7 +634,7 @@ const apiHandlers = {
token: 'placeholder-jwt-token-' + Date.now(),
user: {
id: 1,
email: 'placeholder@' + (process.env.CLOUDRON_APP_DOMAIN || 'localhost'),
email: 'placeholder@example.com',
name: 'Placeholder User'
}
}));
@@ -406,7 +663,7 @@ const apiHandlers = {
token: 'placeholder-jwt-token-' + Date.now(),
user: {
id: 1,
email: 'placeholder@' + (process.env.CLOUDRON_APP_DOMAIN || 'localhost'),
email: 'placeholder@example.com',
name: 'New User'
}
}));
@@ -419,6 +676,54 @@ const apiHandlers = {
}));
}
},
// OTT endpoint
'/users/ott': (req, res) => {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, message: 'Method not allowed' }));
return;
}
let body = '';
req.on('data', chunk => { body += chunk.toString(); });
req.on('end', () => {
let email = 'unknown@example.com';
try {
const payload = JSON.parse(body || '{}');
if (payload.email) {
email = payload.email;
}
} catch (err) {
log(`Failed to parse OTT request body: ${err.message}`);
}
const ott = ('' + Math.floor(100000 + Math.random() * 900000)).slice(-6);
log('============================================================');
log(`HIGHLIGHT: Generated OTT ${ott} for ${email}`);
log('============================================================');
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, ott, email }));
});
},
'/api/users/ott': (req, res) => {
apiHandlers['/users/ott'](req, res);
},
'/users/verify-email': (req, res) => {
apiHandlers['/api/users/verify-email'](req, res);
},
'/api/users/verify': (req, res) => {
apiHandlers['/api/users/verify-email'](req, res);
},
'/users/verify': (req, res) => {
apiHandlers['/api/users/verify-email'](req, res);
},
'/ping': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
log('Ping request - responding with status OK');
res.end(JSON.stringify({ status: 'OK', server: 'Ente Placeholder', time: new Date().toISOString() }));
},
// Files endpoint
'/api/files': (req, res) => {
@@ -536,7 +841,7 @@ EOF
SUCCESS=false
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if curl -s http://localhost:8080/health > /dev/null 2>&1; then
if curl -s http://localhost:8080/ping > /dev/null 2>&1; then
log "INFO" "Node.js placeholder server started successfully"
SUCCESS=true
break
@@ -568,10 +873,12 @@ if [ "$USE_PLACEHOLDER" = true ]; then
create_nodejs_placeholder
else
log "INFO" "Starting actual Museum server"
prepare_ott_highlighter
cd /app/data/ente/server
"$MUSEUM_BIN" > "$MUSEUM_LOG" 2>&1 &
export ENVIRONMENT="${MUSEUM_ENVIRONMENT:-local}"
stdbuf -oL "$MUSEUM_BIN" 2>&1 | node ott-log-highlight.js | tee -a "$MUSEUM_LOG" &
MUSEUM_PID=$!
log "INFO" "Started Museum server with PID: $MUSEUM_PID"
log "INFO" "Started Museum server (pipeline PID: $MUSEUM_PID)"
# Wait for the server to start
MAX_ATTEMPTS=30
@@ -579,7 +886,7 @@ else
SUCCESS=false
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if curl -s http://localhost:8080/health > /dev/null 2>&1; then
if curl -s http://localhost:8080/ping > /dev/null 2>&1; then
log "INFO" "Museum server started successfully"
SUCCESS=true
break
@@ -591,13 +898,15 @@ else
if [ "$SUCCESS" = false ]; then
log "ERROR" "Museum server failed to start within $MAX_ATTEMPTS seconds"
if ps -p "$MUSEUM_PID" > /dev/null 2>&1; then
log "INFO" "Stopping Museum server pipeline"
kill "$MUSEUM_PID" || true
fi
log "ERROR" "Last 20 lines of museum.log:"
tail -n 20 "$MUSEUM_LOG" | while read -r line; do
log "ERROR" " $line"
done
log "WARN" "Falling back to Node.js placeholder server"
create_nodejs_placeholder
exit 1
fi
fi
@@ -633,7 +942,7 @@ cat > "$CADDY_CONFIG" << EOF
respond 204
}
# API endpoints with CORS - strip /api prefix before forwarding
# API endpoints with CORS
handle_path /api/* {
reverse_proxy localhost:8080 {
header_up Host {http.request.host}
@@ -657,60 +966,34 @@ cat > "$CADDY_CONFIG" << EOF
}
}
# Health check endpoints
# Health check endpoint
handle /health {
reverse_proxy localhost:8080
}
handle /ping {
reverse_proxy localhost:8080
handle /images/* {
rewrite * /photos{path}
root * /app/data/web
file_server
}
# Static files for Next.js assets from all apps
# Static files for Next.js assets shared across apps
handle /_next/* {
@photosNext path /_next/*
handle @photosNext {
root * /app/web/photos
file_server
}
root * /app/data/web
try_files photos{path} accounts{path} auth{path} cast{path} {path}
file_server
header {
Cache-Control "public, max-age=31536000"
Access-Control-Allow-Origin "*"
}
}
# Photos app
handle_path /photos/* {
root * /app/web/photos
try_files {path} /index.html
file_server
}
# Accounts app
handle_path /accounts/* {
root * /app/web/accounts
try_files {path} /index.html
# Default to serve SPA assets
handle {
root * /app/data/web
try_files {path}/index.html {path} /photos/index.html
file_server
}
# Auth app
handle_path /auth/* {
root * /app/web/auth
try_files {path} /index.html
file_server
}
# Cast app
handle_path /cast/* {
root * /app/web/cast
try_files {path} /index.html
file_server
}
# Root redirect
handle / {
redir /photos/ permanent
}
}
EOF
@@ -752,7 +1035,7 @@ cat > /app/data/SETUP-INSTRUCTIONS.md << EOF
## Configuration
1. **S3 Storage**: Edit the configuration file at \`/app/data/s3.env\` with your S3-compatible storage credentials.
1. **S3 Storage**: Edit the configuration file at \`/app/data/config/s3.env\` (uncomment lines and add your values) with your S3-compatible storage credentials.
2. **Museum Server**: The server configuration is at \`/app/data/ente/server/museum.yaml\` if you need to customize settings.
@@ -765,10 +1048,10 @@ cat > /app/data/SETUP-INSTRUCTIONS.md << EOF
The following web applications are available:
- Photos: https://${CLOUDRON_APP_DOMAIN}/photos/
- Accounts: https://${CLOUDRON_APP_DOMAIN}/accounts/
- Auth: https://${CLOUDRON_APP_DOMAIN}/auth/
- Cast: https://${CLOUDRON_APP_DOMAIN}/cast/
- Photos: https://${CLOUDRON_APP_FQDN}/photos/
- Accounts: https://${CLOUDRON_APP_FQDN}/accounts/
- Auth: https://${CLOUDRON_APP_FQDN}/auth/
- Cast: https://${CLOUDRON_APP_FQDN}/cast/
## Support
@@ -795,4 +1078,4 @@ fi
log "INFO" "Ente Cloudron app startup complete"
# Keep the script running to prevent container exit
exec tail -f "$MUSEUM_LOG"
exec tail -f "$MUSEUM_LOG"