Pasture: Owncast 0.3.0

Owncast joins the pasture with federation turned on through its admin API; scenarios/owncast.sh has alice follow the
stream, receive its admin's message, like and boost it (both show in Owncast's admin), and unfollow: 13 checks, with
no change to PrivaPub.

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-06 00:32:46 +02:00
1 parent 0aea550c6c
commit a584134858
6 files changed
+79

No files matched your search

+3
View File
@@ -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.
- **Owncast (0.3.0):** its image, its data in the `pasture-owncast` volume, federation turned on through its admin
API (`owncast_admin`, its default admin / abc123) with the server's address and the actor's name.
`scenarios/owncast.sh`, 13 checks.
- **WriteFreely (0.17.2):** built from its release tarball with `openssl` beside it (`images/writefreely`, it makes
each blog's keys with the command), on SQLite in the `pasture-writefreely` volume, which the scenario reads from the
host. wfuser and its blog come from its CLI; its API takes a login. `scenarios/writefreely.sh`, 13 checks.
+1
View File
@@ -36,6 +36,7 @@ and every run starting clean, with signed fetches required (as privapub.thepra.d
- **Mitra 5.9.1**
- **snac2 2.95**
- **WriteFreely 0.17.2**
- **Owncast 0.3.0**
- **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**
+9
View File
@@ -753,6 +753,15 @@ 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.
### Owncast 0.3.0
- The instance is one `Service` actor (`/federation/user/<name>`): it is followed, never follows, posts when it goes
live and what its admin sends, and lists the likes, boosts and follows it receives as "fediverse engagement".
Federation is off until the admin turns it on with the server's address.
- **Pasture evidence (2026-10-05, `tools/pasture/scenarios/owncast.sh`):** 13 checks pass, with no change to PrivaPub:
alice follows the stream and is listed, the admin's message reaches her home, her like and boost show in its admin,
the unfollow, statistics.
### WriteFreely 0.17.2
- Each blog is an actor (`/api/collections/<alias>`) whose posts go out as `Article`s with their title and the whole
+5
View File
@@ -108,6 +108,11 @@ nodebb.test {
reverse_proxy pasture-nodebb:4567
}
owncast.test {
tls internal
reverse_proxy pasture-owncast:8080
}
writefreely.test {
tls internal
reverse_proxy pasture-writefreely:8080
+22
View File
@@ -0,0 +1,22 @@
# Owncast 0.3.0: a live video server whose instance is one ActivityPub actor (a Service): people follow it, it tells them
# when it goes live and posts what its admin sends, and their likes, boosts and follows show in its admin. As
# owncast.test, its data in its own volume; Go trusts the bundle the pasture mounts over the system one. Federation is
# turned on through its admin API (admin / abc123, its default), with the server's address and the actor's name.
OWNCAST_IMAGE=${OWNCAST_IMAGE:-docker.io/owncast/owncast:0.3.0}
owncast_up() {
podman volume exists pasture-owncast || podman volume create --label pasture=1 pasture-owncast >/dev/null
podman run -d --replace --name pasture-owncast --label pasture=1 --network $net -v pasture-owncast:/app/data \
-v "$ca/bundle.pem:/etc/ssl/certs/ca-certificates.crt:z,ro" $OWNCAST_IMAGE >/dev/null
for _ in $(seq 1 60); do
site owncast.test -s -o /dev/null -w '%{http_code}' https://owncast.test:6443/api/status 2>/dev/null | grep -q 200 && break
sleep 1
done
owncast_admin config/serverurl '"https://owncast.test"'
owncast_admin config/federation/username '"owncast"'
owncast_admin config/federation/enable 'true'
echo "owncast: https://owncast.test:6443"
}
# owncast_admin <path> <json value>: an admin API change, {"value": ...}
owncast_admin() { site owncast.test -s -o /dev/null -u admin:abc123 -X POST "https://owncast.test:6443/api/admin/$1" -H 'Content-Type: application/json' -d "{\"value\":$2}"; }
+39
View File
@@ -0,0 +1,39 @@
# Owncast 0.3.0: alice follows the stream; a message its admin sends reaches her home; her like and boost show in its
# admin's list of fediverse actions; the unfollow; statistics. Owncast's one actor follows nobody and takes no replies.
# Driven through its admin API.
OC=https://owncast.test:6443
oc() { site owncast.test -s -u admin:abc123 "$@"; }
p_home_has() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((s['id'] for s in d if '$1' in (s['content'] or '')), ''))"; }
# oc_actions <type>: how many fediverse actions of that type its admin lists from alice
oc_actions() { oc "$OC/api/admin/federation/actions?limit=100" | j "r = d.get('results', d) if isinstance(d, dict) else d; print(sum(1 for a in r if a.get('type') == '$1' and 'alice_owncast' in (a.get('actorIRI') or '')))"; }
echo "owncast"
[ "$(oc "$OC/api/admin/serverconfig" | j "print(d['federation']['enabled'])")" = "True" ] && ok "Owncast federates" || { ko "Owncast's federation is off"; return 1; }
PT=$(privapub_token alice_owncast)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_owncast" || { ko "PrivaPub token for alice_owncast"; return 1; }
run=$(date +%s)
echo " following the stream"
oc_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=owncast@owncast.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$oc_on_p" ] && ok "PrivaPub resolves the stream's actor" || ko "PrivaPub cannot resolve the stream's actor"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$oc_on_p/follow"
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$oc_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows the stream (Accept arrived)" || ko "the stream's Accept never arrived"
until_true 30 'oc "$OC/api/admin/followers" | grep -q alice_owncast' && ok "and is among its followers" || ko "Owncast does not list alice"
echo " posts"
oc -o /dev/null -X POST "$OC/api/admin/federation/send" -H 'Content-Type: application/json' -d "{\"value\":\"an Owncast message $run\"}"
until_true 45 '[ -n "$(p_home_has "an Owncast message $run")" ]' && ok "the stream's message reaches alice's home" || ko "the stream's message never reached alice"
on_p=$(p_home_has "an Owncast message $run")
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$on_p/favourite"
until_true 45 '[ "$(oc_actions FEDIVERSE_ENGAGEMENT_LIKE)" -ge 1 ]' && ok "alice's like shows in its admin" || ko "Owncast never saw alice's like ($(oc_actions FEDIVERSE_ENGAGEMENT_LIKE))"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$on_p/reblog"
until_true 45 '[ "$(oc_actions FEDIVERSE_ENGAGEMENT_REPOST)" -ge 1 ]' && ok "alice's boost shows in its admin" || ko "Owncast never saw alice's boost"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$oc_on_p/unfollow"
until_true 45 '! oc "$OC/api/admin/followers" | grep -q alice_owncast' && ok "alice's unfollow reaches the stream" || ko "Owncast still lists alice"
echo " statistics"
stats_check owncast.test owncast