From fd23627547e1f8e88903cf92b2b722320ae95bf9 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Thu, 2 Apr 2026 20:10:21 -0500 Subject: [PATCH] change to Rust WebRTC TURN server --- .github/workflows/build-images.yml | 6 +- .github/workflows/deploy-stack.yml | 81 +++++++++++++++----- README.md | 10 +-- ansible/templates/nftables.conf.j2 | 2 +- compose/docker-compose.yml | 8 +- compose/turn-server.toml | 32 ++++++++ compose/turnserver.conf | 14 ---- docker/{coturn => turn-server}/Dockerfile | 2 +- docker/{coturn => turn-server}/entrypoint.sh | 41 +++++----- tofu/main.tf | 2 +- 10 files changed, 132 insertions(+), 66 deletions(-) create mode 100644 compose/turn-server.toml delete mode 100644 compose/turnserver.conf rename docker/{coturn => turn-server}/Dockerfile (74%) rename docker/{coturn => turn-server}/entrypoint.sh (62%) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index ed9f2e4..7164ff4 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -32,12 +32,12 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push Coturn image + - name: Build and push TURN server image uses: docker/build-push-action@v5 with: - context: ./docker/coturn + context: ./docker/turn-server push: true - tags: ghcr.io/${{ env.OWNER_LC }}/stoat-coturn:4.9.0-trixie + tags: ghcr.io/${{ env.OWNER_LC }}/stoat-turn-server:4.0.1 - name: Build and push Caddy WAF image uses: docker/build-push-action@v5 diff --git a/.github/workflows/deploy-stack.yml b/.github/workflows/deploy-stack.yml index 4c2ab81..0a7906f 100644 --- a/.github/workflows/deploy-stack.yml +++ b/.github/workflows/deploy-stack.yml @@ -8,8 +8,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + packages: read env: - TF_VAR_gcp_project: ${{ secrets.GCP_PROJECT }} + TF_VAR_gcp_project: ${{ vars.GCP_PROJECT }} TF_VAR_gcp_region: ${{ vars.GCP_REGION }} TF_VAR_gcp_zone: ${{ vars.GCP_ZONE }} @@ -17,17 +18,56 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Validate deploy configuration + shell: bash + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }} + R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }} + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + TURN_SHARED_SECRET: ${{ secrets.TURN_SHARED_SECRET }} + GCP_PROJECT: ${{ vars.GCP_PROJECT }} + GCP_REGION: ${{ vars.GCP_REGION }} + GCP_ZONE: ${{ vars.GCP_ZONE }} + TURN_REALM: ${{ vars.TURN_REALM }} + CADDY_EMAIL: ${{ vars.CADDY_EMAIL }} + run: | + set -eu + + require_value() { + local kind="$1" + local name="$2" + local value="$3" + + if [ -z "${value}" ]; then + echo "::error::Missing required GitHub ${kind} ${name}." >&2 + exit 1 + fi + } + + require_value "secret" "SSH_PRIVATE_KEY" "${SSH_PRIVATE_KEY}" + require_value "secret" "GCP_SA_KEY" "${GCP_SA_KEY}" + require_value "secret" "R2_ENDPOINT_URL" "${R2_ENDPOINT_URL}" + require_value "secret" "R2_ACCESS_KEY_ID" "${R2_ACCESS_KEY_ID}" + require_value "secret" "R2_SECRET_ACCESS_KEY" "${R2_SECRET_ACCESS_KEY}" + require_value "secret" "TURN_SHARED_SECRET" "${TURN_SHARED_SECRET}" + require_value "variable" "GCP_PROJECT" "${GCP_PROJECT}" + require_value "variable" "GCP_REGION" "${GCP_REGION}" + require_value "variable" "GCP_ZONE" "${GCP_ZONE}" + require_value "variable" "TURN_REALM" "${TURN_REALM}" + require_value "variable" "CADDY_EMAIL" "${CADDY_EMAIL}" + - name: Prepare SSH key for provisioning shell: bash + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} run: | install -m 700 -d "${HOME}/.ssh" - printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > "${HOME}/.ssh/id_ed25519" + printf '%s\n' "${SSH_PRIVATE_KEY}" | tr -d '\r' > "${HOME}/.ssh/id_ed25519" chmod 600 "${HOME}/.ssh/id_ed25519" - { - echo "TF_VAR_admin_ssh_public_key<> "${GITHUB_ENV}" + admin_ssh_public_key="$(ssh-keygen -y -f "${HOME}/.ssh/id_ed25519")" + printf 'TF_VAR_admin_ssh_public_key=%s\n' "${admin_ssh_public_key}" >> "${GITHUB_ENV}" - name: Prepare Google credentials file shell: bash @@ -47,7 +87,7 @@ jobs: cat > backend.hcl <> "${GITHUB_ENV}" + relay_ip="$(tofu output -raw relay_ip)" + echo "STATIC_IP=${relay_ip}" >> "${GITHUB_ENV}" + echo "relay_ip=${relay_ip}" >> "${GITHUB_OUTPUT}" - name: Wait for SSH shell: bash run: | for attempt in {1..30}; do - if nc -z -w 5 "${${{ secrets.STATIC_IP }}}" 22; then + if nc -z -w 5 "${STATIC_IP}" 22; then exit 0 fi sleep 10 done - echo "SSH did not become reachable on ${${{ secrets.STATIC_IP }}}" >&2 + echo "SSH did not become reachable on ${STATIC_IP}" >&2 exit 1 - name: Run Ansible Playbook @@ -82,13 +124,13 @@ jobs: key: ${{ secrets.SSH_PRIVATE_KEY }} inventory: | [turn_nodes] - ${{ secrets.STATIC_IP }} ansible_user=ubuntu + ${{ steps.tofu.outputs.relay_ip }} ansible_user=ubuntu options: --ssh-common-args='-o StrictHostKeyChecking=no' - name: SCP compose and config files uses: appleboy/scp-action@v0.1.7 with: - host: ${{ secrets.STATIC_IP }} + host: ${{ steps.tofu.outputs.relay_ip }} username: ubuntu key: ${{ secrets.SSH_PRIVATE_KEY }} source: "compose/*" @@ -98,20 +140,21 @@ jobs: - name: Deploy Podman compose stack uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.STATIC_IP }} + host: ${{ steps.tofu.outputs.relay_ip }} username: ubuntu key: ${{ secrets.SSH_PRIVATE_KEY }} script: | set -eu cd /opt/stoat-turn + owner_lc="$(printf '%s' "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')" cat > .env < "${conf_rendered}" -{ - echo - echo "realm=${TURN_REALM}" - echo "static-auth-secret=${TURN_SHARED_SECRET}" - echo "cert=${tls_cert_file}" - echo "pkey=${tls_key_file}" -} >> "${conf_rendered}" - -set -- turnserver -n -c "${conf_rendered}" - -if [ -n "${EXTERNAL_IP:-}" ]; then - set -- "$@" --external-ip "${EXTERNAL_IP}" -fi - -exec "$@" +exec turn-server --config="${conf_rendered}" diff --git a/tofu/main.tf b/tofu/main.tf index 3d18908..42029d4 100644 --- a/tofu/main.tf +++ b/tofu/main.tf @@ -48,7 +48,7 @@ resource "google_compute_firewall" "webrtc_rules" { allow { protocol = "udp" - ports = ["3478", "5349", "49152-65535"] + ports = ["3478", "49152-65535"] } target_tags = ["webrtc-outpost", "caddy-web"]