Sanitize codebase, reorganize docs, and add missing deploy files
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.
This commit is contained in:
+135
-17
@@ -3,50 +3,168 @@ 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
|
||||
|
||||
# Initialize database if it doesn't exist
|
||||
if [ ! -f /app/instance/dashboard.db ]; then
|
||||
echo "Initializing database..."
|
||||
python -c "
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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()
|
||||
|
||||
# Create or update admin user from environment variables
|
||||
import os
|
||||
|
||||
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:
|
||||
hashed = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
||||
admin = User(username=admin_username, password=hashed, role='admin')
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
print(f'✅ Admin user created ({admin_username})')
|
||||
else:
|
||||
# Update password if it exists
|
||||
hashed = bcrypt.generate_password_hash(admin_password).decode('utf-8')
|
||||
# Keep the stored password in sync with the environment.
|
||||
admin.password = hashed
|
||||
db.session.commit()
|
||||
print(f'✅ Admin user password updated ({admin_username})')
|
||||
db.session.commit()
|
||||
"
|
||||
echo "Database initialized!"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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..."
|
||||
exec gunicorn \
|
||||
gunicorn \
|
||||
--bind 0.0.0.0:5000 \
|
||||
--workers 4 \
|
||||
--timeout 120 \
|
||||
--timeout 300 \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
"app.app:create_app('production')"
|
||||
"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
|
||||
|
||||
Reference in New Issue
Block a user