From d503a760a6a1e9df96ebc1e879dee066a061c579 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Fri, 16 Oct 2026 15:03:19 +0000 Subject: [PATCH 1/2] docs(ProvinceSystem): patch note jobs run as their own users The watch and weekly jobs move from a root cron job to systemd timers with throwaway users, and the rewrite worker runs a root-owned copy installed by tfmc-patchnotes-update instead of the site checkout. Co-Authored-By: Claude Opus 5.5 --- projects/ProvinceSystem/DEPLOY.md | 3 ++ projects/ProvinceSystem/README.md | 1 + projects/ProvinceSystem/docs/README.md | 1 + .../docs/ops/patch-notes-jobs.md | 46 +++++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 projects/ProvinceSystem/docs/ops/patch-notes-jobs.md diff --git a/projects/ProvinceSystem/DEPLOY.md b/projects/ProvinceSystem/DEPLOY.md index d30bbc8..9a20545 100644 --- a/projects/ProvinceSystem/DEPLOY.md +++ b/projects/ProvinceSystem/DEPLOY.md @@ -77,6 +77,9 @@ Deploys to the same site run one at a time. cannot read). 5. Waits up to 3 minutes for both health checks. If they fail, it puts the previous commit back, rebuilds and checks again, then exits 5. It never restores the database; restore a backup by hand if a migration needs undoing. +6. After a healthy www deploy that changes `backend/src/patchnotes/`, runs `sudo tfmc-patchnotes-update ` so the + [patch note jobs](docs/ops/patch-notes-jobs.md) use the same code. A failure there is logged and does not fail the + deploy. Each run appends a line to `~/site-deploy.log`. diff --git a/projects/ProvinceSystem/README.md b/projects/ProvinceSystem/README.md index 242b0d5..5569443 100644 --- a/projects/ProvinceSystem/README.md +++ b/projects/ProvinceSystem/README.md @@ -17,6 +17,7 @@ TFMC runs Minecraft **1.21.10**. See the [shared platform and build baseline](.. - [Rail network](docs/integrations/rail.md) - [Server plugin versions and restart controls](docs/integrations/servers.md) - [Database studio](docs/ops/database-studio.md) +- [Patch note host jobs](docs/ops/patch-notes-jobs.md) - [Patreon integration](docs/integrations/patreon.md) and [backend protocol](docs/integrations/patreon-backend.md) - [Kit default skins](backend/assets/kit_skins/README.md) - [Map regeneration benchmarks](backend/benchmarks/regen/README.md) diff --git a/projects/ProvinceSystem/docs/README.md b/projects/ProvinceSystem/docs/README.md index 41f391d..09f32cc 100644 --- a/projects/ProvinceSystem/docs/README.md +++ b/projects/ProvinceSystem/docs/README.md @@ -27,6 +27,7 @@ This section is the product and technical reference for **ProvinceSystem**. Tech 5. [ops/sheet-render.md](ops/sheet-render.md) - 3D review-sheet renderer (prod deploy + smoke) 6. [ops/dev-config.md](ops/dev-config.md) - dev-only flags and shortcuts 7. [ops/diagnostic.md](ops/diagnostic.md) - player diagnostic page and staff reports +8. [ops/patch-notes-jobs.md](ops/patch-notes-jobs.md) - patch note jobs on the TF server Schema assets: [assets/map-export-schema.json](assets/map-export-schema.json) (SF map export contract). diff --git a/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md b/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md new file mode 100644 index 0000000..e69a8b4 --- /dev/null +++ b/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md @@ -0,0 +1,46 @@ +# Patch note host jobs + +Three systemd units on the TF server feed the patch notes on `www.tfminecraft.net`. They call www's API on +`127.0.0.1:8000` with the staff key from `/etc/tfmc/patchnotes.env` (root, mode 600, `STAFF_KEY` and +`API_BASE_URL`), and run a root-owned copy of +[`backend/src/patchnotes`](https://github.com/TF-Minecraft/ProvinceSystem/tree/main/backend/src/patchnotes), never the +site checkout. + +| Unit | Runs | As | Sees | +| --- | --- | --- | --- | +| `tfmc-patchnotes-watch.timer` | `patchnotes.watch_plugins --once` every ten minutes | throwaway user (`DynamicUser`) | Main's `plugins` folder, read-only; state in `/var/lib/tfmc-patchnotes/` | +| `tfmc-patchnotes-weekly.timer` | `patchnotes.weekly_run` every ten minutes, offset by five | throwaway user | Main's `Backups` folder, read-only | +| `tfmc-patchnotes-rewrite.service` | `patchnotes.rewrite_worker`, always on | root | the Codex CLI and its login in `/root`; no `/home`, no Docker | + +The watcher holds `CAP_DAC_READ_SEARCH` because some plugin files are `amp`'s and mode 600; it can read only what is +bound into its sandbox. The rewrite worker stays root because the Codex login it uses belongs to root. + +## Updating the code + +The copies live in `/usr/local/lib/tfmc-patchnotes//patchnotes/`, and `current` points at the one in use. +`site-deploy` installs a new copy after a www deploy that changes `backend/src/patchnotes/`. By hand: + +```bash +sudo tfmc-patchnotes-update +``` + +[`tfmc-patchnotes-update`](https://github.com/TF-Minecraft/ProvinceSystem/blob/main/scripts/patchnotes-update.py) +refuses commits that are not on `main` or have no passing **Build**, downloads the package's modules from GitHub +(tests are left out), restarts the rewrite worker and puts the previous copy back if it does not stay up. The timers +pick up the new copy on their next run. It keeps the five newest copies and logs to +`/var/log/tfmc-patchnotes-update.log`. + +## Installing + +From a ProvinceSystem checkout, as root: + +```bash +install -m 600 /dev/null /etc/tfmc/patchnotes.env # then set STAFF_KEY=... and API_BASE_URL=... +install -m 755 scripts/patchnotes-update.py /usr/local/sbin/tfmc-patchnotes-update +cp scripts/tfmc-patchnotes-*.service scripts/tfmc-patchnotes-*.timer /etc/systemd/system/ +systemctl daemon-reload +tfmc-patchnotes-update +systemctl enable --now tfmc-patchnotes-rewrite tfmc-patchnotes-watch.timer tfmc-patchnotes-weekly.timer +``` + +Check them with `systemctl list-timers 'tfmc-patchnotes-*'` and `journalctl -u 'tfmc-patchnotes-*'`. From 67f580b0a3d0cc87363f3f3c1a918ec6780257c2 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Fri, 16 Oct 2026 15:12:48 +0000 Subject: [PATCH 2/2] docs(ProvinceSystem): patch note job limits, sudo and refusals Co-Authored-By: Claude Opus 5.5 --- .../docs/ops/patch-notes-jobs.md | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md b/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md index e69a8b4..243eb86 100644 --- a/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md +++ b/projects/ProvinceSystem/docs/ops/patch-notes-jobs.md @@ -12,8 +12,11 @@ site checkout. | `tfmc-patchnotes-weekly.timer` | `patchnotes.weekly_run` every ten minutes, offset by five | throwaway user | Main's `Backups` folder, read-only | | `tfmc-patchnotes-rewrite.service` | `patchnotes.rewrite_worker`, always on | root | the Codex CLI and its login in `/root`; no `/home`, no Docker | -The watcher holds `CAP_DAC_READ_SEARCH` because some plugin files are `amp`'s and mode 600; it can read only what is -bound into its sandbox. The rewrite worker stays root because the Codex login it uses belongs to root. +The watcher holds `CAP_DAC_READ_SEARCH` because some plugin files are `amp`'s and mode 600, so it can read any file +it can see. Its `/home` holds only the plugin folders and its `/var/lib` only its own state; the rest of the system is +read-only, with `/etc/shadow`, `/etc/ssh`, `/etc/ssl/private`, `/etc/tfmc`, the Docker socket and the logs hidden. The +weekly job has no extra rights: the backup zips are world-readable. Both time out after 30 minutes. The rewrite worker +stays root because the Codex login it uses belongs to root. ## Updating the code @@ -25,10 +28,12 @@ sudo tfmc-patchnotes-update ``` [`tfmc-patchnotes-update`](https://github.com/TF-Minecraft/ProvinceSystem/blob/main/scripts/patchnotes-update.py) -refuses commits that are not on `main` or have no passing **Build**, downloads the package's modules from GitHub -(tests are left out), restarts the rewrite worker and puts the previous copy back if it does not stay up. The timers -pick up the new copy on their next run. It keeps the five newest copies and logs to -`/var/log/tfmc-patchnotes-update.log`. +refuses commits that are not on `main` or have no passing **Build**, and commits that change the updater or any of +the five unit files: install those by hand as below. It downloads the package's modules from GitHub (tests are left +out; any other file or folder in the package is refused), restarts the rewrite worker and needs it to stay up with the +same process for 20 seconds. Otherwise it puts the previous copy back, or stops the worker if there was none. The +timers pick up the new copy on their next run. It keeps the five newest copies and logs to +`/var/log/tfmc-patchnotes-update.log`; `site-deploy` also records a failed update in its own log. ## Installing @@ -41,6 +46,10 @@ cp scripts/tfmc-patchnotes-*.service scripts/tfmc-patchnotes-*.timer /etc/system systemctl daemon-reload tfmc-patchnotes-update systemctl enable --now tfmc-patchnotes-rewrite tfmc-patchnotes-watch.timer tfmc-patchnotes-weekly.timer +systemctl start tfmc-patchnotes-watch tfmc-patchnotes-weekly # one run of each, to check ``` +The account that runs `site-deploy` needs `NOPASSWD` sudo for `/usr/local/sbin/tfmc-patchnotes-update`. After a +change to the updater or a unit, repeat the `install`/`cp` lines and `systemctl daemon-reload` before updating. + Check them with `systemctl list-timers 'tfmc-patchnotes-*'` and `journalctl -u 'tfmc-patchnotes-*'`.