Owner decisions (2026-10-04, recorded in docs/ROADMAP.md): production runs everything that is built, and nothing waits
on a person running a command.
- Geolocation updates itself. GeoUpdater, a hosted service, checks daily whether each DB-IP Lite database was built this
month. If not, it fetches this month's, or last month's early in the month. It installs a file only once it opens as
the right kind of database, then swaps it in atomically, and the locator reloads at once. Lookups now run under the
lock, so a reload can no longer dispose a reader mid-lookup. The systemd timer, its script and their setup.sh lines
are gone: the root step they needed never happened, and none is needed now. /stargazing names the database in use.
- The admin CLI runs after the app is built, with every service and nothing started.
- `create-root <login> [--admin]` takes the password on stdin; it is how the first login is made while sign-up is
closed.
- `smoke <persona>` keeps the root `deploy-smoke` and an undiscoverable persona, and gives the root a new password
on every run.
- The deploy signs in as @thepra. It runs the CLI, gets a token through the real OAuth flow (tools/smoke/oauth.sh,
moved out of the pasture's privapub_token, which now uses it), checks the signed-in API and that @thepra is
undiscoverable, then revokes the token. PRIVAPUB_SMOKE_TOKEN is gone.
- The deploy also fails when:
- NodeInfo and the instance API disagree about registrations;
- /stargazing does not say the crawler is on;
- the geolocation databases are missing or more than 40 days old.
- The crawler is on in production, seeded with ten large servers of different kinds. FEDERATION.md now describes it
and how to opt out.
- One registrations switch (Registrations:Mode, default Invitations; Open in tests and the pasture). It is read by
open sign-up (403 when closed), NodeInfo `openRegistrations`, and v1 and v2 of the instance API, so they can no longer
disagree. Before, NodeInfo said open and the instance API said closed. Group invitations always work, so
invites_enabled is true.
- A persona edit through /clientapi no longer resets what the Mastodon API set (discoverable, locked, quote policy…):
the theme is merged into the settings instead of replacing them.
650 tests pass. The deploy's smoke step was rehearsed against the pasture's PrivaPub.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
58 lines
3.8 KiB
Bash
58 lines
3.8 KiB
Bash
# Shared by interop.sh and the scenarios: check helpers, PrivaPub personas and tokens, the statistics check.
|
|
P=http://127.0.0.1:6971
|
|
work=$(mktemp -d); trap 'rm -rf "$work"' EXIT
|
|
pass=0; fail=0; expected=0
|
|
ok() { echo " ok $*"; pass=$((pass+1)); }
|
|
ko() { echo " FAIL $*"; fail=$((fail+1)); }
|
|
xf() { echo " xf $* (expected to fail until a later phase)"; expected=$((expected+1)); }
|
|
j() { python3 -c "import sys,json
|
|
try: d=json.load(sys.stdin)
|
|
except Exception: d=None
|
|
$1" 2>/dev/null; }
|
|
until_true() { local tries=$1; shift; for _ in $(seq 1 "$tries"); do if eval "$@"; then return 0; fi; sleep 2; done; return 1; }
|
|
site() { curl -k --resolve "$1:6443:127.0.0.1" "${@:2}"; }
|
|
# fetches one of PrivaPub's own https URIs (ids, scribbles) from the workstation, through Caddy
|
|
pfetch() { curl -sk --connect-to privapub.test:443:127.0.0.1:6443 "$@"; }
|
|
|
|
# make_png <path>: an 8x8 red PNG, for uploads
|
|
make_png() { python3 -c "
|
|
import struct,zlib
|
|
w=h=8
|
|
raw=b''.join(b'\x00'+bytes([200,60,60])*w for _ in range(h))
|
|
png=b'\x89PNG\r\n\x1a\n'+b''.join(struct.pack('>I',len(c))+t+c+struct.pack('>I',zlib.crc32(t+c)&0xffffffff) for t,c in [(b'IHDR',struct.pack('>IIBBBBB',w,h,8,2,0,0,0)),(b'IDAT',zlib.compress(raw)),(b'IEND',b'')])
|
|
open('$1','wb').write(png)"; }
|
|
|
|
ROOT_USER=pastureroot; ROOT_PASS='Pasture-Pass-1!'
|
|
privapub_root() {
|
|
local root
|
|
root=$(curl -s -X POST $P/clientapi/user/signup -H 'Content-Type: application/json' -d "{\"userName\":\"$ROOT_USER\",\"password\":\"$ROOT_PASS\"}")
|
|
[ -n "$(echo "$root" | j "print(d['token'])")" ] || root=$(curl -s -X POST $P/clientapi/user/login -H 'Content-Type: application/json' -d "{\"userName\":\"$ROOT_USER\",\"password\":\"$ROOT_PASS\"}")
|
|
echo "$root" | j "print(d['token'])"
|
|
}
|
|
|
|
# privapub_token <persona>: creates the persona under the pasture root if needed and returns a Mastodon token for it,
|
|
# through the same OAuth flow the deploy's smoke check uses (tools/smoke/oauth.sh).
|
|
privapub_token() {
|
|
local persona=$1 jwt
|
|
jwt=$(privapub_root)
|
|
curl -s -o /dev/null -X POST $P/clientapi/avatar/private/insert -H 'Content-Type: application/json' -H "Authorization: Bearer $jwt" \
|
|
-d "{\"userName\":\"$persona\",\"name\":\"$persona of PrivaPub\",\"biography\":\"testing federation\"}"
|
|
"$here/../smoke/oauth.sh" "$P" "$ROOT_USER" "$ROOT_PASS" "$persona" "read write follow" | cut -d' ' -f1
|
|
}
|
|
|
|
# stats_check <host> <software>: the admin statistics name the peer's software and count traffic both ways.
|
|
stats_check() {
|
|
local host=$1 software=$2 admin found
|
|
podman exec -w /app pasture-privapub /app/PrivaPub admin promote "$ROOT_USER" >/dev/null 2>&1 || true
|
|
admin=$(curl -s -X POST $P/clientapi/user/login -H 'Content-Type: application/json' -d "{\"userName\":\"$ROOT_USER\",\"password\":\"$ROOT_PASS\"}" | j "print(d['token'])")
|
|
until_true 30 'found=$(curl -s -H "Authorization: Bearer $admin" "$P/clientapi/admin/statistics/hosts/$host?days=1"); [ "$(echo "$found" | j "print((d[\"instance\"] or {}).get(\"software\"))")" = "$software" ]' \
|
|
&& ok "statistics describe $host as $software" || ko "statistics do not describe $host as $software"
|
|
found=$(curl -s -H "Authorization: Bearer $admin" "$P/clientapi/admin/statistics/hosts/$host?days=1")
|
|
[ "$(echo "$found" | j "print(any(k.startswith('in:') for day in d['days'] for k in day['counters']))")" = "True" ] \
|
|
&& ok "statistics count what $host sent" || ko "no inbound statistics for $host"
|
|
[ "$(echo "$found" | j "print(any(k.startswith('out:') and ':ok' in k for day in d['days'] for k in day['counters']))")" = "True" ] \
|
|
&& ok "statistics count what we delivered to $host" || ko "no outbound statistics for $host"
|
|
[ "$(echo "$found" | j "print('$ROOT_USER' not in json.dumps(d['events']) and 'alice' not in json.dumps(d['events']))")" = "True" ] \
|
|
&& ok "statistics for $host name no account" || ko "statistics for $host name an account"
|
|
}
|