mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-01 10:50:32 +02:00
Ente's --no-limit is only available per-user via the interactive admin CLI, so to make unlimited storage the default for all current and future accounts the add-on now reconciles the museum subscriptions table directly (the method documented by the Ente maintainers): every account is granted 100 TB of storage and ~100 years of validity, matching what `ente admin update-subscription --no-limit` writes. - New NO_STORAGE_LIMIT option (default true); set false to keep Ente's limits - Background reconcile re-applies every 60s so newly registered users are covered; storage and expiry are updated independently and tolerate errors so a transient DB hiccup or schema change can't break startup - Document the option and the full port list in the README https://claude.ai/code/session_01MaLKhb2CJiF9Fb3Dyr585r
347 lines
12 KiB
Bash
Executable File
347 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bashio
|
||
# shellcheck shell=bash
|
||
set -euo pipefail
|
||
|
||
# Internal MinIO credentials (not user-configurable; MinIO is 127.0.0.1 only)
|
||
MINIO_CRED_FILE="/config/minio-creds"
|
||
if [ -f "$MINIO_CRED_FILE" ]; then
|
||
# Reuse persisted credentials across restarts
|
||
MINIO_USER="$(sed -n '1p' "$MINIO_CRED_FILE")"
|
||
MINIO_PASS="$(sed -n '2p' "$MINIO_CRED_FILE")"
|
||
# Regenerate if file is corrupted
|
||
if [ -z "$MINIO_USER" ] || [ -z "$MINIO_PASS" ]; then
|
||
MINIO_USER="minio_$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 8 || true)"
|
||
MINIO_PASS="$(head -c 24 /dev/urandom | base64 | tr -d '\n')"
|
||
printf '%s\n%s\n' "$MINIO_USER" "$MINIO_PASS" > "$MINIO_CRED_FILE"
|
||
chmod 600 "$MINIO_CRED_FILE"
|
||
fi
|
||
else
|
||
# Generate random credentials on first run
|
||
MINIO_USER="minio_$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 8 || true)"
|
||
MINIO_PASS="$(head -c 24 /dev/urandom | base64 | tr -d '\n')"
|
||
printf '%s\n%s\n' "$MINIO_USER" "$MINIO_PASS" > "$MINIO_CRED_FILE"
|
||
chmod 600 "$MINIO_CRED_FILE"
|
||
fi
|
||
S3_BUCKET="b2-eu-cen"
|
||
|
||
export ENTE_S3_ARE_LOCAL_BUCKETS=true
|
||
export ENTE_S3_B2_EU_CEN_KEY="$MINIO_USER"
|
||
export ENTE_S3_B2_EU_CEN_SECRET="$MINIO_PASS"
|
||
export ENTE_S3_B2_EU_CEN_ENDPOINT="http://127.0.0.1:3200"
|
||
export ENTE_S3_B2_EU_CEN_REGION=eu-central-2
|
||
export ENTE_S3_B2_EU_CEN_BUCKET="$S3_BUCKET"
|
||
|
||
############################################
|
||
# Paths & constants
|
||
############################################
|
||
CFG=/config/museum.yaml
|
||
PGDATA=/config/postgres
|
||
|
||
# Internal Postgres always bound here
|
||
DB_HOST_INTERNAL=127.0.0.1
|
||
DB_PORT_INTERNAL=5432
|
||
|
||
############################################
|
||
# Read add‑on options
|
||
############################################
|
||
DB_NAME="$(bashio::config 'DB_DATABASE_NAME' || echo ente_db)"
|
||
DB_USER="$(bashio::config 'DB_USERNAME' || echo pguser)"
|
||
DB_PASS="$(bashio::config 'DB_PASSWORD' || echo ente)"
|
||
|
||
# External DB opts (may be blank)
|
||
DB_HOST_EXT="$(bashio::config 'DB_HOSTNAME' || echo '')"
|
||
DB_PORT_EXT="$(bashio::config 'DB_PORT' || echo '')"
|
||
# Default external Postgres port when unset
|
||
[ -z "$DB_PORT_EXT" ] && DB_PORT_EXT=5432
|
||
|
||
USE_EXTERNAL_DB=false
|
||
if bashio::config.true 'USE_EXTERNAL_DB'; then
|
||
USE_EXTERNAL_DB=true
|
||
bashio::log.warning "USE_EXTERNAL_DB enabled: will connect to external Postgres."
|
||
else
|
||
bashio::log.info "Using internal Postgres."
|
||
fi
|
||
|
||
# Disable per-account storage limits by default (mirrors `ente admin
|
||
# update-subscription --no-limit`). Set NO_STORAGE_LIMIT=false to keep limits.
|
||
NO_STORAGE_LIMIT=true
|
||
if bashio::config.false 'NO_STORAGE_LIMIT'; then
|
||
NO_STORAGE_LIMIT=false
|
||
fi
|
||
|
||
# Active DB connection target (may be overridden below)
|
||
if $USE_EXTERNAL_DB; then
|
||
DB_HOST="$DB_HOST_EXT"
|
||
DB_PORT="$DB_PORT_EXT"
|
||
else
|
||
DB_HOST="$DB_HOST_INTERNAL"
|
||
DB_PORT="$DB_PORT_INTERNAL"
|
||
fi
|
||
|
||
############################################
|
||
# Ensure persistent dirs
|
||
############################################
|
||
mkdir -p /config/ente/custom-logs \
|
||
/config/data \
|
||
/config/minio-data \
|
||
"$PGDATA" \
|
||
/config/scripts/compose
|
||
|
||
############################################
|
||
# Locate binaries
|
||
############################################
|
||
INITDB="$(command -v initdb || echo /usr/bin/initdb)"
|
||
POSTGRES_BIN="$(command -v postgres || echo /usr/bin/postgres)"
|
||
MINIO_BIN="/usr/local/bin/minio"
|
||
MC_BIN="/usr/local/bin/mc"
|
||
|
||
MUSEUM_BIN="$(command -v museum || true)"
|
||
[ -z "$MUSEUM_BIN" ] && [ -x /app/museum ] && MUSEUM_BIN=/app/museum
|
||
[ -z "$MUSEUM_BIN" ] && [ -x /museum ] && MUSEUM_BIN=/museum
|
||
[ -z "$MUSEUM_BIN" ] && MUSEUM_BIN=museum # fallback in PATH
|
||
|
||
############################################
|
||
# Config generation
|
||
############################################
|
||
create_config() {
|
||
bashio::log.info "Generating new museum config at $CFG"
|
||
_rand_b64() { head -c "$1" /dev/urandom | base64 | tr -d '\n'; }
|
||
_rand_b64url() { head -c "$1" /dev/urandom | base64 | tr '+/' '-_' | tr -d '\n'; }
|
||
|
||
cat > "$CFG" << EOF
|
||
key:
|
||
encryption: $(_rand_b64 32)
|
||
hash: $(_rand_b64 64)
|
||
|
||
jwt:
|
||
secret: $(_rand_b64url 32)
|
||
|
||
db:
|
||
host: ${DB_HOST}
|
||
port: ${DB_PORT}
|
||
name: ${DB_NAME}
|
||
user: ${DB_USER}
|
||
password: ${DB_PASS}
|
||
|
||
s3:
|
||
are_local_buckets: true
|
||
${S3_BUCKET}:
|
||
key: ${MINIO_USER}
|
||
secret: ${MINIO_PASS}
|
||
endpoint: ${ENTE_S3_B2_EU_CEN_ENDPOINT}
|
||
region: ${ENTE_S3_B2_EU_CEN_REGION}
|
||
bucket: ${S3_BUCKET}
|
||
EOF
|
||
}
|
||
|
||
############################################
|
||
# Postgres
|
||
############################################
|
||
start_postgres() {
|
||
if $USE_EXTERNAL_DB; then
|
||
bashio::log.info "External DB in use; not starting internal Postgres."
|
||
return 0
|
||
fi
|
||
|
||
mkdir -p /run/postgresql
|
||
chown postgres:postgres /run/postgresql
|
||
chmod 775 /run/postgresql
|
||
|
||
chown -R postgres:postgres "$PGDATA"
|
||
chmod 0700 "$PGDATA"
|
||
|
||
if [[ ! -s "$PGDATA/PG_VERSION" ]]; then
|
||
bashio::log.info "Initializing Postgres data directory..."
|
||
su - postgres -c "$INITDB -D $PGDATA"
|
||
fi
|
||
|
||
bashio::log.info "Starting Postgres (${DB_HOST_INTERNAL}:${DB_PORT_INTERNAL})..."
|
||
su - postgres -c "$POSTGRES_BIN -D $PGDATA -c listen_addresses='127.0.0.1'" &
|
||
PG_PID=$!
|
||
}
|
||
|
||
wait_postgres_ready() {
|
||
local host port
|
||
if $USE_EXTERNAL_DB; then
|
||
host="$DB_HOST_EXT"
|
||
port="$DB_PORT_EXT"
|
||
bashio::log.info "Waiting for EXTERNAL Postgres at ${host}:${port}..."
|
||
else
|
||
host="$DB_HOST_INTERNAL"
|
||
port="$DB_PORT_INTERNAL"
|
||
bashio::log.info "Waiting for internal Postgres..."
|
||
fi
|
||
until pg_isready -q -h "$host" -p "$port"; do sleep 1; done
|
||
bashio::log.info "Postgres reachable."
|
||
}
|
||
|
||
bootstrap_internal_db() {
|
||
if $USE_EXTERNAL_DB; then
|
||
return 0
|
||
fi
|
||
|
||
bashio::log.info "Ensuring role & database exist..."
|
||
local esc_pass="${DB_PASS//\'/\'\'}"
|
||
|
||
# role
|
||
if ! psql -v ON_ERROR_STOP=1 -h "$DB_HOST_INTERNAL" -p "$DB_PORT_INTERNAL" -U postgres -tAc \
|
||
"SELECT 1 FROM pg_roles WHERE rolname = '${DB_USER}'" | grep -q 1; then
|
||
psql -v ON_ERROR_STOP=1 -h "$DB_HOST_INTERNAL" -p "$DB_PORT_INTERNAL" -U postgres \
|
||
-c "CREATE ROLE \"${DB_USER}\" LOGIN PASSWORD '${esc_pass}';"
|
||
else
|
||
psql -v ON_ERROR_STOP=1 -h "$DB_HOST_INTERNAL" -p "$DB_PORT_INTERNAL" -U postgres \
|
||
-c "ALTER ROLE \"${DB_USER}\" PASSWORD '${esc_pass}';"
|
||
fi
|
||
|
||
# db
|
||
if ! psql -v ON_ERROR_STOP=1 -h "$DB_HOST_INTERNAL" -p "$DB_PORT_INTERNAL" -U postgres -tAc \
|
||
"SELECT 1 FROM pg_database WHERE datname = '${DB_NAME}'" | grep -q 1; then
|
||
psql -v ON_ERROR_STOP=1 -h "$DB_HOST_INTERNAL" -p "$DB_PORT_INTERNAL" -U postgres \
|
||
-c "CREATE DATABASE \"${DB_NAME}\" OWNER \"${DB_USER}\";"
|
||
else
|
||
psql -v ON_ERROR_STOP=1 -h "$DB_HOST_INTERNAL" -p "$DB_PORT_INTERNAL" -U postgres \
|
||
-c "ALTER DATABASE \"${DB_NAME}\" OWNER TO \"${DB_USER}\";"
|
||
fi
|
||
}
|
||
|
||
############################################
|
||
# MinIO
|
||
############################################
|
||
start_minio() {
|
||
bashio::log.info "Starting MinIO (127.0.0.1:3200)..."
|
||
mkdir -p /config/minio-data
|
||
export MINIO_ROOT_USER="$MINIO_USER"
|
||
export MINIO_ROOT_PASSWORD="$MINIO_PASS"
|
||
"$MINIO_BIN" server /config/minio-data --address "127.0.0.1:3200" --console-address "127.0.0.1:9001" &
|
||
MINIO_PID=$!
|
||
}
|
||
|
||
wait_minio_ready_and_bucket() {
|
||
bashio::log.info "Waiting for MinIO API..."
|
||
until "$MC_BIN" alias set h0 http://127.0.0.1:3200 "$MINIO_USER" "$MINIO_PASS" 2> /dev/null; do
|
||
sleep 1
|
||
done
|
||
bashio::log.info "Ensuring buckets..."
|
||
"$MC_BIN" mb -p "h0/${S3_BUCKET}" || true
|
||
bashio::log.info "MinIO buckets ready."
|
||
}
|
||
|
||
############################################
|
||
# Web (static nginx bundle)
|
||
############################################
|
||
start_web() {
|
||
ENTE_API_ORIGIN="$(bashio::config 'ENTE_ENDPOINT_URL')"
|
||
# Derive albums origin from the same host as the API endpoint, mapped to port 8302
|
||
ENTE_ALBUMS_HOST="$(echo "$ENTE_API_ORIGIN" | sed -E 's#(https?://[^:/]+).*#\1#')"
|
||
ENTE_ALBUMS_ORIGIN="${ENTE_ALBUMS_HOST}:8302"
|
||
export ENTE_API_ORIGIN ENTE_ALBUMS_ORIGIN
|
||
|
||
# Running ente-web-prepare
|
||
echo "[ente-web-prepare] Substituting origins…"
|
||
find /www -name '*.js' | xargs sed -i "s#ENTE_API_ORIGIN_PLACEHOLDER#${ENTE_API_ORIGIN}#g"
|
||
find /www/photos -name '*.js' | xargs sed -i "s#ENTE_ALBUMS_ORIGIN_PLACEHOLDER#${ENTE_ALBUMS_ORIGIN}#g"
|
||
|
||
mkdir -p /run/nginx /var/log/nginx
|
||
|
||
# Set nginx (idempotent: only move if .bak still exists)
|
||
if [ -f /etc/nginx/http.d/web.bak ]; then
|
||
mv /etc/nginx/http.d/web.bak /etc/nginx/http.d/web.conf
|
||
fi
|
||
|
||
bashio::log.info "Starting Ente web (nginx, ports 3000‑3009)..."
|
||
nginx -g 'daemon off;' &
|
||
WEB_PID=$!
|
||
}
|
||
|
||
############################################
|
||
# Storage limit (no-limit) reconcile
|
||
############################################
|
||
# Equivalent of `ente admin update-subscription --no-limit`: grant every
|
||
# account 100 TB of storage and ~100 years of validity. The Ente CLI only
|
||
# supports this per-user and interactively, so to make it the default for all
|
||
# current and future accounts we reconcile the museum `subscriptions` table
|
||
# directly (the method documented by the Ente maintainers). Runs in the
|
||
# background and re-applies periodically so newly registered users are covered.
|
||
NO_LIMIT_STORAGE_BYTES=109951162777600 # 100 * 1024^4 (100 TiB)
|
||
NO_LIMIT_EXPIRY_SQL="(extract(epoch from now() + interval '100 years') * 1000000)::bigint"
|
||
|
||
reconcile_no_limit() {
|
||
export PGPASSWORD="$DB_PASS"
|
||
while true; do
|
||
# Storage is the limit that actually enforces quota; apply it first and
|
||
# independently so a schema change to other columns can't block it.
|
||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
|
||
-c "UPDATE subscriptions SET storage = ${NO_LIMIT_STORAGE_BYTES} WHERE storage < ${NO_LIMIT_STORAGE_BYTES};" \
|
||
> /dev/null 2>&1 || true
|
||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
|
||
-c "UPDATE subscriptions SET expiry_time = ${NO_LIMIT_EXPIRY_SQL} WHERE expiry_time < ${NO_LIMIT_EXPIRY_SQL};" \
|
||
> /dev/null 2>&1 || true
|
||
sleep 60
|
||
done
|
||
}
|
||
|
||
start_no_limit_reconcile() {
|
||
if ! $NO_STORAGE_LIMIT; then
|
||
bashio::log.info "NO_STORAGE_LIMIT disabled; per-account storage limits stay in effect."
|
||
return 0
|
||
fi
|
||
bashio::log.info "NO_STORAGE_LIMIT enabled; granting unlimited storage to all accounts."
|
||
reconcile_no_limit &
|
||
NO_LIMIT_PID=$!
|
||
}
|
||
|
||
############################################
|
||
# Museum (API)
|
||
############################################
|
||
start_museum_foreground() {
|
||
if [ ! -f "$CFG" ]; then
|
||
bashio::log.error "$CFG missing; cannot start museum."
|
||
return 1
|
||
fi
|
||
if [ ! -x "$MUSEUM_BIN" ] && ! command -v "$MUSEUM_BIN" > /dev/null 2>&1; then
|
||
bashio::log.error "Museum binary not found; cannot launch Ente API."
|
||
return 1
|
||
fi
|
||
|
||
# Force env overrides (museum merges env > yaml)
|
||
if $USE_EXTERNAL_DB; then
|
||
export ENTE_DB_HOST="$DB_HOST_EXT"
|
||
export ENTE_DB_PORT="$DB_PORT_EXT"
|
||
else
|
||
export ENTE_DB_HOST="$DB_HOST_INTERNAL"
|
||
export ENTE_DB_PORT="$DB_PORT_INTERNAL"
|
||
fi
|
||
export ENTE_DB_USER="$DB_USER"
|
||
export ENTE_DB_PASSWORD="$DB_PASS"
|
||
export ENTE_DB_NAME="$DB_NAME"
|
||
export ENTE_DB_SSLMODE=disable
|
||
|
||
bashio::log.info "Starting museum (foreground)..."
|
||
exec "$MUSEUM_BIN" --config "$CFG"
|
||
}
|
||
|
||
############################################
|
||
# Main orchestration
|
||
############################################
|
||
bashio::log.info "=== Ente startup sequence ==="
|
||
|
||
if [ ! -f "$CFG" ]; then
|
||
create_config
|
||
else
|
||
bashio::log.info "Using existing $CFG."
|
||
fi
|
||
|
||
start_postgres
|
||
wait_postgres_ready
|
||
bootstrap_internal_db
|
||
|
||
start_minio
|
||
wait_minio_ready_and_bucket
|
||
|
||
start_web
|
||
|
||
start_no_limit_reconcile
|
||
|
||
# Foreground — keeps container alive
|
||
start_museum_foreground
|