# Integration test stack for sunbeam-net VPN client. # Spins up Headscale (coordination + embedded DERP) and two Tailscale # peers so we can test the full TS2021 → WireGuard → DERP pipeline. # # Usage: # docker compose -f sunbeam-net/tests/docker-compose.yml up -d # docker compose -f sunbeam-net/tests/docker-compose.yml exec headscale \ # headscale preauthkeys create --user test --reusable --expiration 1h # # Copy the key, then: # SUNBEAM_NET_TEST_AUTH_KEY= \ # SUNBEAM_NET_TEST_COORD_URL=http://localhost:8080 \ # cargo test -p sunbeam-net --features integration --test integration # docker compose -f sunbeam-net/tests/docker-compose.yml down services: # ── Headscale (coordination server + embedded DERP relay) ─────────── headscale: image: headscale/headscale:0.23 command: serve ports: - "8443:8443" # control plane + embedded DERP, both over TLS - "3478:3478/udp" # STUN - "9090:9090" # metrics volumes: - ./config/headscale.yaml:/etc/headscale/config.yaml:ro - ./config/test-cert.pem:/etc/headscale/test-cert.pem:ro - ./config/test-key.pem:/etc/headscale/test-key.pem:ro - headscale-data:/var/lib/headscale healthcheck: test: ["CMD", "headscale", "nodes", "list"] interval: 5s timeout: 5s retries: 15 # ── Tailscale peer A (validates that Headscale is working) ────────── # This peer registers with Headscale and stays online so our Rust # client can discover it in the netmap and attempt WireGuard tunnels. # Runs in TUN mode (TS_USERSPACE=false) so the host kernel actually # routes packets to peer-a's tailnet IP — this is what makes inbound # TCP from other tailnet members work end-to-end. peer-a: image: tailscale/tailscale:stable hostname: peer-a depends_on: headscale: condition: service_healthy environment: TS_AUTHKEY: "${PEER_A_AUTH_KEY}" TS_STATE_DIR: /var/lib/tailscale TS_USERSPACE: "false" TS_EXTRA_ARGS: --login-server=https://headscale:8443 # Pin the WireGuard listen port (passed to tailscaled itself) so we # can publish it to the host — without this our test daemon (running # outside docker) can't reach peer-a's UDP endpoint. TS_TAILSCALED_EXTRA_ARGS: --port=41641 # Trust the self-signed test cert so the tailscale Go client can # verify HTTPS connections to headscale + the embedded DERP. Go # honors SSL_CERT_FILE for net/http TLS verification. SSL_CERT_FILE: /etc/headscale/test-cert.pem cap_add: - NET_ADMIN - NET_RAW devices: - /dev/net/tun:/dev/net/tun ports: - "41641:41641/udp" volumes: - peer-a-state:/var/lib/tailscale - ./config/test-cert.pem:/etc/headscale/test-cert.pem:ro # Tailscale doesn't have a great healthcheck, but it registers fast healthcheck: test: ["CMD", "tailscale", "status", "--json"] interval: 5s timeout: 5s retries: 20 # ── Tailscale peer B (second peer for relay/direct tests) ─────────── peer-b: image: tailscale/tailscale:stable hostname: peer-b depends_on: headscale: condition: service_healthy environment: TS_AUTHKEY: "${PEER_B_AUTH_KEY}" TS_STATE_DIR: /var/lib/tailscale TS_USERSPACE: "false" TS_EXTRA_ARGS: --login-server=https://headscale:8443 SSL_CERT_FILE: /etc/headscale/test-cert.pem cap_add: - NET_ADMIN - NET_RAW devices: - /dev/net/tun:/dev/net/tun volumes: - peer-b-state:/var/lib/tailscale - ./config/test-cert.pem:/etc/headscale/test-cert.pem:ro healthcheck: test: ["CMD", "tailscale", "status", "--json"] interval: 5s timeout: 5s retries: 20 # ── Simple HTTP echo server on peer-a's tailnet IP ────────────────── # Used to verify end-to-end TCP connectivity through the WireGuard tunnel. # Listens on peer-a's container network; reachable via peer-a's tailnet IP. echo: image: hashicorp/http-echo:latest command: -listen=:5678 -text="sunbeam-net integration test" network_mode: "service:peer-a" depends_on: peer-a: condition: service_healthy volumes: headscale-data: peer-a-state: peer-b-state: