Move activitypub automated test to run under a container
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
certs/
|
||||
README.md
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Executable
+20
@@ -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}"
|
||||
@@ -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}" \
|
||||
"$@"
|
||||
|
||||
@@ -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 &
|
||||
|
||||
Reference in New Issue
Block a user