Pasture: Smithereen 1.0.3, and failed server descriptions retried

Smithereen joins the pasture (tools/pasture/peers/smithereen.sh): its image on the shared MySQL, with imgproxy and a
file server behind Caddy, a JDK trust store with the pasture's CA, accounts from its signup form, and a password grant
for a local application. scenarios/smithereen.sh drives its VKontakte-like API: friends as mutual follows, wall posts,
comments, likes, reposts (quotes there), polls, edits, deletions, private messages, the unfollow and statistics, 30
checks. A post on someone else's wall never reaches PrivaPub, since Smithereen sends it only to servers whose actors
publish a wall: G-0009, waiting for the owner.

PrivaPub: a server description that failed (Smithereen serves no NodeInfo until it has a description) frees its week,
so the server's next arrival asks again instead of a week later.

The shared Postgres takes 400 connections: at 100 the village seed ran it dry (Sharkey's API answered 500, Misskey
dropped deliveries). The seeder records a follow that stands from an earlier seed when the step itself fails, so the
checker no longer expects that follower to see nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 22:03:35 +02:00
1 parent 7b0377c85f
commit 5f150b7a59
12 files changed
+367 -5

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
# Smithereen 1.0.3: walls, friends as mutual follows, threaded comments, likes, reposts, polls, private messages, edits
# and deletions, a post on someone else's wall, groups, the unfollow, statistics. Smithereen is driven through its
# VKontakte-like API (/api/method/<name>, v=1.0) as smuser, with the token peers/smithereen.sh got by a password grant.
SM=https://smithereen.test:6443
SMT=$(cat "$here/.state/smithereen/smuser.token" 2>/dev/null)
SFT=$(cat "$here/.state/smithereen/smfriend.token" 2>/dev/null)
# sm <method> [name=value ...]: an API call as smuser (SMT names whose token), printed as Smithereen answers it
sm() {
local method=$1; shift
local args=()
for kv in "$@"; do args+=(--data-urlencode "$kv"); done
site smithereen.test -s -X POST "$SM/api/method/$method" -H "Authorization: Bearer ${SMTOKEN:-$SMT}" --data-urlencode v=1.0 "${args[@]}"
}
smf() { SMTOKEN=$SFT sm "$@"; }
sm_sql() { podman exec pasture-mysql mysql -uroot -ppasture -N smithereen -e "$1" 2>/dev/null; }
# sm_load <url or handle>: what Smithereen makes of it, as "type id"
sm_load() { sm utils.loadRemoteObject "q=$1" | j "r = d.get('response') or {}; print(r.get('type', ''), r.get('id', ''))"; }
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 "smithereen"
[ "$(sm users.get fields=screen_name | j "print(d['response'][0]['screen_name'])")" = "smuser" ] && ok "Smithereen token for smuser" || { ko "Smithereen token"; return 1; }
PT=$(privapub_token alice_smithereen)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_smithereen" || { ko "PrivaPub token for alice_smithereen"; return 1; }
alice_uri="$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['url'])" | sed 's|/@|/peasants/|')"
run=$(date +%s)
sm_self=$(sm users.get | j "print(d['response'][0]['id'])")
echo " friends"
read -r kind alice_on_sm <<< "$(sm_load "$alice_uri")"
[ "$kind" = "user" ] && ok "Smithereen resolves alice as a user" || ko "Smithereen cannot resolve alice ($kind)"
sm friends.add "user_id=$alice_on_sm" >/dev/null
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d[\"followers_count\"])")" -ge 1 ]' \
&& ok "smuser follows alice" || ko "Smithereen's follow never reached alice"
sm_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=smuser@smithereen.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$sm_on_p" ] && ok "PrivaPub resolves smuser" || ko "PrivaPub cannot resolve smuser"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$sm_on_p/follow"
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$sm_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows smuser (Accept arrived)" || ko "smuser's Accept never arrived"
until_true 30 '[ "$(sm friends.areFriends "user_ids=$alice_on_sm" | j "print(d[\"response\"][0][\"friend_status\"])")" = "friends" ]' \
&& ok "the two follows make them friends on Smithereen" || ko "Smithereen does not count them as friends ($(sm friends.areFriends "user_ids=$alice_on_sm"))"
echo " walls"
sm_post=$(sm wall.post "message=a Smithereen wall post $run" | j "print(d['response'])")
until_true 45 '[ -n "$(p_home_has "a Smithereen wall post $run")" ]' && ok "smuser's wall post reaches alice's home" || ko "smuser's wall post never reached alice"
sm_post_on_p=$(p_home_has "a Smithereen wall post $run")
p_post=$(curl -s -X POST -H "$PH" $P/api/v1/statuses -d "status=a PrivaPub post for Smithereen $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 '[ "$(sm_sql "select count(*) from wall_posts where ap_id='"'$p_post_uri'"'")" = "1" ]' \
&& ok "alice's post reaches Smithereen" || ko "alice's post never reached Smithereen"
p_post_on_sm=$(sm_sql "select id from wall_posts where ap_id='$p_post_uri'")
echo " comments"
sm wall.createComment "post_id=$p_post_on_sm" "message=a Smithereen comment $run" >/dev/null
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$p_post_id/context" | j "print(any(\"a Smithereen comment $run\" in s[\"content\"] for s in d[\"descendants\"]))")" = "True" ]' \
&& ok "smuser's comment threads under alice's post" || ko "smuser's comment missing on PrivaPub"
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=a PrivaPub comment $run&in_reply_to_id=$sm_post_on_p&visibility=public"
until_true 45 '[ "$(sm wall.getComments "post_id=$sm_post" | j "print(any(\"a PrivaPub comment $run\" in (c.get(\"text\") or \"\") for c in d[\"response\"][\"items\"]))")" = "True" ]' \
&& ok "alice's reply becomes a comment on smuser's post" || ko "alice's reply never reached Smithereen ($(sm wall.getComments "post_id=$sm_post" | head -c 300))"
echo " likes and reposts"
sm likes.add type=post "item_id=$p_post_on_sm" >/dev/null
until_true 45 '[ "$(p_status $p_post_id favourites_count)" = "1" ]' && ok "smuser's like counts on PrivaPub" || ko "smuser's like never counted"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$sm_post_on_p/favourite"
until_true 45 '[ "$(sm_sql "select count(*) from likes where object_id=$sm_post and object_type=0")" = "1" ]' \
&& ok "alice's favourite is a like on Smithereen" || ko "alice's favourite never counted on Smithereen"
# a repost there is a quote: a QuoteRequest to alice, which her policy grants, then the quoting post (its API fails
# without a message, even an empty one)
sm wall.repost "post_id=$p_post_on_sm" message= >/dev/null
until_true 45 '[ "$(p_status $p_post_id quotes_count)" = "1" ]' && ok "smuser's repost quotes alice's post, which lets it" || ko "smuser's repost never counted as a quote"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$sm_post_on_p/reblog"
until_true 45 '[ "$(sm_sql "select count(*) from wall_posts where repost_of=$sm_post")" = "1" ]' \
&& ok "alice's boost is a repost on Smithereen" || ko "alice's boost never reached Smithereen"
echo " polls"
poll=$(sm polls.create "question=A Smithereen poll $run" 'answers=["hay","clover"]' | j "print(d['response'])")
sm wall.post "message=vote $run" "attachments=[{\"type\":\"poll\",\"poll_id\":$poll}]" >/dev/null
until_true 45 '[ -n "$(p_home_has "vote $run")" ]' && ok "smuser's poll reaches alice" || ko "smuser's poll never reached alice"
poll_on_p=$(p_home_has "vote $run")
p_poll=$(curl -s -H "$PH" "$P/api/v1/statuses/$poll_on_p" | j "print(d['poll']['id'] if d.get('poll') else '')")
[ -n "$p_poll" ] && ok "as a poll" || ko "the poll arrived without its question"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/polls/$p_poll/votes" -d 'choices[]=1'
until_true 45 '[ "$(sm_sql "select count(*) from poll_votes where poll_id=$poll")" = "1" ]' \
&& ok "alice's vote counts on Smithereen" || ko "alice's vote never counted on Smithereen"
echo " edits and deletions"
sm wall.edit "post_id=$sm_post" "message=a Smithereen wall post $run, edited" >/dev/null
until_true 45 '[ "$(p_status $sm_post_on_p content | grep -c "edited")" = "1" ]' && ok "smuser's edit reaches PrivaPub" || ko "smuser's edit never reached PrivaPub"
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d "status=a PrivaPub post for Smithereen $run, edited"
until_true 45 '[ "$(sm_sql "select text like '"'%edited%'"' from wall_posts where id=$p_post_on_sm")" = "1" ]' \
&& ok "alice's edit reaches Smithereen" || ko "alice's edit never reached Smithereen"
gone=$(sm wall.post "message=a Smithereen post to delete $run" | j "print(d['response'])")
until_true 45 '[ -n "$(p_home_has "a Smithereen post to delete $run")" ]'
gone_on_p=$(p_home_has "a Smithereen post to delete $run")
sm wall.delete "post_id=$gone" >/dev/null
until_true 45 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$gone_on_p")" = "404" ]' \
&& ok "smuser's deletion reaches PrivaPub" || ko "smuser'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 '[ "$(sm_sql "select count(*) from wall_posts where ap_id='"'$p_post_uri'"'")" = "0" ]' \
&& ok "alice's deletion reaches Smithereen" || ko "alice's deleted post still on Smithereen"
echo " someone else's wall"
# smfriend writes on smuser's wall: the post carries the wall as its target, and smuser tells its followers' servers
# with Add{Note}, which is no pin
read -r _ sm_on_friend <<< "$(SMTOKEN=$SFT sm_load "https://smithereen.test/users/$sm_self")"
smf wall.post "owner_id=$sm_self" "message=a word on smuser's wall $run" >/dev/null
# (Smithereen sends a post on someone else's wall, and its owner's Add, only to servers whose actors have a wall: G-0009)
until_true 20 '[ -n "$(p_home_has "a word on smuser'"'"'s wall $run")" ]' && ok "a post on smuser's wall reaches alice" \
|| xf "a post on smuser's wall never reaches alice: Smithereen sends it only to servers with walls (G-0009)"
[ "$(curl -s -H "$PH" "$P/api/v1/accounts/$sm_on_p/statuses?pinned=true" | j "print(len(d))")" = "0" ] \
&& ok "and is not taken for a pin" || ko "PrivaPub took the wall's Add for a pin"
echo " private messages"
sm messages.send "to=$alice_on_sm" "body=a Smithereen message $run" >/dev/null
until_true 45 'curl -s -H "$PH" "$P/api/v1/conversations" | grep -q "a Smithereen message $run"' && ok "smuser's message arrives as a DM" || ko "smuser's message never reached alice"
dm=$(curl -s -H "$PH" "$P/api/v1/conversations" | j "print(next(c['last_status']['id'] for c in d if 'a Smithereen message $run' in c['last_status']['content']))")
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@smuser@smithereen.test a PrivaPub answer $run&in_reply_to_id=$dm&visibility=direct"
until_true 45 '[ "$(sm_sql "select count(*) from mail_messages where text like '"'%a PrivaPub answer $run%'"'")" -ge 1 ]' \
&& ok "alice's answer arrives as a Smithereen message" || ko "alice's answer never reached Smithereen"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$sm_on_p/unfollow"
# smuser still follows alice: no longer friends
until_true 45 '[ "$(sm friends.areFriends "user_ids=$alice_on_sm" | j "print(d[\"response\"][0][\"friend_status\"])")" = "following" ]' \
&& ok "alice's unfollow reaches Smithereen" || ko "Smithereen still counts alice ($(sm friends.areFriends "user_ids=$alice_on_sm"))"
echo " statistics"
stats_check smithereen.test smithereen