diff --git a/.github/workflows/smoke-mastodon-strict.yml b/.github/workflows/smoke-mastodon-strict.yml deleted file mode 100644 index 05d5802dd..000000000 --- a/.github/workflows/smoke-mastodon-strict.yml +++ /dev/null @@ -1,105 +0,0 @@ -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json -# -# Strict-mode interoperability smoke tests (HTTPS + HTTP signature verification). -# Uses a standalone Docker Compose file with Caddy TLS proxies to verify that -# Fedify correctly signs and verifies requests over HTTPS. -# See: https://github.com/fedify-dev/fedify/issues/481 -name: smoke-mastodon-strict - -on: - schedule: - - cron: "0 6 * * *" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - smoke: - runs-on: ubuntu-latest - timeout-minutes: 25 - - env: - COMPOSE: >- - docker compose - -f test/smoke/mastodon/docker-compose.strict.yml - - steps: - - uses: actions/checkout@v4 - - - uses: ./.github/actions/setup-mise - - - name: Generate TLS certificates - run: bash test/smoke/mastodon/generate-certs.sh test/smoke/mastodon/.certs - - - name: Verify certificates - run: | - openssl verify -CAfile test/smoke/mastodon/.certs/ca.crt \ - test/smoke/mastodon/.certs/fedify-harness.crt - openssl verify -CAfile test/smoke/mastodon/.certs/ca.crt \ - test/smoke/mastodon/.certs/mastodon.crt - - - name: Generate Mastodon secrets - run: | - IMAGE=ghcr.io/mastodon/mastodon:v4.3.9 - docker pull "$IMAGE" - - SECRET1=$(docker run --rm "$IMAGE" bundle exec rails secret) - SECRET2=$(docker run --rm "$IMAGE" bundle exec rails secret) - - { - echo "SECRET_KEY_BASE=$SECRET1" - echo "OTP_SECRET=$SECRET2" - docker run --rm "$IMAGE" bundle exec rails mastodon:webpush:generate_vapid_key \ - | grep -E '^[A-Z_]+=.+' - docker run --rm "$IMAGE" bundle exec rails db:encryption:init \ - | grep -E '^[A-Z_]+=.+' - } >> test/smoke/mastodon/mastodon-strict.env - - - name: Start database and redis - run: | - $COMPOSE up -d db redis - $COMPOSE exec -T db \ - sh -c 'until pg_isready -U mastodon; do sleep 1; done' - - - name: Run DB setup and migrations - run: | - $COMPOSE run --rm -T \ - mastodon-web-backend bundle exec rails db:setup - timeout-minutes: 5 - - - name: Start Mastodon stack - run: $COMPOSE up --wait - timeout-minutes: 12 - - - name: Provision Mastodon - run: bash test/smoke/mastodon/provision-strict.sh - - - name: Verify connectivity - run: | - echo "=== Harness health (from mastodon-web-backend, via Caddy TLS) ===" - $COMPOSE exec -T mastodon-web-backend \ - curl -sf https://fedify-harness/_test/health - echo " OK" - - echo "=== Harness health (from mastodon-sidekiq, via Caddy TLS) ===" - $COMPOSE exec -T mastodon-sidekiq \ - curl -sf https://fedify-harness/_test/health - echo " OK" - - - name: Run smoke tests - run: | - set -a && source test/smoke/.env.test && set +a - deno run --allow-net --allow-env --unstable-temporal \ - test/smoke/orchestrator.ts - - - name: Collect logs on failure - if: failure() - run: | - echo "=== Docker Compose logs ===" - $COMPOSE logs --tail=500 - - - name: Teardown - if: always() - run: $COMPOSE down -v diff --git a/.github/workflows/smoke-mastodon.yml b/.github/workflows/smoke-mastodon.yml index 6a01fdf96..de3f655dc 100644 --- a/.github/workflows/smoke-mastodon.yml +++ b/.github/workflows/smoke-mastodon.yml @@ -1,17 +1,14 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json # -# Interoperability smoke tests. -# Spins up a Mastodon instance via Docker Compose and verifies that Fedify -# can correctly exchange ActivityPub messages with it. +# Interoperability smoke tests (HTTPS + HTTP signature verification). +# Uses a standalone Docker Compose file with Caddy TLS proxies to verify that +# Fedify correctly signs and verifies requests over HTTPS. # See: https://github.com/fedify-dev/fedify/issues/481 name: smoke-mastodon on: - push: - branches: - - main - - next - - "*.*-maintenance" + schedule: + - cron: "0 6 * * *" workflow_dispatch: concurrency: @@ -23,11 +20,26 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 + env: + COMPOSE: >- + docker compose + -f test/smoke/mastodon/docker-compose.yml + steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-mise + - name: Generate TLS certificates + run: bash test/smoke/mastodon/generate-certs.sh test/smoke/mastodon/.certs + + - name: Verify certificates + run: | + openssl verify -CAfile test/smoke/mastodon/.certs/ca.crt \ + test/smoke/mastodon/.certs/fedify-harness.crt + openssl verify -CAfile test/smoke/mastodon/.certs/ca.crt \ + test/smoke/mastodon/.certs/mastodon.crt + - name: Generate Mastodon secrets run: | IMAGE=ghcr.io/mastodon/mastodon:v4.3.9 @@ -47,18 +59,18 @@ jobs: - name: Start database and redis run: | - docker compose -f test/smoke/mastodon/docker-compose.yml up -d db redis - docker compose -f test/smoke/mastodon/docker-compose.yml exec -T db \ + $COMPOSE up -d db redis + $COMPOSE exec -T db \ sh -c 'until pg_isready -U mastodon; do sleep 1; done' - name: Run DB setup and migrations run: | - docker compose -f test/smoke/mastodon/docker-compose.yml run --rm -T \ - mastodon-web bundle exec rails db:setup + $COMPOSE run --rm -T \ + mastodon-web-backend bundle exec rails db:setup timeout-minutes: 5 - name: Start Mastodon stack - run: docker compose -f test/smoke/mastodon/docker-compose.yml up --wait + run: $COMPOSE up --wait timeout-minutes: 12 - name: Provision Mastodon @@ -66,14 +78,14 @@ jobs: - name: Verify connectivity run: | - echo "=== Harness health (from mastodon-web) ===" - docker compose -f test/smoke/mastodon/docker-compose.yml exec -T mastodon-web \ - curl -sf http://fedify-harness:3001/_test/health + echo "=== Harness health (from mastodon-web-backend, via Caddy TLS) ===" + $COMPOSE exec -T mastodon-web-backend \ + curl -sf https://fedify-harness/_test/health echo " OK" - echo "=== Harness health (from mastodon-sidekiq) ===" - docker compose -f test/smoke/mastodon/docker-compose.yml exec -T mastodon-sidekiq \ - curl -sf http://fedify-harness:3001/_test/health + echo "=== Harness health (from mastodon-sidekiq, via Caddy TLS) ===" + $COMPOSE exec -T mastodon-sidekiq \ + curl -sf https://fedify-harness/_test/health echo " OK" - name: Run smoke tests @@ -86,8 +98,8 @@ jobs: if: failure() run: | echo "=== Docker Compose logs ===" - docker compose -f test/smoke/mastodon/docker-compose.yml logs --tail=500 + $COMPOSE logs --tail=500 - name: Teardown if: always() - run: docker compose -f test/smoke/mastodon/docker-compose.yml down -v + run: $COMPOSE down -v diff --git a/.gitignore b/.gitignore index 9cfc419b0..cacdc217f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ repomix-output.xml test/smoke/.env.test test/smoke/mastodon/.certs/ test/smoke/mastodon/mastodon.env -test/smoke/mastodon/mastodon-strict.env test/smoke/sharkey/.certs/ test/smoke/sharkey/sharkey.env smoke.log diff --git a/test/smoke/mastodon/docker-compose.strict.yml b/test/smoke/mastodon/docker-compose.strict.yml deleted file mode 100644 index 70bfa096f..000000000 --- a/test/smoke/mastodon/docker-compose.strict.yml +++ /dev/null @@ -1,159 +0,0 @@ -# Standalone Docker Compose for strict-mode smoke tests (HTTPS + signatures). -# Usage: docker compose -f docker-compose.strict.yml ... -# -# This is a standalone file (NOT an override) because Docker Compose merges -# network aliases additively and cannot remove a service's own DNS name from -# a network. Using an override would cause both the backend service and its -# Caddy proxy to resolve to the same hostname, breaking TLS routing. -# -# Architecture: -# - Backend services are renamed (e.g. fedify-harness-backend) so they -# don't collide with the TLS hostnames on the network -# - Caddy proxies claim the canonical hostnames (fedify-harness, mastodon) -# via network aliases and terminate TLS -# - All services share a single network for simplicity; DNS resolution -# is unambiguous because backend names differ from Caddy aliases - -volumes: - harness-node-modules: - -networks: - smoke: - driver: bridge - -services: - db: - image: postgres:15-alpine - environment: - POSTGRES_DB: mastodon - POSTGRES_USER: mastodon - POSTGRES_PASSWORD: mastodon - networks: [smoke] - healthcheck: - test: ["CMD", "pg_isready", "-U", "mastodon"] - interval: 5s - retries: 10 - - redis: - image: redis:7-alpine - networks: [smoke] - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 5s - retries: 10 - - # Fedify test harness — renamed to avoid colliding with the Caddy alias. - fedify-harness-backend: - image: denoland/deno:2.7.1 - working_dir: /workspace - volumes: - - ../../../:/workspace - - harness-node-modules:/workspace/node_modules - - ./.certs:/certs:ro - command: - - run - - --allow-net - - --allow-env - - --allow-read - - --allow-write - - --unstable-temporal - - test/smoke/harness/main.ts - environment: - HARNESS_ORIGIN: "https://fedify-harness" - STRICT_MODE: "1" - DENO_CERT: "/certs/ca.crt" - networks: [smoke] - ports: ["3001:3001"] - healthcheck: - test: - [ - "CMD", - "deno", - "eval", - "const r = await fetch('http://localhost:3001/_test/health'); if (!r.ok) Deno.exit(1);", - ] - interval: 5s - retries: 30 - - # Caddy TLS proxy for the Fedify harness. - # Owns the "fedify-harness" hostname so other containers reach TLS. - caddy-harness: - image: caddy:2.11.2-alpine - volumes: - - ./Caddyfile.fedify-harness:/etc/caddy/Caddyfile:ro - - ./.certs:/certs:ro - networks: - smoke: - aliases: [fedify-harness] - depends_on: - fedify-harness-backend: { condition: service_healthy } - healthcheck: - test: ["CMD", "caddy", "version"] - interval: 5s - retries: 5 - - # Mastodon web — renamed to avoid colliding with the Caddy alias. - mastodon-web-backend: - image: ghcr.io/mastodon/mastodon:v4.3.9 - command: - - bash - - -c - - | - cat /usr/lib/ssl/cert.pem /certs/ca.crt > /tmp/ca-bundle.crt - bundle exec rails s -p 3000 -b 0.0.0.0 - env_file: mastodon-strict.env - environment: - SSL_CERT_FILE: /tmp/ca-bundle.crt - volumes: - - ./disable_force_ssl.rb:/opt/mastodon/config/initializers/zz_disable_force_ssl.rb:ro - - ./.certs:/certs:ro - networks: [smoke] - ports: ["3000:3000"] - depends_on: - db: { condition: service_healthy } - redis: { condition: service_healthy } - healthcheck: - test: - [ - "CMD-SHELL", - "curl -sf http://localhost:3000/health | grep -q OK", - ] - interval: 10s - retries: 18 - - # Caddy TLS proxy for Mastodon. - # Owns the "mastodon" hostname so other containers reach TLS. - caddy-mastodon: - image: caddy:2.11.2-alpine - volumes: - - ./Caddyfile.mastodon:/etc/caddy/Caddyfile:ro - - ./.certs:/certs:ro - networks: - smoke: - aliases: [mastodon] - ports: ["4443:443"] - depends_on: - mastodon-web-backend: { condition: service_healthy } - healthcheck: - test: ["CMD", "caddy", "version"] - interval: 5s - retries: 5 - - mastodon-sidekiq: - image: ghcr.io/mastodon/mastodon:v4.3.9 - command: - - bash - - -c - - | - cat /usr/lib/ssl/cert.pem /certs/ca.crt > /tmp/ca-bundle.crt - bundle exec sidekiq -q ingress -q default -q push - env_file: mastodon-strict.env - environment: - SSL_CERT_FILE: /tmp/ca-bundle.crt - volumes: - - ./disable_force_ssl.rb:/opt/mastodon/config/initializers/zz_disable_force_ssl.rb:ro - - ./.certs:/certs:ro - networks: [smoke] - depends_on: - mastodon-web-backend: { condition: service_healthy } - caddy-harness: { condition: service_healthy } diff --git a/test/smoke/mastodon/docker-compose.yml b/test/smoke/mastodon/docker-compose.yml index b3ecd9cc3..65ae7b67a 100644 --- a/test/smoke/mastodon/docker-compose.yml +++ b/test/smoke/mastodon/docker-compose.yml @@ -1,3 +1,19 @@ +# Standalone Docker Compose for smoke tests (HTTPS + signatures). +# Usage: docker compose -f docker-compose.yml ... +# +# This is a standalone file (NOT an override) because Docker Compose merges +# network aliases additively and cannot remove a service's own DNS name from +# a network. Using an override would cause both the backend service and its +# Caddy proxy to resolve to the same hostname, breaking TLS routing. +# +# Architecture: +# - Backend services are renamed (e.g. fedify-harness-backend) so they +# don't collide with the TLS hostnames on the network +# - Caddy proxies claim the canonical hostnames (fedify-harness, mastodon) +# via network aliases and terminate TLS +# - All services share a single network for simplicity; DNS resolution +# is unambiguous because backend names differ from Caddy aliases + volumes: harness-node-modules: @@ -26,14 +42,14 @@ services: interval: 5s retries: 10 - # Fedify test harness — runs inside the Docker network so Mastodon's - # Resolv::DNS can resolve "fedify-harness" natively via Docker DNS. - fedify-harness: + # Fedify test harness — renamed to avoid colliding with the Caddy alias. + fedify-harness-backend: image: denoland/deno:2.7.1 working_dir: /workspace volumes: - ../../../:/workspace - harness-node-modules:/workspace/node_modules + - ./.certs:/certs:ro command: - run - --allow-net @@ -43,7 +59,9 @@ services: - --unstable-temporal - test/smoke/harness/main.ts environment: - HARNESS_ORIGIN: "http://fedify-harness:3001" + HARNESS_ORIGIN: "https://fedify-harness" + STRICT_MODE: "1" + DENO_CERT: "/certs/ca.crt" networks: [smoke] ports: ["3001:3001"] healthcheck: @@ -57,15 +75,39 @@ services: interval: 5s retries: 30 - mastodon-web: + # Caddy TLS proxy for the Fedify harness. + # Owns the "fedify-harness" hostname so other containers reach TLS. + caddy-harness: + image: caddy:2.11.2-alpine + volumes: + - ./Caddyfile.fedify-harness:/etc/caddy/Caddyfile:ro + - ./.certs:/certs:ro + networks: + smoke: + aliases: [fedify-harness] + depends_on: + fedify-harness-backend: { condition: service_healthy } + healthcheck: + test: ["CMD", "caddy", "version"] + interval: 5s + retries: 5 + + # Mastodon web — renamed to avoid colliding with the Caddy alias. + mastodon-web-backend: image: ghcr.io/mastodon/mastodon:v4.3.9 - command: bundle exec rails s -p 3000 -b 0.0.0.0 + command: + - bash + - -c + - | + cat /usr/lib/ssl/cert.pem /certs/ca.crt > /tmp/ca-bundle.crt + bundle exec rails s -p 3000 -b 0.0.0.0 env_file: mastodon.env + environment: + SSL_CERT_FILE: /tmp/ca-bundle.crt volumes: - ./disable_force_ssl.rb:/opt/mastodon/config/initializers/zz_disable_force_ssl.rb:ro - networks: - smoke: - aliases: [mastodon] + - ./.certs:/certs:ro + networks: [smoke] ports: ["3000:3000"] depends_on: db: { condition: service_healthy } @@ -79,13 +121,39 @@ services: interval: 10s retries: 18 + # Caddy TLS proxy for Mastodon. + # Owns the "mastodon" hostname so other containers reach TLS. + caddy-mastodon: + image: caddy:2.11.2-alpine + volumes: + - ./Caddyfile.mastodon:/etc/caddy/Caddyfile:ro + - ./.certs:/certs:ro + networks: + smoke: + aliases: [mastodon] + ports: ["4443:443"] + depends_on: + mastodon-web-backend: { condition: service_healthy } + healthcheck: + test: ["CMD", "caddy", "version"] + interval: 5s + retries: 5 + mastodon-sidekiq: image: ghcr.io/mastodon/mastodon:v4.3.9 - command: bundle exec sidekiq -q ingress -q default -q push + command: + - bash + - -c + - | + cat /usr/lib/ssl/cert.pem /certs/ca.crt > /tmp/ca-bundle.crt + bundle exec sidekiq -q ingress -q default -q push env_file: mastodon.env + environment: + SSL_CERT_FILE: /tmp/ca-bundle.crt volumes: - ./disable_force_ssl.rb:/opt/mastodon/config/initializers/zz_disable_force_ssl.rb:ro + - ./.certs:/certs:ro networks: [smoke] depends_on: - mastodon-web: { condition: service_healthy } - fedify-harness: { condition: service_healthy } + mastodon-web-backend: { condition: service_healthy } + caddy-harness: { condition: service_healthy } diff --git a/test/smoke/mastodon/mastodon-strict.env b/test/smoke/mastodon/mastodon-strict.env deleted file mode 100644 index 3219bb72c..000000000 --- a/test/smoke/mastodon/mastodon-strict.env +++ /dev/null @@ -1,24 +0,0 @@ -# Mastodon configuration for strict-mode smoke tests (HTTPS). -# SECRET_KEY_BASE, OTP_SECRET, VAPID_*, and ACTIVE_RECORD_ENCRYPTION_* -# are appended by CI (see .github/workflows/smoke-mastodon-strict.yml). - -LOCAL_DOMAIN=mastodon -ALTERNATE_DOMAINS=localhost:3000,localhost:4443 -LOCAL_HTTPS=true -RAILS_ENV=production -DB_HOST=db -DB_PORT=5432 -DB_NAME=mastodon -DB_USER=mastodon -DB_PASS=mastodon -REDIS_HOST=redis -REDIS_PORT=6379 -SMTP_SERVER=localhost -SMTP_PORT=25 -SMTP_FROM_ADDRESS=noreply@localhost -SMTP_AUTH_METHOD=none -SMTP_OPENSSL_VERIFY_MODE=none -SMTP_DELIVERY_METHOD=none -ES_ENABLED=false -RAILS_LOG_TO_STDOUT=true -ALLOWED_PRIVATE_ADDRESSES=0.0.0.0/0 diff --git a/test/smoke/mastodon/mastodon.env b/test/smoke/mastodon/mastodon.env index 16fee3a2c..2222000e7 100644 --- a/test/smoke/mastodon/mastodon.env +++ b/test/smoke/mastodon/mastodon.env @@ -1,10 +1,10 @@ -# Mastodon configuration for smoke tests. +# Mastodon configuration for smoke tests (HTTPS). # SECRET_KEY_BASE, OTP_SECRET, VAPID_*, and ACTIVE_RECORD_ENCRYPTION_* # are appended by CI (see .github/workflows/smoke-mastodon.yml). -LOCAL_DOMAIN=mastodon:3000 -ALTERNATE_DOMAINS=localhost:3000 -LOCAL_HTTPS=false +LOCAL_DOMAIN=mastodon +ALTERNATE_DOMAINS=localhost:3000,localhost:4443 +LOCAL_HTTPS=true RAILS_ENV=production DB_HOST=db DB_PORT=5432 diff --git a/test/smoke/mastodon/provision-strict.sh b/test/smoke/mastodon/provision-strict.sh deleted file mode 100755 index e738c5200..000000000 --- a/test/smoke/mastodon/provision-strict.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash -# Provision Mastodon for strict-mode smoke tests (HTTPS + signature verification). -# -# Differences from provision.sh: -# - Uses WebFinger discovery (ResolveAccountService) instead of DB pre-registration -# - Writes HTTPS URLs to .env.test -# - Talks to mastodon-web backend directly (HTTP on port 3000) for API calls -set -euo pipefail - -COMPOSE="docker compose -f test/smoke/mastodon/docker-compose.strict.yml" - -echo "→ Creating test user..." -$COMPOSE exec -T mastodon-web-backend bin/tootctl accounts create \ - testuser --email=test@localhost --confirmed \ - || true # may already exist on re-run - -echo "→ Approving and activating test user..." -$COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' -user = Account.find_local('testuser').user -user.update!(approved: true, confirmed_at: Time.now.utc) -user.approve! if user.respond_to?(:approve!) -RUBY - -echo "→ Generating API token via Rails..." -RAW=$($COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' 2>&1 | tr -d '\r' -user = Account.find_local('testuser').user -app = Doorkeeper::Application.find_or_create_by!(name: 'smoke-test') do |a| - a.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob' - a.scopes = 'read write follow' -end -token = Doorkeeper::AccessToken.find_or_create_for( - application: app, - resource_owner: user, - scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow'), - expires_in: nil, - use_refresh_token: false -) -print "SMOKE_TOKEN=#{token.token}" -RUBY -) - -TOKEN=$(echo "$RAW" | grep -oP 'SMOKE_TOKEN=\K\S+' | tail -1) - -if [ -z "$TOKEN" ]; then - echo "✗ Failed to generate API token" - exit 1 -fi - -# Verify token works — talk directly to the backend (HTTP, port 3000) -echo "→ Verifying token..." -HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \ - -H "Authorization: Bearer $TOKEN" \ - http://localhost:3000/api/v1/accounts/verify_credentials) -echo " verify_credentials → HTTP $HTTP_CODE" -if [ "$HTTP_CODE" != "200" ]; then - echo "✗ Token verification failed (HTTP $HTTP_CODE)" - exit 1 -fi - -echo "→ Resolving Fedify account via WebFinger (ResolveAccountService)..." -# Use Mastodon's built-in account resolution, which performs WebFinger over -# HTTPS to the Caddy-fronted harness. This validates that the full TLS + -# WebFinger chain works. -$COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' -account = ResolveAccountService.new.call('testuser@fedify-harness') -if account.nil? - abort "✗ ResolveAccountService returned nil — WebFinger discovery failed" -end -print "RESOLVED=#{account.id} (#{account.uri})" -RUBY - -echo "→ Creating follow relationship (Fedify → Mastodon) in DB..." -$COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' -fedify_account = Account.find_by!(username: 'testuser', domain: 'fedify-harness') -local_account = Account.find_local('testuser') -follow = Follow.find_or_create_by!(account: fedify_account, target_account: local_account) -print "FOLLOW=#{follow.id}" -RUBY - -echo "→ Writing test env..." -cat > test/smoke/.env.test <&1 | tr -d '\r' +RAW=$($COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' 2>&1 | tr -d '\r' user = Account.find_local('testuser').user app = Doorkeeper::Application.find_or_create_by!(name: 'smoke-test') do |a| a.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob' @@ -42,7 +45,7 @@ if [ -z "$TOKEN" ]; then exit 1 fi -# Verify token works with a simple API call +# Verify token works — talk directly to the backend (HTTP, port 3000) echo "→ Verifying token..." HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' \ -H "Authorization: Bearer $TOKEN" \ @@ -53,65 +56,21 @@ if [ "$HTTP_CODE" != "200" ]; then exit 1 fi -echo "→ Pre-registering Fedify remote account in Mastodon..." -# Mastodon's WebFinger resolution hardcodes HTTPS, but our harness is HTTP. -# Insert the remote account directly into Mastodon's database with values -# matching the harness actor dispatcher configuration. -HARNESS_ORIGIN="http://fedify-harness:3001" -$COMPOSE exec -T -e HARNESS_ORIGIN="$HARNESS_ORIGIN" \ - mastodon-web bin/rails runner - <<'RUBY' -origin = ENV.fetch('HARNESS_ORIGIN') -account = Account.find_or_initialize_by( - username: 'testuser', - domain: 'fedify-harness:3001' -) -account.update!( - protocol: :activitypub, - uri: "#{origin}/users/testuser", - url: "#{origin}/users/testuser", - inbox_url: "#{origin}/users/testuser/inbox", - shared_inbox_url: "#{origin}/inbox", - outbox_url: "#{origin}/users/testuser/outbox", - followers_url: "#{origin}/users/testuser/followers", - display_name: 'Fedify Smoke Test User', - note: '', - actor_type: 'Person' -) -print "REGISTERED=#{account.id}" -RUBY - -echo "→ Fetching Fedify actor public key..." -# The harness is already running inside Docker. Fetch its actor document -# and store the public key so Mastodon can verify HTTP signatures without -# needing WebFinger (which hardcodes HTTPS). -$COMPOSE exec -T mastodon-web bin/rails runner - <<'RUBY' -require 'net/http' -require 'json' - -uri = URI('http://fedify-harness:3001/users/testuser') -req = Net::HTTP::Get.new(uri) -req['Accept'] = 'application/activity+json' -res = Net::HTTP.start(uri.hostname, uri.port) { |http| http.request(req) } - -unless res.is_a?(Net::HTTPSuccess) - abort "Failed to fetch actor: HTTP #{res.code}" +echo "→ Resolving Fedify account via WebFinger (ResolveAccountService)..." +# Use Mastodon's built-in account resolution, which performs WebFinger over +# HTTPS to the Caddy-fronted harness. This validates that the full TLS + +# WebFinger chain works. +$COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' +account = ResolveAccountService.new.call('testuser@fedify-harness') +if account.nil? + abort "✗ ResolveAccountService returned nil — WebFinger discovery failed" end - -actor = JSON.parse(res.body) -pem = actor.dig('publicKey', 'publicKeyPem') -abort "No publicKey in actor document" if pem.nil? - -account = Account.find_by!(username: 'testuser', domain: 'fedify-harness:3001') -account.update!(public_key: pem) -print "KEY_STORED=#{account.id}" +print "RESOLVED=#{account.id} (#{account.uri})" RUBY echo "→ Creating follow relationship (Fedify → Mastodon) in DB..." -# Ensure the Fedify account follows the Mastodon account in the DB. -# This guarantees that when Mastodon posts a status, it will deliver -# to the Fedify inbox via the followers path (StatusReachFinder). -$COMPOSE exec -T mastodon-web bin/rails runner - <<'RUBY' -fedify_account = Account.find_by!(username: 'testuser', domain: 'fedify-harness:3001') +$COMPOSE exec -T mastodon-web-backend bin/rails runner - <<'RUBY' +fedify_account = Account.find_by!(username: 'testuser', domain: 'fedify-harness') local_account = Account.find_local('testuser') follow = Follow.find_or_create_by!(account: fedify_account, target_account: local_account) print "FOLLOW=#{follow.id}" @@ -120,10 +79,10 @@ RUBY echo "→ Writing test env..." cat > test/smoke/.env.test <