74 lines
5.2 KiB
Bash
74 lines
5.2 KiB
Bash
# Friendica 2026.05: a social network speaking ActivityPub beside its own protocols. Posts (Notes, and Articles when
|
|||
|
|
# titled), comments, likes and dislikes, follows, events. The official image on the shared MySQL (database friendica),
|
||
|
|
# installed by its autoinstall; its worker is the image's own daemon (cron.sh) in a sidecar sharing its files, or
|
||
|
|
# nothing federates. Its cache, locks and sessions live in the shared Redis (db 7), as upstream's compose has them, so
|
||
|
|
# both containers share them. It trusts Caddy's CA through the bundle mounted as its system store. Its API (Mastodon's
|
||
|
|
# and its own) takes HTTP Basic authentication, so a user's token is "nick:password".
|
||
|
|
FRIENDICA_IMAGE=${FRIENDICA_IMAGE:-docker.io/library/friendica:2026.05-apache}
|
||
|
|
FRIENDICA_PASSWORD=Friendica-Pasture-1
|
||
|
|
. "$here/peers/shared.sh"
|
||
|
|
|
||
|
|
friendica_env() {
|
||
|
|
echo -e "MYSQL_HOST=mysql\nMYSQL_USER=pasture\nMYSQL_PASSWORD=pasture\nMYSQL_DATABASE=friendica"
|
||
|
|
echo -e "FRIENDICA_ADMIN_MAIL=admin@friendica.test\nFRIENDICA_URL=https://friendica.test\nFRIENDICA_TZ=UTC\nFRIENDICA_LANG=en"
|
||
|
|
echo -e "FRIENDICA_SITENAME=Pasture Friendica\nREDIS_HOST=redis\nREDIS_DB=7"
|
||
|
|
# no check that an email's or a URL's domain resolves (friendica.test does only once Caddy names it)
|
||
|
|
echo "FRIENDICA_NO_VALIDATION=1"
|
||
|
|
}
|
||
|
|
|
||
|
|
friendica_console() { podman exec -u www-data pasture-friendica php /var/www/html/bin/console.php "$@"; }
|
||
|
|
|
||
|
|
friendica_up() {
|
||
|
|
shared_mysql_up
|
||
|
|
shared_redis_up
|
||
|
|
mysql_db friendica
|
||
|
|
mkdir -p "$here/.state/friendica"
|
||
|
|
friendica_env > "$here/.state/friendica/env"
|
||
|
|
podman volume exists pasture-friendica-html || podman volume create --label pasture=1 pasture-friendica-html >/dev/null
|
||
|
|
podman run -d --replace --name pasture-friendica --network $net --env-file "$here/.state/friendica/env" \
|
||
|
|
-v pasture-friendica-html:/var/www/html -v "$ca/bundle.pem:/etc/ssl/certs/ca-certificates.crt:z,ro" "$FRIENDICA_IMAGE" >/dev/null
|
||
|
|
for _ in $(seq 1 150); do
|
||
|
|
site friendica.test -s -o /dev/null -w '%{http_code}' https://friendica.test:6443/nodeinfo/2.0 2>/dev/null | grep -q 200 && break
|
||
|
|
sleep 3
|
||
|
|
done
|
||
|
|
podman run -d --replace --name pasture-friendica-cron --network $net --env-file "$here/.state/friendica/env" \
|
||
|
|
--volumes-from pasture-friendica --entrypoint /cron.sh "$FRIENDICA_IMAGE" >/dev/null
|
||
|
|
friendica_settle
|
||
|
|
echo "friendica: https://friendica.test:6443"
|
||
|
|
}
|
||
|
|
|
||
|
|
# a named system account, open registrations, a log, and fruser
|
||
|
|
friendica_settle() {
|
||
|
|
# its autoinstall leaves the system user (uid 0) nameless, so the system account that signs every fetch is never made
|
||
|
|
# and every lookup fails ("Could not find owner for uid 0"); a contact a failed attempt left at its address is dropped
|
||
|
|
podman exec pasture-mysql mysql -upasture -ppasture friendica -e "update user set nickname = 'friendica', username = 'System Account'
|
||
|
|
where uid = 0 and nickname = ''; delete from contact where uid = 0 and self = 0 and url = 'https://friendica.test/friendica'" 2>/dev/null
|
||
|
|
# the worker daemon runs in the sidecar, where the web container cannot see its pid, so a queued job never wakes it
|
||
|
|
# and everything waits for its five-minute cron; declared running, the web container signals it through worker-ipc
|
||
|
|
podman exec pasture-mysql mysql -upasture -ppasture friendica -e "replace into \`key-value\` (k, v, updated_at)
|
||
|
|
values ('worker_daemon_mode', '1', unix_timestamp())" 2>/dev/null
|
||
|
|
friendica_console config config register_policy 2 >/dev/null 2>&1 || true
|
||
|
|
# the image names the log (at notice) but leaves the file for Friendica to make, which it cannot, and logs nothing
|
||
|
|
# until debugging is on
|
||
|
|
podman exec pasture-friendica sh -c 'touch /var/www/html/friendica.log && chown www-data /var/www/html/friendica.log'
|
||
|
|
friendica_console config system debugging 1 >/dev/null 2>&1 || true
|
||
|
|
friendica_user fruser
|
||
|
|
echo "fruser:$FRIENDICA_PASSWORD" > "$here/.state/friendica.token"
|
||
|
|
}
|
||
|
|
|
||
|
|
# friendica_user <nick>: an account with the pasture's password that takes followers without asking. Friendica makes
|
||
|
|
# them locked (its normal page type approves every contact by hand); saved through its API with locked=0 (a number:
|
||
|
|
# "false" and "true" both read as 0), it becomes a "soapbox" page, which approves each follower and follows nobody back.
|
||
|
|
# The "automatic friend" type would follow every follower back as a friend.
|
||
|
|
friendica_user() {
|
||
|
|
friendica_console user add "$1" "$1" "$1@friendica.test" en "" >/dev/null 2>&1 || true
|
||
|
|
friendica_console user password "$1" "$FRIENDICA_PASSWORD" >/dev/null 2>&1 || true
|
||
|
|
podman exec pasture-friendica curl -s -o /dev/null -X PATCH -u "$1:$FRIENDICA_PASSWORD" -H "Host: friendica.test" \
|
||
|
|
-H "X-Forwarded-Proto: https" -d locked=0 http://localhost/api/v1/accounts/update_credentials
|
||
|
|
# Friendica caches its own users' actors (apcontact) only once something reads them. A Follow that arrives first makes
|
||
|
|
# it fetch the user from itself, signed as that user, and checking that signature builds the actor, which checks the
|
||
|
|
# signature again: the request recurses for about five minutes and holds the sender's Follow past any timeout.
|
||
|
|
# Reading the user's outbox once, unsigned, caches it (its own search answers from the contact and caches nothing).
|
||
|
|
podman exec pasture-friendica curl -s -o /dev/null -H "Host: friendica.test" -H "X-Forwarded-Proto: https" \
|
||
|
|
-H 'Accept: application/activity+json' "http://localhost/outbox/$1"
|
||
|
|
}
|