Argus is a Django 6 control panel for Kleinanzeigen mailbox operations. It reads Gmail messages through per-mailbox OAuth, classifies marketplace emails into buyer leads, noise/system messages, and operational service events, sends Telegram notifications, and gives operators both a full Django Admin UI and a compact mobile control panel.
The project was built as a commissioned internal operations tool.
Deployed on a VPSas aninternal production service.- Intentionally
deployed without Dockerto keep RAM, CPU, and disk overhead low on a minimal VPS plan. - Full Jazzmin Admin at
/control/. - Mobile staff panel at
/m/. - Public health endpoint at
/health/. - Full health endpoint at
/health/full/for staff users orAuthorization: Bearer $ARGUS_HEALTH_TOKEN. - Multiple Gmail mailboxes with per-mailbox OAuth.
- Encrypted Gmail refresh tokens in
MailboxAccount.gmail_oauth_token. - Buyer alerts, noise/system alerts, service events, unread reminders, cleanup, and mailbox health tracking.
- Telegram bot with inline alert actions, status commands, health/doctor commands, and mobile links.
- PostgreSQL/Neon support through
DATABASE_URL. - Tests use
config.test_settingsand in-memory SQLite, even when.env.localpoints to PostgreSQL. - GitHub Actions CI runs tests, migration checks, and linting on every push to
masterand every pull request. - Production systemd operation scripts under
deploy/. Detailed production operations documentation is indocs/production-operations.md.
/control/ Full Django/Jazzmin Admin
/m/ Mobile control panel
/health/ Simple health JSON
/health/full/ Full operational health JSON
/favicon.ico Redirects to static/favicon.svg
python -m poetry install
copy .env.example .env.local
python -m poetry run python manage.py migrate
python -m poetry run python manage.py init_dev
python -m poetry run python manage.py runserverOpen locally:
http://127.0.0.1:8000/control/
http://127.0.0.1:8000/m/
Simple health check:
curl http://127.0.0.1:8000/health/Full health with token:
curl -H "Authorization: Bearer YOUR_TOKEN" http://127.0.0.1:8000/health/full/init_dev creates or updates the local admin user from DEV_ADMIN_USERNAME, DEV_ADMIN_EMAIL, and DEV_ADMIN_PASSWORD. It also seeds default lead priority/risk rules. Demo alerts are local-only and are added only with DJANGO_DEBUG=True and DEV_SEED_SAMPLE_DATA=True.
Copy .env.example to .env.local and fill local secrets there. Do not commit .env.local.
Important settings:
DJANGO_SECRET_KEY=change-me-in-local-env
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=YOUR-PRODUCTION.com,127.0.0.1,localhost
DJANGO_CSRF_TRUSTED_ORIGINS=
DJANGO_ADMIN_URL=control
ARGUS_PUBLIC_BASE_URL=http://127.0.0.1:8000
ARGUS_ENV_LABEL=LOCAL
ARGUS_HEALTH_TOKEN=GENERATE-A-SECURE-RANDOM-STRING
ARGUS_GMAIL_CHECK_STALE_MINUTES=15
ARGUS_COMMAND_LOCK_TIMEOUT_SECONDS=600
DJANGO_TIME_ZONE=Europe/Berlin
DJANGO_SECURE_SSL_REDIRECT=False
DJANGO_SESSION_COOKIE_SECURE=False
DJANGO_CSRF_COOKIE_SECURE=False
DJANGO_USE_X_FORWARDED_PROTO=False
DJANGO_SECURE_HSTS_SECONDS=0
DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS=False
DJANGO_SECURE_HSTS_PRELOAD=False
DATABASE_URL=
DATABASE_CONN_MAX_AGE=60
DATABASE_CONN_HEALTH_CHECKS=True
DEV_ADMIN_USERNAME=admin
DEV_ADMIN_EMAIL=[email protected]
DEV_ADMIN_PASSWORD=change-me
DEV_SEED_SAMPLE_DATA=True
GOOGLE_CLIENT_SECRETS_FILE=secrets/google/credentials.json
GOOGLE_TOKEN_FILE=secrets/google/token.json
GOOGLE_OAUTH_REDIRECT_URI=
GMAIL_CHECK_MAX_RESULTS=25
GMAIL_CHECK_FAIL_ON_ERROR=False
GMAIL_OAUTH_TOKEN_FERNET_KEY=
TELEGRAM_BOT_TOKEN=
TELEGRAM_DEFAULT_CHAT_ID=
TELEGRAM_ALLOWED_CHAT_IDS=
TELEGRAM_ALLOWED_USER_IDS=
TELEGRAM_SEND_ON_GMAIL_CHECK=False
OAUTHLIB_INSECURE_TRANSPORT=FalseWith DJANGO_DEBUG=True, Argus uses SQLite only when DATABASE_URL is empty. If DATABASE_URL is set, local runtime uses that PostgreSQL database too, including Neon.
Tests are different on purpose: pytest.ini points to config.test_settings, which overrides the database to in-memory SQLite. This keeps python -m poetry run pytest away from Neon.
Fresh PostgreSQL/Neon setup:
python -m poetry run python manage.py migrate
python -m poetry run python manage.py init_devQuick SQL checks:
select current_database(), current_user, version();
select count(*) as users_count from auth_user;
select count(*) as lead_flags_count from alerts_leadflag;
select count(*) as mailboxes_count from alerts_mailboxaccount;
select count(*) as alerts_count from alerts_marketplacealert;After init_dev on a clean database, the expected starter data is:
users_count = 1
lead_flags_count = 13
mailboxes_count = 1 if DEV_SEED_SAMPLE_DATA=True
alerts_count = 3 if DEV_SEED_SAMPLE_DATA=True
There is no SQLite-to-PostgreSQL migration script. New cloud databases are initialized from Django migrations plus init_dev.
GitHub Actions is the required quality gate before merge or deploy. The CI workflow runs on every push to master and on every pull request.
The workflow enforces:
python -m poetry check --lock
python -m poetry run ruff check alerts config tests
python -m poetry run python manage.py makemigrations --check --dry-run
python -m poetry run pytest --cov=alerts --cov=config --cov-report=term-missingA change should not be merged or deployed unless the GitHub Actions CI check is green.
Run production readiness checks before deploy:
python -m poetry run python manage.py argus_check_deploy
python -m poetry run python manage.py argus_check_deploy --json
python -m poetry run python manage.py check --deploy --fail-level ERRORThe deploy check fails if local demo data leaks into production, including the [email protected] mailbox. It also verifies deploy-sensitive settings such as DEBUG, DATABASE_URL, and GMAIL_OAUTH_TOKEN_FERNET_KEY.
Production runs directly on the VPS without Docker. It uses a Python virtual environment, Gunicorn, and systemd services/timers to reduce overhead on a small VPS plan.
Detailed production services, timers, helper scripts, auto-deploy, backup, doctor, and health monitor documentation is in docs/production-operations.md.
Quick production check:
/usr/local/bin/argus-doctor.sh
systemctl list-timers --all | grep argusInstall or update production ops files on the VPS:
cd /opt/argus
./deploy/install-ops.sh- Put Google OAuth client secrets at
GOOGLE_CLIENT_SECRETS_FILE. - Create a
MailboxAccountin Admin with only a human-readable name. - Open the mailbox in Admin and use the Gmail connect action.
- Argus reads the Gmail address from Google OAuth and fills the mailbox email automatically.
- Run a manual check from Admin, Mobile, timer, or command line.
Recommended local OAuth callback:
GOOGLE_OAUTH_REDIRECT_URI=http://127.0.0.1:8000/control/alerts/mailboxaccount/oauth/callback/Add the same URL in Google Cloud Console under "Authorized redirect URIs". Keep the browser host identical: localhost and 127.0.0.1 are different OAuth redirect URIs.
When a mailbox is not connected yet, Admin shows a localized message that asks the operator to connect Gmail through OAuth. When reconnecting an existing mailbox, Argus rejects a different Google account and also rejects a Gmail address already used by another mailbox.
Manual Gmail checks:
python -m poetry run python manage.py check_gmail --max-results 25
python -m poetry run python manage.py check_gmail --mailbox email@example.com --max-results 25Production direct run:
cd /opt/argus
source .venv/bin/activate
python manage.py check_gmail --max-results 25check_gmail checks active mailboxes, skips already processed Gmail message IDs, creates alerts, updates mailbox health, records service events on failures, and continues if one mailbox fails.
check_gmail is protected by an atomic file lock in tmp/command_locks (ARGUS_COMMAND_LOCK_TIMEOUT_SECONDS) so overlapping timers do not run the same command concurrently. cleanup_old_leads uses the same lock pattern.