Restore SMTP username and password for authenticated relay on port 2525. According to Cloudron docs, this port should work with plain SMTP and authentication without STARTTLS.
642 lines
18 KiB
Bash
Executable File
642 lines
18 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
log() {
|
|
local level="$1"
|
|
shift
|
|
local message="$*"
|
|
local timestamp
|
|
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "[$timestamp] [$level] $message"
|
|
}
|
|
|
|
APP_DIR="/app/code"
|
|
DATA_DIR="/app/data"
|
|
LOG_DIR="$DATA_DIR/logs"
|
|
CONFIG_DIR="$DATA_DIR/config"
|
|
TMP_DIR="$DATA_DIR/tmp"
|
|
SECRETS_DIR="$DATA_DIR/secrets"
|
|
MUSEUM_RUNTIME_DIR="$DATA_DIR/museum"
|
|
MUSEUM_CONFIG_DIR="$MUSEUM_RUNTIME_DIR/configurations"
|
|
MUSEUM_CONFIG="$MUSEUM_CONFIG_DIR/local.yaml"
|
|
MUSEUM_BIN="/app/museum-bin/museum"
|
|
WEB_SOURCE_DIR="/app/web"
|
|
WEB_RUNTIME_DIR="$DATA_DIR/web"
|
|
CADDY_CONFIG="$DATA_DIR/Caddyfile"
|
|
STARTUP_FLAG="$DATA_DIR/startup.lock"
|
|
|
|
mkdir -p "$LOG_DIR" "$CONFIG_DIR" "$TMP_DIR" "$SECRETS_DIR" "$MUSEUM_RUNTIME_DIR" "$WEB_RUNTIME_DIR" "$MUSEUM_CONFIG_DIR"
|
|
chown -R cloudron:cloudron "$DATA_DIR"
|
|
|
|
log INFO "Starting Ente for Cloudron"
|
|
|
|
if ! command -v setpriv >/dev/null 2>&1; then
|
|
log ERROR "setpriv command not found"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -f "$STARTUP_FLAG" ]; then
|
|
log WARN "Previous startup did not finish cleanly; removing flag"
|
|
rm -f "$STARTUP_FLAG"
|
|
fi
|
|
touch "$STARTUP_FLAG"
|
|
trap 'rm -f "$STARTUP_FLAG"' EXIT
|
|
|
|
BASE_URL="${CLOUDRON_APP_ORIGIN:-https://$CLOUDRON_APP_FQDN}"
|
|
BASE_URL="${BASE_URL%/}"
|
|
RP_ID="${CLOUDRON_APP_FQDN:-${CLOUDRON_APP_DOMAIN:-localhost}}"
|
|
API_ORIGIN="${BASE_URL}/api"
|
|
|
|
log INFO "Application base URL: $BASE_URL"
|
|
log INFO "Relying party ID: $RP_ID"
|
|
log INFO "API origin: $API_ORIGIN"
|
|
|
|
S3_CONFIG_FILE="$CONFIG_DIR/s3.env"
|
|
if [ ! -f "$S3_CONFIG_FILE" ]; then
|
|
cat > "$S3_CONFIG_FILE" <<'EOF_S3'
|
|
# S3 configuration for Ente (required)
|
|
# Provide credentials for an S3-compatible object storage and restart the app.
|
|
#
|
|
# Supported environment variables (either set here or via Cloudron env vars):
|
|
# S3_ENDPOINT=https://example.s3-provider.com
|
|
# S3_REGION=us-east-1
|
|
# S3_BUCKET=ente-data
|
|
# S3_ACCESS_KEY=your-access-key
|
|
# S3_SECRET_KEY=your-secret-key
|
|
# S3_PREFIX=optional/path/prefix
|
|
#
|
|
# Example for Cloudflare R2 (replace placeholders):
|
|
#S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
|
|
#S3_REGION=auto
|
|
#S3_BUCKET=ente
|
|
#S3_ACCESS_KEY=R2_ACCESS_KEY
|
|
#S3_SECRET_KEY=R2_SECRET_KEY
|
|
EOF_S3
|
|
chown cloudron:cloudron "$S3_CONFIG_FILE"
|
|
chmod 600 "$S3_CONFIG_FILE"
|
|
log INFO "Created S3 configuration template at $S3_CONFIG_FILE"
|
|
fi
|
|
|
|
set +u
|
|
if [ -f "$S3_CONFIG_FILE" ]; then
|
|
# shellcheck disable=SC1090
|
|
. "$S3_CONFIG_FILE"
|
|
fi
|
|
set -u
|
|
|
|
S3_ENDPOINT="${S3_ENDPOINT:-${ENTE_S3_ENDPOINT:-}}"
|
|
S3_REGION="${S3_REGION:-${ENTE_S3_REGION:-}}"
|
|
S3_BUCKET="${S3_BUCKET:-${ENTE_S3_BUCKET:-}}"
|
|
S3_ACCESS_KEY="${S3_ACCESS_KEY:-${ENTE_S3_ACCESS_KEY:-}}"
|
|
S3_SECRET_KEY="${S3_SECRET_KEY:-${ENTE_S3_SECRET_KEY:-}}"
|
|
S3_PREFIX="${S3_PREFIX:-${ENTE_S3_PREFIX:-}}"
|
|
|
|
if [ -z "$S3_ENDPOINT" ] || [ -z "$S3_REGION" ] || [ -z "$S3_BUCKET" ] || [ -z "$S3_ACCESS_KEY" ] || [ -z "$S3_SECRET_KEY" ]; then
|
|
log ERROR "Missing S3 configuration. Update $S3_CONFIG_FILE or set environment variables."
|
|
log ERROR "The application will start in configuration mode. Please configure S3 and restart."
|
|
S3_NOT_CONFIGURED=true
|
|
else
|
|
S3_NOT_CONFIGURED=false
|
|
fi
|
|
|
|
if [ "$S3_NOT_CONFIGURED" = "false" ]; then
|
|
S3_ENDPOINT_HOST="${S3_ENDPOINT#https://}"
|
|
S3_ENDPOINT_HOST="${S3_ENDPOINT_HOST#http://}"
|
|
S3_ENDPOINT_HOST="${S3_ENDPOINT_HOST%%/}"
|
|
S3_ENDPOINT_PATH="${S3_ENDPOINT_HOST#*/}"
|
|
if [ "$S3_ENDPOINT_PATH" != "$S3_ENDPOINT_HOST" ]; then
|
|
if [ -z "$S3_PREFIX" ]; then
|
|
S3_PREFIX="$S3_ENDPOINT_PATH"
|
|
fi
|
|
S3_ENDPOINT_HOST="${S3_ENDPOINT_HOST%%/*}"
|
|
fi
|
|
|
|
log INFO "Using S3 endpoint $S3_ENDPOINT_HOST (region $S3_REGION, bucket $S3_BUCKET)"
|
|
else
|
|
S3_ENDPOINT_HOST="s3.example.com"
|
|
log WARN "S3 not configured - using placeholder values"
|
|
fi
|
|
|
|
MASTER_KEY_FILE="$SECRETS_DIR/master_key"
|
|
HASH_KEY_FILE="$SECRETS_DIR/hash_key"
|
|
JWT_SECRET_FILE="$SECRETS_DIR/jwt_secret"
|
|
SESSION_SECRET_FILE="$SECRETS_DIR/session_secret"
|
|
|
|
SMTP_HOST="${CLOUDRON_MAIL_SMTP_SERVER:-mail}"
|
|
SMTP_PORT="${CLOUDRON_MAIL_SMTP_PORT:-2525}"
|
|
SMTP_ENCRYPTION=""
|
|
SMTP_USERNAME="${CLOUDRON_MAIL_SMTP_USERNAME:-}"
|
|
SMTP_PASSWORD="${CLOUDRON_MAIL_SMTP_PASSWORD:-}"
|
|
SMTP_EMAIL="${CLOUDRON_MAIL_FROM:-no-reply@$RP_ID}"
|
|
SMTP_SENDER_NAME="${CLOUDRON_MAIL_FROM_DISPLAY_NAME:-Ente}"
|
|
|
|
if [ -n "$SMTP_HOST" ]; then
|
|
log INFO "SMTP configured for $SMTP_HOST:$SMTP_PORT (encryption: ${SMTP_ENCRYPTION:-none})"
|
|
else
|
|
log INFO "SMTP not configured; Museum will skip outbound email"
|
|
fi
|
|
|
|
normalize_b64() {
|
|
local value="$1"
|
|
value="$(printf '%s' "$value" | tr -d '\r\n')"
|
|
value="$(printf '%s' "$value" | tr '-_' '+/')"
|
|
local mod=$(( ${#value} % 4 ))
|
|
if [ $mod -eq 2 ]; then
|
|
value="${value}=="
|
|
elif [ $mod -eq 3 ]; then
|
|
value="${value}="
|
|
elif [ $mod -eq 1 ]; then
|
|
value=""
|
|
fi
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
normalize_b64url() {
|
|
local value="$1"
|
|
value="$(printf '%s' "$value" | tr -d '\r\n')"
|
|
value="$(printf '%s' "$value" | tr '+/' '-_')"
|
|
local mod=$(( ${#value} % 4 ))
|
|
if [ $mod -eq 2 ]; then
|
|
value="${value}=="
|
|
elif [ $mod -eq 3 ]; then
|
|
value="${value}="
|
|
elif [ $mod -eq 1 ]; then
|
|
value=""
|
|
fi
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
generate_b64() {
|
|
local bytes="$1"
|
|
openssl rand -base64 "$bytes" | tr -d '\n'
|
|
}
|
|
|
|
generate_b64url() {
|
|
local bytes="$1"
|
|
openssl rand -base64 "$bytes" | tr '+/' '-_' | tr -d '\n'
|
|
}
|
|
|
|
ensure_secret() {
|
|
local file="$1"
|
|
local bytes="$2"
|
|
local mode="$3"
|
|
local current=""
|
|
if [ -f "$file" ]; then
|
|
current="$(tr -d '\n' < "$file")"
|
|
fi
|
|
if [ "$mode" = "b64" ]; then
|
|
current="$(normalize_b64 "$current")"
|
|
if [ -z "$current" ]; then
|
|
current="$(generate_b64 "$bytes")"
|
|
fi
|
|
else
|
|
current="$(normalize_b64url "$current")"
|
|
if [ -z "$current" ]; then
|
|
current="$(generate_b64url "$bytes")"
|
|
fi
|
|
fi
|
|
printf '%s
|
|
' "$current" > "$file"
|
|
}
|
|
|
|
ensure_secret "$MASTER_KEY_FILE" 32 b64
|
|
ensure_secret "$HASH_KEY_FILE" 64 b64
|
|
ensure_secret "$JWT_SECRET_FILE" 32 b64url
|
|
ensure_secret "$SESSION_SECRET_FILE" 32 b64url
|
|
|
|
MASTER_KEY="$(tr -d '\n' < "$MASTER_KEY_FILE")"
|
|
HASH_KEY="$(tr -d '\n' < "$HASH_KEY_FILE")"
|
|
JWT_SECRET="$(tr -d '\n' < "$JWT_SECRET_FILE")"
|
|
SESSION_SECRET="$(tr -d '\n' < "$SESSION_SECRET_FILE")"
|
|
|
|
chown cloudron:cloudron "$MASTER_KEY_FILE" "$HASH_KEY_FILE" "$JWT_SECRET_FILE" "$SESSION_SECRET_FILE"
|
|
chmod 600 "$MASTER_KEY_FILE" "$HASH_KEY_FILE" "$JWT_SECRET_FILE" "$SESSION_SECRET_FILE"
|
|
|
|
log INFO "Ensuring Museum runtime assets"
|
|
|
|
sync_dir() {
|
|
local source="$1"
|
|
local target="$2"
|
|
if [ -d "$source" ]; then
|
|
log INFO "Syncing $(basename "$source") into data directory"
|
|
rm -rf "$target"
|
|
cp -a "$source" "$target"
|
|
chown -R cloudron:cloudron "$target"
|
|
else
|
|
log WARN "Missing expected directory: $source"
|
|
fi
|
|
}
|
|
|
|
sync_dir "$APP_DIR/server/migrations" "$MUSEUM_RUNTIME_DIR/migrations"
|
|
sync_dir "$APP_DIR/server/web-templates" "$MUSEUM_RUNTIME_DIR/web-templates"
|
|
sync_dir "$APP_DIR/server/mail-templates" "$MUSEUM_RUNTIME_DIR/mail-templates"
|
|
sync_dir "$APP_DIR/server/assets" "$MUSEUM_RUNTIME_DIR/assets"
|
|
|
|
if [ ! -x "$MUSEUM_BIN" ]; then
|
|
log ERROR "Museum binary not found at $MUSEUM_BIN"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$MUSEUM_CONFIG" ]; then
|
|
log INFO "Rendering Museum configuration"
|
|
cat > "$MUSEUM_CONFIG" <<EOF_CFG
|
|
log-file: ""
|
|
http:
|
|
port: 8080
|
|
use-tls: false
|
|
|
|
apps:
|
|
public-albums: "$BASE_URL/albums"
|
|
public-locker: "$BASE_URL/photos"
|
|
accounts: "$BASE_URL/accounts"
|
|
cast: "$BASE_URL/cast"
|
|
family: "$BASE_URL/family"
|
|
custom-domain:
|
|
cname: "${CLOUDRON_APP_DOMAIN:-localhost}"
|
|
|
|
db:
|
|
host: ${CLOUDRON_POSTGRESQL_HOST}
|
|
port: ${CLOUDRON_POSTGRESQL_PORT}
|
|
name: ${CLOUDRON_POSTGRESQL_DATABASE}
|
|
user: ${CLOUDRON_POSTGRESQL_USERNAME}
|
|
password: ${CLOUDRON_POSTGRESQL_PASSWORD}
|
|
sslmode: disable
|
|
|
|
s3:
|
|
are_local_buckets: false
|
|
use_path_style_urls: true
|
|
hot_storage:
|
|
primary: primary-storage
|
|
secondary: primary-storage
|
|
primary-storage:
|
|
key: "$S3_ACCESS_KEY"
|
|
secret: "$S3_SECRET_KEY"
|
|
endpoint: "$S3_ENDPOINT_HOST"
|
|
region: "$S3_REGION"
|
|
bucket: "$S3_BUCKET"
|
|
path_prefix: "$S3_PREFIX"
|
|
|
|
smtp:
|
|
host: "${SMTP_HOST}"
|
|
port: "${SMTP_PORT}"
|
|
username: "${SMTP_USERNAME}"
|
|
password: "${SMTP_PASSWORD}"
|
|
email: "${SMTP_EMAIL}"
|
|
sender-name: "${SMTP_SENDER_NAME}"
|
|
encryption: "${SMTP_ENCRYPTION}"
|
|
|
|
internal:
|
|
silent: false
|
|
disable-registration: false
|
|
|
|
webauthn:
|
|
rpid: "$RP_ID"
|
|
rporigins:
|
|
- "$BASE_URL"
|
|
|
|
key:
|
|
encryption: $MASTER_KEY
|
|
hash: $HASH_KEY
|
|
|
|
jwt:
|
|
secret: $JWT_SECRET
|
|
|
|
sessions:
|
|
secret: $SESSION_SECRET
|
|
EOF_CFG
|
|
|
|
if [ -n "${CLOUDRON_OIDC_CLIENT_ID:-}" ] && [ -n "${CLOUDRON_OIDC_CLIENT_SECRET:-}" ] && [ -n "${CLOUDRON_OIDC_IDENTIFIER:-}" ]; then
|
|
cat >> "$MUSEUM_CONFIG" <<EOF_CFG
|
|
|
|
oidc:
|
|
enabled: true
|
|
issuer: "${CLOUDRON_OIDC_IDENTIFIER}"
|
|
client_id: "${CLOUDRON_OIDC_CLIENT_ID}"
|
|
client_secret: "${CLOUDRON_OIDC_CLIENT_SECRET}"
|
|
redirect_url: "$BASE_URL/api/v1/session/callback"
|
|
EOF_CFG
|
|
fi
|
|
|
|
chown cloudron:cloudron "$MUSEUM_CONFIG"
|
|
chmod 600 "$MUSEUM_CONFIG"
|
|
else
|
|
log INFO "Museum configuration already present; leaving untouched"
|
|
fi
|
|
|
|
log INFO "Preparing frontend assets"
|
|
if [ -d "$WEB_SOURCE_DIR" ]; then
|
|
for app in photos accounts auth cast albums family; do
|
|
if [ -d "$WEB_SOURCE_DIR/$app" ]; then
|
|
log INFO "Updating $app frontend assets"
|
|
rm -rf "$WEB_RUNTIME_DIR/$app"
|
|
cp -a "$WEB_SOURCE_DIR/$app" "$WEB_RUNTIME_DIR/$app"
|
|
chown -R cloudron:cloudron "$WEB_RUNTIME_DIR/$app"
|
|
else
|
|
log WARN "Missing built frontend for $app"
|
|
fi
|
|
done
|
|
else
|
|
log ERROR "Frontend assets directory missing at $WEB_SOURCE_DIR"
|
|
fi
|
|
|
|
rewrite_frontend_reference() {
|
|
local search="$1"
|
|
local replace="$2"
|
|
local count=0
|
|
local file
|
|
|
|
if [ -z "$search" ] || [ -z "$replace" ] || [ "$search" = "$replace" ]; then
|
|
return
|
|
fi
|
|
|
|
# Create escaped versions for different contexts
|
|
local search_escaped_slash="${search//\//\\/}"
|
|
local replace_escaped_slash="${replace//\//\\/}"
|
|
local search_json="${search//\//\\/}"
|
|
local replace_json="${replace//\//\\/}"
|
|
|
|
while IFS= read -r -d '' file; do
|
|
local file_changed=false
|
|
|
|
# Check if file contains any variant of the search string
|
|
if LC_ALL=C grep -q -e "$search" -e "$search_escaped_slash" "$file" 2>/dev/null; then
|
|
# Replace plain URL
|
|
if sed -i "s|$search|$replace|g" "$file" 2>/dev/null; then
|
|
file_changed=true
|
|
fi
|
|
|
|
# Replace backslash-escaped URL (common in JavaScript strings)
|
|
if sed -i "s|$search_escaped_slash|$replace_escaped_slash|g" "$file" 2>/dev/null; then
|
|
file_changed=true
|
|
fi
|
|
|
|
# Replace double-backslash-escaped URL (common in JSON)
|
|
if sed -i "s|${search//\//\\\\/}|${replace//\//\\\\/}|g" "$file" 2>/dev/null; then
|
|
file_changed=true
|
|
fi
|
|
|
|
if [ "$file_changed" = true ]; then
|
|
chown cloudron:cloudron "$file"
|
|
count=$((count + 1))
|
|
fi
|
|
fi
|
|
done < <(find "$WEB_RUNTIME_DIR" -type f \( -name "*.js" -o -name "*.json" -o -name "*.html" -o -name "*.css" -o -name "*.txt" \) -print0)
|
|
|
|
if [ "$count" -gt 0 ]; then
|
|
log INFO "Replaced '$search' with '$replace' in $count frontend files"
|
|
fi
|
|
}
|
|
|
|
if [ -d "$WEB_RUNTIME_DIR" ]; then
|
|
log INFO "Rewriting frontend endpoints for local deployment"
|
|
FRONTEND_REPLACEMENTS=(
|
|
"ENTE_API_ORIGIN_PLACEHOLDER|$API_ORIGIN"
|
|
"https://api.ente.io|$API_ORIGIN"
|
|
"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"
|
|
"https://albums.ente.io|$BASE_URL/albums"
|
|
"https://family.ente.io|$BASE_URL/family"
|
|
"https://ente.io|$BASE_URL"
|
|
)
|
|
OLD_IFS="$IFS"
|
|
for entry in "${FRONTEND_REPLACEMENTS[@]}"; do
|
|
IFS="|" read -r search replace <<<"$entry"
|
|
rewrite_frontend_reference "$search" "$replace"
|
|
done
|
|
IFS="$OLD_IFS"
|
|
fi
|
|
|
|
log INFO "Ensuring CLI configuration"
|
|
CLI_HOME="$DATA_DIR/home/.ente"
|
|
mkdir -p "$CLI_HOME"
|
|
cat > "$CLI_HOME/config.yaml" <<EOF_CLI
|
|
endpoint:
|
|
api: ${API_ORIGIN}
|
|
log:
|
|
http: false
|
|
EOF_CLI
|
|
chown -R cloudron:cloudron "$DATA_DIR/home"
|
|
chmod 700 "$DATA_DIR/home"
|
|
|
|
log INFO "Rendering Caddy configuration"
|
|
cat > "$CADDY_CONFIG" <<EOF_CADDY
|
|
{
|
|
admin off
|
|
auto_https off
|
|
}
|
|
|
|
:3080 {
|
|
log {
|
|
level INFO
|
|
output stdout
|
|
}
|
|
|
|
encode gzip
|
|
|
|
@options {
|
|
method OPTIONS
|
|
}
|
|
handle @options {
|
|
header Access-Control-Allow-Origin "*"
|
|
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
|
|
header Access-Control-Allow-Headers "*"
|
|
header Access-Control-Max-Age "3600"
|
|
respond 204
|
|
}
|
|
|
|
handle_path /api/* {
|
|
reverse_proxy localhost:8080 {
|
|
header_up Host {http.request.host}
|
|
header_up X-Real-IP {http.request.remote}
|
|
header_up X-Forwarded-For {http.request.remote}
|
|
header_up X-Forwarded-Proto {http.request.scheme}
|
|
}
|
|
header Access-Control-Allow-Origin "*"
|
|
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
|
|
header Access-Control-Allow-Headers "*"
|
|
header Access-Control-Allow-Credentials "true"
|
|
}
|
|
|
|
handle /health {
|
|
rewrite * /ping
|
|
reverse_proxy localhost:8080 {
|
|
header_up Host {http.request.host}
|
|
header_up X-Real-IP {http.request.remote}
|
|
header_up X-Forwarded-For {http.request.remote}
|
|
header_up X-Forwarded-Proto {http.request.scheme}
|
|
}
|
|
}
|
|
|
|
handle /ping {
|
|
reverse_proxy localhost:8080 {
|
|
header_up Host {http.request.host}
|
|
header_up X-Real-IP {http.request.remote}
|
|
header_up X-Forwarded-For {http.request.remote}
|
|
header_up X-Forwarded-Proto {http.request.scheme}
|
|
}
|
|
}
|
|
|
|
handle /public/* {
|
|
reverse_proxy localhost:8080
|
|
}
|
|
|
|
handle /_next/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} auth{path} accounts{path} photos{path} cast{path} albums{path} family{path}
|
|
file_server
|
|
}
|
|
|
|
handle /images/* {
|
|
root * $WEB_RUNTIME_DIR/photos
|
|
file_server
|
|
}
|
|
|
|
handle /auth/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /auth/index.html
|
|
file_server
|
|
}
|
|
|
|
handle /accounts/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /accounts/index.html
|
|
file_server
|
|
}
|
|
|
|
handle /cast/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /cast/index.html
|
|
file_server
|
|
}
|
|
|
|
handle /family/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /family/index.html
|
|
file_server
|
|
}
|
|
|
|
handle /albums/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /albums/index.html
|
|
file_server
|
|
}
|
|
|
|
handle /photos/* {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /photos/index.html
|
|
file_server
|
|
}
|
|
|
|
handle {
|
|
root * $WEB_RUNTIME_DIR
|
|
try_files {path} {path}/index.html /photos/index.html
|
|
file_server
|
|
}
|
|
}
|
|
EOF_CADDY
|
|
|
|
chown cloudron:cloudron "$CADDY_CONFIG"
|
|
|
|
log INFO "Validating Caddy configuration"
|
|
if ! caddy validate --config "$CADDY_CONFIG" > "$TMP_DIR/caddy-validate.log" 2>&1; then
|
|
cat "$TMP_DIR/caddy-validate.log"
|
|
log ERROR "Caddy configuration validation failed"
|
|
exit 1
|
|
fi
|
|
|
|
log INFO "Testing PostgreSQL connectivity"
|
|
if ! 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; then
|
|
log ERROR "Unable to connect to PostgreSQL"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$S3_NOT_CONFIGURED" = "true" ]; then
|
|
log WARN "S3 not configured - creating configuration page"
|
|
mkdir -p "$WEB_RUNTIME_DIR/config"
|
|
cat > "$WEB_RUNTIME_DIR/config/index.html" <<'EOF_CONFIG'
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Ente Configuration Required</title>
|
|
<style>
|
|
body { font-family: sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
|
h1 { color: #2d2d2d; }
|
|
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
|
|
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
|
.warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Ente Configuration Required</h1>
|
|
<div class="warning">
|
|
<strong>S3 Storage Not Configured</strong>
|
|
<p>Ente requires S3-compatible object storage to function. Please configure your S3 credentials.</p>
|
|
</div>
|
|
<h2>Configuration Steps</h2>
|
|
<ol>
|
|
<li>Open the Cloudron dashboard</li>
|
|
<li>Go to your Ente app and open the Terminal</li>
|
|
<li>Edit <code>/app/data/config/s3.env</code>:
|
|
<pre>nano /app/data/config/s3.env</pre>
|
|
</li>
|
|
<li>Add your S3 credentials:
|
|
<pre>S3_ENDPOINT=https://your-s3-endpoint.com
|
|
S3_REGION=your-region
|
|
S3_BUCKET=your-bucket-name
|
|
S3_ACCESS_KEY=your-access-key
|
|
S3_SECRET_KEY=your-secret-key</pre>
|
|
</li>
|
|
<li>Save the file and restart the app from the Cloudron dashboard</li>
|
|
</ol>
|
|
<p>For more information, see the <a href="https://help.ente.io/self-hosting/guides/external-s3">Ente S3 Configuration Guide</a>.</p>
|
|
</body>
|
|
</html>
|
|
EOF_CONFIG
|
|
chown -R cloudron:cloudron "$WEB_RUNTIME_DIR/config"
|
|
|
|
log INFO "Starting Caddy in configuration mode"
|
|
setpriv --reuid=cloudron --regid=cloudron --init-groups caddy file-server --listen :3080 --root "$WEB_RUNTIME_DIR/config" &
|
|
CADDY_PID=$!
|
|
MUSEUM_PID=""
|
|
else
|
|
log INFO "Starting Museum server and Caddy"
|
|
|
|
setpriv --reuid=cloudron --regid=cloudron --init-groups /bin/bash -lc "cd '$MUSEUM_RUNTIME_DIR' && exec stdbuf -oL '$MUSEUM_BIN'" &
|
|
MUSEUM_PID=$!
|
|
|
|
setpriv --reuid=cloudron --regid=cloudron --init-groups caddy run --config "$CADDY_CONFIG" --watch &
|
|
CADDY_PID=$!
|
|
fi
|
|
|
|
terminate() {
|
|
log INFO "Shutting down services"
|
|
if [ -n "$MUSEUM_PID" ]; then
|
|
kill "$MUSEUM_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$CADDY_PID" ]; then
|
|
kill "$CADDY_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$MUSEUM_PID" ]; then
|
|
wait "$MUSEUM_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$CADDY_PID" ]; then
|
|
wait "$CADDY_PID" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
trap terminate TERM INT
|
|
|
|
if [ -n "$MUSEUM_PID" ]; then
|
|
wait -n "$MUSEUM_PID" "$CADDY_PID"
|
|
else
|
|
wait "$CADDY_PID"
|
|
fi
|
|
EXIT_CODE=$?
|
|
terminate
|
|
log ERROR "Service exited unexpectedly"
|
|
exit "$EXIT_CODE"
|