# ───────────────────────────────────────────────────────────────────────────── # Sipher federation test cluster # # Three independent Sipher instances (A, B, C) sharing one Postgres server # (separate databases) and one Redis server (separate logical DBs 0/1/2). # # All commands below should be run from the repository root with the explicit # compose file (-f tests/docker-compose.yml) — `package.json` already wraps # the common ones (docker:generate-keys, docker:build, docker:setup-discovery). # # Quick start # ─────────── # 1. bun run docker:generate-keys # writes tests/docker/sipher-{a,b,c}.env # 2. docker compose -f tests/docker-compose.yml \ # --profile init up # push DB schema (exits when done) # 3. docker compose -f tests/docker-compose.yml up -d # start the cluster # 4. docker compose -f tests/docker-compose.yml \ # --profile setup up # mutual discovery (exits when done) # # Running integration tests # ───────────────────────── # docker compose -f tests/docker-compose.yml run --rm test-runner \ # tests/integration/proxy-chain.ts \ # --proxy http://sipher-b:3001 --target http://sipher-c:3002 # # docker compose -f tests/docker-compose.yml run --rm test-runner \ # tests/integration/federation-post-delivery.ts \ # --proxy http://sipher-b:3001 --target http://sipher-c:3002 # # Both integration scripts now auto-create their own Better Auth users + identity # keys + follow rows via HTTP, so you no longer need to pass --bearer. # # Teardown # ──────── # docker compose -f tests/docker-compose.yml down # stop (keeps volumes) # docker compose -f tests/docker-compose.yml down -v # stop + wipe all data volumes # ───────────────────────────────────────────────────────────────────────────── # Pinned project name so the docker objects (containers, network, volumes) keep # stable names regardless of the directory containing this compose file. Docker # would otherwise default to the parent directory of the compose file. name: sipher-noai services: # ── Infrastructure ────────────────────────────────────────────────────────── postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: sipher POSTGRES_PASSWORD: sipher_test # sipher_a is created by POSTGRES_DB; init.sql creates sipher_b + sipher_c. POSTGRES_DB: sipher_a volumes: - ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro - postgres_data:/var/lib/postgresql/data # No host port binding — containers reach Postgres over the Docker network. # To connect from the host for debugging: docker compose exec postgres psql -U sipher sipher_a healthcheck: test: ["CMD-SHELL", "pg_isready -U sipher -d sipher_a"] interval: 5s timeout: 5s retries: 12 redis: image: redis:7-alpine restart: unless-stopped # noeviction: BullMQ requires keys to never be silently dropped. command: redis-server --maxmemory-policy noeviction volumes: - redis_data:/data # No host port binding — containers reach Redis over the Docker network. # To connect from the host for debugging: docker compose exec redis redis-cli healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 12 # ── Migrations (profile: init) ─────────────────────────────────────────────── # Run once: docker compose -f tests/docker-compose.yml --profile init up # Each service pushes the Drizzle schema to its database and exits. migrate-a: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-a.env command: ["bunx", "drizzle-kit", "push", "--force"] depends_on: postgres: condition: service_healthy profiles: [init] restart: "no" migrate-b: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-b.env command: ["bunx", "drizzle-kit", "push", "--force"] depends_on: postgres: condition: service_healthy profiles: [init] restart: "no" migrate-c: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-c.env command: ["bunx", "drizzle-kit", "push", "--force"] depends_on: postgres: condition: service_healthy profiles: [init] restart: "no" # ── Sipher instances ───────────────────────────────────────────────────────── # Each instance: # • has its own Postgres database (sipher_a / _b / _c) # • has its own Redis logical DB (0 / 1 / 2) — isolates BullMQ queues and # rate-limit buckets so instances don't interfere with each other # • has a unique federation Ed25519 + X25519 keypair (generated by docker:generate-keys) # • lists sipher-a/b/c in DEV_ALLOWED_HOSTNAMES so the SSRF url-guard allows # outbound federation requests to the other instances over the Docker network sipher-a: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-a.env ports: - "3000:3000" depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped healthcheck: test: ["CMD-SHELL", "curl -fs http://localhost:3000/discover > /dev/null"] interval: 15s timeout: 10s retries: 6 start_period: 30s sipher-b: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-b.env ports: - "3001:3001" depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped healthcheck: test: ["CMD-SHELL", "curl -fs http://localhost:3001/discover > /dev/null"] interval: 15s timeout: 10s retries: 6 start_period: 30s sipher-c: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-c.env ports: - "3002:3002" depends_on: postgres: condition: service_healthy redis: condition: service_healthy restart: unless-stopped healthcheck: test: ["CMD-SHELL", "curl -fs http://localhost:3002/discover > /dev/null"] interval: 15s timeout: 10s retries: 6 start_period: 30s # ── Discovery setup (profile: setup) ──────────────────────────────────────── # Run once after `docker compose up -d` to register all instances with each other. # Waits for all three sipher instances to pass their healthcheck before running. setup-discovery: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-a.env command: ["tests/docker/setup-discovery.ts"] depends_on: sipher-a: condition: service_healthy sipher-b: condition: service_healthy sipher-c: condition: service_healthy profiles: [setup] restart: "no" entrypoint: ["bun", "run"] # ── Integration test runner (profile: test) ────────────────────────────────── # Runs tests/integration scripts inside the Docker network so service names # (sipher-a, sipher-b, sipher-c) resolve correctly. # # Usage: # docker compose -f tests/docker-compose.yml run --rm test-runner \ # tests/integration/proxy-chain.ts \ # --proxy http://sipher-b:3001 --target http://sipher-c:3002 # # The test runner uses Server A's env (DATABASE_URL → sipher_a, federation keys) # because the integration scripts import @/lib/db and read those credentials. test-runner: build: context: .. dockerfile: Dockerfile env_file: docker/sipher-a.env depends_on: sipher-a: condition: service_healthy sipher-b: condition: service_healthy sipher-c: condition: service_healthy profiles: [test] restart: "no" entrypoint: ["bun", "run"] volumes: postgres_data: redis_data: