6 Commits

Author SHA1 Message Date
Your Name
8d6fc6fde0 Document S3 examples and refresh template 2025-09-29 21:18:19 -06:00
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 427 additions and 126 deletions

View File

@@ -7,7 +7,7 @@
"contactEmail": "contact@ente.io", "contactEmail": "contact@ente.io",
"tagline": "Open Source End-to-End Encrypted Photos & Authentication", "tagline": "Open Source End-to-End Encrypted Photos & Authentication",
"upstreamVersion": "1.0.0", "upstreamVersion": "1.0.0",
"version": "0.1.79", "version": "0.1.121",
"healthCheckPath": "/ping", "healthCheckPath": "/ping",
"httpPort": 3080, "httpPort": 3080,
"memoryLimit": 1073741824, "memoryLimit": 1073741824,

View File

@@ -120,7 +120,7 @@ WORKDIR /app/code
# Clone the ente repository during build (for the Museum server) # Clone the ente repository during build (for the Museum server)
RUN git clone --depth=1 https://github.com/ente-io/ente.git . && \ 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 && \ mkdir -p /app/data/go && \
cp -r server/go.mod server/go.sum /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 chmod 777 /app/data/go/go.mod /app/data/go/go.sum

View File

@@ -7,16 +7,12 @@ Before you can use Ente, you need to configure an S3-compatible storage service:
1. Go to your Cloudron dashboard 1. Go to your Cloudron dashboard
2. Click on your Ente app 2. Click on your Ente app
3. Click on "Terminal" 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.) 5. Uncomment the variables you need and fill in your S3 credentials (AWS S3, Cloudflare R2, MinIO, etc.). The file includes commented examples for the previous Wasabi defaults and a generic Cloudflare R2 setup.
6. Save the file and rename it: 6. Save the file and restart your Ente app from the Cloudron dashboard
```
mv /app/data/config/s3.env.template /app/data/config/s3.env
```
7. Restart your Ente app from the Cloudron dashboard
## Next Steps ## Next Steps

523
start.sh
View File

@@ -77,32 +77,131 @@ fi
# =============================================== # ===============================================
log "INFO" "Setting up configuration" log "INFO" "Setting up configuration"
# S3 configuration - HARDCODED VALUES if [ -n "$CLOUDRON_APP_ORIGIN" ]; then
S3_ACCESS_KEY="QZ5M3VMBUHDTIFDFCD8E" BASE_URL="$CLOUDRON_APP_ORIGIN"
S3_SECRET_KEY="pz1eHYjU1NwAbbruedc7swzCuszd57p1rGSFVzjv" else
S3_ENDPOINT="https://s3.eu-central-2.wasabisys.com" BASE_URL="https://${CLOUDRON_APP_DOMAIN:-localhost}"
S3_REGION="eu-central-2" fi
S3_BUCKET="ente-due-ren" 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"
write_default_s3_template() {
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.
#
# Example (previous Wasabi defaults bundled with this package):
#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
#
# Example (Cloudflare R2 — replace placeholders):
#S3_ACCESS_KEY=R2_ACCESS_KEY
#S3_SECRET_KEY=R2_SECRET_KEY
#S3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
#S3_REGION=auto
#S3_BUCKET=<bucket-name>
#
#S3_ACCESS_KEY=
#S3_SECRET_KEY=
#S3_ENDPOINT=
#S3_REGION=
#S3_BUCKET=
EOF
chown cloudron:cloudron "$S3_CONFIG_FILE" || true
}
mkdir -p "$S3_CONFIG_DIR"
if [ -f "$S3_CONFIG_FILE" ]; then
if ! grep -q "previous Wasabi defaults" "$S3_CONFIG_FILE" && ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$S3_CONFIG_FILE"; then
log "INFO" "Refreshing S3 configuration template with example values"
write_default_s3_template
fi
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"
write_default_s3_template
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 Endpoint: $S3_ENDPOINT"
log "INFO" "S3 Region: $S3_REGION" log "INFO" "S3 Region: $S3_REGION"
log "INFO" "S3 Bucket: $S3_BUCKET" 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 server configuration - create configurations directory structure
MUSEUM_CONFIG_DIR="/app/data/ente/server/configurations" MUSEUM_CONFIG_DIR="/app/data/ente/server/configurations"
MUSEUM_CONFIG="$MUSEUM_CONFIG_DIR/local.yaml" MUSEUM_CONFIG="$MUSEUM_CONFIG_DIR/local.yaml"
mkdir -p "$MUSEUM_CONFIG_DIR" mkdir -p "$MUSEUM_CONFIG_DIR"
if [ ! -f "$MUSEUM_CONFIG" ]; then log "INFO" "Rendering Museum server configuration"
log "INFO" "Creating Museum server configuration" cat > "$MUSEUM_CONFIG" << EOF
cat > "$MUSEUM_CONFIG" << EOF
# Museum server configuration # Museum server configuration
# Server settings # Server settings
port: 8080 log-file: ""
host: 0.0.0.0 http:
log_level: info 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 # Database configuration
db: db:
@@ -113,48 +212,103 @@ db:
password: ${CLOUDRON_POSTGRESQL_PASSWORD} password: ${CLOUDRON_POSTGRESQL_PASSWORD}
sslmode: disable sslmode: disable
# CORS settings
cors:
allow_origins:
- "*"
# S3 storage configuration # S3 storage configuration
s3: 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 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 settings
email: smtp:
enabled: true host: "${SMTP_HOST}"
host: "${CLOUDRON_MAIL_SMTP_SERVER:-localhost}" port: "${SMTP_PORT}"
port: ${CLOUDRON_MAIL_SMTP_PORT:-25}
username: "${CLOUDRON_MAIL_SMTP_USERNAME:-}" username: "${CLOUDRON_MAIL_SMTP_USERNAME:-}"
password: "${CLOUDRON_MAIL_SMTP_PASSWORD:-}" 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 configuration for passkey support
webauthn: webauthn:
rpid: "${CLOUDRON_APP_DOMAIN:-localhost}" rpid: "${RP_ID}"
rporigins: rporigins:
- "https://${CLOUDRON_APP_DOMAIN:-localhost}" - "https://${RP_ID}"
# Additional Museum server configuration key:
http: encryption: yvmG/RnzKrbCb9L3mgsmoxXr9H7i2Z4qlbT0mL3ln4w=
allowed_hosts: hash: KXYiG07wC7GIgvCSdg+WmyWdXDAn6XKYJtp/wkEU7x573+byBRAYtpTP0wwvi8i/4l37uicX1dVTUzwH3sLZyw==
- "${CLOUDRON_APP_DOMAIN:-localhost}"
base_url: "https://${CLOUDRON_APP_DOMAIN:-localhost}" jwt:
secret: i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8=
jobs:
cron:
skip: true
EOF EOF
chmod 600 "$MUSEUM_CONFIG" chmod 600 "$MUSEUM_CONFIG"
log "INFO" "Created Museum configuration at ${MUSEUM_CONFIG}" log "INFO" "Wrote Museum configuration to ${MUSEUM_CONFIG}"
else
log "INFO" "Museum configuration already exists"
fi
# =============================================== # ===============================================
# Database check # Database check
@@ -180,7 +334,7 @@ fi
# =============================================== # ===============================================
MUSEUM_BIN="/app/data/ente/server/museum" MUSEUM_BIN="/app/data/ente/server/museum"
MUSEUM_LOG="/app/data/logs/museum.log" MUSEUM_LOG="/app/data/logs/museum.log"
USE_PLACEHOLDER=false USE_PLACEHOLDER=${FORCE_PLACEHOLDER:-false}
log "INFO" "Setting up Museum server binary" log "INFO" "Setting up Museum server binary"
@@ -218,6 +372,17 @@ else
log "INFO" "Web templates already exist or source not available" log "INFO" "Web templates already exist or source not available"
fi 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 # Check if Museum binary exists and is valid
log "INFO" "Checking for Museum binary at: $MUSEUM_BIN" log "INFO" "Checking for Museum binary at: $MUSEUM_BIN"
if [ -f "$MUSEUM_BIN" ]; then if [ -f "$MUSEUM_BIN" ]; then
@@ -247,7 +412,84 @@ fi
# =============================================== # ===============================================
# Web Application Setup # 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 # Node.js Placeholder Server
@@ -349,17 +591,54 @@ const apiHandlers = {
log('Health check request - responded with status OK'); log('Health check request - responded with status OK');
}, },
// User verification endpoint // User verification endpoint (returns minimal structure expected by UI)
'/api/users/verify': (req, res) => { '/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.writeHead(200, { 'Content-Type': 'application/json' });
log('User verify request - responding with success'); res.end(JSON.stringify(responsePayload));
res.end(JSON.stringify({ });
success: true,
isValidEmail: true,
isAvailable: true,
isVerified: true,
canCreateAccount: true
}));
}, },
// User login endpoint // User login endpoint
@@ -377,7 +656,7 @@ const apiHandlers = {
token: 'placeholder-jwt-token-' + Date.now(), token: 'placeholder-jwt-token-' + Date.now(),
user: { user: {
id: 1, id: 1,
email: 'placeholder@' + (process.env.CLOUDRON_APP_DOMAIN || 'localhost'), email: 'placeholder@example.com',
name: 'Placeholder User' name: 'Placeholder User'
} }
})); }));
@@ -406,7 +685,7 @@ const apiHandlers = {
token: 'placeholder-jwt-token-' + Date.now(), token: 'placeholder-jwt-token-' + Date.now(),
user: { user: {
id: 1, id: 1,
email: 'placeholder@' + (process.env.CLOUDRON_APP_DOMAIN || 'localhost'), email: 'placeholder@example.com',
name: 'New User' name: 'New User'
} }
})); }));
@@ -420,6 +699,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 // Files endpoint
'/api/files': (req, res) => { '/api/files': (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -536,7 +863,7 @@ EOF
SUCCESS=false SUCCESS=false
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do 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" log "INFO" "Node.js placeholder server started successfully"
SUCCESS=true SUCCESS=true
break break
@@ -568,10 +895,12 @@ if [ "$USE_PLACEHOLDER" = true ]; then
create_nodejs_placeholder create_nodejs_placeholder
else else
log "INFO" "Starting actual Museum server" log "INFO" "Starting actual Museum server"
prepare_ott_highlighter
cd /app/data/ente/server 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=$! 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 # Wait for the server to start
MAX_ATTEMPTS=30 MAX_ATTEMPTS=30
@@ -579,7 +908,7 @@ else
SUCCESS=false SUCCESS=false
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do 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" log "INFO" "Museum server started successfully"
SUCCESS=true SUCCESS=true
break break
@@ -591,13 +920,15 @@ else
if [ "$SUCCESS" = false ]; then if [ "$SUCCESS" = false ]; then
log "ERROR" "Museum server failed to start within $MAX_ATTEMPTS seconds" 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:" log "ERROR" "Last 20 lines of museum.log:"
tail -n 20 "$MUSEUM_LOG" | while read -r line; do tail -n 20 "$MUSEUM_LOG" | while read -r line; do
log "ERROR" " $line" log "ERROR" " $line"
done done
exit 1
log "WARN" "Falling back to Node.js placeholder server"
create_nodejs_placeholder
fi fi
fi fi
@@ -633,7 +964,7 @@ cat > "$CADDY_CONFIG" << EOF
respond 204 respond 204
} }
# API endpoints with CORS - strip /api prefix before forwarding # API endpoints with CORS
handle_path /api/* { handle_path /api/* {
reverse_proxy localhost:8080 { reverse_proxy localhost:8080 {
header_up Host {http.request.host} header_up Host {http.request.host}
@@ -657,60 +988,34 @@ cat > "$CADDY_CONFIG" << EOF
} }
} }
# Health check endpoints # Health check endpoint
handle /health { handle /health {
reverse_proxy localhost:8080 reverse_proxy localhost:8080
} }
handle /ping { handle /images/* {
reverse_proxy localhost:8080 rewrite * /photos{path}
} root * /app/data/web
# Static files for Next.js assets from all apps
handle /_next/* {
@photosNext path /_next/*
handle @photosNext {
root * /app/web/photos
file_server file_server
} }
# Static files for Next.js assets shared across apps
handle /_next/* {
root * /app/data/web
try_files photos{path} accounts{path} auth{path} cast{path} {path}
file_server
header { header {
Cache-Control "public, max-age=31536000" Cache-Control "public, max-age=31536000"
Access-Control-Allow-Origin "*" Access-Control-Allow-Origin "*"
} }
} }
# Photos app # Default to serve SPA assets
handle_path /photos/* { handle {
root * /app/web/photos root * /app/data/web
try_files {path} /index.html try_files {path}/index.html {path} /photos/index.html
file_server file_server
} }
# Accounts app
handle_path /accounts/* {
root * /app/web/accounts
try_files {path} /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 EOF
@@ -752,7 +1057,7 @@ cat > /app/data/SETUP-INSTRUCTIONS.md << EOF
## Configuration ## 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. 2. **Museum Server**: The server configuration is at \`/app/data/ente/server/museum.yaml\` if you need to customize settings.
@@ -765,10 +1070,10 @@ cat > /app/data/SETUP-INSTRUCTIONS.md << EOF
The following web applications are available: The following web applications are available:
- Photos: https://${CLOUDRON_APP_DOMAIN}/photos/ - Photos: https://${CLOUDRON_APP_FQDN}/photos/
- Accounts: https://${CLOUDRON_APP_DOMAIN}/accounts/ - Accounts: https://${CLOUDRON_APP_FQDN}/accounts/
- Auth: https://${CLOUDRON_APP_DOMAIN}/auth/ - Auth: https://${CLOUDRON_APP_FQDN}/auth/
- Cast: https://${CLOUDRON_APP_DOMAIN}/cast/ - Cast: https://${CLOUDRON_APP_FQDN}/cast/
## Support ## Support