@jian-ai/remote-control 0.8.3 → 0.8.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 +2 -1
- package/bin/jian-remote.mjs +584 -25
- package/macos/computer-helper +0 -0
- package/macos/computer-helper.swift +195 -121
- package/macos/jian-pointer.png +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,7 +37,8 @@ jian-remote stop | start | restart
|
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
`start` / `restart` stop any previous worker before launching one process (LaunchAgent,
|
|
40
|
-
systemd
|
|
40
|
+
systemd `--user` or system unit, or Task Scheduler). On Linux headless/root hosts without a
|
|
41
|
+
user bus, `install` falls back to a system unit (or pass `install --system`).
|
|
41
42
|
|
|
42
43
|
## Configure
|
|
43
44
|
|
package/bin/jian-remote.mjs
CHANGED
|
@@ -2,17 +2,30 @@
|
|
|
2
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import {
|
|
5
|
+
accessSync,
|
|
5
6
|
existsSync,
|
|
7
|
+
constants as fsConstants,
|
|
6
8
|
mkdtempSync,
|
|
9
|
+
readdirSync,
|
|
7
10
|
readFileSync,
|
|
8
11
|
realpathSync,
|
|
9
12
|
rmSync,
|
|
10
13
|
statSync,
|
|
11
14
|
writeFileSync,
|
|
12
15
|
} from 'node:fs';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
import {
|
|
17
|
+
chmod,
|
|
18
|
+
copyFile,
|
|
19
|
+
mkdir,
|
|
20
|
+
readdir,
|
|
21
|
+
readFile,
|
|
22
|
+
rename,
|
|
23
|
+
rm,
|
|
24
|
+
unlink,
|
|
25
|
+
writeFile,
|
|
26
|
+
} from 'node:fs/promises';
|
|
27
|
+
import { homedir, tmpdir, userInfo } from 'node:os';
|
|
28
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
16
29
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
30
|
|
|
18
31
|
const configDir = join(homedir(), '.config', 'jian-remote');
|
|
@@ -24,6 +37,9 @@ const workerLockPath = join(configDir, 'worker.pid');
|
|
|
24
37
|
const updateStatePath = join(configDir, 'update-state.json');
|
|
25
38
|
const MACOS_LAUNCH_AGENT_LABEL = 'com.jian.remote';
|
|
26
39
|
const LINUX_SERVICE_UNIT = 'jian-remote.service';
|
|
40
|
+
/** Remembers whether Linux install used systemd --user or a system unit (headless/root VPS). */
|
|
41
|
+
const linuxServiceScopePath = join(configDir, 'linux-service-scope');
|
|
42
|
+
const LINUX_SYSTEM_UNIT_PATH = join('/etc/systemd/system', LINUX_SERVICE_UNIT);
|
|
27
43
|
const WINDOWS_TASK_NAME = 'Jian Remote';
|
|
28
44
|
/** Minimal PATH for launchd — app-identity Node copies otherwise inherit an empty env. */
|
|
29
45
|
export const LAUNCH_AGENT_PATH = '/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin';
|
|
@@ -53,7 +69,7 @@ export function parseCliInvocation(argv = args) {
|
|
|
53
69
|
* Stamped to the release tag on npm publish and when the gateway serves this script.
|
|
54
70
|
* Compatibility with the gateway requires the same major.minor (see versionsCompatible).
|
|
55
71
|
*/
|
|
56
|
-
export const PACKAGE_VERSION = '0.8.
|
|
72
|
+
export const PACKAGE_VERSION = '0.8.5';
|
|
57
73
|
|
|
58
74
|
/** Soft stdout/stderr caps while capturing a command (chars). Gateway schema matches these. */
|
|
59
75
|
const STDOUT_CAP = 400_000;
|
|
@@ -142,12 +158,291 @@ export function macosAppBundlePaths(home = homedir()) {
|
|
|
142
158
|
appRoot,
|
|
143
159
|
contents: join(appRoot, 'Contents'),
|
|
144
160
|
macosDir: join(appRoot, 'Contents', 'MacOS'),
|
|
161
|
+
/** Homebrew / official Node often load `@rpath/libnode.*.dylib` from here. */
|
|
162
|
+
libDir: join(appRoot, 'Contents', 'lib'),
|
|
145
163
|
executable: join(appRoot, 'Contents', 'MacOS', MACOS_APP_NAME),
|
|
146
164
|
infoPlist: join(appRoot, 'Contents', 'Info.plist'),
|
|
147
165
|
bundleId: MACOS_BUNDLE_ID,
|
|
148
166
|
};
|
|
149
167
|
}
|
|
150
168
|
|
|
169
|
+
/** dyld system locations — leave these on the host; do not copy into the app. */
|
|
170
|
+
export function isMacSystemLibrary(depPath) {
|
|
171
|
+
if (typeof depPath !== 'string' || !depPath) return true;
|
|
172
|
+
if (depPath.startsWith('/usr/lib/')) return true;
|
|
173
|
+
if (depPath.startsWith('/System/')) return true;
|
|
174
|
+
if (depPath.startsWith('/Library/Apple/')) return true;
|
|
175
|
+
if (depPath.startsWith('/usr/local/lib/libSystem')) return true;
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse `otool -L` lines into load-command paths (skips the header "path:" line).
|
|
181
|
+
* Example line: `\t@rpath/libnode.147.dylib (compatibility version 147.0.0, current version …)`
|
|
182
|
+
*/
|
|
183
|
+
export function parseOtoolDependencies(stdout) {
|
|
184
|
+
const deps = [];
|
|
185
|
+
for (const line of String(stdout || '').split('\n')) {
|
|
186
|
+
const trimmed = line.trim();
|
|
187
|
+
if (!trimmed || trimmed.endsWith(':')) continue;
|
|
188
|
+
const match = /^(.+?)\s+\(compatibility version\b/.exec(trimmed);
|
|
189
|
+
if (!match) continue;
|
|
190
|
+
const dep = match[1].trim();
|
|
191
|
+
if (dep) deps.push(dep);
|
|
192
|
+
}
|
|
193
|
+
return deps;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Parse LC_RPATH entries from `otool -l` (or `otool -l -v`) output.
|
|
198
|
+
* Looks for: `cmd LC_RPATH` … `path <value> (offset …)`
|
|
199
|
+
*/
|
|
200
|
+
export function parseOtoolRpaths(stdout) {
|
|
201
|
+
const rpaths = [];
|
|
202
|
+
const lines = String(stdout || '').split('\n');
|
|
203
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
204
|
+
if (!/\bLC_RPATH\b/.test(lines[i])) continue;
|
|
205
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j += 1) {
|
|
206
|
+
const match = /^\s*path\s+(.+?)(?:\s+\(offset\s+\d+\))?\s*$/.exec(lines[j]);
|
|
207
|
+
if (match) {
|
|
208
|
+
rpaths.push(match[1].trim());
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return rpaths;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Expand @executable_path / @loader_path / @rpath against the source Node layout.
|
|
218
|
+
* Absolute paths pass through. Unresolvable @rpath names return null.
|
|
219
|
+
*/
|
|
220
|
+
export function resolveMachODependency(
|
|
221
|
+
dep,
|
|
222
|
+
{ binaryPath, executablePath, rpaths = [], exists = existsSync } = {},
|
|
223
|
+
) {
|
|
224
|
+
if (typeof dep !== 'string' || !dep) return null;
|
|
225
|
+
const loaderDir = dirname(binaryPath);
|
|
226
|
+
const execDir = dirname(executablePath || binaryPath);
|
|
227
|
+
|
|
228
|
+
const expandPrefix = (value) => {
|
|
229
|
+
if (value.startsWith('@loader_path')) {
|
|
230
|
+
return join(loaderDir, value.slice('@loader_path'.length).replace(/^\//, '') || '.');
|
|
231
|
+
}
|
|
232
|
+
if (value.startsWith('@executable_path')) {
|
|
233
|
+
return join(execDir, value.slice('@executable_path'.length).replace(/^\//, '') || '.');
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
if (dep.startsWith('@rpath/')) {
|
|
239
|
+
const rest = dep.slice('@rpath/'.length);
|
|
240
|
+
for (const rpath of rpaths) {
|
|
241
|
+
const base = expandPrefix(rpath);
|
|
242
|
+
const candidate = join(base, rest);
|
|
243
|
+
if (exists(candidate)) return candidate;
|
|
244
|
+
}
|
|
245
|
+
// Common Node layouts when LC_RPATH is missing from a stub parse: bin/ + ../lib/.
|
|
246
|
+
for (const candidate of [
|
|
247
|
+
join(loaderDir, rest),
|
|
248
|
+
join(loaderDir, '..', 'lib', rest),
|
|
249
|
+
join(execDir, rest),
|
|
250
|
+
join(execDir, '..', 'lib', rest),
|
|
251
|
+
]) {
|
|
252
|
+
if (exists(candidate)) return candidate;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (dep.startsWith('@loader_path') || dep.startsWith('@executable_path')) {
|
|
258
|
+
const candidate = expandPrefix(dep);
|
|
259
|
+
return exists(candidate) ? candidate : null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (dep.startsWith('/')) {
|
|
263
|
+
return exists(dep) ? dep : null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const SIBLING_DYLIB_PREFIXES = [
|
|
270
|
+
'libnode.',
|
|
271
|
+
'libuv.',
|
|
272
|
+
'libbrotli',
|
|
273
|
+
'libcares.',
|
|
274
|
+
'libnghttp',
|
|
275
|
+
'libopenssl.',
|
|
276
|
+
'libssl.',
|
|
277
|
+
'libcrypto.',
|
|
278
|
+
'libicu',
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Sibling search when otool is unavailable or incomplete — Homebrew Cellar and
|
|
283
|
+
* official tarballs keep libnode next to bin/ or under ../lib/.
|
|
284
|
+
*/
|
|
285
|
+
export function discoverSiblingNodeLibraries(nodePath, { exists = existsSync, listDir } = {}) {
|
|
286
|
+
const dir = dirname(nodePath);
|
|
287
|
+
const searchDirs = [dir, join(dir, 'lib'), join(dir, '..', 'lib')];
|
|
288
|
+
const found = [];
|
|
289
|
+
const list =
|
|
290
|
+
listDir ||
|
|
291
|
+
((path) => {
|
|
292
|
+
try {
|
|
293
|
+
return readdirSync(path);
|
|
294
|
+
} catch {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
for (const searchDir of searchDirs) {
|
|
299
|
+
if (!exists(searchDir)) continue;
|
|
300
|
+
for (const name of list(searchDir)) {
|
|
301
|
+
if (!name.endsWith('.dylib')) continue;
|
|
302
|
+
if (!SIBLING_DYLIB_PREFIXES.some((prefix) => name.startsWith(prefix))) continue;
|
|
303
|
+
const full = join(searchDir, name);
|
|
304
|
+
if (exists(full)) found.push(full);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return found;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* BFS: every non-system Mach-O dependency of `nodePath` that must live inside the .app.
|
|
312
|
+
* Returns absolute source paths (deduped, stable order).
|
|
313
|
+
*/
|
|
314
|
+
export function collectNodeRuntimeLibraries(
|
|
315
|
+
nodePath,
|
|
316
|
+
{
|
|
317
|
+
exists = existsSync,
|
|
318
|
+
readDeps = defaultReadOtoolDeps,
|
|
319
|
+
readRpaths = defaultReadOtoolRpaths,
|
|
320
|
+
listDir,
|
|
321
|
+
} = {},
|
|
322
|
+
) {
|
|
323
|
+
const realNode = (() => {
|
|
324
|
+
try {
|
|
325
|
+
return realpathSync(nodePath);
|
|
326
|
+
} catch {
|
|
327
|
+
return nodePath;
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
|
|
331
|
+
const queue = [realNode];
|
|
332
|
+
const seenBinaries = new Set();
|
|
333
|
+
const libraries = [];
|
|
334
|
+
const seenLibs = new Set();
|
|
335
|
+
|
|
336
|
+
const enqueueLib = (source) => {
|
|
337
|
+
let real = source;
|
|
338
|
+
try {
|
|
339
|
+
real = realpathSync(source);
|
|
340
|
+
} catch {
|
|
341
|
+
// keep source
|
|
342
|
+
}
|
|
343
|
+
if (seenLibs.has(real)) return;
|
|
344
|
+
seenLibs.add(real);
|
|
345
|
+
libraries.push(real);
|
|
346
|
+
queue.push(real);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// Seed Cellar/tarball siblings first so a missing otool still walks transitive deps.
|
|
350
|
+
for (const sibling of discoverSiblingNodeLibraries(realNode, { exists, listDir })) {
|
|
351
|
+
enqueueLib(sibling);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
while (queue.length > 0) {
|
|
355
|
+
const current = queue.shift();
|
|
356
|
+
if (!current || seenBinaries.has(current)) continue;
|
|
357
|
+
seenBinaries.add(current);
|
|
358
|
+
|
|
359
|
+
const rpaths = readRpaths(current);
|
|
360
|
+
const deps = readDeps(current);
|
|
361
|
+
for (const dep of deps) {
|
|
362
|
+
if (isMacSystemLibrary(dep)) continue;
|
|
363
|
+
// Self-id lines sometimes appear as the dylib's install name — skip when it resolves to self.
|
|
364
|
+
const resolved = resolveMachODependency(dep, {
|
|
365
|
+
binaryPath: current,
|
|
366
|
+
executablePath: realNode,
|
|
367
|
+
rpaths,
|
|
368
|
+
exists,
|
|
369
|
+
});
|
|
370
|
+
if (!resolved) continue;
|
|
371
|
+
let realResolved = resolved;
|
|
372
|
+
try {
|
|
373
|
+
realResolved = realpathSync(resolved);
|
|
374
|
+
} catch {
|
|
375
|
+
// keep
|
|
376
|
+
}
|
|
377
|
+
if (realResolved === realNode || realResolved === current) continue;
|
|
378
|
+
if (isMacSystemLibrary(realResolved)) continue;
|
|
379
|
+
enqueueLib(realResolved);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return libraries;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function defaultReadOtoolDeps(binaryPath) {
|
|
387
|
+
const result = spawnSync('otool', ['-L', binaryPath], { encoding: 'utf8' });
|
|
388
|
+
if (result.status !== 0) return [];
|
|
389
|
+
return parseOtoolDependencies(result.stdout || '');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function defaultReadOtoolRpaths(binaryPath) {
|
|
393
|
+
const result = spawnSync('otool', ['-l', binaryPath], { encoding: 'utf8' });
|
|
394
|
+
if (result.status !== 0) return [];
|
|
395
|
+
return parseOtoolRpaths(result.stdout || '');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Drop Gatekeeper quarantine on a path (best-effort; missing xattr is fine). */
|
|
399
|
+
export function clearMacQuarantine(targetPath) {
|
|
400
|
+
spawnSync('xattr', ['-dr', 'com.apple.quarantine', targetPath], { encoding: 'utf8' });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function runInstallNameTool(argv, run) {
|
|
404
|
+
// -add_rpath fails (status 1) when the rpath already exists — that is fine.
|
|
405
|
+
return run('install_name_tool', argv);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Rewrite load commands so bundled libs resolve via `@rpath/<basename>` inside Contents/lib.
|
|
410
|
+
* Best-effort: missing install_name_tool still leaves copies under Contents/lib for @rpath.
|
|
411
|
+
*/
|
|
412
|
+
export function rewriteBundledMachOLinks({
|
|
413
|
+
executablePath,
|
|
414
|
+
libDir,
|
|
415
|
+
libraryBasenames,
|
|
416
|
+
readDeps = defaultReadOtoolDeps,
|
|
417
|
+
run = (cmd, argv) => spawnSync(cmd, argv, { encoding: 'utf8' }),
|
|
418
|
+
}) {
|
|
419
|
+
const names = new Set(libraryBasenames);
|
|
420
|
+
|
|
421
|
+
runInstallNameTool(['-add_rpath', '@executable_path/../lib', executablePath], run);
|
|
422
|
+
runInstallNameTool(['-add_rpath', '@executable_path', executablePath], run);
|
|
423
|
+
|
|
424
|
+
for (const name of names) {
|
|
425
|
+
runInstallNameTool(['-id', `@rpath/${name}`, join(libDir, name)], run);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const consumers = [executablePath, ...[...names].map((name) => join(libDir, name))];
|
|
429
|
+
for (const consumer of consumers) {
|
|
430
|
+
for (const dep of readDeps(consumer)) {
|
|
431
|
+
if (isMacSystemLibrary(dep)) continue;
|
|
432
|
+
const depBase = basename(
|
|
433
|
+
dep
|
|
434
|
+
.replace(/^@rpath\//, '')
|
|
435
|
+
.replace(/^@loader_path\//, '')
|
|
436
|
+
.replace(/^@executable_path\//, ''),
|
|
437
|
+
);
|
|
438
|
+
if (!names.has(depBase)) continue;
|
|
439
|
+
const desired = `@rpath/${depBase}`;
|
|
440
|
+
if (dep === desired) continue;
|
|
441
|
+
runInstallNameTool(['-change', dep, desired, consumer], run);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
151
446
|
export function buildMacosAppInfoPlist() {
|
|
152
447
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
153
448
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -214,26 +509,78 @@ export function buildLaunchAgentPlist({
|
|
|
214
509
|
}
|
|
215
510
|
|
|
216
511
|
/**
|
|
217
|
-
* Copy the current Node binary
|
|
218
|
-
*
|
|
512
|
+
* Copy the current Node binary *and* its @rpath shared libs (libnode, ICU, …) into
|
|
513
|
+
* Jian Remote.app, then ad-hoc sign so Login Items / TCC attach to Jian — not system node.
|
|
514
|
+
* Homebrew / nvm / fnm / official pkg layouts resolve via Contents/lib (@executable_path/../lib).
|
|
219
515
|
*/
|
|
220
|
-
async function materializeMacosAppRuntime(
|
|
221
|
-
|
|
516
|
+
export async function materializeMacosAppRuntime({
|
|
517
|
+
sourceNode = process.execPath,
|
|
518
|
+
home = homedir(),
|
|
519
|
+
collectLibraries = collectNodeRuntimeLibraries,
|
|
520
|
+
} = {}) {
|
|
521
|
+
const paths = macosAppBundlePaths(home);
|
|
222
522
|
await mkdir(paths.macosDir, { recursive: true, mode: 0o755 });
|
|
523
|
+
await mkdir(paths.libDir, { recursive: true, mode: 0o755 });
|
|
223
524
|
await writeFile(paths.infoPlist, buildMacosAppInfoPlist(), { mode: 0o644 });
|
|
224
|
-
|
|
525
|
+
|
|
526
|
+
let realSource = sourceNode;
|
|
527
|
+
try {
|
|
528
|
+
realSource = realpathSync(sourceNode);
|
|
529
|
+
} catch {
|
|
530
|
+
// keep sourceNode
|
|
531
|
+
}
|
|
532
|
+
|
|
225
533
|
const temp = `${paths.executable}.${process.pid}.tmp`;
|
|
226
|
-
await copyFile(
|
|
534
|
+
await copyFile(realSource, temp);
|
|
227
535
|
await chmod(temp, 0o755);
|
|
228
536
|
await rename(temp, paths.executable);
|
|
229
|
-
|
|
537
|
+
|
|
538
|
+
// Drop stale dylibs from a previous Node major so @rpath cannot pick an old libnode.
|
|
539
|
+
try {
|
|
540
|
+
for (const name of await readdir(paths.libDir)) {
|
|
541
|
+
if (!name.endsWith('.dylib')) continue;
|
|
542
|
+
await rm(join(paths.libDir, name), { force: true });
|
|
543
|
+
}
|
|
544
|
+
} catch {
|
|
545
|
+
// lib dir may be brand new
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const librarySources = collectLibraries(realSource);
|
|
549
|
+
const libraryBasenames = [];
|
|
550
|
+
for (const libSource of librarySources) {
|
|
551
|
+
const name = basename(libSource);
|
|
552
|
+
if (!name.endsWith('.dylib')) continue;
|
|
553
|
+
const dest = join(paths.libDir, name);
|
|
554
|
+
await copyFile(libSource, dest);
|
|
555
|
+
await chmod(dest, 0o755);
|
|
556
|
+
libraryBasenames.push(name);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (libraryBasenames.length > 0) {
|
|
560
|
+
rewriteBundledMachOLinks({
|
|
561
|
+
executablePath: paths.executable,
|
|
562
|
+
libDir: paths.libDir,
|
|
563
|
+
libraryBasenames,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
clearMacQuarantine(paths.appRoot);
|
|
568
|
+
|
|
569
|
+
// Sign nested dylibs first, then the executable, then the bundle (ad-hoc / unsigned path).
|
|
570
|
+
for (const name of libraryBasenames) {
|
|
571
|
+
spawnSync(
|
|
572
|
+
'codesign',
|
|
573
|
+
['--force', '-s', '-', '--identifier', MACOS_BUNDLE_ID, join(paths.libDir, name)],
|
|
574
|
+
{ stdio: 'ignore' },
|
|
575
|
+
);
|
|
576
|
+
}
|
|
230
577
|
spawnSync('codesign', ['--force', '-s', '-', '--identifier', MACOS_BUNDLE_ID, paths.executable], {
|
|
231
578
|
stdio: 'ignore',
|
|
232
579
|
});
|
|
233
580
|
spawnSync('codesign', ['--force', '-s', '-', '--identifier', MACOS_BUNDLE_ID, paths.appRoot], {
|
|
234
581
|
stdio: 'ignore',
|
|
235
582
|
});
|
|
236
|
-
return paths;
|
|
583
|
+
return { ...paths, bundledLibraries: libraryBasenames };
|
|
237
584
|
}
|
|
238
585
|
|
|
239
586
|
export function parseRelease(version) {
|
|
@@ -726,13 +1073,174 @@ function macosPlistPath() {
|
|
|
726
1073
|
return join(homedir(), 'Library', 'LaunchAgents', `${MACOS_LAUNCH_AGENT_LABEL}.plist`);
|
|
727
1074
|
}
|
|
728
1075
|
|
|
729
|
-
function
|
|
1076
|
+
function linuxUserUnitPath() {
|
|
730
1077
|
return join(homedir(), '.config', 'systemd', 'user', LINUX_SERVICE_UNIT);
|
|
731
1078
|
}
|
|
732
1079
|
|
|
733
|
-
|
|
1080
|
+
/**
|
|
1081
|
+
* True when this process can talk to the per-user systemd bus.
|
|
1082
|
+
* Headless VPS / root SSH sessions often lack XDG_RUNTIME_DIR and fail with
|
|
1083
|
+
* "Failed to connect to bus: No medium found".
|
|
1084
|
+
*/
|
|
1085
|
+
export function linuxUserBusAvailable({
|
|
1086
|
+
env = process.env,
|
|
1087
|
+
run = (argv) => spawnSync('systemctl', ['--user', ...argv], { encoding: 'utf8' }),
|
|
1088
|
+
} = {}) {
|
|
1089
|
+
const runtimeDir = env.XDG_RUNTIME_DIR;
|
|
1090
|
+
if (!runtimeDir || typeof runtimeDir !== 'string') return false;
|
|
1091
|
+
try {
|
|
1092
|
+
if (!statSync(runtimeDir).isDirectory()) return false;
|
|
1093
|
+
} catch {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
const result = run(['show-environment']);
|
|
1097
|
+
if (result.error) return false;
|
|
1098
|
+
const stderr = `${result.stderr ?? ''}`;
|
|
1099
|
+
if (/Failed to connect to bus|No medium found|No such file or directory/i.test(stderr)) {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
return result.status === 0;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/** True when this process can install `/etc/systemd/system/jian-remote.service`. */
|
|
1106
|
+
export function canInstallLinuxSystemUnit({
|
|
1107
|
+
uid = typeof process.getuid === 'function' ? process.getuid() : undefined,
|
|
1108
|
+
systemDir = '/etc/systemd/system',
|
|
1109
|
+
access = (path, mode) => accessSync(path, mode),
|
|
1110
|
+
} = {}) {
|
|
1111
|
+
if (uid === 0) return true;
|
|
1112
|
+
try {
|
|
1113
|
+
access(systemDir, fsConstants.W_OK);
|
|
1114
|
+
return true;
|
|
1115
|
+
} catch {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
export function linuxServiceKind(scope = 'user') {
|
|
1121
|
+
return scope === 'system' ? 'systemd (system)' : 'systemd --user';
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Pick systemd --user vs system unit. Prefer --user when the bus works; fall back to a
|
|
1126
|
+
* system unit on headless/root hosts; `--system` forces the system unit.
|
|
1127
|
+
*/
|
|
1128
|
+
export function chooseLinuxServiceScope({
|
|
1129
|
+
preferSystem = false,
|
|
1130
|
+
env = process.env,
|
|
1131
|
+
userBusAvailable = () => linuxUserBusAvailable({ env }),
|
|
1132
|
+
canSystem = () => canInstallLinuxSystemUnit(),
|
|
1133
|
+
} = {}) {
|
|
1134
|
+
if (preferSystem) {
|
|
1135
|
+
if (!canSystem()) {
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
'install --system needs write access to /etc/systemd/system (run as root, or use sudo).',
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return 'system';
|
|
1141
|
+
}
|
|
1142
|
+
if (userBusAvailable()) return 'user';
|
|
1143
|
+
if (canSystem()) return 'system';
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
[
|
|
1146
|
+
'Could not reach systemd --user (no user bus). This is common on a headless VPS or root SSH session',
|
|
1147
|
+
'("Failed to connect to bus: No medium found" / missing XDG_RUNTIME_DIR).',
|
|
1148
|
+
'',
|
|
1149
|
+
'Fix options:',
|
|
1150
|
+
' 1. Prefer a normal user + lingering, then reinstall:',
|
|
1151
|
+
' sudo loginctl enable-linger USERNAME',
|
|
1152
|
+
' # as USERNAME:',
|
|
1153
|
+
' export XDG_RUNTIME_DIR=/run/user/$(id -u)',
|
|
1154
|
+
' jian-remote install --gateway … --machine … --token …',
|
|
1155
|
+
' 2. Install a system unit (as root):',
|
|
1156
|
+
' jian-remote install --system --gateway … --machine … --token …',
|
|
1157
|
+
' 3. Foreground smoke test (config may already be on disk from a failed install):',
|
|
1158
|
+
' node ~/.config/jian-remote/jian-remote.mjs run',
|
|
1159
|
+
].join('\n'),
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function writeLinuxServiceScope(scope) {
|
|
1164
|
+
await mkdir(configDir, { recursive: true, mode: 0o700 });
|
|
1165
|
+
await writeFile(linuxServiceScopePath, `${scope}\n`, { mode: 0o600 });
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function readLinuxServiceScope() {
|
|
1169
|
+
try {
|
|
1170
|
+
const text = (await readFile(linuxServiceScopePath, 'utf8')).trim();
|
|
1171
|
+
if (text === 'system' || text === 'user') return text;
|
|
1172
|
+
} catch {
|
|
1173
|
+
// absent or unreadable — fall through to heuristics
|
|
1174
|
+
}
|
|
1175
|
+
if (existsSync(LINUX_SYSTEM_UNIT_PATH)) return 'system';
|
|
1176
|
+
return 'user';
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Fresh `install` probes the bus (and honors `--system`). Later start/restart/update/configure
|
|
1181
|
+
* keep the saved scope so a system-unit VPS install does not flip back if a user bus appears.
|
|
1182
|
+
*/
|
|
1183
|
+
async function resolveLinuxInstallScope() {
|
|
1184
|
+
const preferSystem = args.includes('--system');
|
|
1185
|
+
if (preferSystem) return chooseLinuxServiceScope({ preferSystem: true });
|
|
1186
|
+
const command = parseCliInvocation(args).command;
|
|
1187
|
+
if (command && command !== 'install') {
|
|
1188
|
+
try {
|
|
1189
|
+
const saved = (await readFile(linuxServiceScopePath, 'utf8')).trim();
|
|
1190
|
+
if (saved === 'system') {
|
|
1191
|
+
if (canInstallLinuxSystemUnit()) return 'system';
|
|
1192
|
+
} else if (saved === 'user' && linuxUserBusAvailable()) {
|
|
1193
|
+
return 'user';
|
|
1194
|
+
}
|
|
1195
|
+
} catch {
|
|
1196
|
+
// choose below
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return chooseLinuxServiceScope({ preferSystem: false });
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
export function buildLinuxServiceUnit({
|
|
1203
|
+
script,
|
|
1204
|
+
scope,
|
|
1205
|
+
home = homedir(),
|
|
1206
|
+
nodePath = process.execPath,
|
|
1207
|
+
uid = typeof process.getuid === 'function' ? process.getuid() : 0,
|
|
1208
|
+
username,
|
|
1209
|
+
} = {}) {
|
|
1210
|
+
const quote = (value) => `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
1211
|
+
const wantedBy = scope === 'system' ? 'multi-user.target' : 'default.target';
|
|
1212
|
+
const lines = [
|
|
1213
|
+
'[Unit]',
|
|
1214
|
+
'Description=Jian Remote worker',
|
|
1215
|
+
'After=network-online.target',
|
|
1216
|
+
'Wants=network-online.target',
|
|
1217
|
+
'',
|
|
1218
|
+
'[Service]',
|
|
1219
|
+
`ExecStart=${quote(nodePath)} ${quote(script)} run`,
|
|
1220
|
+
'Restart=always',
|
|
1221
|
+
'RestartSec=3',
|
|
1222
|
+
// System units do not inherit the installing shell's HOME; pin it so config stays findable.
|
|
1223
|
+
`Environment=${quote(`HOME=${home}`)}`,
|
|
1224
|
+
`WorkingDirectory=${quote(home)}`,
|
|
1225
|
+
];
|
|
1226
|
+
if (scope === 'system' && uid !== 0) {
|
|
1227
|
+
let name = username;
|
|
1228
|
+
if (!name) {
|
|
1229
|
+
try {
|
|
1230
|
+
name = userInfo().username;
|
|
1231
|
+
} catch {
|
|
1232
|
+
name = undefined;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (name) lines.push(`User=${name}`);
|
|
1236
|
+
}
|
|
1237
|
+
lines.push('', '[Install]', `WantedBy=${wantedBy}`, '');
|
|
1238
|
+
return lines.join('\n');
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export function serviceKindForPlatform(platform = process.platform, linuxScope = 'user') {
|
|
734
1242
|
if (platform === 'darwin') return 'LaunchAgent';
|
|
735
|
-
if (platform === 'linux') return
|
|
1243
|
+
if (platform === 'linux') return linuxServiceKind(linuxScope);
|
|
736
1244
|
if (platform === 'win32') return 'Task Scheduler';
|
|
737
1245
|
return 'none';
|
|
738
1246
|
}
|
|
@@ -742,7 +1250,8 @@ export function serviceKindForPlatform(platform = process.platform) {
|
|
|
742
1250
|
* Clear success/failure for the `jian-remote stop` CLI.
|
|
743
1251
|
*/
|
|
744
1252
|
async function stopService() {
|
|
745
|
-
const
|
|
1253
|
+
const linuxScope = process.platform === 'linux' ? await readLinuxServiceScope() : 'user';
|
|
1254
|
+
const kind = serviceKindForPlatform(process.platform, linuxScope);
|
|
746
1255
|
if (process.platform === 'darwin') {
|
|
747
1256
|
const plistPath = macosPlistPath();
|
|
748
1257
|
const domain = `gui/${process.getuid()}`;
|
|
@@ -751,7 +1260,11 @@ async function stopService() {
|
|
|
751
1260
|
stdio: 'ignore',
|
|
752
1261
|
});
|
|
753
1262
|
} else if (process.platform === 'linux') {
|
|
754
|
-
|
|
1263
|
+
if (linuxScope === 'system') {
|
|
1264
|
+
spawnSync('systemctl', ['stop', LINUX_SERVICE_UNIT], { stdio: 'ignore' });
|
|
1265
|
+
} else {
|
|
1266
|
+
spawnSync('systemctl', ['--user', 'stop', LINUX_SERVICE_UNIT], { stdio: 'ignore' });
|
|
1267
|
+
}
|
|
755
1268
|
} else if (process.platform === 'win32') {
|
|
756
1269
|
spawnSync('schtasks.exe', ['/End', '/TN', WINDOWS_TASK_NAME], {
|
|
757
1270
|
stdio: 'ignore',
|
|
@@ -829,17 +1342,62 @@ async function startService(script = installedScript) {
|
|
|
829
1342
|
};
|
|
830
1343
|
}
|
|
831
1344
|
if (process.platform === 'linux') {
|
|
1345
|
+
const scope = await resolveLinuxInstallScope();
|
|
1346
|
+
const unitBody = buildLinuxServiceUnit({ script, scope });
|
|
1347
|
+
if (scope === 'system') {
|
|
1348
|
+
// Drop a leftover user unit so stop/start do not fight two managers.
|
|
1349
|
+
spawnSync('systemctl', ['--user', 'disable', '--now', LINUX_SERVICE_UNIT], {
|
|
1350
|
+
stdio: 'ignore',
|
|
1351
|
+
});
|
|
1352
|
+
try {
|
|
1353
|
+
await unlink(linuxUserUnitPath());
|
|
1354
|
+
} catch {
|
|
1355
|
+
// absent
|
|
1356
|
+
}
|
|
1357
|
+
await writeFile(LINUX_SYSTEM_UNIT_PATH, unitBody, { mode: 0o644 });
|
|
1358
|
+
await writeLinuxServiceScope('system');
|
|
1359
|
+
for (const command of [['daemon-reload'], ['enable', '--now', LINUX_SERVICE_UNIT]]) {
|
|
1360
|
+
const result = spawnSync('systemctl', command, { stdio: 'inherit' });
|
|
1361
|
+
if (result.status !== 0) {
|
|
1362
|
+
throw new Error('Could not install/start Jian Remote with systemd (system unit)');
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (!args.includes('--system') && parseCliInvocation(args).command === 'install') {
|
|
1366
|
+
printInfo(
|
|
1367
|
+
'systemd --user bus unavailable; installed a system unit instead (headless/root). Prefer a normal user + loginctl enable-linger when you can.',
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
return { service: linuxServiceKind('system'), detail: LINUX_SYSTEM_UNIT_PATH };
|
|
1371
|
+
}
|
|
1372
|
+
|
|
832
1373
|
const unitDir = join(homedir(), '.config', 'systemd', 'user');
|
|
833
1374
|
await mkdir(unitDir, { recursive: true, mode: 0o700 });
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1375
|
+
await writeFile(linuxUserUnitPath(), unitBody, { mode: 0o600 });
|
|
1376
|
+
await writeLinuxServiceScope('user');
|
|
1377
|
+
// Best-effort: clear a previous system install if this user can.
|
|
1378
|
+
if (existsSync(LINUX_SYSTEM_UNIT_PATH) && canInstallLinuxSystemUnit()) {
|
|
1379
|
+
spawnSync('systemctl', ['disable', '--now', LINUX_SERVICE_UNIT], { stdio: 'ignore' });
|
|
1380
|
+
try {
|
|
1381
|
+
await unlink(LINUX_SYSTEM_UNIT_PATH);
|
|
1382
|
+
spawnSync('systemctl', ['daemon-reload'], { stdio: 'ignore' });
|
|
1383
|
+
} catch {
|
|
1384
|
+
// leave system unit; user scope marker wins for this CLI
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
837
1387
|
for (const command of [['daemon-reload'], ['enable', '--now', LINUX_SERVICE_UNIT]]) {
|
|
838
1388
|
const result = spawnSync('systemctl', ['--user', ...command], { stdio: 'inherit' });
|
|
839
|
-
if (result.status !== 0)
|
|
840
|
-
throw new Error(
|
|
1389
|
+
if (result.status !== 0) {
|
|
1390
|
+
throw new Error(
|
|
1391
|
+
[
|
|
1392
|
+
'Could not install/start Jian Remote with systemd --user.',
|
|
1393
|
+
'If you see "Failed to connect to bus: No medium found", the user bus is missing',
|
|
1394
|
+
'(common on headless VPS / root SSH). Retry with: jian-remote install --system …',
|
|
1395
|
+
'or enable lingering: sudo loginctl enable-linger "$(whoami)" && export XDG_RUNTIME_DIR=/run/user/$(id -u)',
|
|
1396
|
+
].join(' '),
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
841
1399
|
}
|
|
842
|
-
return { service: '
|
|
1400
|
+
return { service: linuxServiceKind('user'), detail: LINUX_SERVICE_UNIT };
|
|
843
1401
|
}
|
|
844
1402
|
if (process.platform === 'win32') {
|
|
845
1403
|
const action = `"${process.execPath}" "${script}" run`;
|
|
@@ -914,7 +1472,7 @@ async function install() {
|
|
|
914
1472
|
const gatewayBase = parseGatewayBase(gateway);
|
|
915
1473
|
if (!gatewayBase || !MACHINE_ID.test(machineId ?? '') || !token?.trim()) {
|
|
916
1474
|
throw new Error(
|
|
917
|
-
'Usage: jian-remote install --gateway https://... --machine UUID (--token VALUE | --token-prompt | --token-stdin)',
|
|
1475
|
+
'Usage: jian-remote install [--system] --gateway https://... --machine UUID (--token VALUE | --token-prompt | --token-stdin)',
|
|
918
1476
|
);
|
|
919
1477
|
}
|
|
920
1478
|
const gatewayVersion = await assertCompatibleWithGateway(gatewayBase);
|
|
@@ -1744,7 +2302,7 @@ if (isCliEntry()) {
|
|
|
1744
2302
|
const pkg = paint('(@jian-ai/remote-control)', ANSI.dim, color);
|
|
1745
2303
|
console.log(`${title} worker ${pkg}
|
|
1746
2304
|
version | --version | -V
|
|
1747
|
-
install --gateway HTTPS_URL --machine UUID (--token VALUE | --token-prompt | --token-stdin)
|
|
2305
|
+
install [--system] --gateway HTTPS_URL --machine UUID (--token VALUE | --token-prompt | --token-stdin)
|
|
1748
2306
|
configure [--gateway HTTPS_URL] [--machine UUID] (--token VALUE | --token-prompt | --token-stdin)
|
|
1749
2307
|
update [--version X.Y.Z|latest]
|
|
1750
2308
|
stop | start | restart
|
|
@@ -1752,6 +2310,7 @@ if (isCliEntry()) {
|
|
|
1752
2310
|
|
|
1753
2311
|
Install: npm install -g @jian-ai/remote-control
|
|
1754
2312
|
Or: npx @jian-ai/remote-control@latest install --gateway … --machine … --token …
|
|
2313
|
+
(Linux headless/root VPS: add --system, or use a normal user + loginctl enable-linger)
|
|
1755
2314
|
Configure: jian-remote configure --token … (or --gateway / --machine; restarts the service)
|
|
1756
2315
|
Update: npx @jian-ai/remote-control@latest update
|
|
1757
2316
|
(upgrades the worker binary; reads ~/.config/jian-remote)
|
package/macos/computer-helper
CHANGED
|
Binary file
|
|
@@ -94,127 +94,201 @@ private let hotspot = CGPoint(x: 12, y: 12)
|
|
|
94
94
|
|
|
95
95
|
/// Ship asset `macos/jian-pointer.png` (@3x master) as base64 — single-file helper stays
|
|
96
96
|
/// reliable when only the binary is copied into Application Support (no sidecar PNG).
|
|
97
|
-
private let jianPointerPngBase64 =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
97
|
+
private let jianPointerPngBase64 = """
|
|
98
|
+
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
|
|
291
|
+
"""
|
|
218
292
|
|
|
219
293
|
private let jianPointerImage: NSImage? = {
|
|
220
294
|
guard let data = Data(base64Encoded: jianPointerPngBase64, options: .ignoreUnknownCharacters)
|
package/macos/jian-pointer.png
CHANGED
|
Binary file
|