Pasture: Mitra 5.9.1
Mitra joins the pasture (peers/mitra.sh) on the shared Postgres; scenarios/mitra.sh drives its Mastodon API through follows, posts, replies, likes, reposts, a reaction, a poll, edits, deletions, direct messages, the unfollow and statistics: 28 checks, with no change to PrivaPub. Mitra carries FEP-8b32 proofs made with Ed25519, which PrivaPub does not verify yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
f6fc0c535c
commit
1c0dfeb9a4
6 files changed
+162
No files matched your search
@@ -561,6 +561,9 @@ tools/pasture/run.sh down # removes e
|
||||
checks.
|
||||
- **Pins (`scenarios/pins.sh`, needs mastodon):** a Mastodon account pins and unpins while alice follows it, alice pins
|
||||
and unpins while it follows her, and a fresh account's earlier pin shows once PrivaPub resolves it. 8 checks.
|
||||
- **Mitra (5.9.1):** its image on the shared Postgres (database `mitra`), as `mitra.test`, with the pasture's bundle as
|
||||
the system's roots; mitrauser comes from its CLI and its token from a password grant. Its search resolves an account
|
||||
elsewhere only without `type`. `scenarios/mitra.sh`, 28 checks.
|
||||
- **Relays (`peers/relay.sh` Activity-Relay 2.0.9 as `relay.test`, on the shared Redis's database 13;
|
||||
`peers/aoderelay.sh` aode-relay 0.3.129 as `aoderelay.test`):** `appsettings.Pasture.json` names both in
|
||||
`Federation:Relays`, so PrivaPub subscribes a minute after it starts. `scenarios/relay.sh` has Mastodon subscribe to
|
||||
|
||||
@@ -33,6 +33,7 @@ and every run starting clean, with signed fetches required (as privapub.thepra.d
|
||||
- **Mbin 1.10.1**
|
||||
- **NodeBB 4.16.1**
|
||||
- **Smithereen 1.0.3**
|
||||
- **Mitra 5.9.1**
|
||||
- **Activity-Relay 2.0.9** and **aode-relay 0.3.129**, as relays PrivaPub reads from
|
||||
- in the town only (a seeded community checked server by server): **Hollo 0.9.19**, **Iceshrimp.NET 2026.1.2-beta**,
|
||||
**Pleroma 2.10.2**
|
||||
|
||||
@@ -753,6 +753,17 @@ PeerTube's own instance account announces each new video too, which we drop: nob
|
||||
follow only PeerTube-like channels and accounts, never a persona. It checks the `Host` header against its own name,
|
||||
without a port, before it gives out its OAuth client.
|
||||
|
||||
### Mitra 5.9.1
|
||||
|
||||
- Signs its deliveries with RSA (draft-cavage) and adds an FEP-8b32 proof made with its Ed25519 key (FEP-521a), which
|
||||
PrivaPub does not verify yet; the HTTP signature is enough for what it delivers itself.
|
||||
- Its Mastodon API resolves an account elsewhere only when the search is not limited to a type
|
||||
(`/api/v2/search?resolve=true`, no `type=accounts`); reactions go through Pleroma's route
|
||||
(`PUT /api/v1/pleroma/statuses/:id/reactions/:emoji`).
|
||||
- **Pasture evidence (2026-10-05, `tools/pasture/scenarios/mitra.sh`):** 28 checks pass, with no change to PrivaPub:
|
||||
follows both ways, posts, replies both ways, likes and reposts both ways, an emoji reaction, a poll and alice's vote,
|
||||
edits and deletions both ways, direct messages both ways, the unfollow, statistics.
|
||||
|
||||
### Relays: Activity-Relay 2.0.9 and aode-relay 0.3.129
|
||||
|
||||
- **Activity-Relay** takes a subscription as a `Follow` of `Public` from an actor with a shared inbox (Mastodon's way)
|
||||
|
||||
@@ -108,6 +108,11 @@ nodebb.test {
|
||||
reverse_proxy pasture-nodebb:4567
|
||||
}
|
||||
|
||||
mitra.test {
|
||||
tls internal
|
||||
reverse_proxy pasture-mitra:8383
|
||||
}
|
||||
|
||||
aoderelay.test {
|
||||
tls internal
|
||||
reverse_proxy pasture-aoderelay:8080
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Mitra 5.9.1 (silverpill): a microblog in Rust with Ed25519 keys beside RSA (FEP-521a), integrity proofs (FEP-8b32),
|
||||
# nomadic identity (FEP-ef61) and account moves. On the shared Postgres (database mitra), as mitra.test; its TLS client
|
||||
# reads the system's roots, over which the pasture's bundle goes. mitrauser is made by its CLI, and its Mastodon API
|
||||
# takes a password grant.
|
||||
MITRA_IMAGE=${MITRA_IMAGE:-codeberg.org/silverpill/mitra:5.9.1}
|
||||
MITRA_PASSWORD=Mitra-Pasture-Pass-1
|
||||
. "$here/peers/shared.sh"
|
||||
|
||||
mitra_up() {
|
||||
shared_postgres_up
|
||||
pg_db mitra
|
||||
local st="$here/.state/mitra"
|
||||
mkdir -p "$st"
|
||||
cat > "$st/config.yaml" <<-EOF2
|
||||
database_url: postgres://pasture:pasture@postgres:5432/mitra
|
||||
storage_dir: /var/lib/mitra
|
||||
web_client_dir: /usr/share/mitra/www
|
||||
http_host: '0.0.0.0'
|
||||
http_port: 8383
|
||||
instance_url: https://mitra.test
|
||||
instance_title: Mitra pasture
|
||||
instance_short_description: the pasture's Mitra
|
||||
instance_description: The pasture's Mitra
|
||||
instance_staff_public: true
|
||||
instance_timeline_public: true
|
||||
registration:
|
||||
type: open
|
||||
default_role: user
|
||||
EOF2
|
||||
podman volume exists pasture-mitra || podman volume create --label pasture=1 pasture-mitra >/dev/null
|
||||
podman run -d --replace --name pasture-mitra --label pasture=1 --network $net -v pasture-mitra:/var/lib/mitra:U \
|
||||
-v "$st/config.yaml:/etc/mitra/config.yaml:Z,ro" -v "$ca/bundle.pem:/etc/ssl/certs/ca-certificates.crt:z,ro" \
|
||||
$MITRA_IMAGE >/dev/null
|
||||
for _ in $(seq 1 60); do
|
||||
site mitra.test -s -o /dev/null -w '%{http_code}' https://mitra.test:6443/api/v1/instance 2>/dev/null | grep -q 200 && break
|
||||
sleep 1
|
||||
done
|
||||
podman exec pasture-mitra mitra create-account mitrauser "$MITRA_PASSWORD" user >/dev/null 2>&1 || true
|
||||
site mitra.test -s -X POST https://mitra.test:6443/oauth/token -H 'Content-Type: application/json' \
|
||||
-d "{\"grant_type\":\"password\",\"username\":\"mitrauser\",\"password\":\"$MITRA_PASSWORD\"}" \
|
||||
| python3 -c 'import json, sys; print(json.load(sys.stdin).get("access_token", ""))' > "$st/mitrauser.token"
|
||||
echo "mitra: https://mitra.test:6443"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
# Mitra 5.9.1: follows both ways, posts, replies, likes, reposts, an emoji reaction, a poll, an edit and a deletion both
|
||||
# ways, direct messages, the unfollow, statistics. Mitra signs its deliveries with RSA and carries FEP-8b32 proofs made
|
||||
# with its Ed25519 key. Driven through its Mastodon API as mitrauser, with the token peers/mitra.sh got.
|
||||
MI=https://mitra.test:6443
|
||||
MIT=$(cat "$here/.state/mitra/mitrauser.token" 2>/dev/null)
|
||||
mi() { site mitra.test -s -H "Authorization: Bearer $MIT" "$@"; }
|
||||
mi_json() { local method=$1 path=$2 body=$3; mi -X "$method" "$MI$path" -H 'Content-Type: application/json' -d "$body"; }
|
||||
mi_status_of() { mi "$MI/api/v1/statuses/${1:-none}"; }
|
||||
p_home_has() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next(((s.get('reblog') or s)['id'] for s in d if '$1' in ((s.get('reblog') or s)['content'] or '')), ''))"; }
|
||||
p_status() { curl -s -H "$PH" "$P/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
|
||||
|
||||
echo "mitra"
|
||||
[ "$(mi "$MI/api/v1/accounts/verify_credentials" | j "print(d['username'])")" = "mitrauser" ] && ok "Mitra token for mitrauser" || { ko "Mitra token"; return 1; }
|
||||
PT=$(privapub_token alice_mitra)
|
||||
PH="Authorization: Bearer $PT"
|
||||
[ -n "$PT" ] && ok "PrivaPub token for alice_mitra" || { ko "PrivaPub token for alice_mitra"; return 1; }
|
||||
run=$(date +%s)
|
||||
|
||||
echo " follows"
|
||||
# (its search resolves an account elsewhere only when it is not limited to a type)
|
||||
alice_on_mi=$(mi "$MI/api/v2/search?q=alice_mitra%40privapub.test&resolve=true" | j "print(d['accounts'][0]['id'])")
|
||||
[ -n "$alice_on_mi" ] && ok "Mitra resolves alice" || ko "Mitra cannot resolve alice"
|
||||
mi -o /dev/null -X POST "$MI/api/v1/accounts/$alice_on_mi/follow"
|
||||
until_true 45 '[ "$(mi "$MI/api/v1/accounts/relationships?id[]=$alice_on_mi" | j "print(d[0][\"following\"])")" = "True" ]' \
|
||||
&& ok "mitrauser follows alice (Accept arrived)" || ko "alice's Accept never reached Mitra"
|
||||
mi_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=mitrauser@mitra.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
||||
[ -n "$mi_on_p" ] && ok "PrivaPub resolves mitrauser" || ko "PrivaPub cannot resolve mitrauser"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$mi_on_p/follow"
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$mi_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
|
||||
&& ok "alice follows mitrauser (Accept arrived)" || ko "Mitra's Accept never arrived"
|
||||
|
||||
echo " posts"
|
||||
mi_post=$(mi_json POST /api/v1/statuses "{\"status\":\"a Mitra post $run\",\"visibility\":\"public\"}")
|
||||
mi_post_id=$(echo "$mi_post" | j "print(d['id'])")
|
||||
until_true 45 '[ -n "$(p_home_has "a Mitra post $run")" ]' && ok "mitrauser's post reaches alice's home" || ko "mitrauser's post never reached alice"
|
||||
mi_on_p_post=$(p_home_has "a Mitra post $run")
|
||||
p_post=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=a PrivaPub post for Mitra $run&visibility=public")
|
||||
p_post_id=$(echo "$p_post" | j "print(d['id'])"); p_post_uri=$(echo "$p_post" | j "print(d['uri'])")
|
||||
until_true 45 '[ "$(mi "$MI/api/v1/timelines/home?limit=40" | j "print(any(s[\"uri\"] == \"$p_post_uri\" for s in d))")" = "True" ]' \
|
||||
&& ok "alice's post reaches mitrauser's home" || ko "alice's post never reached Mitra"
|
||||
p_on_mi=$(mi "$MI/api/v1/timelines/home?limit=40" | j "print(next(s['id'] for s in d if s['uri'] == '$p_post_uri'))")
|
||||
|
||||
echo " replies"
|
||||
mi_json POST /api/v1/statuses "{\"status\":\"@alice_mitra@privapub.test a Mitra reply $run\",\"in_reply_to_id\":\"$p_on_mi\",\"visibility\":\"public\"}" >/dev/null
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id/context" | j "print(any(\"a Mitra reply $run\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
|
||||
&& ok "mitrauser's reply threads under alice's post" || ko "mitrauser's reply missing on PrivaPub"
|
||||
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@mitrauser@mitra.test a PrivaPub reply $run&in_reply_to_id=$mi_on_p_post&visibility=public"
|
||||
until_true 45 '[ "$(mi "$MI/api/v1/statuses/$mi_post_id/context" | j "print(any(\"a PrivaPub reply $run\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
|
||||
&& ok "alice's reply threads under mitrauser's post" || ko "alice's reply missing on Mitra"
|
||||
|
||||
echo " likes, reposts and reactions"
|
||||
mi -o /dev/null -X POST "$MI/api/v1/statuses/$p_on_mi/favourite"
|
||||
until_true 45 '[ "$(p_status $p_post_id favourites_count)" = "1" ]' && ok "mitrauser's like counts on PrivaPub" || ko "mitrauser's like never counted"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$mi_on_p_post/favourite"
|
||||
until_true 45 '[ "$(mi_status_of $mi_post_id | j "print(d[\"favourites_count\"])")" = "1" ]' && ok "alice's like counts on Mitra" || ko "alice's like never counted on Mitra"
|
||||
mi -o /dev/null -X POST "$MI/api/v1/statuses/$p_on_mi/reblog"
|
||||
until_true 45 '[ "$(p_status $p_post_id reblogs_count)" = "1" ]' && ok "mitrauser's repost is a boost on PrivaPub" || ko "mitrauser's repost never counted"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$mi_on_p_post/reblog"
|
||||
until_true 45 '[ "$(mi_status_of $mi_post_id | j "print(d[\"reblogs_count\"])")" = "1" ]' && ok "alice's boost counts on Mitra" || ko "alice's boost never counted on Mitra"
|
||||
mi -o /dev/null -X PUT "$MI/api/v1/pleroma/statuses/$p_on_mi/reactions/%F0%9F%8E%89"
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id" | j "print(any(r[\"name\"] == \"🎉\" for r in (d.get(\"pleroma\") or {}).get(\"emoji_reactions\") or d.get(\"reactions\") or []))")" = "True" ]' \
|
||||
&& ok "mitrauser's reaction reaches alice's post" || ko "mitrauser's reaction never arrived ($(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id" | j "print((d.get('pleroma') or {}).get('emoji_reactions'))"))"
|
||||
|
||||
echo " polls"
|
||||
mi_poll=$(mi_json POST /api/v1/statuses "{\"status\":\"a Mitra poll $run\",\"visibility\":\"public\",\"poll\":{\"options\":[\"hay\",\"clover\"],\"expires_in\":3600}}")
|
||||
until_true 45 '[ -n "$(p_home_has "a Mitra poll $run")" ]' && ok "mitrauser's poll reaches alice" || ko "mitrauser's poll never reached alice"
|
||||
p_poll=$(curl -s -H "$PH" "$P/api/v1/statuses/$(p_home_has "a Mitra poll $run")" | j "print(d['poll']['id'] if d.get('poll') else '')")
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/polls/$p_poll/votes" -d 'choices[]=1'
|
||||
until_true 45 '[ "$(mi_status_of $(echo "$mi_poll" | j "print(d[\"id\"])") | j "print(d[\"poll\"][\"votes_count\"])")" = "1" ]' \
|
||||
&& ok "alice's vote counts on Mitra" || ko "alice's vote never counted on Mitra"
|
||||
|
||||
echo " edits and deletions"
|
||||
mi_json PUT "/api/v1/statuses/$mi_post_id" "{\"status\":\"a Mitra post $run, edited\"}" >/dev/null
|
||||
until_true 45 '[ "$(p_status $mi_on_p_post content | grep -c edited)" = "1" ]' && ok "mitrauser's edit reaches PrivaPub" || ko "mitrauser's edit never arrived"
|
||||
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d "status=a PrivaPub post for Mitra $run, edited"
|
||||
until_true 45 '[ "$(mi_status_of $p_on_mi | j "print(\"edited\" in d[\"content\"])")" = "True" ]' && ok "alice's edit reaches Mitra" || ko "alice's edit never reached Mitra"
|
||||
gone=$(mi_json POST /api/v1/statuses "{\"status\":\"a Mitra post to delete $run\",\"visibility\":\"public\"}" | j "print(d['id'])")
|
||||
until_true 45 '[ -n "$(p_home_has "a Mitra post to delete $run")" ]'
|
||||
gone_on_p=$(p_home_has "a Mitra post to delete $run")
|
||||
mi -o /dev/null -X DELETE "$MI/api/v1/statuses/$gone"
|
||||
until_true 45 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$gone_on_p")" = "404" ]' \
|
||||
&& ok "mitrauser's deletion reaches PrivaPub" || ko "mitrauser's deleted post still shows on PrivaPub"
|
||||
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/statuses/$p_post_id"
|
||||
until_true 45 '[ "$(mi -o /dev/null -w "%{http_code}" "$MI/api/v1/statuses/$p_on_mi")" = "404" ]' \
|
||||
&& ok "alice's deletion reaches Mitra" || ko "alice's deleted post still on Mitra"
|
||||
|
||||
echo " direct messages"
|
||||
mi_json POST /api/v1/statuses "{\"status\":\"@alice_mitra@privapub.test a Mitra secret $run\",\"visibility\":\"direct\"}" >/dev/null
|
||||
until_true 45 'curl -s -H "$PH" "$P/api/v1/conversations" | grep -q "a Mitra secret $run"' && ok "mitrauser's DM arrives" || ko "mitrauser's DM never arrived"
|
||||
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@mitrauser@mitra.test a PrivaPub secret $run&visibility=direct"
|
||||
until_true 45 'mi "$MI/api/v1/timelines/direct" "$MI/api/v1/conversations" | grep -q "a PrivaPub secret $run"' && ok "alice's DM reaches Mitra" || ko "alice's DM never reached Mitra"
|
||||
|
||||
echo " unfollow"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$mi_on_p/unfollow"
|
||||
until_true 45 '[ "$(mi "$MI/api/v1/accounts/relationships?id[]=$alice_on_mi" | j "print(d[0][\"followed_by\"])")" = "False" ]' \
|
||||
&& ok "alice's unfollow reaches Mitra" || ko "Mitra still counts alice as a follower"
|
||||
|
||||
echo " statistics"
|
||||
stats_check mitra.test mitra
|
||||
Reference in new issue
Block a user