Pasture: WriteFreely 0.17.2
WriteFreely joins the pasture, built from its release with openssl beside it (it makes each blog's keys with the command); scenarios/writefreely.sh has alice follow a blog and receive its post as a titled Article, its edit and its deletion, then 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:
1 parent
5721bfd796
commit
0aea550c6c
7 files changed
+123
No files matched your search
@@ -108,6 +108,11 @@ nodebb.test {
|
||||
reverse_proxy pasture-nodebb:4567
|
||||
}
|
||||
|
||||
writefreely.test {
|
||||
tls internal
|
||||
reverse_proxy pasture-writefreely:8080
|
||||
}
|
||||
|
||||
snac.test {
|
||||
tls internal
|
||||
reverse_proxy pasture-snac:8001
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# WriteFreely 0.17.2 from its release tarball: the project's image stopped at 0.9. It makes each blog's keys with the
|
||||
# openssl command
|
||||
FROM docker.io/library/alpine:3.22
|
||||
RUN apk -U --no-progress --no-cache add ca-certificates curl libc6-compat openssl \
|
||||
&& curl -fsSL https://github.com/writefreely/writefreely/releases/download/v0.17.2/writefreely_0.17.2_linux_amd64.tar.gz \
|
||||
| tar -xz -C /opt \
|
||||
&& apk del curl
|
||||
WORKDIR /opt/writefreely
|
||||
EXPOSE 8080
|
||||
@@ -0,0 +1,49 @@
|
||||
# WriteFreely 0.17.2: blogs, each an ActivityPub actor whose posts go out as Articles (title, summary, the whole text).
|
||||
# A blog can be followed, never follow; it takes no replies. Built from its release (images/writefreely), on SQLite in its
|
||||
# own volume, as writefreely.test; Go trusts the bundle the pasture mounts over the system one. wfuser and its blog
|
||||
# come from its CLI, and its API takes a login.
|
||||
WRITEFREELY_IMAGE=${WRITEFREELY_IMAGE:-localhost/pasture-writefreely:0.17.2}
|
||||
WRITEFREELY_PASSWORD=WriteFreely-Pasture-Pass-1
|
||||
|
||||
writefreely_up() {
|
||||
podman image exists "$WRITEFREELY_IMAGE" || podman build -q -t "$WRITEFREELY_IMAGE" "$here/images/writefreely" >/dev/null
|
||||
local st="$here/.state/writefreely"
|
||||
mkdir -p "$st"
|
||||
cat > "$st/config.ini" <<-EOF2
|
||||
[server]
|
||||
port = 8080
|
||||
bind = 0.0.0.0
|
||||
keys_parent_dir = /data
|
||||
|
||||
[database]
|
||||
type = sqlite3
|
||||
filename = /data/writefreely.db
|
||||
|
||||
[app]
|
||||
site_name = WriteFreely pasture
|
||||
host = https://writefreely.test
|
||||
single_user = false
|
||||
open_registration = true
|
||||
max_blogs = 4
|
||||
federation = true
|
||||
public_stats = true
|
||||
local_timeline = true
|
||||
EOF2
|
||||
podman volume exists pasture-writefreely || podman volume create --label pasture=1 pasture-writefreely >/dev/null
|
||||
local run="podman run --rm -v pasture-writefreely:/data -v $st/config.ini:/opt/writefreely/config.ini:Z,ro $WRITEFREELY_IMAGE"
|
||||
if ! $run test -s /data/writefreely.db; then
|
||||
$run sh -c 'mkdir -p /data/keys && ./writefreely keys generate && ./writefreely db init' >/dev/null 2>&1
|
||||
$run ./writefreely user create --admin "wfuser:$WRITEFREELY_PASSWORD" >/dev/null 2>&1
|
||||
fi
|
||||
podman run -d --replace --name pasture-writefreely --label pasture=1 --network $net -v pasture-writefreely:/data \
|
||||
-v "$st/config.ini:/opt/writefreely/config.ini:Z,ro" -v "$ca/bundle.pem:/etc/ssl/certs/ca-certificates.crt:z,ro" \
|
||||
$WRITEFREELY_IMAGE ./writefreely serve >/dev/null
|
||||
for _ in $(seq 1 30); do
|
||||
site writefreely.test -s -o /dev/null -w '%{http_code}' https://writefreely.test:6443/api/me 2>/dev/null | grep -q -E '200|401' && break
|
||||
sleep 1
|
||||
done
|
||||
site writefreely.test -s -X POST https://writefreely.test:6443/api/auth/login -H 'Content-Type: application/json' \
|
||||
-d "{\"alias\":\"wfuser\",\"pass\":\"$WRITEFREELY_PASSWORD\"}" \
|
||||
| python3 -c 'import json, sys; print(json.load(sys.stdin)["data"]["access_token"])' > "$st/wfuser.token"
|
||||
echo "writefreely: https://writefreely.test:6443"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
# WriteFreely 0.17.2: alice follows a blog; a post there reaches her home as an Article with its title; its edit and its
|
||||
# deletion follow; the unfollow; statistics. A blog follows nobody and takes no replies or likes, so nothing goes the
|
||||
# other way. Driven through its API as wfuser, with the token peers/writefreely.sh got.
|
||||
WF=https://writefreely.test:6443
|
||||
WFT=$(cat "$here/.state/writefreely/wfuser.token" 2>/dev/null)
|
||||
wf() { site writefreely.test -s -H "Authorization: Token $WFT" -H 'Content-Type: application/json' "$@"; }
|
||||
# wf_sql <query>: from WriteFreely's SQLite, read in its volume
|
||||
wf_sql() { python3 -c 'import sqlite3, sys; print("\n".join("|".join(map(str, r)) for r in sqlite3.connect(sys.argv[1]).execute(sys.argv[2])))' \
|
||||
"$(podman volume inspect pasture-writefreely --format '{{.Mountpoint}}')/writefreely.db" "$1"; }
|
||||
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 '') or '$1' in ((s.get('privapub') or {}).get('title') or '')), ''))"; }
|
||||
|
||||
echo "writefreely"
|
||||
[ "$(wf "$WF/api/me" | j "print(d['data']['username'])")" = "wfuser" ] && ok "WriteFreely token for wfuser" || { ko "WriteFreely token"; return 1; }
|
||||
PT=$(privapub_token alice_wf)
|
||||
PH="Authorization: Bearer $PT"
|
||||
[ -n "$PT" ] && ok "PrivaPub token for alice_wf" || { ko "PrivaPub token for alice_wf"; return 1; }
|
||||
run=$(date +%s)
|
||||
|
||||
echo " following the blog"
|
||||
blog_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=wfuser@writefreely.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
|
||||
[ -n "$blog_on_p" ] && ok "PrivaPub resolves the blog" || ko "PrivaPub cannot resolve the blog"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$blog_on_p/follow"
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$blog_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
|
||||
&& ok "alice follows the blog (Accept arrived)" || ko "the blog's Accept never arrived"
|
||||
|
||||
echo " articles"
|
||||
post=$(wf -X POST "$WF/api/collections/wfuser/posts" -d "{\"title\":\"A WriteFreely article $run\",\"body\":\"Written on a blog, $run.\"}")
|
||||
post_id=$(echo "$post" | j "print(d['data']['id'])")
|
||||
until_true 45 '[ -n "$(p_home_has "A WriteFreely article $run")" ]' && ok "the blog's post reaches alice's home" || ko "the blog's post never reached alice"
|
||||
on_p=$(p_home_has "A WriteFreely article $run")
|
||||
[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$on_p" | j "print((d.get('privapub') or {}).get('title'))")" = "A WriteFreely article $run" ] \
|
||||
&& ok "as an Article with its title" || ko "the article arrived without its title"
|
||||
wf -o /dev/null -X POST "$WF/api/posts/$post_id" -d "{\"title\":\"A WriteFreely article $run\",\"body\":\"Written on a blog, $run, then edited.\"}"
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/statuses/$on_p" | j "print(\"then edited\" in d[\"content\"])")" = "True" ]' \
|
||||
&& ok "its edit reaches PrivaPub" || ko "its edit never reached PrivaPub"
|
||||
wf -o /dev/null -X DELETE "$WF/api/posts/$post_id"
|
||||
until_true 45 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$on_p")" = "404" ]' \
|
||||
&& ok "its deletion reaches PrivaPub" || ko "the deleted article still shows on PrivaPub"
|
||||
|
||||
echo " unfollow"
|
||||
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$blog_on_p/unfollow"
|
||||
until_true 45 '[ "$(wf_sql "select count(*) from remotefollows f join remoteusers u on u.id = f.remote_user_id where u.actor_id like '"'%alice_wf'"'")" = "0" ]' \
|
||||
&& ok "alice's unfollow reaches the blog" || ko "the blog still counts alice"
|
||||
|
||||
echo " statistics"
|
||||
stats_check writefreely.test writefreely
|
||||
Reference in new issue
Block a user