Files
SocialPub/tools/pasture/peers/wordpress.sh
T
thepraandClaude Opus 5.5 a5d493a8c9 Pasture: Friendica joins, in the scenarios and the town
Friendica 2026.05 on the shared MySQL and Redis, with its worker daemon as a sidecar. friendica_settle repairs what
its install leaves: the system user has no name, so the system account that signs its fetches is never made; the web
container cannot see the sidecar's daemon, so a queued job waits for the five-minute cron unless the daemon is
declared running; its log needs a file and debugging on. Accounts are saved through its API with locked=0 (a number:
"true" reads as 0, unlocked), which makes them soapbox pages that take followers without following back, and each
one's outbox is read once: a Follow that reaches an account Friendica has not cached makes it fetch the account from
itself, signed, and checking that signature recursed for five minutes, holding PrivaPub's first follow past its timeout.

scenarios/friendica.sh passes its 25 checks from a clean install: follows and unfollows, posts (a titled one with its
title), comments, likes, Friendica's dislike as a downvote, boosts, edits (through its web editor: its Mastodon API
never federates one) and deletes, both ways.

The town gets a Friendica driver (HTTP Basic, its MySQL read through mysql_json, edits in the web editor, no bookmark
on a reply) and specs/friendica-pair.json, which passes its 275 checks. On the way:
- the checker knows Friendica's thread model: a non-public reply reaches an account only under posts it holds, and a
  Friendica account's non-public reply in a thread another server owns reaches nobody else there;
- the seeder answers a follow request the target still holds whatever the follower's server says: Friendica reports a
  follow of someone already following its account as made at once (and shows that persona's followers-only posts
  while a locked persona still holds the request);
- the selftest skips polls where a platform has none; the shared MySQL helpers move to peers/shared.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-05 10:14:02 +02:00

64 lines
4.1 KiB
Bash

# WordPress 6 with the ActivityPub plugin 9.3.1: posts as Articles (or Notes when short), comments as replies, each
# author an actor and the blog one too, likes and boosts kept as comments of their kind. On a shared MySQL 8.4 (database
# wordpress). The plugin federates from WP-Cron, so a sidecar runs the due events every few seconds. WordPress sends its
# requests through its own CA bundle (wp-includes/certificates), to which Caddy's root is appended; its URL guard refuses
# private addresses with no switch, which the pasture's public-looking subnet passes. The admin is wpuser; the town's
# accounts are authors with application passwords for the REST API.
WORDPRESS_IMAGE=${WORDPRESS_IMAGE:-docker.io/library/wordpress:6-apache}
WORDPRESS_CLI_IMAGE=${WORDPRESS_CLI_IMAGE:-docker.io/library/wordpress:cli}
WORDPRESS_ACTIVITYPUB=9.3.1
WORDPRESS_PASSWORD=Wordpress-Pasture-1
. "$here/peers/shared.sh"
# wp <args>: wp-cli against the site, sharing its files
wp() {
podman run --rm --network $net --volumes-from pasture-wordpress --user 33:33 -e HOME=/tmp \
-e WORDPRESS_DB_HOST=mysql -e WORDPRESS_DB_USER=pasture -e WORDPRESS_DB_PASSWORD=pasture -e WORDPRESS_DB_NAME=wordpress \
"$WORDPRESS_CLI_IMAGE" wp "$@"
}
wordpress_up() {
shared_mysql_up
mysql_db wordpress
podman volume exists pasture-wordpress-html || podman volume create --label pasture=1 pasture-wordpress-html >/dev/null
# behind Caddy: https as the proxy says, and the site's own address
local extra="if (isset(\$_SERVER['HTTP_X_FORWARDED_PROTO']) && \$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { \$_SERVER['HTTPS'] = 'on'; }
define('WP_HOME', 'https://wordpress.test'); define('WP_SITEURL', 'https://wordpress.test'); define('DISABLE_WP_CRON', true);
define('FS_METHOD', 'direct');"
podman run -d --replace --name pasture-wordpress --network $net \
-e WORDPRESS_DB_HOST=mysql -e WORDPRESS_DB_USER=pasture -e WORDPRESS_DB_PASSWORD=pasture -e WORDPRESS_DB_NAME=wordpress \
-e WORDPRESS_CONFIG_EXTRA="$extra" -v pasture-wordpress-html:/var/www/html -v "$ca:/pasture/ca:z,ro" \
"$WORDPRESS_IMAGE" >/dev/null
for _ in $(seq 1 60); do podman exec pasture-wordpress test -f /var/www/html/wp-config.php 2>/dev/null && break; sleep 2; done
if ! wp core is-installed >/dev/null 2>&1; then
wp core install --url=https://wordpress.test --title="Pasture WordPress" --admin_user=wpuser \
--admin_password="$WORDPRESS_PASSWORD" --admin_email=wpuser@wordpress.test --skip-email >/dev/null
fi
wp rewrite structure '/%postname%/' --hard >/dev/null
wp plugin is-installed activitypub 2>/dev/null || wp plugin install activitypub --version=$WORDPRESS_ACTIVITYPUB >/dev/null
wp plugin activate activitypub >/dev/null
# both the blog and its authors are actors
wp option update activitypub_actor_mode actor_blog >/dev/null
podman exec pasture-wordpress sh -c 'grep -q "Caddy Local Authority" wp-includes/certificates/ca-bundle.crt || { echo; cat /pasture/ca/root.crt; } >> wp-includes/certificates/ca-bundle.crt'
podman run -d --replace --name pasture-wordpress-cron --network $net --volumes-from pasture-wordpress --user 33:33 -e HOME=/tmp \
-e WORDPRESS_DB_HOST=mysql -e WORDPRESS_DB_USER=pasture -e WORDPRESS_DB_PASSWORD=pasture -e WORDPRESS_DB_NAME=wordpress \
--entrypoint sh "$WORDPRESS_CLI_IMAGE" -c 'while :; do wp cron event run --due-now >/dev/null 2>&1; sleep 5; done' >/dev/null
wait_http https://wordpress.test:6443/ 1 >/dev/null 2>&1 || true
for _ in $(seq 1 60); do
site wordpress.test -s -o /dev/null -w '%{http_code}' "https://wordpress.test:6443/.well-known/webfinger?resource=acct:wpuser@wordpress.test" 2>/dev/null | grep -q 200 && break
sleep 2
done
wordpress_app_password wpuser > "$here/.state/wordpress.token"
echo "wordpress: https://wordpress.test:6443"
}
# wordpress_user <name>: an author (who publishes, and so is an actor)
wordpress_user() {
wp user get "$1" >/dev/null 2>&1 || wp user create "$1" "$1@wordpress.test" --role=author --user_pass="$WORDPRESS_PASSWORD" >/dev/null
}
# wordpress_app_password <name>: "name:password" for the REST API's basic authentication
wordpress_app_password() {
echo "$1:$(wp user application-password create "$1" pasture --porcelain)"
}