# Production ("deploy") Conjurer bot A second Conjurer bot that runs **alongside** the test `bot` in the `conjurer` namespace, sharing the same librarian / musician / radio and the shared `CONJURER_API_KEY`. It differs from the test bot only in: - its Discord token: the **`deploy-conjurer-netrc`** secret (already created), - distinct names/labels (`deploy-bot`) and NodePort **32443**, - **`/data` on NFS (RWX)** instead of a block PVC — so you can seed/upload files while it runs and back it up concurrently. Files: `deploy-bot.yaml` (Deployment + Service), `deploy-bot-backup.yaml` (daily backup CronJob). Both are wired into `kustomization.yaml`. ## Update channel — only on `[deploy]` Unlike the test bot (which tracks every build), the production bot updates **only when you promote a version**. It runs a **separate image**, `conjurer-bot-deploy`, which the conjurer CI tags **only when the commit message contains `[deploy]`** (it re-tags the already-built `conjurer-bot:` — same bytes). The image-updater's `deploy-bot` alias then bumps this bot's tag. So: normal commits update the test bot + librarian; a commit with `[deploy]` in its message is the one that also rolls the production bot. **Already bootstrapped:** the channel was seeded by the first `[deploy]` commit (the merge of conjurer#20), which promoted `conjurer-bot-deploy:fbd1ec9f` — the tag pinned in `kustomization.yaml`. From here the image-updater keeps it current on each future `[deploy]` commit. Note the librarian is shared and still tracks latest, so mind large bot⇄librarian version skews. If you ever need to seed a tag by hand: ```bash docker pull gitea.czernobog.pl/gitea/conjurer-bot: docker tag gitea.czernobog.pl/gitea/conjurer-bot: \ gitea.czernobog.pl/gitea/conjurer-bot-deploy: docker push gitea.czernobog.pl/gitea/conjurer-bot-deploy: ``` ## One-time setup 1. **Secret** — already exists as `deploy-conjurer-netrc` (a netrc carrying the deploy Discord token, key `.netrc`). Nothing to do; the Deployment mounts it at `/secrets/.netrc`. 2. **NFS export** — create the data + backup dirs on the NFS server (`192.168.1.34`, adjust to your real export): ``` /mnt/Tank1/conjurer_swap/deploy-bot/data /mnt/Tank1/conjurer_swap/deploy-bot/backups ``` ## Seeding / uploading config & state into /data Because `/data` is a plain NFS export, you upload files by copying them onto the share — **no `kubectl cp`, no scaling the bot down**. From the PVE box that holds `/srv/data` (mount the same export there, or rsync over it): ```bash # on a host that can see the NFS export: rsync -a /srv/data/ 192.168.1.34:/mnt/Tank1/conjurer_swap/deploy-bot/data/ ``` The bot roots everything under `CONJURER_DATA_DIR=/data`, so these land where it expects them: ``` accident_log.json Conjurer_graphics/ music/ pamiec.json pamiec_muzyki.json settings.json system_gpt_settings.json transcripts/ ``` Upload as much or as little as you like — the bot seeds any missing JSON on first run. Live-editing `pamiec.json` while the bot writes to it can race; prefer seeding before first start or during a brief `kubectl -n conjurer scale deploy/deploy-bot --replicas=0` window. ## Backups (production only) `deploy-bot-backup` runs daily (04:17) and writes to the `backups` export: - `current/` — a full rsync mirror of `/data` (incl. music/graphics), always the latest state, - `snapshots/deploy-state-.tgz` — dated archives of the critical small state (both memories, settings, accident log, transcripts), kept `RETENTION_DAYS` (30) days. The test bot is deliberately **not** backed up (amnesia there is fine). Restore example: ```bash tar xzf snapshots/deploy-state-2026-08-03-0417.tgz -C /mnt/.../deploy-bot/data/ ``` ## Routing librarian / musician callbacks **Librarian — handled automatically.** Each bot advertises its own `CONJURER_SELF_CALLBACK` (test `…:32442`, this bot `…:32443`) on every query and ping, and the librarian answers each result/pong back to the bot that asked. One librarian serves both bots — no `CONJURER_MAIN_BOT` repointing needed. (The librarian keeps `CONJURER_MAIN_BOT: http://bot:5000` only as a fallback for a bot that doesn't send a callback.) **Musician — still single-target.** The musician pushes "now playing" events to its one `CONJURER_MAIN_BOT` (currently the test bot, `192.168.1.73:32442`). Only one bot gets radio events; repoint the musician's `CONJURER_MAIN_BOT` to `192.168.1.73:32443` if the production bot should show them instead.