From 4d4ab086c998bf5da6c8bb18cd0dabf7ffe4938b Mon Sep 17 00:00:00 2001 From: bramval Date: Sat, 24 Jan 2026 10:09:33 +0100 Subject: [PATCH] Update app templates and routes --- Dockerfile | 5 ++++- README.md | 15 +++++++++++++++ app/__init__.py | 3 ++- app/cli.py | 43 ++++++++++++++++++++++++++++++------------- docker-compose.yml | 7 +++++++ docker/entrypoint.sh | 12 ++++++++++++ 6 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 docker/entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 3f64def..bb55436 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,9 @@ RUN pip install --no-cache-dir -r requirements.txt \ COPY . . +# Ensure entrypoint is executable +RUN chmod +x docker/entrypoint.sh + # Create runtime dirs (also mountable as volumes) RUN mkdir -p instance app/static/uploads @@ -27,4 +30,4 @@ ENV FLASK_ENV=production \ GUNICORN_BIND=0.0.0.0:8000 # Run via WSGI entrypoint -CMD ["sh", "-c", "gunicorn -w ${GUNICORN_WORKERS} -b ${GUNICORN_BIND} wsgi:app"] +CMD ["sh", "-c", "./docker/entrypoint.sh"] diff --git a/README.md b/README.md index 08c314c..855027e 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,23 @@ environment variables via your process manager / secrets. This repo includes a `docker-compose.yml` for a one-command startup. +On first run, the container will ensure the SQLite schema exists. +If you provide `ADMIN_PASS`, it will also create/update the initial admin user. + ```powershell docker compose up --build ``` +Create an admin on startup (recommended): + +```powershell +$env:ADMIN_EMAIL="you@example.com" +$env:ADMIN_PASS="YourStrongPassword" +docker compose up --build +``` + +Or put these in a `.env` file used by Compose. + Run in the background: ```powershell @@ -217,5 +230,7 @@ If the reset email is not received: + + diff --git a/app/__init__.py b/app/__init__.py index 3a762ff..d155248 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -4,7 +4,7 @@ from werkzeug.exceptions import RequestEntityTooLarge from .extensions import db, login_manager from .models import AppSettings, User -from .cli import init_db_command +from .cli import ensure_db_command, init_db_command def create_app(): @@ -85,6 +85,7 @@ def create_app(): return db.session.get(User, int(user_id)) # CLI + app.cli.add_command(ensure_db_command) app.cli.add_command(init_db_command) # Blueprints diff --git a/app/cli.py b/app/cli.py index 9aaa8ed..f9d4fa2 100644 --- a/app/cli.py +++ b/app/cli.py @@ -5,21 +5,11 @@ from .extensions import db from .models import AppSettings, User -@click.command("init-db") -@click.option( - "--admin-email", - required=False, - default="beheer@alphen.cloud", - show_default=True, - help="Email for the initial admin", -) -@click.option("--admin-pass", required=True, help="Password for the initial admin") -@with_appcontext -def init_db_command(admin_email: str, admin_pass: str): - """Create tables and ensure an admin account exists.""" +def _ensure_schema_and_settings() -> None: + """Create tables + run lightweight SQLite migrations + ensure settings row exists.""" db.create_all() - # Lightweight migration for older SQLite DBs: ensure User.email column exists. + # Lightweight migration for older SQLite DBs: ensure columns exist. # This avoids requiring Alembic for this small project. try: cols = [r[1] for r in db.session.execute(db.text("PRAGMA table_info(user)")).fetchall()] @@ -50,6 +40,33 @@ def init_db_command(admin_email: str, admin_pass: str): db.session.add(AppSettings(id=1)) db.session.commit() + +@click.command("ensure-db") +@with_appcontext +def ensure_db_command(): + """Create tables / apply lightweight migrations. + + This is useful for container startup where you want schema readiness, + without requiring admin credentials. + """ + _ensure_schema_and_settings() + click.echo("Database ready.") + + +@click.command("init-db") +@click.option( + "--admin-email", + required=False, + default="beheer@alphen.cloud", + show_default=True, + help="Email for the initial admin", +) +@click.option("--admin-pass", required=True, help="Password for the initial admin") +@with_appcontext +def init_db_command(admin_email: str, admin_pass: str): + """Create tables and ensure an admin account exists.""" + _ensure_schema_and_settings() + admin_email = (admin_email or "").strip().lower() if not admin_email: raise click.UsageError("--admin-email is required") diff --git a/docker-compose.yml b/docker-compose.yml index 4a057c8..5d9e863 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,16 @@ services: environment: # Override in a .env file or your shell; this default is only for convenience. SECRET_KEY: "change-me" + # Optional bootstrap (only runs if ADMIN_PASS is set) + ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@admin.admin} + ADMIN_PASS: ${ADMIN_PASS:-} # Optional overrides (the Dockerfile already defaults these) GUNICORN_WORKERS: "2" GUNICORN_BIND: "0.0.0.0:8000" + # Entrypoint (from Dockerfile) runs: + # - `flask ensure-db` + # - optional `flask init-db` when ADMIN_PASS is set + # - gunicorn volumes: # Persist SQLite DB and uploads on the host - ./instance:/app/instance diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..139ae73 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +set -eu + +# Ensure DB schema exists +flask --app app ensure-db + +# Optional: create/update initial admin account +if [ -n "${ADMIN_PASS:-}" ]; then + flask --app app init-db --admin-email "${ADMIN_EMAIL:-admin@admin.admin}" --admin-pass "${ADMIN_PASS}" +fi + +exec gunicorn -w "${GUNICORN_WORKERS:-2}" -b "${GUNICORN_BIND:-0.0.0.0:8000}" wsgi:app