This repository contains two separate applications in one repo:
Training Hubfor player-contributed training data and admin-side pipeline controlMarketGuard APIfor Hypixel SkyBlock market data, including Lowest BIN aggregation
- Clear package split between
app/training_hubandapp/marketguard_api - Player registration + login
- Optional admin MFA step-up with one-time email code
- Branded HTML emails with plain-text fallback for password reset and admin MFA
- Admin backup create/restore for DB + uploads + bundles
- Forgot-password + token-based password reset flow
- Player dashboard with own contribution stats
- Upload form for
training-cases-v2.jsonlfiles - Per-account upload history with download links
- Admin view over users, basic case list, training runs, and audit log
- Monitoring metrics endpoint (
/api/v1/metrics) and auth-spike alerting - Public Lowest BIN endpoint at
/api/v1/lowestbin - Admin button to:
- build one merged training bundle from all accepted uploads
- Audit log also records upload and bundle downloads
Data/state:
- the default deployment stores app state under
/app/data - SQLite stores users, sessions, uploads, cases, and audit metadata by default
- uploaded raw payloads and generated bundles are kept in the persistent app data volume
Frontend files:
- HTML templates:
sites/ - CSS:
css/
Application packages:
app/training_hub/contains the Training Hub app, routes, storage, auth, and admin flowsapp/marketguard_api/contains the Hypixel auction client, Lowest BIN cache, and API routesapp/main.pyis the primary production entrypoint for the combined single-container deployment
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .envThe sample .env.example is a local-development baseline. Before a real deployment, switch the production-only flags called out in section 4.
It is intentionally minimal: anything omitted falls back to the app defaults in app/training_hub/config/settings.py and app/marketguard_api/config.py.
Set at least:
TRAINING_HUB_SECRET_KEYto a long random value (at least 32 characters recommended)
Optional:
TRAINING_HUB_ADMIN_USERNAMES(comma-separated bootstrap allowlist for first admin account)- MariaDB settings (
TRAINING_HUB_DB_*) if you intentionally want to use an external MariaDB instance
Bootstrap note: first registration is locked until TRAINING_HUB_ADMIN_USERNAMES contains the first admin username.
.\.venv\Scripts\Activate.ps1
uvicorn app.training_hub.main:create_app --factory --host 0.0.0.0 --port 8080In a second shell for the MarketGuard API:
.\.venv\Scripts\Activate.ps1
uvicorn app.marketguard_api.main:create_marketguard_app --factory --host 0.0.0.0 --port 8081Open:
http://localhost:8080(Training Hub landing page)http://localhost:8080/hub(redirects to login/dashboard)http://localhost:8081/api/v1/lowestbin(MarketGuard Lowest BIN JSON)
The repository now ships a single Compose stack: the combined app behind bundled Caddy. It keeps persistent state under /app/data, auto-generates a strong app secret on first boot when you do not provide one, and serves both the Training Hub and lowestbin from one internal app process.
One-time setup:
Copy-Item .env.production.example .env.production
# edit .env.productionThen start production:
python scripts/update.pyWhat this path expects:
- a real public domain in
CADDY_SITE_ADDRESSsuch asscamscreener.creepans.net TRAINING_HUB_PUBLIC_BASE_URLis set to the real publichttps://...URL- SMTP is configured for password reset and admin MFA mail
TRAINING_HUB_SITE_*values are reviewed for/impressumand/datenschutz- persistent storage is kept on the Docker volumes
What this path provides automatically:
- one internal app container plus one public Caddy container
- automatic HTTPS via Caddy
/api/v1/healthcontainer healthcheck that works with host validation and HTTPS enforcement- public blocking of
/api/v1/healthand/api/v1/metrics - default bootstrap admin username
adminwhenTRAINING_HUB_ADMIN_USERNAMESis omitted - generated persistent secret key when
TRAINING_HUB_SECRET_KEYis omitted
Operational helpers for this path:
python scripts/update.pyruns preflight, rebuilds the image, restarts the stack, and waits for app healthpython scripts/update.py --skip-pullskips upstream base-image pulls during rebuildpython scripts/reset.pyasks for confirmation and then deletes the full compose deployment state for a clean restartpython scripts/reset.py --yes --prune-imagesalso removes the locally built app image
Direct docker run is also supported if you prefer not to use Compose, but then you still need external HTTPS termination in front of the container:
docker build -t scamscreener .
docker run -d --name scamscreener `
--env-file .env.production `
-p 8080:8080 `
-v scamscreener_data:/app/data `
--init `
--read-only `
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m `
--cap-drop ALL `
--security-opt no-new-privileges:true `
scamscreenerCADDY_SITE_ADDRESSdefaulthttp://localhost(set a real domain for public Caddy TLS)CADDY_HTTP_PORTdefault80CADDY_HTTPS_PORTdefault443PORToptional runtime port override used by the single-container imageWEB_CONCURRENCYoptional worker count for the single-container image (default1)TRAINING_HUB_HOSTdefault0.0.0.0TRAINING_HUB_PORTdefault8080TRAINING_HUB_ENVdefaultdevelopment(productionenforces strict startup checks)TRAINING_HUB_PUBLIC_BASE_URLoptional absolute public base URL; recommended for production and used for reset links plus allowed-host fallbackTRAINING_HUB_ALLOWED_HOSTSoptional allowlist forHostheader validationTRAINING_HUB_DB_DRIVERdefaultsqlite(mariadbif you intentionally use an external MariaDB instance)TRAINING_HUB_DATABASE_URLoptional full DSN override (mariadb://user:pass@host:3306/db)TRAINING_HUB_DB_HOSTdefault127.0.0.1TRAINING_HUB_DB_PORTdefault3306TRAINING_HUB_DB_NAMEdefaultscamscreener_hubTRAINING_HUB_DB_USERdefaultscamscreenerTRAINING_HUB_DB_PASSWORDrequired when driver ismariadbTRAINING_HUB_DB_REQUIRE_TLSdefaultfalse(truerequired for MariaDB in production)TRAINING_HUB_DB_SSL_CAoptional CA path for MariaDB TLS verification (required for verified MariaDB TLS in production)TRAINING_HUB_DB_SSL_CERToptional client certificate for MariaDB TLSTRAINING_HUB_DB_SSL_KEYoptional client key for MariaDB TLSTRAINING_HUB_DB_SSL_VERIFY_HOSTNAMEdefaulttrueTRAINING_HUB_SECRET_KEYrequiredTRAINING_HUB_SESSION_TTL_MINUTESdefault720TRAINING_HUB_SESSION_BIND_IPdefaultfalseTRAINING_HUB_SESSION_BIND_USER_AGENTdefaultfalseTRAINING_HUB_REGISTRATION_MODEdefaultopen(open,invite,closed)TRAINING_HUB_REGISTRATION_INVITE_CODErequired when mode isinviteTRAINING_HUB_PASSWORD_RESET_TTL_MINUTESdefault30TRAINING_HUB_PASSWORD_RESET_SHOW_TOKENdefaultfalse(dev only)TRAINING_HUB_PASSWORD_RESET_SEND_EMAILdefaultfalseTRAINING_HUB_SMTP_HOSTSMTP server hostTRAINING_HUB_SMTP_PORTSMTP server port (default587)TRAINING_HUB_SMTP_USERNAMEoptional SMTP usernameTRAINING_HUB_SMTP_PASSWORDoptional SMTP passwordTRAINING_HUB_SMTP_FROM_EMAILsender address for reset emailsTRAINING_HUB_SMTP_USE_TLSdefaultfalse(implicit TLS/SMTPS)TRAINING_HUB_SMTP_USE_STARTTLSdefaulttrue(explicit STARTTLS)TRAINING_HUB_SITE_PROJECT_CLASSIFICATIONdefaultPrivate non-commercial community projectTRAINING_HUB_SITE_OPERATOR_NAMEoptional operator/provider name rendered on/impressumTRAINING_HUB_SITE_POSTAL_ADDRESSoptional postal address rendered on/impressumTRAINING_HUB_SITE_CONTACT_CHANNELoptional public contact channel rendered on/impressumTRAINING_HUB_SITE_PRIVACY_CONTACToptional privacy contact rendered on/datenschutzTRAINING_HUB_SITE_HOSTING_LOCATIONdefaultAshburn, Virginia, USATRAINING_HUB_ADMIN_MFA_REQUIREDdefaultfalseTRAINING_HUB_ADMIN_MFA_TTL_MINUTESdefault30TRAINING_HUB_ADMIN_MFA_MAX_ATTEMPTSdefault5TRAINING_HUB_ENFORCE_HTTPSdefaultfalse(truein production)TRAINING_HUB_ENABLE_RATE_LIMITdefaulttrueTRAINING_HUB_ENFORCE_ORIGIN_CHECKdefaulttrueTRAINING_HUB_MAX_UPLOAD_BYTESdefault5242880TRAINING_HUB_MAX_UPLOAD_DOWNLOADS_PER_MINUTE_PER_USERdefault60TRAINING_HUB_MAX_BUNDLE_DOWNLOADS_PER_MINUTE_PER_USERdefault30TRAINING_HUB_MAX_UPLOADS_PER_DAY_PER_USERdefault40TRAINING_HUB_MAX_UPLOAD_BYTES_PER_DAY_PER_USERdefault209715200TRAINING_HUB_MAX_UPLOAD_CASES_PER_DAY_PER_USERdefault20000TRAINING_HUB_MAX_UPLOADS_PER_DAY_PER_IPdefault120TRAINING_HUB_GLOBAL_UPLOAD_STORAGE_CAP_BYTESdefault5368709120TRAINING_HUB_RETENTION_SESSIONS_DAYSdefault30TRAINING_HUB_RETENTION_PASSWORD_RESET_DAYSdefault7TRAINING_HUB_RETENTION_AUDIT_LOGS_DAYSdefault180TRAINING_HUB_RETENTION_UPLOADS_DAYSdefault365TRAINING_HUB_RETENTION_BUNDLES_DAYSdefault365TRAINING_HUB_RETENTION_BACKUPS_DAYSdefault30TRAINING_HUB_RETENTION_RATE_LIMIT_DAYSdefault7TRAINING_HUB_RETENTION_AUTO_ENABLEDdefaultfalseTRAINING_HUB_RETENTION_AUTO_INTERVAL_MINUTESdefault1440TRAINING_HUB_BACKUP_RESTORE_MAX_BYTESdefault536870912TRAINING_HUB_SECURITY_ALERT_WINDOW_MINUTESdefault15TRAINING_HUB_SECURITY_ALERT_COOLDOWN_MINUTESdefault15TRAINING_HUB_SECURITY_ALERT_FAILED_LOGIN_THRESHOLDdefault10TRAINING_HUB_SECURITY_ALERT_MFA_FAILED_THRESHOLDdefault6TRAINING_HUB_SECURITY_ALERT_PASSWORD_RESET_THRESHOLDdefault10TRAINING_HUB_STORAGE_DIRdefault./dataTRAINING_HUB_ADMIN_EMAILSoptional, comma-separated (informational only)TRAINING_HUB_ADMIN_USERNAMESrequired for first-account admin bootstrapTRAINING_HUB_TRUSTED_PROXIESoptional, comma-separated exact IPs or CIDR ranges (docker-compose.ymlkeeps127.0.0.1for the internal healthcheck and appends the internal Caddy IP automatically)TRAINING_HUB_PROJECT_ROOToptionalMARKETGUARD_HYPIXEL_API_BASE_URLdefaulthttps://api.hypixel.net/v2MARKETGUARD_REQUEST_TIMEOUT_SECONDSdefault10MARKETGUARD_MAX_PARALLEL_PAGESdefault8MARKETGUARD_SNAPSHOT_RETRIESdefault3MARKETGUARD_CACHE_TTL_SECONDSdefault60MARKETGUARD_STALE_IF_ERROR_SECONDSdefault300MARKETGUARD_LOWESTBIN_RATE_LIMIT_PER_MINUTEdefault30MARKETGUARD_HTTP_USER_AGENTdefaultScamScreener-MarketGuard/1.0MARKETGUARD_TRUSTED_PROXIESoptional, comma-separated exact IPs or CIDR ranges (falls back toTRAINING_HUB_TRUSTED_PROXIESwhen unset)
Production-mode startup checks (TRAINING_HUB_ENV=production) enforce:
TRAINING_HUB_ENFORCE_HTTPS=true- strong
TRAINING_HUB_SECRET_KEY(>= 32 chars) TRAINING_HUB_ADMIN_MFA_REQUIRED=trueTRAINING_HUB_ENABLE_RATE_LIMIT=trueTRAINING_HUB_ENFORCE_ORIGIN_CHECK=true- explicit
TRAINING_HUB_ALLOWED_HOSTS(no wildcard) - MariaDB TLS enabled when MariaDB is configured
- no token disclosure in forgot-password UI (
TRAINING_HUB_PASSWORD_RESET_SHOW_TOKEN=false)
Admin trigger creates a merged bundle and records the run as prepared.
Security headers include CSP, COOP/CORP, X-Frame-Options, and Permissions-Policy.
Failed/locked login attempts for known accounts are written to the audit log.
Users can change their password from the dashboard; this revokes other active sessions.
Admin can run retention cleanup from /admin to prune stale sessions, reset tokens, MFA challenges, logs, uploads, bundles, backups, and rate-limit rows.
Automatic retention cleanup runs in the background when TRAINING_HUB_RETENTION_AUTO_ENABLED=true.
Admin can create and restore backups from /admin (archive includes DB export + uploads + bundles; restore requires valid signed manifest).
Prometheus-compatible monitoring is available at /api/v1/metrics.
Container hardening defaults:
- runs as non-root user
- read-only root filesystem in
docker-compose.yml - dropped Linux capabilities (
cap_drop: ALL) no-new-privilegesenabled
Supply-chain checks:
- GitHub Actions workflow
.github/workflows/server-security.ymlrunspip-auditandtrivy - Dependabot config
.github/dependabot.ymlenables weekly dependency updates
GET /api/v1/healthGET /api/v1/lowestbin
/api/v1/health returns status, UTC time, user/upload counts, and storage metadata.
/api/v1/lowestbin returns a flat Moulberry-compatible JSON object whose keys are item identifiers and whose values are the current Lowest BIN prices.