Move activitypub automated test to run under a container

This commit is contained in:
Gabe Kangas
2026-03-30 12:32:04 -07:00
parent 5d4fcf9686
commit a2981903c2
7 changed files with 191 additions and 95 deletions
+2
View File
@@ -0,0 +1,2 @@
certs/
README.md
+36
View File
@@ -0,0 +1,36 @@
FROM golang:1.25-bookworm
RUN apt-get update && apt-get install -y \
gcc make \
curl libcurl4-openssl-dev libssl-dev \
jq sqlite3 ffmpeg \
libnss3-tools \
lsof procps \
&& rm -rf /var/lib/apt/lists/*
# Install mkcert (architecture-aware)
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sL "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-${ARCH}" -o /usr/local/bin/mkcert && \
chmod +x /usr/local/bin/mkcert
# Install Caddy (architecture-aware)
RUN ARCH="$(dpkg --print-architecture)" && \
curl -sL "https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_${ARCH}.tar.gz" | tar -xz -C /usr/local/bin caddy
# Build and install snac2
RUN git clone --depth 1 https://codeberg.org/grunfink/snac2.git /tmp/snac2-src \
&& cd /tmp/snac2-src && make && cp snac /usr/local/bin/snac \
&& rm -rf /tmp/snac2-src
# Install mkcert CA into system trust store so snac2 trusts the test certs
RUN mkcert -install
# Allow git operations on the mounted repo
RUN git config --global --add safe.directory /owncast
WORKDIR /owncast
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+33 -73
View File
@@ -2,83 +2,29 @@
This test verifies Owncast's ActivityPub federation by having snac2 users follow the Owncast instance and confirming message delivery.
## One-Time Setup
All test infrastructure (snac2, Caddy, mkcert, Go) runs inside a Docker container so you don't need to install anything on the host besides Docker.
### 1. Install mkcert
## Prerequisites
- Docker installed and running
## Running the Tests
```bash
# Ubuntu/Debian
sudo apt install mkcert
# Or download directly
curl -sLO https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x mkcert-v1.4.4-linux-amd64
sudo mv mkcert-v1.4.4-linux-amd64 /usr/local/bin/mkcert
```
### 2. Install the local CA
This installs a Certificate Authority into your system's trust store. All certificates generated by mkcert will be trusted.
```bash
mkcert -install
```
### 3. Generate certificates for the test domains
```bash
cd test/automated/activitypub
mkdir -p certs
mkcert -cert-file certs/cert.pem -key-file certs/key.pem owncast.local snac.local localhost 127.0.0.1
```
### 4. Add hosts entries
```bash
sudo sh -c 'echo "127.0.0.1 owncast.local snac.local" >> /etc/hosts'
```
### 5. Install snac2
```bash
# Ubuntu/Debian
sudo apt install snac2
# Or build from source
git clone https://codeberg.org/grunfink/snac2.git
cd snac2
make
sudo make install
```
### 6. Install Caddy
Caddy is used as the HTTPS reverse proxy for TLS termination. It will be installed automatically by `setup.sh`, or you can install it manually:
```bash
# Download binary directly
curl -sL "https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_amd64.tar.gz" | sudo tar -xz -C /usr/local/bin caddy
# Or see: https://caddyserver.com/docs/install
```
## Running the Test
```bash
# Run with default 100 users
# Run the federation test with default 100 users
./run.sh
# Run with fewer users for quick testing
USER_COUNT=10 ./run.sh
# Run the follower validation test
./run.sh test-follower-validation.sh
# Keep servers running after test for debugging
KEEP_RUNNING=true ./run.sh
# Adjust follow request throttling (default 0.1s)
FOLLOW_DELAY=0.2 ./run.sh
# Run in CI mode (skip interactive prompts)
CI=true ./run.sh
```
## Configuration Options
@@ -88,7 +34,7 @@ CI=true ./run.sh
| `USER_COUNT` | 100 | Number of test users to create |
| `FOLLOW_DELAY` | 0.1 | Delay in seconds between follow requests |
| `KEEP_RUNNING` | false | Keep servers running after test for debugging |
| `CI` | false | Skip interactive prompts for CI environments |
| `CI` | false | Always true inside the container |
| `PROXY_PORT` | 8443 | HTTPS proxy port |
| `SNAC_PORT` | 9080 | snac2 HTTP port |
| `OWNCAST_PORT` | 8080 | Owncast HTTP port |
@@ -111,23 +57,37 @@ The test reports:
- **Follow Success Rate**: Percentage of follow requests that succeeded
- **Delivery Rate**: Percentage of registered followers who received the message
## Docker Image Details
The Docker image (`owncast-ap-test`) bundles all dependencies:
- Go (for building Owncast)
- snac2 (built from source)
- Caddy (HTTPS reverse proxy)
- mkcert (TLS certificates trusted by the container)
- sqlite3, jq, curl
Go module and build caches are stored in named Docker volumes (`owncast-ap-test-gomod`, `owncast-ap-test-gobuild`) so repeated runs are faster.
## Troubleshooting
### Certificate errors from snac2
### Docker build fails
Make sure you ran `mkcert -install` and generated the certificates. The CA must be in the system trust store for snac2 to trust the certificates.
Make sure Docker is running. On macOS, Docker Desktop or a compatible runtime (colima, OrbStack, etc.) is required.
### Port already in use
Kill any leftover processes:
If a previous container didn't shut down cleanly:
```bash
pkill -f "snac httpd /tmp"
pkill -f "caddy run"
docker ps -a | grep owncast-ap-test
docker rm -f <container_id>
```
### Hosts file not configured
### Cleaning up Docker resources
Verify the entries exist:
```bash
grep -E 'owncast.local|snac.local' /etc/hosts
# Remove the image
docker rmi owncast-ap-test
# Remove Go caches
docker volume rm owncast-ap-test-gomod owncast-ap-test-gobuild
```
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
set -e
# Generate mkcert certificates for the test domains.
# These are placed in a container-local path so they don't leak into the
# mounted source tree.
export CERT_DIR="/tmp/test-certs"
mkdir -p "${CERT_DIR}"
mkcert -cert-file "${CERT_DIR}/cert.pem" \
-key-file "${CERT_DIR}/key.pem" \
owncast.local snac.local localhost 127.0.0.1
# Change CWD away from the mounted repo root so Owncast doesn't pick up the
# host's (possibly wrong-architecture) ffmpeg binary via ./ffmpeg detection.
# Stay inside the git repo so `git rev-parse --show-toplevel` still works.
cd /owncast/test/automated/activitypub
# Run the specified test script (default: test-federation.sh)
TEST_SCRIPT="${1:-test-federation.sh}"
exec "/owncast/test/automated/activitypub/${TEST_SCRIPT}"
+44 -6
View File
@@ -1,14 +1,52 @@
#!/bin/bash
# Run the ActivityPub federation test
# Run the ActivityPub federation test inside a Docker container.
#
# Usage:
# ./run.sh # Run with 100 users
# USER_COUNT=50 ./run.sh # Run with 50 users
# KEEP_RUNNING=true ./run.sh # Keep servers running after test
# ./run.sh # Run federation test with 100 users
# ./run.sh test-follower-validation.sh # Run follower validation test
# USER_COUNT=50 ./run.sh # Run with 50 users
# KEEP_RUNNING=true ./run.sh # Keep servers running after test
#
# Prerequisites:
# Add to /etc/hosts: 127.0.0.1 owncast.local snac.local
# Docker must be installed and running.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec "${SCRIPT_DIR}/test-federation.sh" "$@"
REPO_ROOT="$(git rev-parse --show-toplevel)"
IMAGE_NAME="owncast-ap-test"
echo "Building Docker image..."
docker build -t "${IMAGE_NAME}" "${SCRIPT_DIR}"
# Collect environment variables to pass through
ENV_ARGS=()
for var in USER_COUNT FOLLOW_DELAY KEEP_RUNNING CI PROXY_PORT SNAC_PORT OWNCAST_PORT CLEAR_SHARED_INBOX_PERCENT; do
if [[ -n "${!var}" ]]; then
ENV_ARGS+=("-e" "${var}=${!var}")
fi
done
# Always skip interactive prompts inside the container
ENV_ARGS+=("-e" "CI=true")
# Port-forward when KEEP_RUNNING is set so the user can access the services
EXTRA_ARGS=()
if [[ "${KEEP_RUNNING}" == "true" ]]; then
OWNCAST_PORT="${OWNCAST_PORT:-8080}"
PROXY_PORT="${PROXY_PORT:-8443}"
EXTRA_ARGS+=("-p" "${OWNCAST_PORT}:${OWNCAST_PORT}" "-p" "${PROXY_PORT}:${PROXY_PORT}")
fi
echo "Running test in Docker container..."
docker run --rm \
--add-host owncast.local:127.0.0.1 \
--add-host snac.local:127.0.0.1 \
-v "${REPO_ROOT}:/owncast" \
-v owncast-ap-test-gomod:/go/pkg/mod \
-v owncast-ap-test-gobuild:/root/.cache/go-build \
"${ENV_ARGS[@]}" \
"${EXTRA_ARGS[@]}" \
"${IMAGE_NAME}" \
"$@"
+51 -13
View File
@@ -47,6 +47,7 @@ OWNCAST_URL="https://${OWNCAST_HOSTNAME}:${PROXY_PORT}"
TEMP_DIR=""
SNAC_DATA_DIR=""
SNAC_BIN=""
OWNCAST_BIN=""
OWNCAST_DB=""
# PIDs and state
@@ -55,6 +56,7 @@ OWNCAST_PID=""
PROXY_PID=""
TEST_STREAM_PID=""
SNAC_USERNAMES=()
CONFIRMED_FOLLOWERS=()
# Colors
RED='\033[0;31m'
@@ -182,8 +184,8 @@ install_snac2() {
}
check_certs() {
# Use pre-generated mkcert certificates from the script directory
CERT_DIR="${SCRIPT_DIR}/certs"
# CERT_DIR may be set by the Docker entrypoint; fall back to local certs/
CERT_DIR="${CERT_DIR:-${SCRIPT_DIR}/certs}"
if [[ ! -f "${CERT_DIR}/cert.pem" ]] || [[ ! -f "${CERT_DIR}/key.pem" ]]; then
log_error "Certificates not found in ${CERT_DIR}"
@@ -390,11 +392,12 @@ verify_snac_shared_inbox() {
build_owncast() {
log_info "Building Owncast..."
OWNCAST_BIN="${TEMP_DIR}/owncast"
pushd "${REPO_ROOT}" > /dev/null
CGO_ENABLED=1 go build -o owncast main.go
CGO_ENABLED=1 go build -o "${OWNCAST_BIN}" main.go
popd > /dev/null
log_info "Owncast built"
log_info "Owncast built: ${OWNCAST_BIN}"
}
start_owncast() {
@@ -403,7 +406,7 @@ start_owncast() {
# Start Owncast with test environment variables and debug flags
OWNCAST_ALLOW_INTERNAL_FEDERATION=true \
OWNCAST_INSECURE_SKIP_VERIFY=true \
"${REPO_ROOT}/owncast" -database "${OWNCAST_DB}" &
"${OWNCAST_BIN}" -database "${OWNCAST_DB}" &
OWNCAST_PID=$!
log_info "Owncast started with PID ${OWNCAST_PID}"
@@ -539,8 +542,8 @@ send_follow_requests() {
log_info "snac2 queue has ${queue_count} pending items"
# Give snac2 background thread time to process all pending follows
# Wait longer for more users
local wait_time=$((10 + USER_COUNT / 10))
# Each follow requires an HTTP round-trip; scale wait with user count
local wait_time=$((10 + USER_COUNT / 5))
log_info "Waiting ${wait_time}s for snac2 to process follow requests..."
sleep "${wait_time}"
}
@@ -562,6 +565,28 @@ verify_followers() {
echo "${count}"
}
populate_confirmed_followers() {
# Query Owncast API for actual registered followers and map their IRIs
# back to snac2 usernames so we only check those inboxes for delivery.
CONFIRMED_FOLLOWERS=()
local auth
auth=$(echo -n "${ADMIN_USER}:${ADMIN_PASS}" | base64)
local response
response=$(curl -s "http://localhost:${OWNCAST_PORT}/api/admin/followers?limit=1000" \
-H "Authorization: Basic ${auth}" 2>/dev/null || echo '{"results":[]}')
# Follower IRIs look like https://snac.local:8443/username
while IFS= read -r iri; do
if [[ -n "${iri}" ]]; then
local username="${iri##*/}"
CONFIRMED_FOLLOWERS+=("${username}")
fi
done < <(echo "${response}" | jq -r '.results[]?.link // empty' 2>/dev/null)
log_info "Confirmed ${#CONFIRMED_FOLLOWERS[@]} follower usernames from Owncast API"
}
send_test_message() {
log_info "Sending test message from Owncast..."
@@ -579,9 +604,16 @@ send_test_message() {
}
check_snac_inboxes_count() {
local users_with_messages=0
# Only check users confirmed as followers by Owncast, not all snac2 users.
# snac2's shared inbox distributes messages to all users it considers followers,
# which may include users whose follow hasn't been registered by Owncast yet.
local usernames_to_check=("${CONFIRMED_FOLLOWERS[@]}")
if [[ ${#usernames_to_check[@]} -eq 0 ]]; then
usernames_to_check=("${SNAC_USERNAMES[@]}")
fi
for username in "${SNAC_USERNAMES[@]}"; do
local users_with_messages=0
for username in "${usernames_to_check[@]}"; do
if user_has_message "${username}"; then
users_with_messages=$((users_with_messages + 1))
fi
@@ -614,11 +646,16 @@ user_has_message() {
check_snac_inboxes() {
log_info "Checking snac2 user inboxes for delivered messages..." >&2
local usernames_to_check=("${CONFIRMED_FOLLOWERS[@]}")
if [[ ${#usernames_to_check[@]} -eq 0 ]]; then
usernames_to_check=("${SNAC_USERNAMES[@]}")
fi
local users_with_messages=0
local users_without_messages=()
local total=${#SNAC_USERNAMES[@]}
local total=${#usernames_to_check[@]}
for username in "${SNAC_USERNAMES[@]}"; do
for username in "${usernames_to_check[@]}"; do
if user_has_message "${username}"; then
users_with_messages=$((users_with_messages + 1))
else
@@ -626,7 +663,7 @@ check_snac_inboxes() {
fi
done
log_test "${users_with_messages}/${total} users received the message" >&2
log_test "${users_with_messages}/${total} confirmed followers received the message" >&2
if [[ ${#users_without_messages[@]} -gt 0 ]] && [[ ${#users_without_messages[@]} -le 10 ]]; then
log_warn "Users missing messages: ${users_without_messages[*]}" >&2
@@ -847,7 +884,7 @@ main() {
# Wait for all followers to be registered (with timeout)
local followers=0
local max_wait=60 # Maximum seconds to wait for followers
local max_wait=$((60 + USER_COUNT)) # Scale timeout with user count
local waited=0
local check_interval=2
@@ -889,6 +926,7 @@ main() {
echo "----------------------------------------"
echo "STEP 4: Owncast sends message to followers"
echo "----------------------------------------"
populate_confirmed_followers
send_test_message
echo ""
@@ -31,6 +31,7 @@ TEST_START_TIME=""
# Directories
TEMP_DIR=""
OWNCAST_BIN=""
OWNCAST_DB=""
# PIDs
@@ -151,11 +152,12 @@ setup_temp_dir() {
build_owncast() {
log_info "Building Owncast..."
OWNCAST_BIN="${TEMP_DIR}/owncast"
pushd "${REPO_ROOT}" > /dev/null
CGO_ENABLED=1 go build -o owncast main.go
CGO_ENABLED=1 go build -o "${OWNCAST_BIN}" main.go
popd > /dev/null
log_info "Owncast built"
log_info "Owncast built: ${OWNCAST_BIN}"
}
start_owncast() {
@@ -164,7 +166,7 @@ start_owncast() {
# Start Owncast with test configuration
OWNCAST_ALLOW_INTERNAL_FEDERATION=true \
OWNCAST_INSECURE_SKIP_VERIFY=true \
"${REPO_ROOT}/owncast" \
"${OWNCAST_BIN}" \
-database "${OWNCAST_DB}" \
-followervalidationinterval "${VALIDATION_INTERVAL}" \
-enableVerboseLogging &