76 lines
5.1 KiB
Bash
76 lines
5.1 KiB
Bash
# Ghost 6.67 with its ActivityPub service 1.2.14 (Fedify): a publication whose posts go out as Articles from its
|
|||
|
|
# @index actor, and which follows, likes, reposts and replies from its "Network" screen. Ghost and the service share the
|
||
|
|
# pasture's MySQL (databases ghost and activitypub); Caddy sends /.ghost/activitypub, WebFinger and NodeInfo to the
|
||
|
|
# service and everything else to Ghost, as Ghost's own Caddyfile does. Both trust the CA through NODE_EXTRA_CA_CERTS.
|
||
|
|
# The owner is made through Ghost's setup API, staff device verification is off (the pasture sends no mail), and the
|
||
|
|
# service learns of the site the first time Ghost's identity token asks it.
|
||
|
|
GHOST_IMAGE=${GHOST_IMAGE:-docker.io/library/ghost:6.67.0-alpine}
|
||
|
|
GHOST_AP_IMAGE=${GHOST_AP_IMAGE:-ghcr.io/tryghost/activitypub:1.2.14}
|
||
|
|
GHOST_AP_MIGRATIONS_IMAGE=${GHOST_AP_MIGRATIONS_IMAGE:-ghcr.io/tryghost/activitypub-migrations:1.2.14}
|
||
|
|
# (Ghost refuses a password that repeats the site's or the owner's name)
|
||
|
|
GHOST_PASSWORD=Lantern-Meadow-Quartz-71
|
||
|
|
. "$here/peers/shared.sh"
|
||
|
|
|
||
|
|
ghost_up() {
|
||
|
|
shared_mysql_up
|
||
|
|
mysql_db ghost
|
||
|
|
mysql_db activitypub
|
||
|
|
podman run --rm --network $net -e MYSQL_DB='mysql://pasture:pasture@tcp(mysql:3306)/activitypub' $GHOST_AP_MIGRATIONS_IMAGE >/dev/null 2>&1
|
||
|
|
podman volume exists pasture-ghost || podman volume create --label pasture=1 pasture-ghost >/dev/null
|
||
|
|
podman run -d --replace --name pasture-ghost-ap --label pasture=1 --network $net -v pasture-ghost:/opt/activitypub/content \
|
||
|
|
-e NODE_ENV=production -e MYSQL_HOST=mysql -e MYSQL_USER=pasture -e MYSQL_PASSWORD=pasture -e MYSQL_DATABASE=activitypub \
|
||
|
|
-e LOCAL_STORAGE_PATH=/opt/activitypub/content/images/activitypub -e LOCAL_STORAGE_HOSTING_URL=https://ghost.test/content/images/activitypub \
|
||
|
|
-e ALLOW_PRIVATE_ADDRESS=true -e NODE_EXTRA_CA_CERTS=/pasture/ca/root.crt -v "$ca:/pasture/ca:z,ro" $GHOST_AP_IMAGE >/dev/null
|
||
|
|
podman run -d --replace --name pasture-ghost --label pasture=1 --network $net -v pasture-ghost:/var/lib/ghost/content \
|
||
|
|
-e NODE_ENV=production -e url=https://ghost.test -e database__client=mysql -e database__connection__host=mysql \
|
||
|
|
-e database__connection__user=pasture -e database__connection__password=pasture -e database__connection__database=ghost \
|
||
|
|
-e security__staffDeviceVerification=false -e NODE_EXTRA_CA_CERTS=/pasture/ca/root.crt -v "$ca:/pasture/ca:z,ro" \
|
||
|
|
$GHOST_IMAGE >/dev/null
|
||
|
|
for _ in $(seq 1 120); do
|
||
|
|
site ghost.test -s -o /dev/null -w '%{http_code}' https://ghost.test:6443/ghost/api/admin/site/ 2>/dev/null | grep -q 200 && break
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
ghost_settle
|
||
|
|
echo "ghost: https://ghost.test:6443"
|
||
|
|
}
|
||
|
|
|
||
|
|
# ghost_api <method> <path> [json]: Ghost's admin API as the owner, with the session cookie ghost_settle keeps
|
||
|
|
ghost_api() {
|
||
|
|
local method=$1 path=$2 body=${3:-}
|
||
|
|
site ghost.test -s -X "$method" "https://ghost.test:6443/ghost/api/admin/$path" -b "$here/.state/ghost/cookies" -c "$here/.state/ghost/cookies" \
|
||
|
|
-H 'Origin: https://ghost.test' -H 'Content-Type: application/json' ${body:+-d "$body"}
|
||
|
|
}
|
||
|
|
|
||
|
|
# the owner (ghostuser@ghost.test), its session, and the ActivityPub service told of the site
|
||
|
|
ghost_settle() {
|
||
|
|
local st="$here/.state/ghost"
|
||
|
|
mkdir -p "$st"
|
||
|
|
site ghost.test -s -o /dev/null -X POST https://ghost.test:6443/ghost/api/admin/authentication/setup/ -H 'Origin: https://ghost.test' \
|
||
|
|
-H 'Content-Type: application/json' \
|
||
|
|
-d "{\"setup\":[{\"name\":\"Ghost User\",\"email\":\"ghostuser@ghost.test\",\"password\":\"$GHOST_PASSWORD\",\"blogTitle\":\"Ghost pasture\"}]}"
|
||
|
|
rm -f "$st/cookies"
|
||
|
|
site ghost.test -s -o /dev/null -X POST https://ghost.test:6443/ghost/api/admin/session/ -c "$st/cookies" -H 'Origin: https://ghost.test' \
|
||
|
|
-H 'Content-Type: application/json' -d "{\"username\":\"ghostuser@ghost.test\",\"password\":\"$GHOST_PASSWORD\"}"
|
||
|
|
ghost_ap GET v1/site >/dev/null
|
||
|
|
# Ghost sets up the webhooks that publish its posts when it starts, and only once it has an owner and the service
|
||
|
|
# knows the site: the first time, it starts again
|
||
|
|
if [ "$(podman exec pasture-mysql mysql -uroot -ppasture -N ghost -e 'select count(*) from webhooks' 2>/dev/null)" != "4" ]; then
|
||
|
|
podman restart pasture-ghost >/dev/null
|
||
|
|
for _ in $(seq 1 60); do
|
||
|
|
site ghost.test -s -o /dev/null -w '%{http_code}' https://ghost.test:6443/ghost/api/admin/site/ 2>/dev/null | grep -q 200 && break
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
rm -f "$st/cookies"
|
||
|
|
site ghost.test -s -o /dev/null -X POST https://ghost.test:6443/ghost/api/admin/session/ -c "$st/cookies" -H 'Origin: https://ghost.test' \
|
||
|
|
-H 'Content-Type: application/json' -d "{\"username\":\"ghostuser@ghost.test\",\"password\":\"$GHOST_PASSWORD\"}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# ghost_ap <method> <path> [json]: the ActivityPub service's own API (/.ghost/activitypub/<path>), with Ghost's identity token
|
||
|
|
ghost_ap() {
|
||
|
|
local method=$1 path=$2 body=${3:-} token
|
||
|
|
token=$(ghost_api GET identities/ | python3 -c 'import json, sys; print(json.load(sys.stdin)["identities"][0]["token"])')
|
||
|
|
# (the service names the site after the request's host, so the call says ghost.test without the workstation's port)
|
||
|
|
site ghost.test -s -X "$method" "https://ghost.test:6443/.ghost/activitypub/$path" -H 'Host: ghost.test' -H "Authorization: Bearer $token" \
|
||
|
|
-H 'Content-Type: application/json' ${body:+-d "$body"}
|
||
|
|
}
|