Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions apps/desktop/src/main/__tests__/runtime-host-management.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
runtimeHostAccessCredentialFingerprint,
type RuntimeHostServiceManagementFrame,
} from '@maka/runtime-host/operator';
Expand Down Expand Up @@ -937,11 +937,11 @@ test('keeps the SSH profile while adding and removing its managed Direct peer',
assert.equal(input.action, 'status');
assert.equal(
input.capabilityRequest,
RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
);
return {
...serviceResult('status'),
operatorCapabilities: [RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY],
operatorCapabilities: [RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY],
};
},
runAccessManagement: async () => assert.fail('access management is not expected'),
Expand Down Expand Up @@ -971,9 +971,9 @@ test('keeps the SSH profile while adding and removing its managed Direct peer',

const configure = handlers.get('runtime-host-management:configure-direct-peer');
assert.ok(configure);
const enabled = await configure({}, profile.id, true, []);
const enabled = await configure({}, profile.id, true, [], true);
assert.equal((enabled as { profilePresent: boolean }).profilePresent, true);
const disabled = await configure({}, profile.id, false, []);
const disabled = await configure({}, profile.id, false, [], true);
assert.equal((disabled as { profilePresent: boolean }).profilePresent, false);
assert.deepEqual(actions, ['enable', 'disable']);
});
Expand Down Expand Up @@ -1005,7 +1005,7 @@ test('disables a newly enabled listener when its Desktop profile cannot be commi
directPeerClientAvailable: true,
runServiceManagement: async () => ({
...serviceResult('status'),
operatorCapabilities: [RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY],
operatorCapabilities: [RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY],
}),
runAccessManagement: async () => assert.fail('access management is not expected'),
runPeerManagement: async (input) => {
Expand Down Expand Up @@ -1037,7 +1037,7 @@ test('disables a newly enabled listener when its Desktop profile cannot be commi
const configure = handlers.get('runtime-host-management:configure-direct-peer');
assert.ok(configure);
await assert.rejects(
configure({}, 'office', true, []) as Promise<unknown>,
configure({}, 'office', true, [], true) as Promise<unknown>,
failure === 'descriptor' ? /usable direct-peer descriptor/u : /profile store failed/u,
);
assert.deepEqual(actions, ['enable', 'disable']);
Expand Down Expand Up @@ -1077,13 +1077,14 @@ test('does not invoke peer management when the remote operator lacks its capabil
state: 'unsupported',
routeHints: [],
coordinationRelays: [],
automaticRelayDiscovery: false,
profilePresent: false,
profileEnabled: false,
clientAvailable: true,
managementAvailable: false,
});
await assert.rejects(
configure({}, 'office', true, []) as Promise<unknown>,
configure({}, 'office', true, [], true) as Promise<unknown>,
/Update this Runtime Host/u,
);
});
Expand Down
40 changes: 40 additions & 0 deletions apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { type RuntimeHostSshProcessFactory } from '@maka/runtime-host/client';
import {
encodeRuntimeHostActivationFrame,
encodeRuntimeHostAccessManagementFrame,
encodeRuntimeHostPeerManagementFrame,
encodeRuntimeHostServiceManagementFrame,
encodeRuntimeHostSetupFrame,
encodeRuntimeHostPeerMeshManagementFrame,
Expand Down Expand Up @@ -638,6 +639,45 @@ test('keeps a prepared access credential out of the SSH terminal projection', as
await harness.terminal.close();
});

test('requests relay-discovery status only on the peer-management frame', async () => {
const harness = createHarness('pending');
const management = harness.terminal.runPeerManagement({
destination: 'operator@example.com',
operatorPath: '/home/operator/.local/share/maka/operator',
action: 'status',
expectedTarget: {
serviceId: 'b'.repeat(64),
rootPath: '/srv/maka',
rootId: 'a'.repeat(64),
deploymentId: '00000000-0000-4000-8000-000000000001',
},
});
await waitFor(() => harness.pty.hasDataListener());
const command = harness.launchArgs.at(-1)?.at(-1) ?? '';
assert.match(command, /peer.*status.*--framed.*--relay-discovery-status/u);

harness.pty.emitData(
encodeRuntimeHostPeerManagementFrame({
kind: 'result',
action: 'status',
status: {
state: 'enabled',
serviceState: 'running',
peerId: '12D3KooWpeer',
rootId: 'a'.repeat(64),
routeHints: ['/ip4/192.0.2.1/udp/41000/quic-v1'],
coordinationRelays: [],
automaticRelayDiscovery: true,
},
}),
);
harness.pty.exit(0);

const result = await management;
assert.equal(result.kind === 'result' && result.status.automaticRelayDiscovery, true);
await harness.terminal.close();
});

test('sends a Mesh invitation only after the authenticated remote operator requests it', async () => {
const harness = createHarness('pending');
const invitation = JSON.stringify({ secret: 'one-time-mesh-secret' });
Expand Down
27 changes: 22 additions & 5 deletions apps/desktop/src/main/runtime-host-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import type { IpcMain } from 'electron';
import {
RUNTIME_HOST_OPERATOR_ACCESS_MANAGEMENT_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
isProductReleaseVersion,
runtimeHostAccessCredentialFingerprint,
type RuntimeHostManagedUpdatePolicy,
Expand Down Expand Up @@ -317,6 +317,7 @@ export function createDesktopRuntimeHostManagement(input: {
...(status.peerId ? { peerId: status.peerId } : {}),
routeHints: status.routeHints,
coordinationRelays: status.coordinationRelays,
automaticRelayDiscovery: status.automaticRelayDiscovery ?? false,
profilePresent: profile.exists,
profileEnabled: profile.enabled,
clientAvailable: input.directPeerClientAvailable,
Expand All @@ -334,7 +335,7 @@ export function createDesktopRuntimeHostManagement(input: {
operatorPath: target.managed.control.operatorPath,
action: 'status',
expectedTarget: target.expectedTarget,
capabilityRequest: RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
capabilityRequest: RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
});
if (capability.kind === 'error') throw new Error(capability.error.message);
if (capability.action !== 'status') {
Expand All @@ -343,7 +344,7 @@ export function createDesktopRuntimeHostManagement(input: {
return {
...target,
available: capability.operatorCapabilities?.includes(
RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
RUNTIME_HOST_OPERATOR_PEER_RELAY_DISCOVERY_CAPABILITY,
) === true,
};
};
Expand All @@ -356,6 +357,7 @@ export function createDesktopRuntimeHostManagement(input: {
state: 'unsupported',
routeHints: [],
coordinationRelays: [],
automaticRelayDiscovery: false,
profilePresent: profile.exists,
profileEnabled: profile.enabled,
clientAvailable: input.directPeerClientAvailable,
Expand Down Expand Up @@ -390,11 +392,15 @@ export function createDesktopRuntimeHostManagement(input: {
profileIdValue: unknown,
enabledValue: unknown,
coordinationRelaysValue: unknown,
automaticRelayDiscoveryValue: unknown,
): Promise<DesktopRuntimeHostDirectPeerSnapshot> => {
if (typeof enabledValue !== 'boolean') {
throw new Error('Runtime Host direct-peer state is invalid');
}
const coordinationRelays = requireCoordinationRelays(coordinationRelaysValue);
if (typeof automaticRelayDiscoveryValue !== 'boolean') {
throw new Error('Runtime Host relay discovery state is invalid');
}
const { profileId, managed, transport, expectedTarget, available } =
await peerManagementTarget(profileIdValue);
if (!available) {
Expand All @@ -410,6 +416,7 @@ export function createDesktopRuntimeHostManagement(input: {
operatorPath: managed.control.operatorPath,
action: enabledValue ? 'enable' : 'disable',
...(enabledValue ? { coordinationRelays } : {}),
...(enabledValue ? { automaticRelayDiscovery: automaticRelayDiscoveryValue } : {}),
expectedTarget,
});
if (response.kind !== 'result') {
Expand Down Expand Up @@ -829,8 +836,18 @@ export function createDesktopRuntimeHostManagement(input: {
getDirectPeer(profileId));
input.ipcMain.handle(
channels.configureDirectPeer,
(_event, profileId: unknown, enabled: unknown, coordinationRelays: unknown) =>
configureDirectPeer(profileId, enabled, coordinationRelays),
(
_event,
profileId: unknown,
enabled: unknown,
coordinationRelays: unknown,
automaticRelayDiscovery: unknown,
) => configureDirectPeer(
profileId,
enabled,
coordinationRelays,
automaticRelayDiscovery,
),
);

return {
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/src/main/runtime-host-ssh-terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export interface DesktopRuntimeHostSshPeerManagementInput {
readonly operatorPath: string;
readonly action: Extract<RuntimeHostPeerManagementAction, 'enable' | 'disable' | 'status'>;
readonly coordinationRelays?: readonly string[];
readonly automaticRelayDiscovery?: boolean;
readonly expectedTarget: DesktopRuntimeHostSshManagementInput['expectedTarget'];
readonly signal?: AbortSignal;
}
Expand Down Expand Up @@ -1343,11 +1344,17 @@ function runtimeHostPeerManagementRemoteCommand(
'peer',
input.action,
'--framed',
'--relay-discovery-status',
...(input.action === 'enable' && input.coordinationRelays
? input.coordinationRelays.length === 0
? ['--clear-coordination-relays']
: input.coordinationRelays.flatMap((relay) => ['--coordination-relay', relay])
: []),
...(input.action === 'enable' && input.automaticRelayDiscovery !== undefined
? [input.automaticRelayDiscovery
? '--automatic-relay-discovery'
: '--no-automatic-relay-discovery']
: []),
...managedServiceTargetArgs(input.expectedTarget),
].map(quotePosix).join(' ');
return `exec "\${SHELL:-/bin/sh}" -lic ${quotePosix(`exec ${command}`)}`;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/preload/bridge-contract.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ export interface DesktopRuntimeHostDirectPeerSnapshot {
readonly peerId?: string;
readonly routeHints: readonly string[];
readonly coordinationRelays: readonly string[];
readonly automaticRelayDiscovery: boolean;
readonly profilePresent: boolean;
readonly profileEnabled: boolean;
readonly clientAvailable: boolean;
Expand Down Expand Up @@ -731,6 +732,7 @@ export interface MakaBridge {
profileId: string,
enabled: boolean,
coordinationRelays: readonly string[],
automaticRelayDiscovery: boolean,
): Promise<DesktopRuntimeHostDirectPeerSnapshot>;
listCredentials(profileId: string): Promise<DesktopRuntimeHostAccessSnapshot>;
rotateCredential(profileId: string): Promise<DesktopRuntimeHostAccessSnapshot>;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/preload/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1349,12 +1349,14 @@ const makaBridge = {
profileId: string,
enabled: boolean,
coordinationRelays: readonly string[],
automaticRelayDiscovery: boolean,
) {
return ipcRenderer.invoke(
'runtime-host-management:configure-direct-peer',
profileId,
enabled,
coordinationRelays,
automaticRelayDiscovery,
);
},
listCredentials(profileId: string): Promise<DesktopRuntimeHostAccessSnapshot> {
Expand Down
19 changes: 15 additions & 4 deletions apps/desktop/src/renderer/locales/settings-projects-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ export type SettingsProjectsCopy = {
directPeerRoutes: string;
directPeerCoordinationRelays: string;
directPeerCoordinationRelaysPlaceholder: string;
directPeerAdvancedCoordination: string;
directPeerAutomaticRelayDiscovery: string;
directPeerAutomaticRelayDiscoveryHelp: string;
directPeerEnable: string;
directPeerDisable: string;
directPeerAddProfile: string;
Expand Down Expand Up @@ -298,7 +301,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
useConnectionCode: '使用连接码',
configureManually: '手动配置',
thisComputerRemoteAccess: '远程访问',
thisComputerRemoteAccessHelp: '让其他 Maka Desktop 通过实验性 Direct peer 连接此 Host',
thisComputerRemoteAccessHelp: '通过实验性端到端直连访问此 Host;可自动发现公共协调节点来辅助打洞',
remoteAccessOn: '已开启',
remoteAccessOff: '未开启',
enableRemoteAccess: '开启',
Expand Down Expand Up @@ -407,7 +410,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
failed: '启动失败',
},
directPeer: 'Direct peer(实验性)',
directPeerDescription: '创建独立的实验性 Direct profile。受限 NAT 或被阻止的 UDP 可能使其不可达,且不会自动回退;保留 SSH profile 用于手动恢复。',
directPeerDescription: '创建独立的实验性 Direct profile。可自动发现或手动指定协调节点来辅助打洞;受限 NAT 或被阻止的 UDP 仍可能使其不可达,且不会回退到中继传输。保留 SSH profile 用于手动恢复。',
directPeerState: {
unsupported: '需要更新',
not_configured: '未配置',
Expand All @@ -423,6 +426,10 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
directPeerRoutes: '可用路径',
directPeerCoordinationRelays: '连接协调节点(可选)',
directPeerCoordinationRelaysPlaceholder: '多个地址用逗号分隔',
directPeerAdvancedCoordination: '手动设置协调节点',
directPeerAutomaticRelayDiscovery: '自动发现协调节点',
directPeerAutomaticRelayDiscoveryHelp:
'协调节点使用 Circuit Relay v2 协议,仅帮助建立端到端直连,不承载应用流量。Maka 会通过公共 IPFS 网络尽力发现可用节点;手动设置的节点优先。',
directPeerEnable: '启用并添加',
directPeerDisable: '停用',
directPeerAddProfile: '添加到 Desktop',
Expand Down Expand Up @@ -587,7 +594,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
useConnectionCode: 'Use connection code',
configureManually: 'Configure manually',
thisComputerRemoteAccess: 'Remote access',
thisComputerRemoteAccessHelp: 'Let another Maka Desktop reach this Host through experimental Direct peer',
thisComputerRemoteAccessHelp: 'Reach this Host through experimental end-to-end direct connections, with automatic public coordination discovery',
remoteAccessOn: 'On',
remoteAccessOff: 'Off',
enableRemoteAccess: 'Enable',
Expand Down Expand Up @@ -696,7 +703,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
failed: 'Failed',
},
directPeer: 'Direct peer (experimental)',
directPeerDescription: 'Create an independent experimental Direct profile. Restrictive NAT or blocked UDP may make it unreachable, and it does not fall back automatically; keep the SSH profile for manual recovery.',
directPeerDescription: 'Create an independent experimental Direct profile. Discover coordination peers automatically or provide them manually to assist hole punching; restrictive NAT or blocked UDP may still make it unreachable, and traffic does not fall back to a relay. Keep the SSH profile for manual recovery.',
directPeerState: {
unsupported: 'Update required',
not_configured: 'Not configured',
Expand All @@ -712,6 +719,10 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
directPeerRoutes: 'Routes',
directPeerCoordinationRelays: 'Connection coordination peers (optional)',
directPeerCoordinationRelaysPlaceholder: 'Separate multiple addresses with commas',
directPeerAdvancedCoordination: 'Set coordination peers manually',
directPeerAutomaticRelayDiscovery: 'Discover coordination peers automatically',
directPeerAutomaticRelayDiscoveryHelp:
'Coordination peers use Circuit Relay v2 only to establish an end-to-end direct connection; they never carry application traffic. Maka discovers candidates through the public IPFS network on a best-effort basis, while manually configured peers remain preferred.',
directPeerEnable: 'Enable and add',
directPeerDisable: 'Disable',
directPeerAddProfile: 'Add to Desktop',
Expand Down
Loading
Loading