@jonathanyee/devtunnel-ctl 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +328 -2
- package/dist/auth.d.ts +24 -0
- package/dist/auth.js +151 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +246 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +94 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +16 -0
- package/dist/core.js +114 -0
- package/dist/core.js.map +1 -0
- package/dist/daemon.d.ts +31 -0
- package/dist/daemon.js +153 -0
- package/dist/daemon.js.map +1 -0
- package/dist/forwarding.d.ts +15 -0
- package/dist/forwarding.js +87 -0
- package/dist/forwarding.js.map +1 -0
- package/dist/host-process.d.ts +4 -0
- package/dist/host-process.js +110 -0
- package/dist/host-process.js.map +1 -0
- package/dist/host-sdk.d.ts +38 -0
- package/dist/host-sdk.js +327 -0
- package/dist/host-sdk.js.map +1 -0
- package/dist/host-service.d.ts +21 -0
- package/dist/host-service.js +193 -0
- package/dist/host-service.js.map +1 -0
- package/dist/host-worker.d.ts +1 -0
- package/dist/host-worker.js +80 -0
- package/dist/host-worker.js.map +1 -0
- package/dist/host.d.ts +43 -0
- package/dist/host.js +158 -0
- package/dist/host.js.map +1 -0
- package/dist/launchd.d.ts +15 -0
- package/dist/launchd.js +135 -0
- package/dist/launchd.js.map +1 -0
- package/dist/lock.d.ts +10 -0
- package/dist/lock.js +110 -0
- package/dist/lock.js.map +1 -0
- package/dist/log.d.ts +26 -0
- package/dist/log.js +67 -0
- package/dist/log.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +5 -0
- package/dist/main.js.map +1 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +39 -0
- package/dist/paths.js.map +1 -0
- package/dist/probe.d.ts +1 -0
- package/dist/probe.js +45 -0
- package/dist/probe.js.map +1 -0
- package/dist/recovery.d.ts +77 -0
- package/dist/recovery.js +265 -0
- package/dist/recovery.js.map +1 -0
- package/dist/rpc.d.ts +30 -0
- package/dist/rpc.js +208 -0
- package/dist/rpc.js.map +1 -0
- package/dist/sdk-transport.d.ts +33 -0
- package/dist/sdk-transport.js +239 -0
- package/dist/sdk-transport.js.map +1 -0
- package/npm-shrinkwrap.json +810 -0
- package/package.json +31 -4
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { record, safeFault, bounded, Fault } from "./core.js";
|
|
2
|
+
import { validateHost } from "./host.js";
|
|
3
|
+
import { runHostSdk } from "./host-sdk.js";
|
|
4
|
+
const life = new AbortController();
|
|
5
|
+
let started = false, pending = 0;
|
|
6
|
+
let endpointAck;
|
|
7
|
+
const send = (message) => {
|
|
8
|
+
if (!process.connected || (pending > 0 && message.kind === 'heartbeat'))
|
|
9
|
+
return;
|
|
10
|
+
if (pending >= 32) {
|
|
11
|
+
life.abort();
|
|
12
|
+
throw new Fault('protocol', 'Host worker IPC backlog exceeded its limit.');
|
|
13
|
+
}
|
|
14
|
+
pending++;
|
|
15
|
+
process.send?.(message, error => {
|
|
16
|
+
pending--;
|
|
17
|
+
if (error)
|
|
18
|
+
life.abort();
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const report = (message) => {
|
|
22
|
+
if (message.kind !== 'endpoint') {
|
|
23
|
+
send(message);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
return bounded(() => new Promise((resolve, reject) => {
|
|
27
|
+
if (endpointAck) {
|
|
28
|
+
reject(new Fault('protocol'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
endpointAck = { id: message.id, resolve };
|
|
32
|
+
send(message);
|
|
33
|
+
}), 2000, life.signal).finally(() => { endpointAck = undefined; });
|
|
34
|
+
};
|
|
35
|
+
const disconnected = () => life.abort();
|
|
36
|
+
process.once('disconnect', disconnected);
|
|
37
|
+
process.once('SIGTERM', disconnected);
|
|
38
|
+
process.once('SIGINT', disconnected);
|
|
39
|
+
const initial = setTimeout(() => { life.abort(); process.disconnect?.(); }, 5000);
|
|
40
|
+
process.on('message', value => {
|
|
41
|
+
if (!record(value)) {
|
|
42
|
+
life.abort();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (value.kind === 'stop') {
|
|
46
|
+
life.abort();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (value.kind === 'endpoint-recorded' && value.id === endpointAck?.id) {
|
|
50
|
+
endpointAck?.resolve();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (value.kind !== 'start' || started) {
|
|
54
|
+
life.abort();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
started = true;
|
|
58
|
+
clearTimeout(initial);
|
|
59
|
+
let options;
|
|
60
|
+
try {
|
|
61
|
+
options = validateHost(value.options);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
send({ kind: 'fault', code: 'invalid-config', retryAfterMs: 0 });
|
|
65
|
+
process.disconnect?.();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const heartbeat = setInterval(() => send({ kind: 'heartbeat' }), 1000);
|
|
69
|
+
void runHostSdk(options, report, life.signal).catch(error => {
|
|
70
|
+
const fault = safeFault(error);
|
|
71
|
+
send({ kind: 'fault', code: fault.code, retryAfterMs: fault.retryAfterMs });
|
|
72
|
+
}).finally(() => {
|
|
73
|
+
clearInterval(heartbeat);
|
|
74
|
+
if (process.connected)
|
|
75
|
+
process.disconnect();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
if (!process.send)
|
|
79
|
+
throw new Fault('invalid-config', 'The host worker requires a supervisor IPC channel.');
|
|
80
|
+
//# sourceMappingURL=host-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-worker.js","sourceRoot":"","sources":["../src/host-worker.ts"],"names":[],"mappings":"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"}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Clock, FaultCode } from './core.ts';
|
|
2
|
+
export interface HostOptions {
|
|
3
|
+
tunnelId: string;
|
|
4
|
+
clusterId?: string;
|
|
5
|
+
helperPath: string;
|
|
6
|
+
probePorts: number[];
|
|
7
|
+
}
|
|
8
|
+
export interface HostStatus {
|
|
9
|
+
kind: 'status';
|
|
10
|
+
state: 'connecting' | 'connected' | 'verified' | 'degraded';
|
|
11
|
+
relayConnected: boolean;
|
|
12
|
+
ports: number[];
|
|
13
|
+
localUnavailable: number[];
|
|
14
|
+
verifiedPorts: number[];
|
|
15
|
+
probeFault?: FaultCode;
|
|
16
|
+
}
|
|
17
|
+
export type HostMessage = HostStatus | {
|
|
18
|
+
kind: 'heartbeat';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'endpoint';
|
|
21
|
+
id: string;
|
|
22
|
+
tunnelId: string;
|
|
23
|
+
clusterId: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'fault';
|
|
26
|
+
code: FaultCode;
|
|
27
|
+
retryAfterMs: number;
|
|
28
|
+
} | {
|
|
29
|
+
kind: 'retry';
|
|
30
|
+
attempt: number;
|
|
31
|
+
nextRetryAt: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function validateHost(value: unknown): HostOptions;
|
|
34
|
+
export declare function parseHostMessage(value: unknown): HostMessage | undefined;
|
|
35
|
+
export interface HostDependencies {
|
|
36
|
+
worker(options: HostOptions, emit: (message: HostMessage) => void, signal: AbortSignal): Promise<void>;
|
|
37
|
+
report(message: HostMessage): void;
|
|
38
|
+
clock?: Clock;
|
|
39
|
+
random?: () => number;
|
|
40
|
+
sleep?(ms: number, signal: AbortSignal): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export declare function sleep(ms: number, signal: AbortSignal): Promise<void>;
|
|
43
|
+
export declare function superviseHost(options: HostOptions, deps: HostDependencies, signal: AbortSignal): Promise<void>;
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
import { bounded, clock, Fault, isFaultCode, record, safeFault } from "./core.js";
|
|
3
|
+
const port = (v) => Number.isInteger(v) && Number(v) > 0 && Number(v) <= 65535;
|
|
4
|
+
const id = /^[a-z0-9][a-z0-9-]{0,62}$/i;
|
|
5
|
+
const terminal = (code) => ['login-required', 'access-denied', 'invalid-config', 'host-superseded', 'host-cleanup-failed'].includes(code);
|
|
6
|
+
export function validateHost(value) {
|
|
7
|
+
if (!record(value) || typeof value.tunnelId !== 'string' ||
|
|
8
|
+
typeof value.helperPath !== 'string' || !isAbsolute(value.helperPath) ||
|
|
9
|
+
/[\0\r\n]/.test(value.helperPath) || !Array.isArray(value.probePorts) ||
|
|
10
|
+
value.probePorts.length > 16 || !value.probePorts.every(port) ||
|
|
11
|
+
new Set(value.probePorts).size !== value.probePorts.length)
|
|
12
|
+
throw new Fault('invalid-config', 'Invalid host options or probe ports.');
|
|
13
|
+
const [tunnelId, suffix, extra] = value.tunnelId.split('.');
|
|
14
|
+
const clusterId = value.clusterId ?? suffix;
|
|
15
|
+
if (!tunnelId || !id.test(tunnelId) || extra !== undefined ||
|
|
16
|
+
(clusterId !== undefined && (typeof clusterId !== 'string' || !id.test(clusterId))) ||
|
|
17
|
+
(suffix !== undefined && value.clusterId !== undefined && suffix !== value.clusterId)) {
|
|
18
|
+
throw new Fault('invalid-config', 'Invalid host tunnel or cluster ID.');
|
|
19
|
+
}
|
|
20
|
+
return { tunnelId, ...(clusterId ? { clusterId: String(clusterId) } : {}),
|
|
21
|
+
helperPath: value.helperPath, probePorts: [...value.probePorts] };
|
|
22
|
+
}
|
|
23
|
+
export function parseHostMessage(value) {
|
|
24
|
+
if (!record(value))
|
|
25
|
+
return;
|
|
26
|
+
if (value.kind === 'heartbeat')
|
|
27
|
+
return { kind: 'heartbeat' };
|
|
28
|
+
if (value.kind === 'retry' && Number.isSafeInteger(value.attempt) && Number(value.attempt) > 0 &&
|
|
29
|
+
typeof value.nextRetryAt === 'number' && Number.isFinite(value.nextRetryAt) && value.nextRetryAt >= 0) {
|
|
30
|
+
return { kind: 'retry', attempt: Number(value.attempt), nextRetryAt: value.nextRetryAt };
|
|
31
|
+
}
|
|
32
|
+
if (value.kind === 'endpoint' && typeof value.id === 'string' && /^[a-f0-9-]{36}-relay$/.test(value.id) &&
|
|
33
|
+
typeof value.tunnelId === 'string' && id.test(value.tunnelId) && typeof value.clusterId === 'string' && id.test(value.clusterId)) {
|
|
34
|
+
return { kind: 'endpoint', id: value.id, tunnelId: value.tunnelId, clusterId: value.clusterId };
|
|
35
|
+
}
|
|
36
|
+
if (value.kind === 'fault' && isFaultCode(value.code) && typeof value.retryAfterMs === 'number' &&
|
|
37
|
+
Number.isFinite(value.retryAfterMs) && value.retryAfterMs >= 0 && value.retryAfterMs <= 86400000) {
|
|
38
|
+
return { kind: 'fault', code: value.code, retryAfterMs: value.retryAfterMs };
|
|
39
|
+
}
|
|
40
|
+
if (value.kind !== 'status' || !['connecting', 'connected', 'verified', 'degraded'].includes(String(value.state)) ||
|
|
41
|
+
typeof value.relayConnected !== 'boolean' || (value.probeFault !== undefined && !isFaultCode(value.probeFault)))
|
|
42
|
+
return;
|
|
43
|
+
const arrays = [value.ports, value.localUnavailable, value.verifiedPorts];
|
|
44
|
+
if (!arrays.every(a => Array.isArray(a) && a.length <= 1024 && a.every(port) && new Set(a).size === a.length))
|
|
45
|
+
return;
|
|
46
|
+
const ports = value.ports, localUnavailable = value.localUnavailable, verifiedPorts = value.verifiedPorts;
|
|
47
|
+
if (localUnavailable.some(p => !ports.includes(p)) || verifiedPorts.some(p => !ports.includes(p) || localUnavailable.includes(p)) ||
|
|
48
|
+
(verifiedPorts.length > 0 && !value.relayConnected) || (value.state === 'verified' && (!value.relayConnected || !verifiedPorts.length)))
|
|
49
|
+
return;
|
|
50
|
+
return { kind: 'status', state: value.state, relayConnected: value.relayConnected,
|
|
51
|
+
ports: [...ports], localUnavailable: [...localUnavailable], verifiedPorts: [...verifiedPorts],
|
|
52
|
+
...(isFaultCode(value.probeFault) ? { probeFault: value.probeFault } : {}) };
|
|
53
|
+
}
|
|
54
|
+
export function sleep(ms, signal) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (signal.aborted) {
|
|
57
|
+
reject(new Fault('aborted'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const aborted = () => { clearTimeout(timer); reject(new Fault('aborted')); };
|
|
61
|
+
const timer = setTimeout(() => { signal.removeEventListener('abort', aborted); resolve(); }, ms);
|
|
62
|
+
signal.addEventListener('abort', aborted, { once: true });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export async function superviseHost(options, deps, signal) {
|
|
66
|
+
const timer = deps.clock ?? clock;
|
|
67
|
+
let attempt = 0;
|
|
68
|
+
while (!signal.aborted) {
|
|
69
|
+
const life = new AbortController();
|
|
70
|
+
const parentAbort = () => life.abort();
|
|
71
|
+
signal.addEventListener('abort', parentAbort, { once: true });
|
|
72
|
+
const started = timer.now();
|
|
73
|
+
let lastHeartbeat = started, established = false, failure;
|
|
74
|
+
let cancelWatch = () => { };
|
|
75
|
+
const watch = () => {
|
|
76
|
+
if (timer.now() - lastHeartbeat > 15000 || (!established && timer.now() - started > 45000)) {
|
|
77
|
+
failure ??= new Fault('timeout');
|
|
78
|
+
life.abort();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
cancelWatch = timer.later(1000, watch);
|
|
82
|
+
};
|
|
83
|
+
watch();
|
|
84
|
+
let cancelAbort = () => { };
|
|
85
|
+
const aborted = new Promise(resolve => {
|
|
86
|
+
const abort = () => resolve();
|
|
87
|
+
life.signal.addEventListener('abort', abort, { once: true });
|
|
88
|
+
cancelAbort = () => life.signal.removeEventListener('abort', abort);
|
|
89
|
+
});
|
|
90
|
+
const work = Promise.resolve().then(() => {
|
|
91
|
+
if (signal.aborted)
|
|
92
|
+
life.abort();
|
|
93
|
+
return deps.worker(options, message => {
|
|
94
|
+
if (life.signal.aborted)
|
|
95
|
+
return;
|
|
96
|
+
if (message.kind === 'heartbeat') {
|
|
97
|
+
lastHeartbeat = timer.now();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (message.kind === 'fault') {
|
|
101
|
+
failure = new Fault(message.code, undefined, message.retryAfterMs);
|
|
102
|
+
life.abort();
|
|
103
|
+
}
|
|
104
|
+
else if (message.kind === 'status') {
|
|
105
|
+
established ||= message.relayConnected;
|
|
106
|
+
if ((message.state === 'verified' && options.probePorts.every(p => message.verifiedPorts.includes(p))) ||
|
|
107
|
+
(!options.probePorts.length && message.relayConnected))
|
|
108
|
+
attempt = 0;
|
|
109
|
+
deps.report(message);
|
|
110
|
+
}
|
|
111
|
+
}, life.signal);
|
|
112
|
+
});
|
|
113
|
+
try {
|
|
114
|
+
await Promise.race([work, aborted]);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
failure ??= safeFault(error);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
life.abort();
|
|
121
|
+
cancelWatch();
|
|
122
|
+
cancelAbort();
|
|
123
|
+
signal.removeEventListener('abort', parentAbort);
|
|
124
|
+
}
|
|
125
|
+
// A replacement cannot overlap a worker that has not confirmed its exit.
|
|
126
|
+
try {
|
|
127
|
+
await bounded(() => work.catch(error => {
|
|
128
|
+
const fault = safeFault(error);
|
|
129
|
+
if (!failure || terminal(fault.code))
|
|
130
|
+
failure = fault;
|
|
131
|
+
}), 25000, undefined, undefined, timer);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
throw new Fault('internal', 'Host worker exit could not be confirmed; refusing overlapping hosts.');
|
|
135
|
+
}
|
|
136
|
+
if (signal.aborted) {
|
|
137
|
+
if (failure?.code === 'host-cleanup-failed') {
|
|
138
|
+
deps.report({ kind: 'fault', code: failure.code, retryAfterMs: failure.retryAfterMs });
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
failure ??= new Fault('retrying');
|
|
143
|
+
deps.report({ kind: 'fault', code: failure.code, retryAfterMs: failure.retryAfterMs });
|
|
144
|
+
if (terminal(failure.code))
|
|
145
|
+
return;
|
|
146
|
+
const backoff = Math.min(15000, 1000 * 2 ** Math.min(attempt++, 5));
|
|
147
|
+
const delay = Math.max(failure.retryAfterMs, Math.round(backoff * (0.5 + (deps.random ?? Math.random)() * 0.5)));
|
|
148
|
+
deps.report({ kind: 'retry', attempt, nextRetryAt: timer.now() + delay });
|
|
149
|
+
try {
|
|
150
|
+
await (deps.sleep ?? sleep)(delay, signal);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!signal.aborted)
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Store } from './config.ts';
|
|
2
|
+
interface AgentPaths {
|
|
3
|
+
label: string;
|
|
4
|
+
node: string;
|
|
5
|
+
entry: string;
|
|
6
|
+
state: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const xml: (value: string) => string;
|
|
9
|
+
export declare function launchAgent(paths: AgentPaths): string;
|
|
10
|
+
export declare function launchctl(args: string[], allowAbsent?: boolean): Promise<{
|
|
11
|
+
loaded: boolean;
|
|
12
|
+
pid?: number;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function service(action: string, store: Store, entry: string): Promise<unknown>;
|
|
15
|
+
export {};
|
package/dist/launchd.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { isAbsolute, join } from 'node:path';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { realpath, mkdir, writeFile, lstat, unlink } from 'node:fs/promises';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { bounded, Fault } from "./core.js";
|
|
7
|
+
import { DaemonLock } from "./lock.js";
|
|
8
|
+
import { request } from "./rpc.js";
|
|
9
|
+
import { executable } from "./paths.js";
|
|
10
|
+
export const xml = (value) => value.replace(/[<>&"']/g, ch => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' })[ch]);
|
|
11
|
+
export function launchAgent(paths) {
|
|
12
|
+
if (![paths.node, paths.entry, paths.state].every(p => isAbsolute(p) && !/[\0\r\n]/.test(p))) {
|
|
13
|
+
throw new Fault('invalid-config', 'launchd requires absolute runtime, entrypoint and state paths.');
|
|
14
|
+
}
|
|
15
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
16
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
17
|
+
<plist version="1.0"><dict>
|
|
18
|
+
<key>Label</key><string>${xml(paths.label)}</string>
|
|
19
|
+
<key>ProgramArguments</key><array>${[paths.node, paths.entry, '--state-dir', paths.state, 'daemon'].map(arg => `<string>${xml(arg)}</string>`).join('')}</array>
|
|
20
|
+
<key>RunAtLoad</key><true/>
|
|
21
|
+
<key>KeepAlive</key><true/>
|
|
22
|
+
<key>ThrottleInterval</key><integer>15</integer>
|
|
23
|
+
<key>ProcessType</key><string>Background</string>
|
|
24
|
+
<key>StandardOutPath</key><string>/dev/null</string>
|
|
25
|
+
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
26
|
+
</dict></plist>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
export async function launchctl(args, allowAbsent = false) {
|
|
30
|
+
return bounded(signal => new Promise((resolve, reject) => {
|
|
31
|
+
const child = spawn('/bin/launchctl', args, { shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
32
|
+
let bytes = 0, stdout = '';
|
|
33
|
+
const abort = () => { child.kill('SIGKILL'); };
|
|
34
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
35
|
+
child.stdout.on('data', b => { bytes += b.length; if (bytes > 65536)
|
|
36
|
+
abort();
|
|
37
|
+
else
|
|
38
|
+
stdout += b.toString(); });
|
|
39
|
+
child.stderr.on('data', b => { bytes += b.length; if (bytes > 65536)
|
|
40
|
+
abort(); });
|
|
41
|
+
child.on('error', () => { signal.removeEventListener('abort', abort); reject(new Fault('internal', 'Cannot execute launchctl.')); });
|
|
42
|
+
child.on('close', code => {
|
|
43
|
+
signal.removeEventListener('abort', abort);
|
|
44
|
+
if (code === 0) {
|
|
45
|
+
const pid = /^\s*pid = ([1-9][0-9]*)\s*$/m.exec(stdout)?.[1];
|
|
46
|
+
resolve({ loaded: true, ...(pid ? { pid: Number(pid) } : {}) });
|
|
47
|
+
}
|
|
48
|
+
else if (allowAbsent && code === 113)
|
|
49
|
+
resolve({ loaded: false });
|
|
50
|
+
else
|
|
51
|
+
reject(new Fault('internal', 'launchctl failed. Check the service registration with launchctl print gui/$(id -u)/LABEL; no persistent action is assumed successful.'));
|
|
52
|
+
});
|
|
53
|
+
}), 5000);
|
|
54
|
+
}
|
|
55
|
+
export async function service(action, store, entry) {
|
|
56
|
+
if (process.platform !== 'darwin' || process.getuid?.() === 0)
|
|
57
|
+
throw new Fault('invalid-config', 'Service management requires a non-root macOS user.');
|
|
58
|
+
const label = `local.devtunnel-ctl.${createHash('sha256').update(store.dir).digest('hex').slice(0, 12)}`;
|
|
59
|
+
const directory = join(homedir(), 'Library', 'LaunchAgents');
|
|
60
|
+
const path = join(directory, label + '.plist');
|
|
61
|
+
const target = `gui/${process.getuid()}`;
|
|
62
|
+
if (action === 'install') {
|
|
63
|
+
const lock = new DaemonLock(store.dir);
|
|
64
|
+
await lock.acquire();
|
|
65
|
+
try {
|
|
66
|
+
const config = await store.load();
|
|
67
|
+
const node = await executable(process.execPath), main = await realpath(entry), helper = await executable(config.helperPath);
|
|
68
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
69
|
+
try {
|
|
70
|
+
await writeFile(path, launchAgent({ label, node, entry: main, state: store.dir }), { flag: 'wx', mode: 0o600 });
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error instanceof Error && 'code' in error && error.code === 'EEXIST') {
|
|
74
|
+
throw new Fault('invalid-config', 'Service is already installed. Stop and uninstall it before reinstalling paths.');
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await store.save({ ...config, helperPath: helper });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
await unlink(path);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
return { installed: true, loaded: false, label, path, node, entry: main, helper };
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await lock.release();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (action === 'status') {
|
|
92
|
+
let installed = true;
|
|
93
|
+
try {
|
|
94
|
+
await lstat(path);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
98
|
+
installed = false;
|
|
99
|
+
else
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
const result = await launchctl(['print', `${target}/${label}`], true);
|
|
103
|
+
if (!result.loaded)
|
|
104
|
+
return { label, installed, loaded: false, responsive: false };
|
|
105
|
+
try {
|
|
106
|
+
const status = await request(join(store.dir, 'control.sock'), { v: 1, command: 'status' });
|
|
107
|
+
return { label, installed, loaded: true, responsive: true, status };
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return { label, installed, loaded: true, responsive: false,
|
|
111
|
+
detail: 'Registered service is unresponsive. Stop it, then run daemon in a terminal to see its sanitized startup error.' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const info = await lstat(path);
|
|
115
|
+
if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid?.() || (info.mode & 0o077)) {
|
|
116
|
+
throw new Fault('invalid-config', 'Service plist is not a private owned file.');
|
|
117
|
+
}
|
|
118
|
+
if (action === 'start') {
|
|
119
|
+
await launchctl(['bootstrap', target, path]);
|
|
120
|
+
return { loaded: true, label };
|
|
121
|
+
}
|
|
122
|
+
if (action === 'stop') {
|
|
123
|
+
await launchctl(['bootout', `${target}/${label}`]);
|
|
124
|
+
return { loaded: false, label };
|
|
125
|
+
}
|
|
126
|
+
if (action === 'uninstall') {
|
|
127
|
+
const result = await launchctl(['print', `${target}/${label}`], true);
|
|
128
|
+
if (result.loaded)
|
|
129
|
+
await launchctl(['bootout', `${target}/${label}`]);
|
|
130
|
+
await unlink(path);
|
|
131
|
+
return { installed: false, loaded: false, label };
|
|
132
|
+
}
|
|
133
|
+
throw new Fault('invalid-config', 'Use service install, start, stop, uninstall or status.');
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=launchd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchd.js","sourceRoot":"","sources":["../src/launchd.ts"],"names":[],"mappings":"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"}
|
package/dist/lock.d.ts
ADDED
package/dist/lock.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { mkdir, writeFile, readFile, lstat, unlink, rmdir, rename, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { Fault, record } from "./core.js";
|
|
5
|
+
import { privatePath } from "./paths.js";
|
|
6
|
+
export class DaemonLock {
|
|
7
|
+
path;
|
|
8
|
+
candidate;
|
|
9
|
+
nonce = randomUUID();
|
|
10
|
+
owned = false;
|
|
11
|
+
constructor(dir) {
|
|
12
|
+
this.path = join(dir, 'daemon.lock');
|
|
13
|
+
this.candidate = join(dir, `.daemon-lock-${this.nonce}`);
|
|
14
|
+
}
|
|
15
|
+
async acquire() {
|
|
16
|
+
await mkdir(this.candidate, { mode: 0o700 });
|
|
17
|
+
const marker = `owner-${this.nonce}.json`;
|
|
18
|
+
try {
|
|
19
|
+
await writeFile(join(this.candidate, marker), JSON.stringify({ pid: process.pid, nonce: this.nonce }), { flag: 'wx', mode: 0o600 });
|
|
20
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
21
|
+
try {
|
|
22
|
+
// A populated candidate atomically replaces only an absent or empty directory.
|
|
23
|
+
await rename(this.candidate, this.path);
|
|
24
|
+
this.owned = true;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (!(error instanceof Error && 'code' in error &&
|
|
29
|
+
(error.code === 'ENOTEMPTY' || error.code === 'EEXIST')))
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
if (!await this.reclaim())
|
|
33
|
+
throw new Fault('daemon-unavailable', 'A live daemon owns the state lock.');
|
|
34
|
+
}
|
|
35
|
+
throw new Fault('daemon-unavailable', 'Daemon lock is changing; retry after the other starter finishes.');
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (!this.owned) {
|
|
39
|
+
await unlink(join(this.candidate, marker));
|
|
40
|
+
await rmdir(this.candidate);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async reclaim() {
|
|
45
|
+
try {
|
|
46
|
+
await privatePath(this.path, true);
|
|
47
|
+
const names = await readdir(this.path);
|
|
48
|
+
if (!names.length)
|
|
49
|
+
return true;
|
|
50
|
+
if (names.length !== 1 || !/^owner-[a-z0-9-]{1,64}\.json$/.test(names[0])) {
|
|
51
|
+
throw new Fault('daemon-unavailable', 'Unrecognized daemon lock contents; refusing automatic cleanup.');
|
|
52
|
+
}
|
|
53
|
+
const marker = join(this.path, names[0]);
|
|
54
|
+
await privatePath(marker);
|
|
55
|
+
if ((await lstat(marker)).size > 1024)
|
|
56
|
+
throw new Fault('daemon-unavailable', 'Invalid daemon lock record.');
|
|
57
|
+
const owner = JSON.parse(await readFile(marker, 'utf8'));
|
|
58
|
+
if (!record(owner) || !Number.isSafeInteger(owner.pid) || Number(owner.pid) <= 0 ||
|
|
59
|
+
typeof owner.nonce !== 'string' || names[0] !== `owner-${owner.nonce}.json`) {
|
|
60
|
+
throw new Fault('daemon-unavailable', 'Invalid daemon lock record.');
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
process.kill(Number(owner.pid), 0);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ESRCH'))
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// Unique marker names keep a racing reclaimer from deleting a new owner's record.
|
|
71
|
+
await unlink(marker);
|
|
72
|
+
try {
|
|
73
|
+
await rmdir(this.path);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!(error instanceof Error && 'code' in error &&
|
|
77
|
+
(error.code === 'ENOENT' || error.code === 'ENOTEMPTY' || error.code === 'EEXIST')))
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
|
|
84
|
+
return true;
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async release() {
|
|
89
|
+
if (!this.owned)
|
|
90
|
+
return;
|
|
91
|
+
const owner = join(this.path, `owner-${this.nonce}.json`);
|
|
92
|
+
const info = await lstat(owner);
|
|
93
|
+
if (info.isSymbolicLink() || !info.isFile())
|
|
94
|
+
throw new Fault('internal', 'Daemon lock ownership changed.');
|
|
95
|
+
const value = JSON.parse(await readFile(owner, 'utf8'));
|
|
96
|
+
if (value.nonce !== this.nonce)
|
|
97
|
+
throw new Fault('internal', 'Daemon lock ownership changed.');
|
|
98
|
+
await unlink(owner);
|
|
99
|
+
try {
|
|
100
|
+
await rmdir(this.path);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (!(error instanceof Error && 'code' in error &&
|
|
104
|
+
(error.code === 'ENOENT' || error.code === 'ENOTEMPTY' || error.code === 'EEXIST')))
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
this.owned = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=lock.js.map
|
package/dist/lock.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"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"}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface LogEntry {
|
|
2
|
+
event: string;
|
|
3
|
+
profile?: string;
|
|
4
|
+
state?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
attempt?: number;
|
|
7
|
+
relayConnected?: boolean;
|
|
8
|
+
lastHealthyAt?: number;
|
|
9
|
+
nextRetryAt?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class Log {
|
|
12
|
+
private readonly dir;
|
|
13
|
+
private readonly maxBytes;
|
|
14
|
+
private pending;
|
|
15
|
+
private tail;
|
|
16
|
+
private sequence;
|
|
17
|
+
private readonly recent;
|
|
18
|
+
constructor(dir: string, maxBytes?: number);
|
|
19
|
+
init(): Promise<void>;
|
|
20
|
+
write(entry: LogEntry): Promise<void>;
|
|
21
|
+
flush(): Promise<void>;
|
|
22
|
+
snapshot(): {
|
|
23
|
+
sequence: number;
|
|
24
|
+
line: string;
|
|
25
|
+
}[];
|
|
26
|
+
}
|