diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..8ace215 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# LEGO Instructions Manager - container entrypoint. +# +# Runs as root just long enough to fix volume ownership, then waits for the +# database, creates any missing tables, and drops to the unprivileged user. +# +# Created by: Jess Rogerson (yelling commands at Claude.AI) +set -euo pipefail + +APP_USER=lim + +log() { echo "[entrypoint] $*"; } + +# --- volume ownership ------------------------------------------------------- +# A freshly created named volume mounts as root:root, so the app user cannot +# write to it until we hand it over. Cheap to repeat on every start. +if [ "$(id -u)" = '0' ]; then + mkdir -p "${UPLOAD_FOLDER:-/data/uploads}" "${BACKUP_FOLDER:-/data/backups}" + chown -R "${APP_USER}:${APP_USER}" /data +fi + +# --- wait for the database -------------------------------------------------- +if [ "${DATABASE_URL:-}" != "" ] && [ "${SKIP_DB_WAIT:-0}" != "1" ]; then + log "Waiting for database..." + for attempt in $(seq 1 60); do + if python - <<'PY' +import os, sys +from sqlalchemy import create_engine, text +try: + engine = create_engine(os.environ['DATABASE_URL'], pool_pre_ping=True) + with engine.connect() as conn: + conn.execute(text('SELECT 1')) +except Exception as exc: + print(f' not ready: {exc.__class__.__name__}', file=sys.stderr) + sys.exit(1) +PY + then + log "Database is up." + break + fi + if [ "$attempt" = "60" ]; then + log "ERROR: database did not become available in time." + exit 1 + fi + sleep 2 + done +fi + +# --- schema ----------------------------------------------------------------- +# db.create_all() is additive: it creates missing tables and leaves existing +# ones untouched. It does NOT alter columns, so a schema change still needs a +# migration — see docs/DOCKER_DEPLOYMENT.md. +if [ "${SKIP_DB_INIT:-0}" != "1" ]; then + log "Ensuring schema exists..." + python - <<'PY' +import os +from app import create_app, db +app = create_app(os.getenv('FLASK_ENV', 'production')) +with app.app_context(): + db.create_all() + print('[entrypoint] Schema ready.') +PY +fi + +# --- hand over -------------------------------------------------------------- +# Re-chown: create_app() ran as root above and will have created the +# uploads subdirectories (pdfs/, images/, covers/, thumbnails/, extra_files/) +# owned by root. Without this the app user cannot write uploads. +if [ "$(id -u)" = '0' ]; then + chown -R "${APP_USER}:${APP_USER}" /data + log "Starting as ${APP_USER}: $*" + if command -v runuser >/dev/null 2>&1; then + exec runuser -u "${APP_USER}" -- "$@" + else + exec su -s /bin/bash -c 'exec "$0" "$@"' "${APP_USER}" -- "$@" + fi +fi + +log "Starting: $*" +exec "$@"