Files
Media-Server-Setup/bot
Brandyn / Techy 223458550a Bot hosting: releases are self-contained CI bundles, and the bot serves its own web UI
The image no longer installs the bot's dependencies: its CI builds one tar.gz holding the app plus a
vendor/ directory of prebuilt wheels, and PYTHONPATH=/app/vendor makes the code import them. A new
dependency therefore needs no image rebuild and the server contacts no package index.

- deployBotBundle.sh: install / --latest / --status / --rollback, verifies every file against BUILD.json
  and keeps the previous release beside the live one.
- compose: mount ${BOT_APP_DIR:-/srv/bot/app} at /app, set DB_BACKEND, DATA_DIR, WEB_ALLOWED_GROUPS,
  WEB_SERVE_UI and PYTHONPATH. bot/app placeholder untracked; install.sh creates /srv/bot/app.
- The placeholder requirements would have crashed the bot on import (no sqlglot), which is why the
  image now ships none of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-02 21:18:15 -06:00
..

discord-bot — for the bot dev

Start with AGENT.md in this folder: the stack, the rules, the layout, the web-UI auth contract.

How code reaches the server

Your repo's Gitea Actions workflow builds a release bundle: one tar.gz holding main.py, app/, website/, version.json, BUILD.json and vendor/ (your dependencies as prebuilt wheels). The admin installs it with ./deployBotBundle.sh <bundle.tar.gz> from the stack root; it lands in /srv/bot/app, which is bind-mounted into the container at /app, and the previous release is kept for --rollback.

The image contains no bot dependencies: the container imports yours from /app/vendor (PYTHONPATH=/app/vendor). Adding a dependency means editing app/requirements.txt and pushing — no image rebuild, no package index on the server. The image only changes if you need a new system package (ffmpeg and curl are already there); ask the admin for that.

Day to day (on the server, in ~/vanguard-stream)

./deployBotBundle.sh vanguard-bot-bundle.tar.gz   # install a release
./deployBotBundle.sh --status                     # what is installed; container state
./deployBotBundle.sh --rollback                   # previous release
docker compose logs -f discord-bot                # live log
docker compose restart discord-bot                # restart without changing code
docker compose exec discord-bot python            # a REPL inside the container (your vendor/ is on sys.path)

Environment the container gives you

var value
DISCORD_BOT_TOKEN from .env (stack root) — the Bot added to the existing Authentik Discord application
DISCORD_GUILD_ID the server
DB_BACKEND mariadb
DB_HOST / DB_PORT host.docker.internal / 3306 (MariaDB runs on the host network)
DB_NAME / DB_USER / DB_PASSWORD your own database + user (vanguardbot / bot / generated)
DATA_DIR /data — persistent, owned by you
MEDIA_CLIPS /media/clips — rw mount of /srv/media/clips (folder per user, for clip submissions)
WEB_HOST / WEB_PORT 0.0.0.0 / 8000 — published as https://bot.<domain>/ via Caddy + Authentik
WEB_PUBLIC_URL https://bot.<domain> for absolute links
WEB_SERVE_UI true — serve your pages at / on the same port as /api
WEB_ALLOWED_GROUPS bot-users,admins — the groups your own code must accept
AUTH_HEADER_USER / _GROUPS / _EMAIL X-Authentik-Username / X-Authentik-Groups / X-Authentik-Email — identity injected by Caddy, trust these, implement no login
PYTHONPATH /app/vendor
TZ America/New_York

SQLAlchemy URL: mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME} async: mysql+aiomysql://...

Database from your own machine

MariaDB is reachable directly: media.dumpstervigilantes.rip:3306, user bot, database vanguardbot, password from the admin (./configure.py --show on the server). Root is local-only. New connections are rate limited at the firewall; if yours get dropped, ask the admin to add your IP to db_allow.

Conventions

  • Run as uid 1000 (files you write under /media/clips are owned like everything else there).
  • /app holds a released bundle; treat it as read-only and write to /data instead.
  • If you write clips: /media/clips/<discord-username>/<name>.mp4 plus <name>.json with {"timestamp": "<UTC ISO-8601>", "source": "medal", "source_url": "...", "uploader": "...", "title": "..."} — the Jellyfin plugin reads that sidecar for the exact recording time.
  • Log to stdout; the container captures it. One event per line, key=value, keeps it searchable (LOGGING.md).