Repository navigation
feat: start jobs and hand mau its export from Discord - #72
Merged
Merged
Conversation
A Discord app on webhooks.session.codes, answering two guild slash commands: - /run job:<job> starts session-ops@<job>.service for a job jobs.toml marks `discord = true` (crowdin-sync and crowdin-duplicates), refused while that job or any queued job is running or waiting to. - /mau-upload file:<csv> downloads the attachment, checks it with mau's own parser, and moves it into mau's inbox, whose path unit runs the job. The relay runs as opsbot behind nginx on 127.0.0.1:8081, with no gateway connection. It refuses a request that Discord did not sign, that comes from another server, or whose author is in neither allowlist. The polkit rule `session-ops polkit` writes from jobs.toml lets opsbot start the discord jobs and nothing else, and the mau inbox is the one path it can write.
/run refused while the queue was busy, but nothing stopped the queue's timer starting while a /run job was still going: /run crowdin-duplicates at 09:55 then ran beside the 10:00 crowdin-sync, two Crowdin clients against one rate limit. /run now also refuses when the queue timer's next run is sooner than now plus the job's timeout, since systemd kills the job by then. The next run comes from `systemctl list-timers --output=json`: on systemd 252, the host's version, `show --timestamp=unix -P NextElapseUSecRealtime` still prints local time. The check and `systemctl start --no-block` now run under one lock, so a second /run a moment later sees the first's job queued. A job's timeout is parsed as a systemd time span when jobs.toml loads, so a bad one fails there.
…the URL Discord checks the Interactions Endpoint URL with a request that, without the route, hits the catch-all 404, and it then refuses to save the URL.
default_member_permissions "0" hides a command from every member without Administrator; the owner and administrators still see it. The relay's allowlist refuses them like anyone else.
install.sh made every watched job's inbox writable by opsbot, though the relay writes only mau's. Other watched inboxes stay the job account's alone, at 0700. A test ties the relay unit's ReadWritePaths= and install.sh to mau's watch.
The doc page named only the busy check. The start lock holds only within one process, which is how session-ops-discord.service runs uvicorn.
/run refuses a job whose timeout would carry it past the queue's next run, read from list-timers. With RandomizedDelaySec, re-arming the timer draws a new delay, so the queue could start up to two minutes before the time /run read. On one host the delay spread nothing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on #71:
/mau-uploadfeeds themaujob's inbox. Retarget tomainonce #71 merges.Two guild slash commands, answered by a new always-on service on the webhooks host.
Commands
/run job:<job>startssession-ops@<job>.service. The choices come from the jobsjobs.tomlmarksdiscord = true:crowdin-syncandcrowdin-duplicatesfor now. It is refused while that job or any queued job is running or waiting to, and when the queue timer's next run (fromsystemctl list-timers --output=json) is sooner than now plus the job'stimeout, since systemd kills the job by then. That keeps the queue's no-overlap rule both ways. The check and the start run under one lock, so two/runa moment apart cannot both pass. The queue timer no longer hasRandomizedDelaySec, so its next run is the scheduled time. The confirmation naming who started it is posted in the channel; refusals are ephemeral./mau-upload file:<csv>downloads the attachment from Discord's CDN and checks it withmau.parse_export. Only then is it renamed intomau/inbox/(written as a dotfile first, which the path unit's glob skips). A filemauwould reject is refused in Discord and never lands. The monthly reminder now points at it beforersync.What the app sees
HTTP interactions only,
applications.commandsscope, no gateway, so Discord sends nothing but invocations of its own commands. Commands are registered withdefault_member_permissions: "0", hidden from everyone but server administrators until Server Settings → Integrations grants them to a role.Gates
DISCORD_GUILD_IDmatch (DMs refused).ALLOWED_USER_IDS/ALLOWED_ROLE_IDS; both empty refuses everybody.opsbot.session-ops polkitgenerates a rule fromjobs.tomlallowing that accountverb == "start"on exactly thediscord = trueunits. The mau inbox (sessionops:opsbot 0770) is its only writable path.Deploy
deploy/session-ops-discord.serviceon127.0.0.1:8081, hardened likezendesk-relay, plusAF_UNIXfor systemctl's D-Bus calls.install.sh: theopsbotaccount, the group on mau's inbox (other watched inboxes stay 0700), the polkit rule, and starting the service oncediscord.envhas content. The two relays now share one enable/restart helper.nginx-webhooks.conf: alocation = /discord/interactionsblock. certbot owns the live file, so it needs adding by hand.deploy/README.mdunder "Discord commands". The bot token is only forsession-ops discord-registerand is typed in at registration, never stored indiscord.env.Not yet done
/etc/polkit-1). Untilapt install polkitd(Debian 12 ships 122, which reads the JSrules.drule), every/rungets "Access denied".runuser -u opsbot -- systemctl --no-ask-password start session-ops@token-expiry.serviceis denied./run crowdin-duplicatesstarts the job and its post appears./mau-uploadwith a real export leads to amaurun.Tests
uv run --locked python -m unittest discover -s tests -t .(838 OK),ruff check .clean.New:
tests/ops/test_discord_relay.pycovers:The polkit rule has a golden.