diff --git a/.github/workflows/cli-deno-tests.yml b/.github/workflows/cli-deno-tests.yml index 2bf8753..4d3790c 100644 --- a/.github/workflows/cli-deno-tests.yml +++ b/.github/workflows/cli-deno-tests.yml @@ -148,5 +148,5 @@ jobs: - name: Stop leftover containers if: always() run: | - docker stop couchdb-test minio-test relay-test >/dev/null 2>&1 || true - docker rm couchdb-test minio-test relay-test >/dev/null 2>&1 || true + docker stop couchdb-test minio-test relay-test coturn-test >/dev/null 2>&1 || true + docker rm couchdb-test minio-test relay-test coturn-test >/dev/null 2>&1 || true diff --git a/src/apps/cli/testdeno/helpers/docker.ts b/src/apps/cli/testdeno/helpers/docker.ts index e9c91ea..c20ccd0 100644 --- a/src/apps/cli/testdeno/helpers/docker.ts +++ b/src/apps/cli/testdeno/helpers/docker.ts @@ -627,3 +627,42 @@ export async function startP2pRelay(): Promise { export function isLocalP2pRelay(relayUrl: string): boolean { return relayUrl === "ws://localhost:4000" || relayUrl === "ws://localhost:4000/"; } + +// --------------------------------------------------------------------------- +// Coturn (STUN/TURN) +// --------------------------------------------------------------------------- +const COTURN_CONTAINER = "coturn-test"; +const COTURN_IMAGE = "coturn/coturn:latest"; + +export async function stopCoturn(): Promise { + await stopAndRemoveContainer(COTURN_CONTAINER); + untrackContainer(COTURN_CONTAINER); +} + +export async function startCoturn( + port = 3478, + user = "testuser", + pass = "testpass", + realm = "livesync.test" +): Promise { + console.log("[INFO] stopping leftover Coturn container if present"); + await stopCoturn().catch(() => {}); + + console.log("[INFO] starting local Coturn container"); + await dockerOrFail( + "run", + "-d", + "--name", + COTURN_CONTAINER, + "-p", + `${port}:${port}`, + "-p", + `${port}:${port}/udp`, + COTURN_IMAGE, + "--log-file=stdout", + "--external-ip=127.0.0.1", + `--user=${user}:${pass}`, + `--realm=${realm}` + ); + trackContainer(COTURN_CONTAINER); +} diff --git a/src/apps/cli/testdeno/helpers/p2p.ts b/src/apps/cli/testdeno/helpers/p2p.ts index a7381d7..f3e0574 100644 --- a/src/apps/cli/testdeno/helpers/p2p.ts +++ b/src/apps/cli/testdeno/helpers/p2p.ts @@ -1,5 +1,5 @@ import { runCli } from "./cli.ts"; -import { isLocalP2pRelay, startP2pRelay, stopP2pRelay } from "./docker.ts"; +import { isLocalP2pRelay, startP2pRelay, stopP2pRelay, startCoturn, stopCoturn } from "./docker.ts"; import { waitForPort } from "./net.ts"; export type PeerEntry = { @@ -95,3 +95,17 @@ export async function stopLocalRelayIfStarted(started: boolean): Promise { await stopP2pRelay().catch(() => {}); } } + +export async function maybeStartCoturn(turnServers: string): Promise { + if (turnServers.includes("localhost") || turnServers.includes("127.0.0.1")) { + await startCoturn(); + return true; + } + return false; +} + +export async function stopCoturnIfStarted(started: boolean): Promise { + if (started) { + await stopCoturn().catch(() => {}); + } +} diff --git a/src/apps/cli/testdeno/helpers/settings.ts b/src/apps/cli/testdeno/helpers/settings.ts index 7f114dd..316025f 100644 --- a/src/apps/cli/testdeno/helpers/settings.ts +++ b/src/apps/cli/testdeno/helpers/settings.ts @@ -172,7 +172,8 @@ export async function applyP2pSettings( passphrase: string, appId = "self-hosted-livesync-cli-tests", relays = "ws://localhost:4000/", - autoAccept = "~.*" + autoAccept = "~.*", + turnServers = "turn:127.0.0.1:3478" ): Promise { const data = JSON.parse(await Deno.readTextFile(settingsFile)); data.P2P_Enabled = true; @@ -184,7 +185,9 @@ export async function applyP2pSettings( data.P2P_relays = relays; data.P2P_AutoAcceptingPeers = autoAccept; data.P2P_AutoDenyingPeers = ""; - data.P2P_turnServers = "none"; + data.P2P_turnServers = turnServers; + data.P2P_turnUsername = "testuser"; + data.P2P_turnCredential = "testpass"; data.P2P_IsHeadless = true; data.isConfigured = true; await Deno.writeTextFile(settingsFile, JSON.stringify(data, null, 2)); diff --git a/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts b/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts index c87c8a9..f1c481f 100644 --- a/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts +++ b/src/apps/cli/testdeno/test-p2p-peers-local-relay.ts @@ -2,7 +2,13 @@ import { assert } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; import { initSettingsFile, applyP2pSettings, applyP2pTestTweaks } from "./helpers/settings.ts"; import { startCliInBackground } from "./helpers/backgroundCli.ts"; -import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; +import { + discoverPeer, + maybeStartLocalRelay, + stopLocalRelayIfStarted, + maybeStartCoturn, + stopCoturnIfStarted, +} from "./helpers/p2p.ts"; Deno.test("p2p-peers: discovers host through local relay", async () => { const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/"; @@ -12,6 +18,7 @@ Deno.test("p2p-peers: discovers host through local relay", async () => { const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`; const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`; const clientPeerName = Deno.env.get("CLIENT_PEER_NAME") ?? `p2p-client-${nonce}`; + const turnServers = Deno.env.get("TURN_SERVERS") ?? "turn:127.0.0.1:3478"; await using workDir = await TempDir.create("livesync-cli-p2p-peers-local-relay"); const hostVault = workDir.join("vault-host"); @@ -22,11 +29,28 @@ Deno.test("p2p-peers: discovers host through local relay", async () => { await Deno.mkdir(clientVault, { recursive: true }); const relayStarted = await maybeStartLocalRelay(relay); + const coturnStarted = await maybeStartCoturn(turnServers); try { await initSettingsFile(hostSettings); await initSettingsFile(clientSettings); - await applyP2pSettings(hostSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); - await applyP2pSettings(clientSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); + await applyP2pSettings( + hostSettings, + roomId, + passphrase, + "self-hosted-livesync-cli-tests", + relay, + "~.*", + turnServers + ); + await applyP2pSettings( + clientSettings, + roomId, + passphrase, + "self-hosted-livesync-cli-tests", + relay, + "~.*", + turnServers + ); await applyP2pTestTweaks(hostSettings, hostPeerName, passphrase); await applyP2pTestTweaks(clientSettings, clientPeerName, passphrase); @@ -42,5 +66,6 @@ Deno.test("p2p-peers: discovers host through local relay", async () => { } } finally { await stopLocalRelayIfStarted(relayStarted); + await stopCoturnIfStarted(coturnStarted); } }); diff --git a/src/apps/cli/testdeno/test-p2p-sync.ts b/src/apps/cli/testdeno/test-p2p-sync.ts index 2a50745..9772232 100644 --- a/src/apps/cli/testdeno/test-p2p-sync.ts +++ b/src/apps/cli/testdeno/test-p2p-sync.ts @@ -2,7 +2,13 @@ import { assert } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; import { initSettingsFile, applyP2pSettings, applyP2pTestTweaks } from "./helpers/settings.ts"; import { startCliInBackground } from "./helpers/backgroundCli.ts"; -import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; +import { + discoverPeer, + maybeStartLocalRelay, + stopLocalRelayIfStarted, + maybeStartCoturn, + stopCoturnIfStarted, +} from "./helpers/p2p.ts"; import { runCli } from "./helpers/cli.ts"; Deno.test("p2p-sync: discovers peer and completes sync", async () => { @@ -14,6 +20,7 @@ Deno.test("p2p-sync: discovers peer and completes sync", async () => { const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`; const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`; const clientPeerName = Deno.env.get("CLIENT_PEER_NAME") ?? `p2p-client-${nonce}`; + const turnServers = Deno.env.get("TURN_SERVERS") ?? "turn:127.0.0.1:3478"; await using workDir = await TempDir.create("livesync-cli-p2p-sync"); const hostVault = workDir.join("vault-host"); @@ -24,11 +31,28 @@ Deno.test("p2p-sync: discovers peer and completes sync", async () => { await Deno.mkdir(clientVault, { recursive: true }); const relayStarted = await maybeStartLocalRelay(relay); + const coturnStarted = await maybeStartCoturn(turnServers); try { await initSettingsFile(hostSettings); await initSettingsFile(clientSettings); - await applyP2pSettings(hostSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); - await applyP2pSettings(clientSettings, roomId, passphrase, "self-hosted-livesync-cli-tests", relay); + await applyP2pSettings( + hostSettings, + roomId, + passphrase, + "self-hosted-livesync-cli-tests", + relay, + "~.*", + turnServers + ); + await applyP2pSettings( + clientSettings, + roomId, + passphrase, + "self-hosted-livesync-cli-tests", + relay, + "~.*", + turnServers + ); await applyP2pTestTweaks(hostSettings, hostPeerName, passphrase); await applyP2pTestTweaks(clientSettings, clientPeerName, passphrase); @@ -58,5 +82,6 @@ Deno.test("p2p-sync: discovers peer and completes sync", async () => { } } finally { await stopLocalRelayIfStarted(relayStarted); + await stopCoturnIfStarted(coturnStarted); } }); diff --git a/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts b/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts index 6752f9b..ba6ed1a 100644 --- a/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts +++ b/src/apps/cli/testdeno/test-p2p-three-nodes-conflict.ts @@ -2,7 +2,13 @@ import { assert } from "@std/assert"; import { TempDir } from "./helpers/temp.ts"; import { applyP2pSettings, applyP2pTestTweaks, initSettingsFile } from "./helpers/settings.ts"; import { startCliInBackground } from "./helpers/backgroundCli.ts"; -import { discoverPeer, maybeStartLocalRelay, stopLocalRelayIfStarted } from "./helpers/p2p.ts"; +import { + discoverPeer, + maybeStartLocalRelay, + stopLocalRelayIfStarted, + maybeStartCoturn, + stopCoturnIfStarted, +} from "./helpers/p2p.ts"; import { jsonStringField, runCliOrFail, runCliWithInputOrFail, sanitiseCatStdout } from "./helpers/cli.ts"; Deno.test("p2p: three nodes detect and resolve conflicts", async () => { @@ -16,6 +22,7 @@ Deno.test("p2p: three nodes detect and resolve conflicts", async () => { const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`; const peerNameB = Deno.env.get("PEER_NAME_B") ?? `p2p-client-b-${nonce}`; const peerNameC = Deno.env.get("PEER_NAME_C") ?? `p2p-client-c-${nonce}`; + const turnServers = Deno.env.get("TURN_SERVERS") ?? "turn:127.0.0.1:3478"; await using workDir = await TempDir.create("livesync-cli-p2p-3nodes"); const vaultA = workDir.join("vault-a"); @@ -29,13 +36,14 @@ Deno.test("p2p: three nodes detect and resolve conflicts", async () => { await Deno.mkdir(vaultC, { recursive: true }); const relayStarted = await maybeStartLocalRelay(relay); + const coturnStarted = await maybeStartCoturn(turnServers); try { await initSettingsFile(settingsA); await initSettingsFile(settingsB); await initSettingsFile(settingsC); - await applyP2pSettings(settingsA, roomId, passphrase, appId, relay); - await applyP2pSettings(settingsB, roomId, passphrase, appId, relay); - await applyP2pSettings(settingsC, roomId, passphrase, appId, relay); + await applyP2pSettings(settingsA, roomId, passphrase, appId, relay, "~.*", turnServers); + await applyP2pSettings(settingsB, roomId, passphrase, appId, relay, "~.*", turnServers); + await applyP2pSettings(settingsC, roomId, passphrase, appId, relay, "~.*", turnServers); await applyP2pTestTweaks(settingsA, hostPeerName, passphrase); await applyP2pTestTweaks(settingsB, peerNameB, passphrase); await applyP2pTestTweaks(settingsC, peerNameC, passphrase); @@ -123,5 +131,6 @@ Deno.test("p2p: three nodes detect and resolve conflicts", async () => { } } finally { await stopLocalRelayIfStarted(relayStarted); + await stopCoturnIfStarted(coturnStarted); } });