T11: Mastodon 4.7.3 in the pasture

peers/mastodon.sh runs Mastodon's web and sidekiq containers on a shared Postgres and Redis
(peers/shared.sh). They trust Caddy's CA through SSL_CERT_FILE and reach private addresses
through ALLOWED_PRIVATE_ADDRESSES. Its users and tokens come from tootctl and rails runner.

scenarios/mastodon.sh adds 49 checks, as listed in docs/INTEROP.md: follows, posts,
replies, likes and boosts with undos, DMs, polls, FEP-044f quotes both ways, media through
the proxy, edits, deletes, locked follows, a circle request, reports, blocks and statistics.
Two are expected failures:
- inbound Block (P7);
- circle posts. Mastodon 4.7 loses the recipient of deliveries to its numeric
  /ap/users/<id>/inbox and then drops a post that names no local account. The fix on our
  side changes what a circle reveals, so it waits for the owner.

GoToSocial and Mastodon together: 86 passed, 0 failed.

The pasture now copies Caddy's root certificate reliably, readable by the peers, and
rebuilds the bundle each time. The CA directory is mounted shared (:z), because a private
:Z label locks out every container but the last. pfetch reaches PrivaPub's own https URIs
through Caddy. PRIVAPUB_ENV passes settings to PrivaPub, which scenarios/crawler.sh uses to
check the opt-in crawler against Mastodon: it visits, describes and reads the peers list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-03 12:30:05 +02:00
1 parent d0a13d9cc2
commit e0f2eb7da7
9 files changed
+367 -28

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
# The opt-in crawler against the pasture's own peers. Needs PrivaPub started with the crawler on:
# PRIVAPUB_ENV="Statistics__Crawler__Enabled=true Statistics__Crawler__Seeds__0=mastodon.test" tools/pasture/run.sh up mastodon
# PrivaPub starts before its peers, so a first visit can find robots.txt answering 502 and rightly keep out for a week;
# the scenario then forgets that visit and restarts PrivaPub, which plans again.
echo "crawler"
privapub_root >/dev/null
podman exec -w /app pasture-privapub /app/PrivaPub admin promote "$ROOT_USER" >/dev/null 2>&1 || true
admin=$(curl -s -X POST $P/clientapi/user/login -H 'Content-Type: application/json' -d "{\"userName\":\"$ROOT_USER\",\"password\":\"$ROOT_PASS\"}" | j "print(d['token'])")
crawler() { curl -s -H "Authorization: Bearer $admin" "$P/clientapi/admin/statistics/crawler"; }
if [ "$(crawler | j "print(any(r['robotsDisallowed'] for r in d['recent']))")" = "True" ]; then
podman exec pasture-mongo mongosh --quiet PrivaPub --eval 'db.Job.deleteMany({Kind:{$in:[8,9]}}); db.RemoteInstance.updateMany({}, {$unset:{LastCrawledAt:1, RobotsDisallowed:1, CrawlError:1}})' >/dev/null
podman restart pasture-privapub >/dev/null
until_true 60 'curl -fs -o /dev/null $P/build.json'
fi
[ "$(crawler | j "print(d['enabled'])")" = "True" ] && ok "the crawler is on" || { ko "the crawler is off: start PrivaPub with PRIVAPUB_ENV=Statistics__Crawler__Enabled=true"; return 0; }
until_true 60 '[ "$(crawler | j "print(any(r[\"host\"]==\"mastodon.test\" and r[\"lastCrawledAt\"] for r in d[\"recent\"]))")" = "True" ]' \
&& ok "the crawler visited mastodon.test" || ko "the crawler never visited mastodon.test"
[ "$(crawler | j "print(next(r['peersCount'] is not None for r in d['recent'] if r['host']=='mastodon.test'))")" = "True" ] \
&& ok "it read mastodon.test's peers list" || ko "no peers list read from mastodon.test"
[ "$(curl -s -H "Authorization: Bearer $admin" "$P/clientapi/admin/statistics/hosts/mastodon.test?days=1" | j "print((d['instance'] or {}).get('software'))")" = "mastodon" ] \
&& ok "mastodon.test is described" || ko "mastodon.test is not described"