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:
2026-09-11 12:18:34 +03:00
parent 1c5186463a
commit 46602f1933
226 changed files with 3999 additions and 15737 deletions
+135 -17
View File
@@ -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