11 Commits

Author SHA1 Message Date
Your Name
61046f1d42 Auto-configure CLI endpoint 2025-09-30 09:34:25 -06:00
Your Name
7a5fac90ab Persist Museum configuration for manual edits 2025-09-29 22:36:37 -06:00
Your Name
b38bd6a249 Make Ente CLI usable out of the box 2025-09-29 22:05:24 -06:00
Your Name
a8b22a95c8 Bundle Ente CLI for Cloudron console 2025-09-29 21:37:33 -06:00
Your Name
93cdf1f2f1 Remove OTT log highlighter 2025-09-29 21:26:21 -06:00
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 455 additions and 121 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.133",
"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
@@ -143,6 +143,7 @@ ENV GOFLAGS="-modfile=/app/data/go/go.mod -mod=mod"
ENV PATH="/usr/local/go/bin:${PATH}" ENV PATH="/usr/local/go/bin:${PATH}"
ENV GOSUMDB=off ENV GOSUMDB=off
ENV GOMODCACHE="/app/data/go/pkg/mod" ENV GOMODCACHE="/app/data/go/pkg/mod"
ENV HOME=/app/data/home
# Copy the web app built files from the first stage # Copy the web app built files from the first stage
COPY --from=web-builder /build/web/photos /app/web/photos COPY --from=web-builder /build/web/photos /app/web/photos
@@ -150,6 +151,18 @@ COPY --from=web-builder /build/web/accounts /app/web/accounts
COPY --from=web-builder /build/web/auth /app/web/auth COPY --from=web-builder /build/web/auth /app/web/auth
COPY --from=web-builder /build/web/cast /app/web/cast COPY --from=web-builder /build/web/cast /app/web/cast
# Build Ente CLI and place binary in /app/code
WORKDIR /app/code/cli
RUN env GOFLAGS= GOMODCACHE=/tmp/cli-go-cache GO111MODULE=on go build -o /app/code/ente . && chmod +x /app/code/ente
WORKDIR /app/code
# Symlink CLI into PATH for convenience
RUN ln -sf /app/code/ente /usr/local/bin/ente
# Prepare CLI data directory symlink to persistent storage
RUN mkdir -p /app/data/cli-data && ln -s /app/data/cli-data /cli-data
# Copy Museum server binary from builder stage to app directory (not data volume) # Copy Museum server binary from builder stage to app directory (not data volume)
RUN mkdir -p /app/museum-bin RUN mkdir -p /app/museum-bin
COPY --from=museum-builder /ente/server/museum /app/museum-bin/museum COPY --from=museum-builder /ente/server/museum /app/museum-bin/museum

View File

@@ -7,19 +7,28 @@ 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
1. Once S3 is configured, visit your app URL to create an admin account 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) 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!
## Ente CLI
- The Ente CLI binary is pre-built at `/app/code/ente` inside the app container.
- Open the Cloudron web terminal (working directory `/app/code`) and run commands with `ente ...` or `./ente ...`.
- The CLI configuration at `/app/data/home/.ente/config.yaml` already points to your instance (`https://<your-domain>/api`).
- CLI state is stored under `/app/data/cli-data/` so re-logins persist.
## Museum Server Configuration
- The active configuration lives at `/app/data/ente/server/configurations/local.yaml` and is created the first time the app starts.
- Subsequent restarts leave this file untouched, so you can whitelist admin accounts or adjust other settings as documented by Ente.
- Delete the file to regenerate the default template (environment values such as database and S3 credentials are rendered during creation).

520
start.sh
View File

@@ -22,6 +22,16 @@ log() {
log "INFO" "Starting Ente Cloudron app" log "INFO" "Starting Ente Cloudron app"
log "INFO" "Running in Cloudron environment with domain: ${CLOUDRON_APP_DOMAIN}" log "INFO" "Running in Cloudron environment with domain: ${CLOUDRON_APP_DOMAIN}"
# Ensure HOME is writable (needed for CLI usage)
HOME_DIR="/app/data/home"
export HOME="$HOME_DIR"
mkdir -p "$HOME"
# Ensure CLI data directory persists across restarts
CLI_DATA_PERSIST="/app/data/cli-data"
mkdir -p "$CLI_DATA_PERSIST"
# Prevent infinite loops through startup flag # Prevent infinite loops through startup flag
if [ -f "/app/data/startup_in_progress" ]; then if [ -f "/app/data/startup_in_progress" ]; then
if [ "$(find /app/data/startup_in_progress -mmin +2)" ]; then if [ "$(find /app/data/startup_in_progress -mmin +2)" ]; then
@@ -77,32 +87,158 @@ 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
# Seed Ente CLI configuration directory
ENTE_CLI_CONFIG_DIR="$HOME/.ente"
ENTE_CLI_CONFIG_FILE="$ENTE_CLI_CONFIG_DIR/config.yaml"
if [ -f "$ENTE_CLI_CONFIG_FILE" ] && grep -q "^# Ente CLI configuration" "$ENTE_CLI_CONFIG_FILE"; then
rm -f "$ENTE_CLI_CONFIG_FILE"
fi
mkdir -p "$ENTE_CLI_CONFIG_DIR"
write_cli_config_if_needed() {
cat > "$ENTE_CLI_CONFIG_FILE" << EOF
endpoint:
api: ${BASE_URL%/}/api
log:
http: false
EOF
chown -R cloudron:cloudron "$HOME_DIR" || true
}
if [ ! -f "$ENTE_CLI_CONFIG_FILE" ]; then
write_cli_config_if_needed
else
if ! grep -q "endpoint:" "$ENTE_CLI_CONFIG_FILE" || grep -q "\\n" "$ENTE_CLI_CONFIG_FILE"; then
write_cli_config_if_needed
elif ! grep -q "${BASE_URL%/}/api" "$ENTE_CLI_CONFIG_FILE"; then
write_cli_config_if_needed
fi
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 if [ ! -f "$MUSEUM_CONFIG" ]; then
log "INFO" "Creating Museum server configuration" log "INFO" "Rendering 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,47 +249,105 @@ 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 else
log "INFO" "Museum configuration already exists" log "INFO" "Museum configuration already present at ${MUSEUM_CONFIG}; preserving existing file"
fi fi
# =============================================== # ===============================================
@@ -180,7 +374,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 +412,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 +452,56 @@ 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
# =============================================== # ===============================================
# Node.js Placeholder Server # Node.js Placeholder Server
@@ -349,17 +603,52 @@ 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) => {
res.writeHead(200, { 'Content-Type': 'application/json' }); const buildResponse = (emailAddress) => {
log('User verify request - responding with success'); const email = emailAddress || 'unknown@example.com';
res.end(JSON.stringify({ const stableId = Math.abs(Buffer.from(email).reduce((acc, byte) => (acc * 31 + byte) % 100000, 17)) || 1;
success: true, return {
isValidEmail: true, id: stableId,
isAvailable: true, token: `placeholder-token-${stableId}`,
isVerified: true, encryptedToken: `placeholder-encrypted-token-${stableId}`,
canCreateAccount: true 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(`Verifying OTT ${ott} for ${email}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(responsePayload));
});
}, },
// User login endpoint // User login endpoint
@@ -377,7 +666,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 +695,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 +709,52 @@ 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(`Generated OTT ${ott} for ${email}`);
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 +871,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
@@ -569,9 +904,10 @@ if [ "$USE_PLACEHOLDER" = true ]; then
else else
log "INFO" "Starting actual Museum server" log "INFO" "Starting actual Museum server"
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 | 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 +915,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 +927,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 +971,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 +995,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
file_server
} }
# Static files for Next.js assets from all apps # Static files for Next.js assets shared across apps
handle /_next/* { handle /_next/* {
@photosNext path /_next/* root * /app/data/web
handle @photosNext { try_files photos{path} accounts{path} auth{path} cast{path} {path}
root * /app/web/photos file_server
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 +1064,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 +1077,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