metaharness 0.4.3 → 0.4.5
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/README.md +22 -2
- package/dist/bin.js +0 -0
- package/dist/harness-bin.js +0 -0
- package/dist/host-config.d.ts.map +1 -1
- package/dist/host-config.js +18 -1
- package/dist/host-config.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +281 -4
- package/dist/index.js.map +1 -1
- package/dist/meta-proxy-service.d.ts +72 -0
- package/dist/meta-proxy-service.d.ts.map +1 -0
- package/dist/meta-proxy-service.js +753 -0
- package/dist/meta-proxy-service.js.map +1 -0
- package/dist/meta-proxy.d.ts +11 -2
- package/dist/meta-proxy.d.ts.map +1 -1
- package/dist/meta-proxy.js +155 -10
- package/dist/meta-proxy.js.map +1 -1
- package/dist/secrets.d.ts +11 -1
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js +20 -7
- package/dist/secrets.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
// Supervised lifecycle for the Meta-Proxy sidecar (cognitum-one/meta-proxy#82).
|
|
2
|
+
//
|
|
3
|
+
// `metaharness proxy start` launches the sidecar for the life of that invocation
|
|
4
|
+
// only. Nothing starts it at login and nothing restarts it if it exits, so after
|
|
5
|
+
// a reboot `proxy status` reports "not running" and every tool pointed at
|
|
6
|
+
// 127.0.0.1:11435 — the VS Code console, the configured terminal, any
|
|
7
|
+
// Anthropic-compatible client — fails until someone remembers a CLI command.
|
|
8
|
+
// The failure lands on whatever the user was doing at the time, not at a moment
|
|
9
|
+
// when they are thinking about proxy lifecycle.
|
|
10
|
+
//
|
|
11
|
+
// ## Opt-in, always
|
|
12
|
+
//
|
|
13
|
+
// Installing something that survives reboots is not done silently. There is no
|
|
14
|
+
// implicit enable inside `proxy install`; the user runs `proxy enable` and the
|
|
15
|
+
// command says exactly which file it wrote and how to undo it.
|
|
16
|
+
//
|
|
17
|
+
// ## Shape
|
|
18
|
+
//
|
|
19
|
+
// Every unit-file renderer is a pure string function, and the OS commands go
|
|
20
|
+
// through an injected runner, so the whole matrix is testable on one machine
|
|
21
|
+
// without touching launchctl/systemctl/schtasks. That mirrors `installFromSource`
|
|
22
|
+
// in meta-proxy.ts, which already takes an injected `run`.
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
|
+
import { metaProxyBinaryPath, metaProxyDataDir } from './meta-proxy.js';
|
|
28
|
+
/** Reverse-DNS label, shared by the launchd job and the scheduled task. */
|
|
29
|
+
export const SERVICE_LABEL = 'one.cognitum.meta-proxy';
|
|
30
|
+
function assertServiceLabel(label) {
|
|
31
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9.-]{0,127}$/.test(label)) {
|
|
32
|
+
throw new Error('Invalid service label.');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function defaultRunner(invocation) {
|
|
36
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
37
|
+
encoding: 'utf8',
|
|
38
|
+
timeout: 15_000,
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
});
|
|
41
|
+
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim();
|
|
42
|
+
if (result.error)
|
|
43
|
+
return { ok: false, output: result.error.message, error: result.error.message };
|
|
44
|
+
return { ok: result.status === 0, output };
|
|
45
|
+
}
|
|
46
|
+
// --- unit file locations ----------------------------------------------------
|
|
47
|
+
export function launchAgentPath(home = homedir(), label = SERVICE_LABEL) {
|
|
48
|
+
assertServiceLabel(label);
|
|
49
|
+
return join(home, 'Library', 'LaunchAgents', `${label}.plist`);
|
|
50
|
+
}
|
|
51
|
+
export function systemdUnitPath(home = homedir()) {
|
|
52
|
+
return join(home, '.config', 'systemd', 'user', 'meta-proxy.service');
|
|
53
|
+
}
|
|
54
|
+
/** The XML definition handed to `schtasks /create /xml`. */
|
|
55
|
+
export function scheduledTaskPath(home = homedir(), label = SERVICE_LABEL) {
|
|
56
|
+
assertServiceLabel(label);
|
|
57
|
+
const filename = label === SERVICE_LABEL ? 'meta-proxy-task.xml' : `${label}-task.xml`;
|
|
58
|
+
return join(metaProxyDataDir(home), filename);
|
|
59
|
+
}
|
|
60
|
+
export function serviceUnitPath(platform, home = homedir(), label = SERVICE_LABEL) {
|
|
61
|
+
if (platform === 'darwin')
|
|
62
|
+
return launchAgentPath(home, label);
|
|
63
|
+
if (platform === 'linux')
|
|
64
|
+
return systemdUnitPath(home);
|
|
65
|
+
if (platform === 'win32')
|
|
66
|
+
return scheduledTaskPath(home, label);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
// --- pure renderers ---------------------------------------------------------
|
|
70
|
+
/** Minimal XML escaping — a home directory can legitimately contain `&`. */
|
|
71
|
+
function escapeXml(value) {
|
|
72
|
+
return value
|
|
73
|
+
.replace(/&/g, '&')
|
|
74
|
+
.replace(/</g, '<')
|
|
75
|
+
.replace(/>/g, '>')
|
|
76
|
+
.replace(/"/g, '"')
|
|
77
|
+
.replace(/'/g, ''');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* launchd job. `RunAtLoad` covers start-at-login and `KeepAlive.SuccessfulExit
|
|
81
|
+
* = false` covers restart-after-crash while still letting a deliberate
|
|
82
|
+
* `proxy stop` (SIGTERM, exit 0) stay stopped — a plain `KeepAlive: true` would
|
|
83
|
+
* fight the user's own stop command forever.
|
|
84
|
+
*/
|
|
85
|
+
export function renderLaunchAgent(binaryPath, logPath, label = SERVICE_LABEL) {
|
|
86
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
87
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
88
|
+
<plist version="1.0">
|
|
89
|
+
<dict>
|
|
90
|
+
<key>Label</key>
|
|
91
|
+
<string>${escapeXml(label)}</string>
|
|
92
|
+
<key>ProgramArguments</key>
|
|
93
|
+
<array>
|
|
94
|
+
<string>${escapeXml(binaryPath)}</string>
|
|
95
|
+
<string>--supervised</string>
|
|
96
|
+
</array>
|
|
97
|
+
<key>RunAtLoad</key>
|
|
98
|
+
<true/>
|
|
99
|
+
<key>KeepAlive</key>
|
|
100
|
+
<dict>
|
|
101
|
+
<key>SuccessfulExit</key>
|
|
102
|
+
<false/>
|
|
103
|
+
</dict>
|
|
104
|
+
<key>ThrottleInterval</key>
|
|
105
|
+
<integer>10</integer>
|
|
106
|
+
<key>StandardOutPath</key>
|
|
107
|
+
<string>${escapeXml(logPath)}</string>
|
|
108
|
+
<key>StandardErrorPath</key>
|
|
109
|
+
<string>${escapeXml(logPath)}</string>
|
|
110
|
+
<key>ProcessType</key>
|
|
111
|
+
<string>Background</string>
|
|
112
|
+
</dict>
|
|
113
|
+
</plist>
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* systemd **user** unit. `default.target` is the user-session target, so this is
|
|
118
|
+
* start-at-login rather than start-at-boot; booting a machine nobody has logged
|
|
119
|
+
* into should not run a user's proxy. `Restart=on-failure` matches the launchd
|
|
120
|
+
* choice: a clean stop stays stopped.
|
|
121
|
+
*/
|
|
122
|
+
export function renderSystemdUnit(binaryPath) {
|
|
123
|
+
return `[Unit]
|
|
124
|
+
Description=Cognitum Meta-Proxy sidecar
|
|
125
|
+
Documentation=https://github.com/cognitum-one/meta-proxy
|
|
126
|
+
After=network-online.target
|
|
127
|
+
|
|
128
|
+
[Service]
|
|
129
|
+
Type=simple
|
|
130
|
+
ExecStart=${binaryPath} --supervised
|
|
131
|
+
Restart=on-failure
|
|
132
|
+
RestartSec=10
|
|
133
|
+
|
|
134
|
+
[Install]
|
|
135
|
+
WantedBy=default.target
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
/** Task Scheduler definition — Windows has no launchd/systemd equivalent. */
|
|
139
|
+
export function renderScheduledTask(binaryPath) {
|
|
140
|
+
// Task Scheduler's XML importer requires its canonical BOM-marked UTF-16
|
|
141
|
+
// representation; enableMetaProxyService persists this string as utf16le.
|
|
142
|
+
return `\uFEFF<?xml version="1.0" encoding="UTF-16"?>
|
|
143
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
144
|
+
<RegistrationInfo>
|
|
145
|
+
<Description>Cognitum Meta-Proxy sidecar</Description>
|
|
146
|
+
</RegistrationInfo>
|
|
147
|
+
<Principals>
|
|
148
|
+
<Principal id="Author">
|
|
149
|
+
<LogonType>InteractiveToken</LogonType>
|
|
150
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
151
|
+
</Principal>
|
|
152
|
+
</Principals>
|
|
153
|
+
<Triggers>
|
|
154
|
+
<LogonTrigger>
|
|
155
|
+
<Enabled>true</Enabled>
|
|
156
|
+
</LogonTrigger>
|
|
157
|
+
</Triggers>
|
|
158
|
+
<Settings>
|
|
159
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
160
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
161
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
162
|
+
<RestartOnFailure>
|
|
163
|
+
<Interval>PT1M</Interval>
|
|
164
|
+
<Count>3</Count>
|
|
165
|
+
</RestartOnFailure>
|
|
166
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
167
|
+
<Hidden>true</Hidden>
|
|
168
|
+
</Settings>
|
|
169
|
+
<Actions Context="Author">
|
|
170
|
+
<Exec>
|
|
171
|
+
<Command>${escapeXml(binaryPath)}</Command>
|
|
172
|
+
<Arguments>--supervised</Arguments>
|
|
173
|
+
</Exec>
|
|
174
|
+
</Actions>
|
|
175
|
+
</Task>
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
// --- the OS commands, as data so they can be asserted -----------------------
|
|
179
|
+
export function enableCommands(platform, unitPath, label = SERVICE_LABEL) {
|
|
180
|
+
if (platform === 'darwin') {
|
|
181
|
+
// `bootstrap` is the modern replacement for `load -w`; `kickstart` starts it
|
|
182
|
+
// now so the user does not have to log out and back in to get a daemon.
|
|
183
|
+
return [
|
|
184
|
+
{ command: 'launchctl', args: ['bootstrap', `gui/${process.getuid?.() ?? 0}`, unitPath] },
|
|
185
|
+
{ command: 'launchctl', args: ['kickstart', `gui/${process.getuid?.() ?? 0}/${label}`] },
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
if (platform === 'linux') {
|
|
189
|
+
return [
|
|
190
|
+
{ command: 'systemctl', args: ['--user', 'daemon-reload'] },
|
|
191
|
+
{ command: 'systemctl', args: ['--user', 'enable', '--now', 'meta-proxy.service'] },
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
if (platform === 'win32') {
|
|
195
|
+
return [
|
|
196
|
+
{ command: 'schtasks', args: ['/create', '/tn', label, '/xml', unitPath, '/f'] },
|
|
197
|
+
{ command: 'schtasks', args: ['/run', '/tn', label] },
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
export function disableCommands(platform, unitPath, label = SERVICE_LABEL) {
|
|
203
|
+
if (platform === 'darwin') {
|
|
204
|
+
return [{ command: 'launchctl', args: ['bootout', `gui/${process.getuid?.() ?? 0}/${label}`] }];
|
|
205
|
+
}
|
|
206
|
+
if (platform === 'linux') {
|
|
207
|
+
return [
|
|
208
|
+
{ command: 'systemctl', args: ['--user', 'disable', '--now', 'meta-proxy.service'] },
|
|
209
|
+
{ command: 'systemctl', args: ['--user', 'daemon-reload'] },
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
if (platform === 'win32') {
|
|
213
|
+
return [{ command: 'schtasks', args: ['/delete', '/tn', label, '/f'] }];
|
|
214
|
+
}
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
export function serviceStateCommand(platform, label = SERVICE_LABEL) {
|
|
218
|
+
if (platform === 'darwin') {
|
|
219
|
+
return { command: 'launchctl', args: ['print', `gui/${process.getuid?.() ?? 0}/${label}`] };
|
|
220
|
+
}
|
|
221
|
+
if (platform === 'linux') {
|
|
222
|
+
return {
|
|
223
|
+
command: 'systemctl',
|
|
224
|
+
args: ['--user', 'show', 'meta-proxy.service', '--property=LoadState,UnitFileState,ActiveState,MainPID'],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (platform === 'win32') {
|
|
228
|
+
// schtasks renders both headings and states in the user's UI language.
|
|
229
|
+
// Query the ScheduledTasks API and serialize numeric enum/bool values so
|
|
230
|
+
// state and login enablement are authoritative and locale-neutral.
|
|
231
|
+
const script = `$task = Get-ScheduledTask -ErrorAction Stop | Where-Object { $_.TaskName -ceq '${label}' -and $_.TaskPath -eq '\\' } | Select-Object -First 1; if ($null -eq $task) { Write-Output 'TaskState=Missing'; exit 0 }; Write-Output ('TaskState=' + [int]$task.State)`;
|
|
232
|
+
return { command: 'powershell.exe', args: ['-NoProfile', '-NonInteractive', '-Command', script] };
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function knownMissingService(platform, output) {
|
|
237
|
+
const text = output.toLowerCase();
|
|
238
|
+
if (platform === 'darwin') {
|
|
239
|
+
return text.includes('could not find service') || text.includes('service not found');
|
|
240
|
+
}
|
|
241
|
+
if (platform === 'linux') {
|
|
242
|
+
return text.includes('loadstate=not-found') || text.includes('unit meta-proxy.service could not be found');
|
|
243
|
+
}
|
|
244
|
+
if (platform === 'win32') {
|
|
245
|
+
return /^taskstate=missing$/m.test(text);
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
function parseManagerState(platform, outcome, definitionPresent) {
|
|
250
|
+
if (outcome.error)
|
|
251
|
+
return { managerState: 'unknown', loaded: null, enabledAtLogin: false, running: null, pid: null };
|
|
252
|
+
if (!outcome.ok) {
|
|
253
|
+
return knownMissingService(platform, outcome.output)
|
|
254
|
+
? { managerState: 'disabled', loaded: false, enabledAtLogin: platform === 'darwin' && definitionPresent, running: false, pid: null }
|
|
255
|
+
: { managerState: 'unknown', loaded: null, enabledAtLogin: false, running: null, pid: null };
|
|
256
|
+
}
|
|
257
|
+
if (platform === 'darwin') {
|
|
258
|
+
const pid = outcome.output.match(/\bpid\s*=\s*(\d+)/)?.[1];
|
|
259
|
+
return {
|
|
260
|
+
managerState: 'enabled',
|
|
261
|
+
loaded: true,
|
|
262
|
+
enabledAtLogin: definitionPresent,
|
|
263
|
+
running: /\bstate\s*=\s*running\b/.test(outcome.output) || pid !== undefined,
|
|
264
|
+
pid: pid ? Number.parseInt(pid, 10) : null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (platform === 'linux') {
|
|
268
|
+
if (/^LoadState=not-found$/m.test(outcome.output)) {
|
|
269
|
+
return { managerState: 'disabled', loaded: false, enabledAtLogin: false, running: false, pid: null };
|
|
270
|
+
}
|
|
271
|
+
const enabled = /^UnitFileState=(?:enabled|enabled-runtime|linked|linked-runtime)$/m.test(outcome.output);
|
|
272
|
+
const loaded = /^LoadState=loaded$/m.test(outcome.output);
|
|
273
|
+
const pidText = outcome.output.match(/^MainPID=(\d+)$/m)?.[1];
|
|
274
|
+
const pid = pidText && pidText !== '0' ? Number.parseInt(pidText, 10) : null;
|
|
275
|
+
return {
|
|
276
|
+
managerState: loaded ? 'enabled' : 'disabled',
|
|
277
|
+
loaded,
|
|
278
|
+
enabledAtLogin: enabled,
|
|
279
|
+
running: /^ActiveState=active$/m.test(outcome.output),
|
|
280
|
+
pid,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (platform === 'win32') {
|
|
284
|
+
if (/^TaskState=Missing$/m.test(outcome.output)) {
|
|
285
|
+
return { managerState: 'disabled', loaded: false, enabledAtLogin: false, running: false, pid: null };
|
|
286
|
+
}
|
|
287
|
+
const state = outcome.output.match(/^TaskState=(\d+)$/m)?.[1];
|
|
288
|
+
if (state === undefined || !['0', '1', '2', '3', '4'].includes(state)) {
|
|
289
|
+
return { managerState: 'unknown', loaded: null, enabledAtLogin: false, running: null, pid: null };
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
managerState: 'enabled',
|
|
293
|
+
loaded: true,
|
|
294
|
+
// TASK_STATE_DISABLED is the authoritative registered-but-disabled state;
|
|
295
|
+
// all other known registered states remain enabled for their triggers.
|
|
296
|
+
enabledAtLogin: state !== '1',
|
|
297
|
+
running: state === '4' ? true : state === '1' || state === '3' ? false : null,
|
|
298
|
+
pid: null,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return { managerState: 'unknown', loaded: null, enabledAtLogin: false, running: null, pid: null };
|
|
302
|
+
}
|
|
303
|
+
// --- public API -------------------------------------------------------------
|
|
304
|
+
export function metaProxyServiceState(platform = process.platform, home = homedir(), run = defaultRunner, label = SERVICE_LABEL) {
|
|
305
|
+
const unitPath = serviceUnitPath(platform, home, label);
|
|
306
|
+
if (!unitPath)
|
|
307
|
+
return { supported: false, installed: false, unitPath: null, managerState: 'unknown', definitionPresent: false, loaded: null, enabledAtLogin: false, running: null, pid: null };
|
|
308
|
+
const definitionPresent = existsSync(unitPath);
|
|
309
|
+
const command = serviceStateCommand(platform, label);
|
|
310
|
+
const outcome = command ? run(command) : { ok: false, output: '', error: 'unsupported' };
|
|
311
|
+
const manager = parseManagerState(platform, outcome, definitionPresent);
|
|
312
|
+
return { supported: true, installed: manager.loaded === true, unitPath, definitionPresent, ...manager };
|
|
313
|
+
}
|
|
314
|
+
function defaultWait(milliseconds) {
|
|
315
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
316
|
+
}
|
|
317
|
+
export function enableMetaProxyService(options = {}) {
|
|
318
|
+
const home = options.home ?? homedir();
|
|
319
|
+
const platform = options.platform ?? process.platform;
|
|
320
|
+
const run = options.run ?? defaultRunner;
|
|
321
|
+
const label = options.label ?? SERVICE_LABEL;
|
|
322
|
+
const wait = options.wait ?? defaultWait;
|
|
323
|
+
const unitPath = serviceUnitPath(platform, home, label);
|
|
324
|
+
if (!unitPath) {
|
|
325
|
+
return { ok: false, message: `Start-at-login is not supported on ${platform}.` };
|
|
326
|
+
}
|
|
327
|
+
const binaryPath = metaProxyBinaryPath(platform, home);
|
|
328
|
+
if (!existsSync(binaryPath)) {
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
message: 'Meta-Proxy is not installed. Run `metaharness proxy install --yes` first.',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const logPath = join(metaProxyDataDir(home), 'meta-proxy.log');
|
|
335
|
+
const definition = platform === 'darwin'
|
|
336
|
+
? renderLaunchAgent(binaryPath, logPath, label)
|
|
337
|
+
: platform === 'linux'
|
|
338
|
+
? renderSystemdUnit(binaryPath)
|
|
339
|
+
: renderScheduledTask(binaryPath);
|
|
340
|
+
const before = metaProxyServiceState(platform, home, run, label);
|
|
341
|
+
if (before.managerState === 'unknown') {
|
|
342
|
+
return { ok: false, message: `Could not determine service-manager state; preserved ${unitPath}.`, unitPath };
|
|
343
|
+
}
|
|
344
|
+
if (before.loaded === true && !before.definitionPresent) {
|
|
345
|
+
// Something answers to our label but no owned definition exists — another
|
|
346
|
+
// installer's job, or a unit shadowing ours from a different path. On
|
|
347
|
+
// darwin/linux a failed enable from this state would be "compensated" by
|
|
348
|
+
// booting out that foreign service, so refuse up front on every platform.
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
message: `A Meta-Proxy ${platform === 'win32' ? 'task' : 'service'} is registered without the owned definition at ${unitPath}. Inspect and remove it manually before replacement.`,
|
|
352
|
+
unitPath,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const persistedEncoding = platform === 'win32' ? 'utf16le' : 'utf8';
|
|
356
|
+
if (before.definitionPresent && readFileSync(unitPath, persistedEncoding) !== definition) {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
message: `A different Meta-Proxy service definition already exists at ${unitPath}. Disable it before replacement.`,
|
|
360
|
+
unitPath,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (before.managerState === 'enabled' && before.enabledAtLogin) {
|
|
364
|
+
if (before.definitionPresent) {
|
|
365
|
+
return { ok: true, message: `Meta-Proxy is already set to start at login. Definition: ${unitPath}`, unitPath };
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
message: `A Meta-Proxy service is already registered with a different or missing definition. Disable it before replacing ${unitPath}.`,
|
|
370
|
+
unitPath,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
mkdirSync(dirname(unitPath), { recursive: true, mode: 0o700 });
|
|
374
|
+
writeFileSync(unitPath, definition, { encoding: persistedEncoding, mode: 0o600 });
|
|
375
|
+
for (const invocation of enableCommands(platform, unitPath, label)) {
|
|
376
|
+
const outcome = run(invocation);
|
|
377
|
+
if (!outcome.ok) {
|
|
378
|
+
const state = metaProxyServiceState(platform, home, run, label);
|
|
379
|
+
if (before.definitionPresent) {
|
|
380
|
+
// This enable started from an existing owned definition. Never erase it
|
|
381
|
+
// or stop a pre-existing active service while compensating a failed
|
|
382
|
+
// attempt to change only its login-start state.
|
|
383
|
+
if (platform === 'linux') {
|
|
384
|
+
const restore = [];
|
|
385
|
+
if (!before.enabledAtLogin && state.enabledAtLogin) {
|
|
386
|
+
// Deliberately omit --now: a service that was already active must
|
|
387
|
+
// remain active while only its prior login-enable bit is restored.
|
|
388
|
+
restore.push({ command: 'systemctl', args: ['--user', 'disable', 'meta-proxy.service'] });
|
|
389
|
+
}
|
|
390
|
+
if (before.running === false && state.running === true) {
|
|
391
|
+
restore.push({ command: 'systemctl', args: ['--user', 'stop', 'meta-proxy.service'] });
|
|
392
|
+
}
|
|
393
|
+
const restoreFailure = restore.map((command) => run(command)).find((result) => !result.ok);
|
|
394
|
+
if (restoreFailure) {
|
|
395
|
+
return {
|
|
396
|
+
ok: false,
|
|
397
|
+
message: `Could not enable start-at-login and restoring the prior service state failed: ${restoreFailure.output || 'command failed'}. Definition preserved at ${unitPath}.`,
|
|
398
|
+
unitPath,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const restored = metaProxyServiceState(platform, home, run, label);
|
|
402
|
+
if (restored.managerState === 'unknown'
|
|
403
|
+
|| restored.enabledAtLogin !== before.enabledAtLogin
|
|
404
|
+
|| restored.running !== before.running) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
message: `Could not prove the prior service state was restored. Definition preserved at ${unitPath}.`,
|
|
408
|
+
unitPath,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (platform === 'win32' && before.loaded === false && state.loaded === true) {
|
|
413
|
+
const cleanup = stopAndDeleteWindowsTask(home, unitPath, label, run, wait, false);
|
|
414
|
+
if (!cleanup.ok) {
|
|
415
|
+
return {
|
|
416
|
+
ok: false,
|
|
417
|
+
message: `Could not enable start-at-login and restoring the prior absent task failed: ${cleanup.message}`,
|
|
418
|
+
unitPath,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
ok: false,
|
|
423
|
+
message: `Could not enable start-at-login (${invocation.command} ${invocation.args.join(' ')}): ${outcome.output || 'command failed'}. Prior definition preserved at ${unitPath}.`,
|
|
424
|
+
unitPath,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (platform === 'win32' && before.loaded === true && !before.enabledAtLogin) {
|
|
428
|
+
if (state.managerState === 'unknown') {
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
message: `Could not enable start-at-login and manager state is unknown. Prior definition preserved at ${unitPath}.`,
|
|
432
|
+
unitPath,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
if (state.loaded === false) {
|
|
436
|
+
const recreate = enableCommands('win32', unitPath, label)[0];
|
|
437
|
+
const recreated = recreate ? run(recreate) : { ok: false, output: 'create command unavailable' };
|
|
438
|
+
// `/create /f` can apply and still lose its response. Its exit
|
|
439
|
+
// status is not authoritative; re-read before deciding whether
|
|
440
|
+
// the prior task is still absent or available for restoration.
|
|
441
|
+
const afterRecreate = metaProxyServiceState('win32', home, run, label);
|
|
442
|
+
if (afterRecreate.managerState === 'unknown' || afterRecreate.loaded !== true) {
|
|
443
|
+
return {
|
|
444
|
+
ok: false,
|
|
445
|
+
message: `Could not enable start-at-login and could not prove the prior disabled task was recreated: ${recreated.output || 'state mismatch'}. Definition preserved at ${unitPath}.`,
|
|
446
|
+
unitPath,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const stopped = confirmWindowsTaskStopped(home, unitPath, label, run, wait);
|
|
451
|
+
if (!stopped.ok) {
|
|
452
|
+
return {
|
|
453
|
+
ok: false,
|
|
454
|
+
message: `Could not enable start-at-login and restoring the prior disabled task failed: ${stopped.message}`,
|
|
455
|
+
unitPath,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const disabled = run({ command: 'schtasks', args: ['/change', '/tn', label, '/disable'] });
|
|
459
|
+
const restored = disabled.ok ? metaProxyServiceState('win32', home, run, label) : null;
|
|
460
|
+
if (!disabled.ok || restored?.loaded !== true || restored.enabledAtLogin || restored.running !== false) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
message: `Could not enable start-at-login and restoring the prior disabled task failed: ${disabled.output || 'state mismatch'}. Definition preserved at ${unitPath}.`,
|
|
464
|
+
unitPath,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
ok: false,
|
|
469
|
+
message: `Could not enable start-at-login (${invocation.command} ${invocation.args.join(' ')}): ${outcome.output || 'command failed'}. Prior disabled task and definition restored at ${unitPath}.`,
|
|
470
|
+
unitPath,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
if (before.loaded === false && state.managerState === 'enabled') {
|
|
474
|
+
const cleanup = disableCommands(platform, unitPath, label)[0];
|
|
475
|
+
const cleanupOutcome = cleanup ? run(cleanup) : { ok: false, output: 'cleanup command unavailable' };
|
|
476
|
+
if (!cleanupOutcome.ok) {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
message: `Could not enable start-at-login and compensating cleanup failed: ${cleanupOutcome.output || 'command failed'}. Definition preserved at ${unitPath}.`,
|
|
480
|
+
unitPath,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
ok: false,
|
|
486
|
+
message: `Could not enable start-at-login (${invocation.command} ${invocation.args.join(' ')}): ${outcome.output || 'command failed'}. Definition preserved at ${unitPath}.`,
|
|
487
|
+
unitPath,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (state.managerState === 'enabled') {
|
|
491
|
+
if (platform === 'win32') {
|
|
492
|
+
const cleanup = stopAndDeleteWindowsTask(home, unitPath, label, run, wait);
|
|
493
|
+
if (!cleanup.ok) {
|
|
494
|
+
return {
|
|
495
|
+
ok: false,
|
|
496
|
+
message: `Could not enable start-at-login and compensating cleanup failed: ${cleanup.message}`,
|
|
497
|
+
unitPath,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
ok: false,
|
|
502
|
+
message: `Could not enable start-at-login (${invocation.command} ${invocation.args.join(' ')}): ${outcome.output || 'command failed'}`,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const cleanupFailures = disableCommands(platform, unitPath, label)
|
|
506
|
+
.map((cleanup) => run(cleanup))
|
|
507
|
+
.filter((cleanup) => !cleanup.ok);
|
|
508
|
+
if (cleanupFailures.length > 0) {
|
|
509
|
+
return {
|
|
510
|
+
ok: false,
|
|
511
|
+
message: `Could not enable start-at-login and compensating cleanup failed: ${cleanupFailures.map((failure) => failure.output || 'command failed').join('; ')}. Definition preserved at ${unitPath}.`,
|
|
512
|
+
unitPath,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else if (state.managerState === 'unknown') {
|
|
517
|
+
return {
|
|
518
|
+
ok: false,
|
|
519
|
+
message: `Could not enable start-at-login and manager state is unknown. Definition preserved at ${unitPath}.`,
|
|
520
|
+
unitPath,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
rmSync(unitPath, { force: true });
|
|
524
|
+
return {
|
|
525
|
+
ok: false,
|
|
526
|
+
message: `Could not enable start-at-login (${invocation.command} ${invocation.args.join(' ')}): ${outcome.output || 'command failed'}`,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
ok: true,
|
|
532
|
+
message: `Meta-Proxy will start at login. Definition: ${unitPath}\nDisable with: metaharness proxy disable`,
|
|
533
|
+
unitPath,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function startCommand(platform, label = SERVICE_LABEL) {
|
|
537
|
+
if (platform === 'darwin')
|
|
538
|
+
return { command: 'launchctl', args: ['kickstart', `gui/${process.getuid?.() ?? 0}/${label}`] };
|
|
539
|
+
if (platform === 'linux')
|
|
540
|
+
return { command: 'systemctl', args: ['--user', 'start', 'meta-proxy.service'] };
|
|
541
|
+
if (platform === 'win32')
|
|
542
|
+
return { command: 'schtasks', args: ['/run', '/tn', label] };
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
function stopCommand(platform, label = SERVICE_LABEL) {
|
|
546
|
+
// `launchctl kill SIGTERM` may still be classified by launchd as an
|
|
547
|
+
// unsuccessful termination and restarted under KeepAlive. Bootout unloads
|
|
548
|
+
// the current job but deliberately leaves the plist in LaunchAgents, so the
|
|
549
|
+
// explicit stop stays stopped now and remains configured for next login.
|
|
550
|
+
if (platform === 'darwin')
|
|
551
|
+
return { command: 'launchctl', args: ['bootout', `gui/${process.getuid?.() ?? 0}/${label}`] };
|
|
552
|
+
if (platform === 'linux')
|
|
553
|
+
return { command: 'systemctl', args: ['--user', 'stop', 'meta-proxy.service'] };
|
|
554
|
+
if (platform === 'win32')
|
|
555
|
+
return { command: 'schtasks', args: ['/end', '/tn', label] };
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
export function startMetaProxyService(options = {}) {
|
|
559
|
+
const home = options.home ?? homedir();
|
|
560
|
+
const platform = options.platform ?? process.platform;
|
|
561
|
+
const run = options.run ?? defaultRunner;
|
|
562
|
+
const label = options.label ?? SERVICE_LABEL;
|
|
563
|
+
const state = metaProxyServiceState(platform, home, run, label);
|
|
564
|
+
if (state.managerState === 'unknown')
|
|
565
|
+
return { ok: false, message: 'Could not determine service-manager state.' };
|
|
566
|
+
if (state.managerState !== 'enabled' && !(platform === 'darwin' && state.definitionPresent)) {
|
|
567
|
+
return { ok: false, message: 'Meta-Proxy is not enabled at login.' };
|
|
568
|
+
}
|
|
569
|
+
if (state.running)
|
|
570
|
+
return { ok: true, message: `Meta-Proxy is already running${state.pid ? ` (pid ${state.pid})` : ''}.` };
|
|
571
|
+
const commands = platform === 'darwin' && state.managerState === 'disabled' && state.unitPath
|
|
572
|
+
? enableCommands(platform, state.unitPath, label)
|
|
573
|
+
: [startCommand(platform, label)].filter((command) => command !== null);
|
|
574
|
+
if (commands.length === 0)
|
|
575
|
+
return { ok: false, message: `Managed start is not supported on ${platform}.` };
|
|
576
|
+
for (const command of commands) {
|
|
577
|
+
const outcome = run(command);
|
|
578
|
+
if (!outcome.ok)
|
|
579
|
+
return { ok: false, message: `Could not start Meta-Proxy: ${outcome.output || 'command failed'}` };
|
|
580
|
+
}
|
|
581
|
+
return { ok: true, message: 'Meta-Proxy managed start requested.' };
|
|
582
|
+
}
|
|
583
|
+
export function stopMetaProxyService(options = {}) {
|
|
584
|
+
const home = options.home ?? homedir();
|
|
585
|
+
const platform = options.platform ?? process.platform;
|
|
586
|
+
const run = options.run ?? defaultRunner;
|
|
587
|
+
const label = options.label ?? SERVICE_LABEL;
|
|
588
|
+
const state = metaProxyServiceState(platform, home, run, label);
|
|
589
|
+
if (state.managerState === 'unknown')
|
|
590
|
+
return { ok: false, message: 'Could not determine service-manager state.' };
|
|
591
|
+
if (state.managerState !== 'enabled') {
|
|
592
|
+
return state.enabledAtLogin
|
|
593
|
+
? { ok: true, message: 'Meta-Proxy managed service is already stopped.' }
|
|
594
|
+
: { ok: false, message: 'Meta-Proxy is not enabled at login.' };
|
|
595
|
+
}
|
|
596
|
+
if (state.running === false)
|
|
597
|
+
return { ok: true, message: 'Meta-Proxy managed service is already stopped.' };
|
|
598
|
+
const command = stopCommand(platform, label);
|
|
599
|
+
if (!command)
|
|
600
|
+
return { ok: false, message: `Managed stop is not supported on ${platform}.` };
|
|
601
|
+
const outcome = run(command);
|
|
602
|
+
return outcome.ok
|
|
603
|
+
? { ok: true, message: 'Meta-Proxy managed stop requested.' }
|
|
604
|
+
: { ok: false, message: `Could not stop Meta-Proxy: ${outcome.output || 'command failed'}` };
|
|
605
|
+
}
|
|
606
|
+
export function disableMetaProxyService(options = {}) {
|
|
607
|
+
const home = options.home ?? homedir();
|
|
608
|
+
const platform = options.platform ?? process.platform;
|
|
609
|
+
const run = options.run ?? defaultRunner;
|
|
610
|
+
const label = options.label ?? SERVICE_LABEL;
|
|
611
|
+
const wait = options.wait ?? defaultWait;
|
|
612
|
+
const unitPath = serviceUnitPath(platform, home, label);
|
|
613
|
+
if (!unitPath) {
|
|
614
|
+
return { ok: true, message: `Start-at-login is not supported on ${platform}; nothing to disable.` };
|
|
615
|
+
}
|
|
616
|
+
const state = metaProxyServiceState(platform, home, run, label);
|
|
617
|
+
if (state.managerState === 'unknown') {
|
|
618
|
+
return { ok: false, message: `Could not determine service-manager state; preserved ${unitPath}.`, unitPath };
|
|
619
|
+
}
|
|
620
|
+
if (state.managerState === 'disabled' && !state.definitionPresent) {
|
|
621
|
+
return { ok: true, message: 'Meta-Proxy is not set to start at login.' };
|
|
622
|
+
}
|
|
623
|
+
if (state.loaded === true && !state.definitionPresent) {
|
|
624
|
+
// Same ownership rule as enable: without the owned definition on disk we
|
|
625
|
+
// cannot prove the registered job is ours, and unregistering by label
|
|
626
|
+
// alone would stop another installer's service.
|
|
627
|
+
return {
|
|
628
|
+
ok: false,
|
|
629
|
+
message: `A Meta-Proxy ${platform === 'win32' ? 'task' : 'service'} is registered without the owned definition at ${unitPath}; refusing to stop or delete it by label alone.`,
|
|
630
|
+
unitPath,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
if (platform === 'win32' && state.managerState === 'enabled') {
|
|
634
|
+
return stopAndDeleteWindowsTask(home, unitPath, label, run, wait);
|
|
635
|
+
}
|
|
636
|
+
const unregister = state.managerState === 'enabled'
|
|
637
|
+
? disableCommands(platform, unitPath, label)[0]
|
|
638
|
+
: undefined;
|
|
639
|
+
if (unregister) {
|
|
640
|
+
const outcome = run(unregister);
|
|
641
|
+
if (!outcome.ok) {
|
|
642
|
+
if (platform === 'linux') {
|
|
643
|
+
const after = metaProxyServiceState(platform, home, run, label);
|
|
644
|
+
if (after.managerState === 'unknown') {
|
|
645
|
+
return {
|
|
646
|
+
ok: false,
|
|
647
|
+
message: `Could not disable start-at-login and could not determine whether it partially changed state: ${outcome.output || `${unregister.command} failed`}. Definition preserved at ${unitPath}.`,
|
|
648
|
+
unitPath,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
const restore = [];
|
|
652
|
+
if (state.enabledAtLogin !== after.enabledAtLogin) {
|
|
653
|
+
restore.push({ command: 'systemctl', args: ['--user', state.enabledAtLogin ? 'enable' : 'disable', 'meta-proxy.service'] });
|
|
654
|
+
}
|
|
655
|
+
if (state.running !== after.running) {
|
|
656
|
+
restore.push({ command: 'systemctl', args: ['--user', state.running ? 'start' : 'stop', 'meta-proxy.service'] });
|
|
657
|
+
}
|
|
658
|
+
const restoreFailure = restore.map((command) => run(command)).find((result) => !result.ok);
|
|
659
|
+
const restored = restoreFailure ? null : metaProxyServiceState(platform, home, run, label);
|
|
660
|
+
if (restoreFailure
|
|
661
|
+
|| restored?.managerState === 'unknown'
|
|
662
|
+
|| restored?.loaded !== state.loaded
|
|
663
|
+
|| restored?.enabledAtLogin !== state.enabledAtLogin
|
|
664
|
+
|| restored?.running !== state.running) {
|
|
665
|
+
return {
|
|
666
|
+
ok: false,
|
|
667
|
+
message: `Could not disable start-at-login and restoring its possible partial changes failed: ${restoreFailure?.output || 'state mismatch'}. Definition preserved at ${unitPath}.`,
|
|
668
|
+
unitPath,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
ok: false,
|
|
673
|
+
message: `Could not disable start-at-login (${outcome.output || `${unregister.command} failed`}); prior login enablement and running state were restored. Definition preserved at ${unitPath}.`,
|
|
674
|
+
unitPath,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
ok: false,
|
|
679
|
+
message: `Could not disable start-at-login: ${outcome.output || `${unregister.command} failed`}. Definition preserved at ${unitPath}.`,
|
|
680
|
+
unitPath,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const definition = state.definitionPresent ? readFileSync(unitPath, 'utf8') : null;
|
|
685
|
+
rmSync(unitPath, { force: true });
|
|
686
|
+
if (platform === 'linux') {
|
|
687
|
+
const refresh = disableCommands(platform, unitPath, label)[1];
|
|
688
|
+
const outcome = refresh ? run(refresh) : { ok: false, output: 'daemon-reload command unavailable' };
|
|
689
|
+
if (!outcome.ok) {
|
|
690
|
+
if (definition !== null) {
|
|
691
|
+
mkdirSync(dirname(unitPath), { recursive: true, mode: 0o700 });
|
|
692
|
+
writeFileSync(unitPath, definition, { encoding: 'utf8', mode: 0o600 });
|
|
693
|
+
}
|
|
694
|
+
const restoreReload = refresh ? run(refresh) : { ok: false, output: 'daemon-reload command unavailable' };
|
|
695
|
+
const restoreCommands = [];
|
|
696
|
+
if (state.enabledAtLogin)
|
|
697
|
+
restoreCommands.push({ command: 'systemctl', args: ['--user', 'enable', 'meta-proxy.service'] });
|
|
698
|
+
if (state.running)
|
|
699
|
+
restoreCommands.push({ command: 'systemctl', args: ['--user', 'start', 'meta-proxy.service'] });
|
|
700
|
+
const restoreFailure = !restoreReload.ok
|
|
701
|
+
? restoreReload
|
|
702
|
+
: restoreCommands.map((command) => run(command)).find((result) => !result.ok);
|
|
703
|
+
const restored = restoreFailure ? null : metaProxyServiceState(platform, home, run, label);
|
|
704
|
+
if (restoreFailure
|
|
705
|
+
|| restored?.managerState === 'unknown'
|
|
706
|
+
|| restored?.enabledAtLogin !== state.enabledAtLogin
|
|
707
|
+
|| restored?.running !== state.running) {
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
message: `Disabled service and daemon-reload failed; restoring the prior manager state also failed: ${restoreFailure?.output || 'state mismatch'}. Definition preserved at ${unitPath}.`,
|
|
711
|
+
unitPath,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
ok: false,
|
|
716
|
+
message: `Disable rolled back after daemon-reload failed: ${outcome.output || 'command failed'}. Prior definition, login enablement, and running state restored at ${unitPath}.`,
|
|
717
|
+
unitPath,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return { ok: true, message: `Meta-Proxy will no longer start at login. Removed ${unitPath}.` };
|
|
722
|
+
}
|
|
723
|
+
function stopAndDeleteWindowsTask(home, unitPath, label, run, wait, removeDefinition = true) {
|
|
724
|
+
const stopped = confirmWindowsTaskStopped(home, unitPath, label, run, wait);
|
|
725
|
+
if (!stopped.ok)
|
|
726
|
+
return stopped;
|
|
727
|
+
const removeTask = disableCommands('win32', unitPath, label)[0];
|
|
728
|
+
const removed = removeTask ? run(removeTask) : { ok: false, output: 'delete command unavailable' };
|
|
729
|
+
if (!removed.ok) {
|
|
730
|
+
return { ok: false, message: `Could not delete the stopped Meta-Proxy task: ${removed.output || 'command failed'}. Definition preserved at ${unitPath}.`, unitPath };
|
|
731
|
+
}
|
|
732
|
+
if (removeDefinition)
|
|
733
|
+
rmSync(unitPath, { force: true });
|
|
734
|
+
return { ok: true, message: removeDefinition ? `Meta-Proxy will no longer start at login. Removed ${unitPath}.` : 'Newly-created Meta-Proxy task removed; prior definition preserved.', unitPath };
|
|
735
|
+
}
|
|
736
|
+
function confirmWindowsTaskStopped(home, unitPath, label, run, wait) {
|
|
737
|
+
const end = stopCommand('win32', label);
|
|
738
|
+
if (!end)
|
|
739
|
+
return { ok: false, message: 'Could not construct the Windows stop command.', unitPath };
|
|
740
|
+
run(end);
|
|
741
|
+
let stopped = metaProxyServiceState('win32', home, run, label);
|
|
742
|
+
let consecutiveStopped = stopped.running === false ? 1 : 0;
|
|
743
|
+
for (let attempt = 0; attempt < 20 && stopped.managerState !== 'unknown' && consecutiveStopped < 2; attempt++) {
|
|
744
|
+
wait(100);
|
|
745
|
+
stopped = metaProxyServiceState('win32', home, run, label);
|
|
746
|
+
consecutiveStopped = stopped.running === false ? consecutiveStopped + 1 : 0;
|
|
747
|
+
}
|
|
748
|
+
if (stopped.managerState === 'unknown' || consecutiveStopped < 2) {
|
|
749
|
+
return { ok: false, message: `Could not confirm the Meta-Proxy task stopped; preserved task and ${unitPath}.`, unitPath };
|
|
750
|
+
}
|
|
751
|
+
return { ok: true, message: 'Meta-Proxy task is stably stopped.', unitPath };
|
|
752
|
+
}
|
|
753
|
+
//# sourceMappingURL=meta-proxy-service.js.map
|