# 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. ## 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./` via Caddy + Authentik | | `WEB_PUBLIC_URL` | `https://bot.` 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//.mp4` plus `.json` with `{"timestamp": "", "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`).