46602f1933
Remove dead code identified in docs/SANITIZATION-REVIEW.md:
- app/blueprints/content_old.py and app/blueprints/playlist.py
- app/models/group.py, app/utils/nginx_config_reader.py
- orphaned templates (content_list, edit_content, upload_content,
player_page) and the related group/Template references
Result: 6 blueprints, 82 routes, no dead modules or orphan templates.
Add files that deploy.sh and docker-entrypoint.sh already require but
which were never tracked:
- https_manager.py (referenced by deploy.sh, migrate_network.sh,
docker-entrypoint.sh)
- Caddyfile.example (seeded by deploy.sh; its absence aborts deploy)
Relocate generated Graphify artifacts from graphify-out/ to
docs/graphify-out/ (110 files, no content change) and archive the
superseded docs under docs/.
Ignore hygiene:
- ignore ad-hoc .env backups (.env.bak*) — they contain live secrets
- keep the pre-sanitization snapshots (docs/legacy code/,
docs/old_code_documentation/) on disk but out of the repo
Fix .env.example: drop a duplicated config block, genericize the
hardcoded host IP, and document HOSTNAME_INTERNAL.
171 lines
6.3 KiB
Bash
Executable File
171 lines
6.3 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
echo "Starting DigiServer v2..."
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pin the database explicitly.
|
|
#
|
|
# The migration scripts and the application must target the SAME SQLite file.
|
|
# Without this, they do not: the config classes default to different files
|
|
# (dev.db for DevelopmentConfig, dashboard.db for ProductionConfig), and most
|
|
# migration scripts call create_app() without an argument, which selects the
|
|
# *development* config. The app itself runs as create_app('production').
|
|
# Exporting DATABASE_URL makes every code path below resolve to the same
|
|
# database, regardless of which config gets loaded.
|
|
# ---------------------------------------------------------------------------
|
|
export DATABASE_URL="${DATABASE_URL:-sqlite:////app/instance/dashboard.db}"
|
|
export FLASK_ENV="${FLASK_ENV:-production}"
|
|
|
|
echo "Database: ${DATABASE_URL}"
|
|
|
|
# Create necessary directories
|
|
mkdir -p /app/instance
|
|
mkdir -p /app/app/static/uploads
|
|
# Staged player source (bind-mounted from ./data/player). Created here so the
|
|
# container works even if the volume was not pre-created on the host.
|
|
mkdir -p /app/data/player
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Ensure the schema exists and bootstrap the admin user.
|
|
#
|
|
# Both operations are idempotent, so this runs on every container start:
|
|
# db.create_all() only creates missing tables, and the admin block creates the
|
|
# user if absent or otherwise refreshes its password from the environment.
|
|
# ---------------------------------------------------------------------------
|
|
echo "Ensuring database schema and admin user..."
|
|
python -c "
|
|
from app.app import create_app
|
|
from app.extensions import db, bcrypt
|
|
from app.models import User
|
|
import os
|
|
|
|
app = create_app('production')
|
|
with app.app_context():
|
|
db.create_all()
|
|
|
|
admin_username = os.getenv('ADMIN_USERNAME', 'admin')
|
|
admin_password = os.getenv('ADMIN_PASSWORD', 'admin123')
|
|
|
|
admin = User.query.filter_by(username=admin_username).first()
|
|
hashed = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
|
if not admin:
|
|
admin = User(username=admin_username, password=hashed, role='admin')
|
|
db.session.add(admin)
|
|
print(f'✅ Admin user created ({admin_username})')
|
|
else:
|
|
# Keep the stored password in sync with the environment.
|
|
admin.password = hashed
|
|
print(f'✅ Admin user password updated ({admin_username})')
|
|
db.session.commit()
|
|
"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Apply schema migrations.
|
|
#
|
|
# Every migration script is idempotent (it guards against duplicate columns and
|
|
# already-migrated tables), so this is safe to run on every start and upgrades
|
|
# databases created by older releases.
|
|
#
|
|
# ORDER MATTERS: migrations that create/repair a table must run before the ones
|
|
# that alter that table (e.g. add_player_user_table.py before
|
|
# migrate_player_user_global.py, add_https_config_table.py before
|
|
# add_email_to_https_config.py).
|
|
#
|
|
# Migrations are deliberately NON-FATAL: a failure is logged and startup
|
|
# continues, so one bad migration cannot strand the container in a restart
|
|
# loop. Look for the WARNING lines in the logs if something looks wrong.
|
|
# ---------------------------------------------------------------------------
|
|
MIGRATIONS=(
|
|
"add_https_config_table.py"
|
|
"add_player_user_table.py"
|
|
"add_email_to_https_config.py"
|
|
"migrate_player_user_global.py"
|
|
"add_url_to_content.py"
|
|
"add_original_filename_to_content.py"
|
|
"add_deployment_fields_to_player.py"
|
|
)
|
|
|
|
echo "Running database migrations..."
|
|
FAILED_MIGRATIONS=()
|
|
|
|
for migration in "${MIGRATIONS[@]}"; do
|
|
migration_path="/app/migrations/${migration}"
|
|
|
|
if [ ! -f "$migration_path" ]; then
|
|
echo "⚠️ Skipping missing migration: ${migration}"
|
|
continue
|
|
fi
|
|
|
|
echo " • ${migration}"
|
|
if ! python "$migration_path"; then
|
|
echo "⚠️ WARNING: migration failed: ${migration}"
|
|
FAILED_MIGRATIONS+=("${migration}")
|
|
fi
|
|
done
|
|
|
|
if [ ${#FAILED_MIGRATIONS[@]} -gt 0 ]; then
|
|
echo "⚠️ WARNING: ${#FAILED_MIGRATIONS[@]} migration(s) failed: ${FAILED_MIGRATIONS[*]}"
|
|
echo "⚠️ Starting anyway — check the errors above."
|
|
else
|
|
echo "✅ All database migrations applied."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Bootstrap HTTPS from environment variables.
|
|
#
|
|
# HOSTNAME_INTERNAL + HOST_IP (compose → env) configure Caddy for HTTPS at
|
|
# startup, so a fresh deployment is reachable over TLS without a manual step.
|
|
# When either is unset this is a deliberate NO-OP: the app stays on plain HTTP
|
|
# and HTTPS can be enabled later from Admin → HTTPS Configuration, which
|
|
# reloads Caddy live.
|
|
#
|
|
# ORDERING MATTERS: gunicorn is started FIRST (below) and only then is HTTPS
|
|
# configured. The bootstrap probes https://…/api/health to decide whether the
|
|
# certificate really works; if it ran before the app was listening, Caddy would
|
|
# return 502 and the probe would wrongly conclude HTTPS was broken.
|
|
#
|
|
# Non-fatal: if this fails the app still runs, and the admin page remains the
|
|
# fallback path for configuring HTTPS.
|
|
# ---------------------------------------------------------------------------
|
|
bootstrap_https() {
|
|
echo "Checking HTTPS bootstrap..."
|
|
if ! python /app/https_manager.py bootstrap; then
|
|
echo "⚠️ WARNING: HTTPS bootstrap failed — continuing; configure HTTPS from the admin UI."
|
|
fi
|
|
}
|
|
|
|
# Start the application
|
|
# --timeout is a safety net for any remaining synchronous long operation. The
|
|
# player build no longer blocks a worker (it runs in a background thread), but
|
|
# a generous margin avoids surprise worker kills during large uploads or
|
|
# dependency installs triggered from the admin UI.
|
|
echo "Starting Gunicorn..."
|
|
gunicorn \
|
|
--bind 0.0.0.0:5000 \
|
|
--workers 4 \
|
|
--timeout 300 \
|
|
--access-logfile - \
|
|
--error-logfile - \
|
|
"app.app:create_app('production')" &
|
|
GUNICORN_PID=$!
|
|
|
|
# Wait for the app to answer before configuring HTTPS, then run the bootstrap.
|
|
for _ in $(seq 1 30); do
|
|
if python -c "
|
|
import sys, urllib.request
|
|
try:
|
|
urllib.request.urlopen('http://127.0.0.1:5000/health', timeout=2)
|
|
except Exception:
|
|
sys.exit(1)
|
|
" 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
bootstrap_https
|
|
|
|
# Keep the container attached to gunicorn so signals and healthchecks behave.
|
|
wait $GUNICORN_PID
|