mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-21 18:17:05 +00:00
detect loopback and coturn option
This commit is contained in:
@@ -34,6 +34,10 @@ on:
|
|||||||
description: 'Enable verbose and debug logging'
|
description: 'Enable verbose and debug logging'
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
use_coturn:
|
||||||
|
description: 'Enable local coturn container for P2P tests'
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -140,6 +144,7 @@ jobs:
|
|||||||
LIVESYNC_CLI_RETRY: 3
|
LIVESYNC_CLI_RETRY: 3
|
||||||
LIVESYNC_CLI_DEBUG: ${{ inputs.enable_debug == true && '1' || '0' }}
|
LIVESYNC_CLI_DEBUG: ${{ inputs.enable_debug == true && '1' || '0' }}
|
||||||
LIVESYNC_CLI_VERBOSE: ${{ inputs.enable_debug == true && '1' || '0' }}
|
LIVESYNC_CLI_VERBOSE: ${{ inputs.enable_debug == true && '1' || '0' }}
|
||||||
|
LIVESYNC_USE_COTURN: ${{ inputs.use_coturn == true && '1' || '0' }}
|
||||||
run: |
|
run: |
|
||||||
TASK="${{ matrix.task }}"
|
TASK="${{ matrix.task }}"
|
||||||
echo "[INFO] Running Deno task: $TASK"
|
echo "[INFO] Running Deno task: $TASK"
|
||||||
|
|||||||
@@ -625,7 +625,7 @@ export async function startP2pRelay(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isLocalP2pRelay(relayUrl: string): boolean {
|
export function isLocalP2pRelay(relayUrl: string): boolean {
|
||||||
return relayUrl === "ws://localhost:4000" || relayUrl === "ws://localhost:4000/";
|
return relayUrl.includes("localhost") || relayUrl.includes("127.0.0.1") || relayUrl.includes("[::1]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -648,7 +648,10 @@ export async function startCoturn(
|
|||||||
console.log("[INFO] stopping leftover Coturn container if present");
|
console.log("[INFO] stopping leftover Coturn container if present");
|
||||||
await stopCoturn().catch(() => {});
|
await stopCoturn().catch(() => {});
|
||||||
|
|
||||||
console.log("[INFO] starting local Coturn container");
|
const { getOptimalLoopbackIp } = await import("./net.ts");
|
||||||
|
const externalIp = await getOptimalLoopbackIp();
|
||||||
|
|
||||||
|
console.log(`[INFO] starting local Coturn container with external-ip ${externalIp}`);
|
||||||
await dockerOrFail(
|
await dockerOrFail(
|
||||||
"run",
|
"run",
|
||||||
"-d",
|
"-d",
|
||||||
@@ -660,7 +663,7 @@ export async function startCoturn(
|
|||||||
`${port}:${port}/udp`,
|
`${port}:${port}/udp`,
|
||||||
COTURN_IMAGE,
|
COTURN_IMAGE,
|
||||||
"--log-file=stdout",
|
"--log-file=stdout",
|
||||||
"--external-ip=127.0.0.1",
|
`--external-ip=${externalIp}`,
|
||||||
`--user=${user}:${pass}`,
|
`--user=${user}:${pass}`,
|
||||||
`--realm=${realm}`
|
`--realm=${realm}`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,3 +47,22 @@ export async function waitForPort(hostname: string, port: number, options: WaitF
|
|||||||
(lastError ? ` (last error: ${String(lastError)})` : "")
|
(lastError ? ` (last error: ${String(lastError)})` : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getOptimalLoopbackIp(): Promise<string> {
|
||||||
|
const ipv4 = "127.0.0.1";
|
||||||
|
const ipv6 = "::1";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const l = Deno.listen({ hostname: ipv4, port: 0 });
|
||||||
|
l.close();
|
||||||
|
return ipv4;
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
const l = Deno.listen({ hostname: ipv6, port: 0 });
|
||||||
|
l.close();
|
||||||
|
return ipv6;
|
||||||
|
} catch {
|
||||||
|
return ipv4; // fallback to default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export async function stopLocalRelayIfStarted(started: boolean): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function maybeStartCoturn(turnServers: string): Promise<boolean> {
|
export async function maybeStartCoturn(turnServers: string): Promise<boolean> {
|
||||||
if (turnServers.includes("localhost") || turnServers.includes("127.0.0.1")) {
|
if (turnServers.includes("localhost") || turnServers.includes("127.0.0.1") || turnServers.includes("[::1]")) {
|
||||||
await startCoturn();
|
await startCoturn();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,21 @@ import {
|
|||||||
maybeStartCoturn,
|
maybeStartCoturn,
|
||||||
stopCoturnIfStarted,
|
stopCoturnIfStarted,
|
||||||
} from "./helpers/p2p.ts";
|
} from "./helpers/p2p.ts";
|
||||||
|
import { getOptimalLoopbackIp } from "./helpers/net.ts";
|
||||||
|
|
||||||
Deno.test("p2p-peers: discovers host through local relay", async () => {
|
Deno.test("p2p-peers: discovers host through local relay", async () => {
|
||||||
const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/";
|
const loopbackIp = await getOptimalLoopbackIp();
|
||||||
|
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
|
||||||
|
|
||||||
|
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
|
||||||
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
||||||
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
||||||
const timeoutSeconds = Number(Deno.env.get("TIMEOUT_SECONDS") ?? "8");
|
const timeoutSeconds = Number(Deno.env.get("TIMEOUT_SECONDS") ?? "8");
|
||||||
const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
||||||
const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`;
|
const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`;
|
||||||
const clientPeerName = Deno.env.get("CLIENT_PEER_NAME") ?? `p2p-client-${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";
|
const useCoturn = Deno.env.get("LIVESYNC_USE_COTURN") !== "0";
|
||||||
|
const turnServers = Deno.env.get("TURN_SERVERS") ?? (useCoturn ? `turn:${loopbackHost}:3478` : "none");
|
||||||
|
|
||||||
await using workDir = await TempDir.create("livesync-cli-p2p-peers-local-relay");
|
await using workDir = await TempDir.create("livesync-cli-p2p-peers-local-relay");
|
||||||
const hostVault = workDir.join("vault-host");
|
const hostVault = workDir.join("vault-host");
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ import {
|
|||||||
stopCoturnIfStarted,
|
stopCoturnIfStarted,
|
||||||
} from "./helpers/p2p.ts";
|
} from "./helpers/p2p.ts";
|
||||||
import { runCli } from "./helpers/cli.ts";
|
import { runCli } from "./helpers/cli.ts";
|
||||||
|
import { getOptimalLoopbackIp } from "./helpers/net.ts";
|
||||||
|
|
||||||
Deno.test("p2p-sync: discovers peer and completes sync", async () => {
|
Deno.test("p2p-sync: discovers peer and completes sync", async () => {
|
||||||
const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/";
|
const loopbackIp = await getOptimalLoopbackIp();
|
||||||
|
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
|
||||||
|
|
||||||
|
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
|
||||||
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
||||||
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
||||||
const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "12");
|
const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "12");
|
||||||
@@ -20,7 +24,8 @@ Deno.test("p2p-sync: discovers peer and completes sync", async () => {
|
|||||||
const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
||||||
const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`;
|
const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`;
|
||||||
const clientPeerName = Deno.env.get("CLIENT_PEER_NAME") ?? `p2p-client-${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";
|
const useCoturn = Deno.env.get("LIVESYNC_USE_COTURN") !== "0";
|
||||||
|
const turnServers = Deno.env.get("TURN_SERVERS") ?? (useCoturn ? `turn:${loopbackHost}:3478` : "none");
|
||||||
|
|
||||||
await using workDir = await TempDir.create("livesync-cli-p2p-sync");
|
await using workDir = await TempDir.create("livesync-cli-p2p-sync");
|
||||||
const hostVault = workDir.join("vault-host");
|
const hostVault = workDir.join("vault-host");
|
||||||
|
|||||||
@@ -10,19 +10,24 @@ import {
|
|||||||
stopCoturnIfStarted,
|
stopCoturnIfStarted,
|
||||||
} from "./helpers/p2p.ts";
|
} from "./helpers/p2p.ts";
|
||||||
import { jsonStringField, runCliOrFail, runCliWithInputOrFail, sanitiseCatStdout } from "./helpers/cli.ts";
|
import { jsonStringField, runCliOrFail, runCliWithInputOrFail, sanitiseCatStdout } from "./helpers/cli.ts";
|
||||||
|
import { getOptimalLoopbackIp } from "./helpers/net.ts";
|
||||||
|
|
||||||
Deno.test("p2p: three nodes detect and resolve conflicts", async () => {
|
Deno.test("p2p: three nodes detect and resolve conflicts", async () => {
|
||||||
const relay = Deno.env.get("RELAY") ?? "ws://localhost:4000/";
|
const loopbackIp = await getOptimalLoopbackIp();
|
||||||
const roomId = `${Deno.env.get("ROOM_ID_PREFIX") ?? "p2p-room"}-${Date.now()}`;
|
const loopbackHost = loopbackIp === "::1" ? "[::1]" : loopbackIp;
|
||||||
const passphrase = `${Deno.env.get("PASSPHRASE_PREFIX") ?? "p2p-pass"}-${Date.now()}`;
|
|
||||||
const appId = Deno.env.get("APP_ID") ?? "self-hosted-livesync-cli-tests";
|
const relay = Deno.env.get("RELAY") ?? `ws://${loopbackHost}:4000/`;
|
||||||
|
const roomId = Deno.env.get("ROOM_ID") ?? `room-${Date.now()}`;
|
||||||
|
const passphrase = Deno.env.get("PASSPHRASE") ?? "test";
|
||||||
|
const appId = "self-hosted-livesync-cli-tests";
|
||||||
const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "10");
|
const peersTimeout = Number(Deno.env.get("PEERS_TIMEOUT") ?? "10");
|
||||||
const syncTimeout = Number(Deno.env.get("SYNC_TIMEOUT") ?? "15");
|
const syncTimeout = Number(Deno.env.get("SYNC_TIMEOUT") ?? "15");
|
||||||
const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
const nonce = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
||||||
const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`;
|
const hostPeerName = Deno.env.get("HOST_PEER_NAME") ?? `p2p-host-${nonce}`;
|
||||||
const peerNameB = Deno.env.get("PEER_NAME_B") ?? `p2p-client-b-${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 peerNameC = Deno.env.get("PEER_NAME_C") ?? `p2p-client-c-${nonce}`;
|
||||||
const turnServers = Deno.env.get("TURN_SERVERS") ?? "turn:127.0.0.1:3478";
|
const useCoturn = Deno.env.get("LIVESYNC_USE_COTURN") !== "0";
|
||||||
|
const turnServers = Deno.env.get("TURN_SERVERS") ?? (useCoturn ? `turn:${loopbackHost}:3478` : "none");
|
||||||
|
|
||||||
await using workDir = await TempDir.create("livesync-cli-p2p-3nodes");
|
await using workDir = await TempDir.create("livesync-cli-p2p-3nodes");
|
||||||
const vaultA = workDir.join("vault-a");
|
const vaultA = workDir.join("vault-a");
|
||||||
|
|||||||
Reference in New Issue
Block a user