4.8 KiB
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). A push to
main installs itself: the workflow's last job posts the bundle to the server's deploy service, which
checks its signature and every checksum, swaps it into /srv/bot/app (bind-mounted at /app, previous
release kept for --rollback) and restarts the container. The install log comes back in the CI job.
A branch or pull request runs the tests and builds the artifact, and deploys nothing. The python tests gate the build; the website's node tests are advisory and only add a warning when they fail.
Your secrets live in Gitea
Set them on this repo under Settings → Actions. The deploy job sends them with the bundle, signed, and the server writes the ones it allows into the environment the container gets:
| where | name | what |
|---|---|---|
| Secrets | BOT_DEPLOY_SECRET |
signs the upload; must match the server (the admin has it) |
| Secrets | DISCORD_BOT_TOKEN |
the bot token |
| Variables | DISCORD_GUILD_ID |
the Discord server id |
Rotating the token is an edit here plus a re-run of the workflow; nobody needs to touch the server. The
server keeps what it generates, chiefly DB_PASSWORD, and accepts only the names its BOT_ENV_KEYS lists —
anything else is reported in the job as settings_refused. Need a new setting? Ask the admin to add its name
to that list, then add it here.
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.zip # install a release by hand (zip or tar.gz)
./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/clipsare owned like everything else there). /appholds a released bundle; treat it as read-only and write to/datainstead.- If you write clips:
/media/clips/<discord-username>/<name>.mp4plus<name>.jsonwith{"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).