#!/bin/sh apply_migrations() { echo "Applying database migrations..." # Fix Inconsistent migration history after adding sites app uv run python manage.py check_and_fix_socialaccount_sites_migration --database admin uv run python manage.py migrate --database admin } apply_fixtures() { echo "Applying Django fixtures..." for fixture in api/fixtures/dev/*.json; do if [ -f "$fixture" ]; then echo "Loading $fixture" uv run python manage.py loaddata "$fixture" --database admin fi done } start_dev_server() { echo "Starting the development server (Gunicorn ASGI, debug + reload)..." # Same server/worker as prod (config.asgi via the native `asgi` worker), so # SSE streams run on the event loop exactly as they do in production. DEBUG is # on so guniconf's `reload = DEBUG` hot-reloads edited code (and flips # `preload_app` off so reload actually takes). export DJANGO_DEBUG="${DJANGO_DEBUG:-True}" export DJANGO_BIND_ADDRESS="${DJANGO_BIND_ADDRESS:-0.0.0.0}" exec uv run gunicorn -c config/guniconf.py config.asgi:application } start_prod_server() { echo "Starting the Gunicorn server..." exec uv run gunicorn -c config/guniconf.py config.asgi:application } resolve_worker_hostname() { TASK_ID="" if [ -n "$ECS_CONTAINER_METADATA_URI_V4" ]; then TASK_ID=$(wget -qO- --timeout=2 "${ECS_CONTAINER_METADATA_URI_V4}/task" | \ python3 -c "import sys,json; print(json.load(sys.stdin)['TaskARN'].split('/')[-1])" 2>/dev/null) fi if [ -z "$TASK_ID" ]; then TASK_ID=$(python3 -c "import uuid; print(uuid.uuid4().hex)") fi echo "${TASK_ID}@$(hostname)" } start_worker() { echo "Starting the worker..." exec uv run python -m celery -A config.celery worker \ -n "$(resolve_worker_hostname)" \ -l "${DJANGO_LOGGING_LEVEL:-info}" \ -Q celery,scans,scan-reports,deletion,backfill,overview,integrations,compliance,attack-paths-scans \ -E --max-tasks-per-child 1 } start_worker_beat() { echo "Starting the worker-beat..." exec uv run python -m celery -A config.celery beat -l "${DJANGO_LOGGING_LEVEL:-info}" --scheduler django_celery_beat.schedulers:DatabaseScheduler } manage_db_partitions() { if [ "${DJANGO_MANAGE_DB_PARTITIONS}" = "True" ]; then echo "Managing DB partitions..." # For now we skip the deletion of partitions until we define the data retention policy # --yes auto approves the operation without the need of an interactive terminal uv run python manage.py pgpartition --using admin --skip-delete --yes fi } # Identify this process to Postgres (application_name=:) so # connections are attributable by component in pg_stat_activity. Web tiers # report "api"; everything else uses the launch subcommand. case "$1" in prod|dev) DJANGO_APP_COMPONENT="api" ;; *) DJANGO_APP_COMPONENT="$1" ;; esac export DJANGO_APP_COMPONENT case "$1" in dev) apply_migrations apply_fixtures manage_db_partitions start_dev_server ;; prod) apply_migrations manage_db_partitions start_prod_server ;; worker) start_worker ;; beat) start_worker_beat ;; *) echo "Usage: $0 {dev|prod|worker|beat}" exit 1 ;; esac