tautulli: placed config, the build ace runs, and a runtime that reads its own key #151
Closed
mesh-admin
wants to merge 2 commits from
feat/tautulli-for-ace into main
pull from: feat/tautulli-for-ace
merge into: :main
:main
:fix/resolver-passes-the-dnssec-bit
:fix/mailu-admin-asks-the-machines-resolver
:fix/110-the-resolver-answers-a-container
:feat/qbittorrent-for-ace
:feat/servarr-api-provision
:feat/home-assistant-for-ace
:feat/tautulli-for-ace
:feat/bookshelf-for-ace
:feat/lidarr-for-ace
:feat/radarr-for-ace
:feat/sonarr-for-ace
:feat/kometa-for-ace
:feat/plex-for-ace
:fix/manifests-publish-software-ports
:feat/nzbget-for-ace
:feat/bazarr-for-ace
:fix/sidecars-dial-the-port-they-were-given
:feat/ombi-for-ace
:chore/remove-the-network-checker-module
:feat/a-network-checker-module
:feat/modules-name-their-endpoints
:fix/a-routed-module-listens-from-the-mesh
:fix/the-resolver-declares-both-protocols
:fix/sshd-declares-the-daemon-it-owns
:fix/fail2ban-bans-through-what-every-machine-has
:fix/fail2ban-declares-the-log-its-own-jail-reads
:fix/fail2ban-restarts-on-its-log-target
:fix/fail2ban-declares-where-it-logs
:feat/the-catalogue-hears-what-it-missed
:feat/the-catalogue-prepares-its-own-schema
:fix/the-catalogue-declares-the-event-it-emits
:feat/a-merge-rebuilds-what-it-changed
:fix/a-merge-older-than-the-watching-is-history
:fix/a-merge-announced-is-said
:fix/the-forge-watches-every-repository
:feat/the-forge-announces-every-merge
:feat/nats-serves-the-meshs-certificate
:fix/nats-declares-its-base
:feat/amqp-leaves-the-catalogue
:restore/broker-claim
:revert/broker-seat-claim
:fix/broker-seat-must-stay-held
:fix/go-126-base
:feat/nats-genesis
:feat/ssh-client-module
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Prepares tautulli for ace's HAL → nox-mesh move (preparation only, nothing deployed).
configis a placed directory (${dir:config}); the route binds into the placed state (${dir:state}/route.json). No/services/...path left (ADR 0112).sha256:e35570d6…). Tautulli migrates its own DB schema, so the pin follows what ace runs.http://127.0.0.1:8181(the software port); it now uses${port:8181}, as gitea does.config.ini, which the runtime already mounted read-only but never read.client.tsnow reads[General] api_keyfrom there. Nothing to mint or accept.Verified:
go test ./internal/catalogue/ -run Cataloguewith MESH_CATALOGUE set (not skipped). The pinned image in a throwaway container answers/status200, and on start it re-owns a 1001:2000/configto PUID/PGID 1000.client.tsrun under node read the key from that instance's config.ini, and get_activity and get_history returned success.The module stated /services/tautulli/config and /var/lib/mesh/tautulli/route.json, novox's layout, which no definition may carry (ADR 0112). Tautulli's config dir is now a placed directory (${dir:config}) and the route binds into the placed state (${dir:state}), as searxng and mosquitto do. The image was pinned to v2.18.1-ls242; ace runs ls244 (2026-09-11), and Tautulli migrates its own database schema, so the pin moves to the digest ace runs. The runtime called http://127.0.0.1:8181, which is the software's port, not the machine port the mesh assigns; it now asks with ${port:8181}, as gitea does. The runtime mounted Tautulli's config dir read-only but never read it: its API key could only come from MESH_TAUTULLI_APIKEY or the settings-merged config.json, i.e. a secret in settings. Tautulli mints and owns that key in its config.ini, so the runtime now reads it from there. Nothing for the mesh to mint or accept. Verified: catalogue tests with MESH_CATALOGUE pointing here (not skipped); the pinned image started in a throwaway container on a dir owned 1001:2000 with PUID/PGID 1000 answers /status 200 and re-owns /config to 1000:1000 on start; client.ts, run under node, read the key from that instance's config.ini and got success from get_activity and get_history; without a config.ini it throws, which the tools and events entrypoints already treat as "not configured".tautulli now reaches plex through
plex-api(commit991e33f). This replaces the plan's hand edit ofpms_ipin the window.Why the write happens before Tautulli starts. Tautulli keeps its Plex connection only in config.ini. It reads that file at start and writes its whole in-memory config back on every shutdown. Its API has no command that sets the connection, and its settings form needs an admin login. So a step that edits the file after start is overwritten as soon as the container is recreated.
The write therefore runs in the one place nothing can overwrite it. The linuxserver image's custom-init runs
plex/mesh-plex.pyas root, after the old container has stopped and before Tautulli reads the file. The server container restarts onbound-plex-apiandsecret-plex-api, so a moved plex or a newly accepted token lands.What the write changes. Only
[PMS]keys, only when they differ, and every other line stays byte for byte:pms_ip,pms_port,pms_sslandpms_urlcome from the binding.pms_identifiercomes from plex's/identity.pms_tokenis written only when plex accepts it.A minted value is never written, but the address still is. So Tautulli's existing working token keeps working at plex's new address even before the accept.
The check step. A custom-init failure is only a log line, so a run-once
plexstep checks what the mesh can report. It is declared last, so it gates nothing. It fails in three cases:secret accept ace tautulli plex-api --provider ace --from <file>;server_statusis not connected.The step writes nothing.
Checks run:
npm test. They cover the script under python3 against a fake plex, the step against fakes, and a check that module.json carries exactlyplex/mesh-plex.pyandplex/50-mesh-plex.go test ./internal/catalogue/passes.pms_tokenwas written, Tautulli reported itself connected, and the step passed.pms_identifierequals plex'smachineIdentifier, so on ace the window changes only ip, url and token.Superseded: this module now lives in novox/mesh-media-catalog (the media chain, consolidated from #145–#168 in stack order; its non-media parts merged via #195). Closing.
Pull request closed