benchgo-plugin 0.0.0-stage → 0.0.3
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/DEEPSEEK-LICENSE +21 -0
- package/LICENSE +21 -0
- package/NOTICE +17 -0
- package/PASEO-LICENSE +211 -0
- package/README.md +256 -2
- package/TOOL-CONFIG.md +33 -0
- package/VERIFICATION.md +297 -0
- package/assets/fonts/README.md +13 -0
- package/assets/fonts/manifest.json +437 -0
- package/assets/fonts/web/fangyuan-latin-238bc49130b1.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u30-0fe8d99641c3.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u32-202a573131a1.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u4e-cc2f8b20be49.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u4f-9189b5f2d321.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u50-e2560e003872.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u51-a9c5e7a8c7c4.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u52-0cfd00b473c8.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u53-18122a07f60a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u54-8ca41aa9cff2.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u55-7bd55e14647e.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u56-26f093e8a810.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u57-36bde83a8378.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u58-d1df14217098.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u59-4e41ab22c51a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5a-746a367174e6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5b-3d1dd404d70c.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5c-fe0a273f69e7.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5d-af1f0a1c8d2d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5e-598c65420e17.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u5f-a92eaaa5265a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u60-48ee3d26462d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u61-7a57f35bc077.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u62-d2d0b7b36631.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u63-cade7c6635e6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u64-ed34e40d7f65.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u65-e13e83e69c38.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u66-1dbcfa5da147.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u67-6b0a26b82da6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u68-1b8217cfceed.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u69-673e63627790.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6a-3c94008a4640.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6b-44572c410fa0.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6c-1b323b366e88.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6d-cbc376d6fe3f.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6e-7f7e211e2fee.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u6f-ff280ce052fa.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u70-cb727eaf73b6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u71-0bb1747439fe.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u72-cccd39590d69.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u73-e67705133584.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u74-03a4e7ac9df5.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u75-a2d30b327e65.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u76-c08fe029c988.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u77-76f03be1f4cb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u78-5b721deaf582.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u79-db7713c2eda4.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7a-3e40d3726a09.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7b-a8204d1c9dba.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7c-89bcdae046de.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7d-bcc45fa6351a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7e-49fab96d9269.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u7f-dc110e1ecbf3.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u80-c5ce9be0335e.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u81-b026091b3779.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u82-a7c6960e875d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u83-4b7d1c2c50d4.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u84-4119128100c8.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u85-ca43dd4c5411.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u86-54fc0e3158fb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u87-b64f5eb55720.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u88-00feb1251f08.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u89-db419435b316.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8a-06c11d45fb87.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8b-5c2f6fda436a.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8c-2b46d2d1f4cc.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8d-1a67d064000d.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8e-eb87baf0091c.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u8f-96c1f0632da6.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u90-86752dba8cbe.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u91-0573b8548552.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u92-f85ecf735974.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u93-e7e0451735db.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u94-f3957ff98bcd.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u95-b31129d29013.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u96-ca7f779c2c13.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u97-100a25198b2c.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u98-59e4b784a94f.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u99-d35af6e30868.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9a-1eb6f7cdfcfe.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9b-8f428d81dcbb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9c-e10f7f8f0274.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9e-2be65003b759.woff2 +0 -0
- package/assets/fonts/web/fangyuan-u9f-02c3d5224fdb.woff2 +0 -0
- package/assets/fonts/web/fangyuan-ufb-3eaee54bc476.woff2 +0 -0
- package/assets/fonts/web/fangyuan-ufe-f64da8a3acce.woff2 +0 -0
- package/assets/fonts/web/fangyuan-uff-0a6dcfb689fd.woff2 +0 -0
- package/client/assets/iconsax/LICENSE +16 -0
- package/client/assets/iconsax/SOURCE.md +14 -0
- package/config/routergo-deepseek.example.yml +25 -0
- package/config.example.json +24 -0
- package/cordis.patch.yml +5 -0
- package/lib/LINK-THIRD-PARTY-LICENSES.txt +1197 -0
- package/lib/client.js +3577 -0
- package/lib/link-web/app.css +92 -0
- package/lib/link-web/app.js +599 -0
- package/lib/link-web/index.html +1 -0
- package/lib/link-web/workbench.js +118 -0
- package/lib/link.mjs +244 -0
- package/lib/relay/ip-certificate.mjs +559 -0
- package/lib/relay/relay.example.json +8 -0
- package/lib/relay/run-relay-server.cjs +4100 -0
- package/lib/server/bootstrap.mjs +147 -0
- package/lib/server/bootstrap.sh +39 -0
- package/lib/server/manifest.json +8 -0
- package/lib/server/server-seed.tar.gz +0 -0
- package/locale/en.json +6 -0
- package/locale/zh-CN.json +6 -0
- package/openapi.json +629 -0
- package/package.json +104 -4
- package/shared/plugin-install.mjs +198 -0
- package/shared/tool-quick-settings.mjs +70 -0
- package/shared/update-feed.mjs +191 -0
- package/shared/update-public-key.pem +3 -0
- package/src/cli.mjs +102 -0
- package/src/commands.mjs +17 -0
- package/src/delegation.mjs +47 -0
- package/src/fonts.mjs +17 -0
- package/src/harness-adapter.mjs +228 -0
- package/src/harness-commands.mjs +39 -0
- package/src/harness-selection.mjs +5 -0
- package/src/harness-usage.mjs +31 -0
- package/src/isolated-launch.mjs +22 -0
- package/src/line-process.mjs +114 -0
- package/src/link-plugin.mjs +21 -0
- package/src/mcp.mjs +21 -0
- package/src/model-providers.mjs +125 -0
- package/src/models.mjs +37 -0
- package/src/native-auth.mjs +32 -0
- package/src/picker-catalog.mjs +82 -0
- package/src/plugin.mjs +138 -0
- package/src/projects.mjs +96 -0
- package/src/provider-access.mjs +11 -0
- package/src/providers/acp.mjs +138 -0
- package/src/providers/claude.mjs +115 -0
- package/src/providers/codex.mjs +134 -0
- package/src/providers/opencode.mjs +138 -0
- package/src/providers/pi-permissions.ts +24 -0
- package/src/providers/pi.mjs +97 -0
- package/src/questions.mjs +34 -0
- package/src/recent-workspace.mjs +36 -0
- package/src/runtime.mjs +421 -0
- package/src/session-routes.mjs +89 -0
- package/src/store.mjs +98 -0
- package/src/tool-catalog.mjs +22 -0
- package/src/tool-config.mjs +94 -0
- package/src/tool-install.mjs +31 -0
- package/src/tool-profiles.mjs +114 -0
- package/src/tooling.mjs +90 -0
- package/src/updates.mjs +156 -0
- package/src/usage-import-parsers.mjs +37 -0
- package/src/usage-import.mjs +114 -0
- package/src/usage-timeline.mjs +36 -0
- package/src/usage.mjs +142 -0
- package/src/web.mjs +274 -0
- package/voice/README.md +49 -0
- package/voice/STREAMING.md +141 -0
- package/voice/VERIFICATION.md +36 -0
- package/voice/client/audio-worklet.js +35 -0
- package/voice/locale/en.json +1 -0
- package/voice/locale/zh-CN.json +1 -0
- package/voice/src/asr.mjs +44 -0
- package/voice/src/calls.mjs +37 -0
- package/voice/src/config.mjs +139 -0
- package/voice/src/media.mjs +70 -0
- package/voice/src/plugin.mjs +10 -0
- package/voice/src/realtime-protocols.mjs +30 -0
- package/voice/src/realtime-socket.mjs +86 -0
- package/voice/src/realtime.mjs +130 -0
- package/voice/src/tts.mjs +16 -0
- package/voice/src/web.mjs +171 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/** Run only through a verified SSH channel. Final stdout JSON is a private protocol response. */
|
|
2
|
+
import { createHash, randomUUID, X509Certificate } from 'node:crypto';
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createServer } from 'node:net';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { request as httpsRequest } from 'node:https';
|
|
10
|
+
const source = process.argv[2] ?? 'mirror', operation = process.argv[3] ?? 'connect';
|
|
11
|
+
if (!['mirror', 'github'].includes(source) || !['connect', 'update'].includes(operation)) throw new Error('无效的服务器安装参数');
|
|
12
|
+
const assets = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const manifest = JSON.parse(await readFile(join(assets, 'manifest.json'), 'utf8'));
|
|
14
|
+
if (manifest.schemaVersion !== 1 || !/^[0-9a-f]{64}$/.test(manifest.sha256) || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(manifest.harnessVersion) || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(manifest.pluginVersion)) throw new Error('服务器安装清单无效');
|
|
15
|
+
const archive = join(assets, 'server-seed.tar.gz');
|
|
16
|
+
if (createHash('sha256').update(await readFile(archive)).digest('hex') !== manifest.sha256) throw new Error('服务器插件包校验失败');
|
|
17
|
+
const base = join(homedir(), '.local/share/benchgo/server');
|
|
18
|
+
const stateFile = join(base, 'installation.json'), ownerFile = join(base, 'owner.json');
|
|
19
|
+
const registry = source === 'mirror' ? 'https://registry.npmmirror.com' : 'https://registry.npmjs.org';
|
|
20
|
+
const wait = ms => new Promise(r => setTimeout(r, ms));
|
|
21
|
+
const present = async path => { try { await readFile(path); return true; } catch (e) { if (e.code === 'ENOENT') return false; throw e; } };
|
|
22
|
+
async function run(command, args, options = {}) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const child = spawn(command, args, { env: { ...process.env, DSH_TELEMETRY_DISABLED: '1', npm_config_registry: registry }, stdio: ['ignore', 'pipe', 'pipe'], ...options });
|
|
25
|
+
let output = '';
|
|
26
|
+
for (const stream of [child.stdout, child.stderr]) stream.on('data', data => { output = (output + data).slice(-12000); });
|
|
27
|
+
child.once('error', reject);
|
|
28
|
+
child.once('exit', code => {
|
|
29
|
+
if (code === 0) return resolve(output);
|
|
30
|
+
const detail = output.trim();
|
|
31
|
+
reject(new Error((command === 'systemctl' ? '服务器服务操作' : '安装依赖') + '失败(' + code + ')' + (detail ? ':\n' + detail : '')));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function atomic(path, value) { const tmp = path + '.' + randomUUID() + '.tmp'; await writeFile(tmp, value, { mode: 0o600 }); await rename(tmp, path); }
|
|
36
|
+
async function freePort() { const server = createServer(); await new Promise((r, j) => { server.once('error', j); server.listen(0, '127.0.0.1', r); }); const port = server.address().port; await new Promise(r => server.close(r)); return port; }
|
|
37
|
+
await mkdir(base, { recursive: true, mode: 0o700 });
|
|
38
|
+
let installed; try { installed = JSON.parse(await readFile(stateFile, 'utf8')); } catch (e) { if (e.code !== 'ENOENT') throw e; }
|
|
39
|
+
const lock = join(base, 'install.lock');
|
|
40
|
+
try { await mkdir(lock, { mode: 0o700 }); } catch (e) { if (e.code === 'EEXIST') throw new Error('已有服务器安装正在进行,请稍后重试'); throw e; }
|
|
41
|
+
try {
|
|
42
|
+
if (!installed || operation === 'update') {
|
|
43
|
+
if (installed && installed.harnessVersion !== manifest.harnessVersion) throw new Error('此包需要升级服务器引擎,请由维护者处理,现有数据已保留');
|
|
44
|
+
const runtime = join(base, 'runtimes', manifest.harnessVersion);
|
|
45
|
+
if (!(await present(join(runtime, 'node_modules/@deepseek-ai/dsh/package.json')))) {
|
|
46
|
+
process.stderr.write('安装固定版本 Harness…\n');
|
|
47
|
+
await mkdir(runtime, { recursive: true, mode: 0o700 });
|
|
48
|
+
await atomic(join(runtime, 'package.json'), JSON.stringify({ private: true, dependencies: {
|
|
49
|
+
'@deepseek-ai/dsh': manifest.harnessVersion,
|
|
50
|
+
'@deepseek-ai/cordis': '4.0.5-alpha.2',
|
|
51
|
+
'@deepseek-ai/dsh-atomic-write': manifest.harnessVersion,
|
|
52
|
+
'@deepseek-ai/dsh-llm': manifest.harnessVersion
|
|
53
|
+
} }));
|
|
54
|
+
await run('npm', ['install', '--prefix', runtime, '--omit=dev', '--no-audit', '--no-fund', '--registry', registry]);
|
|
55
|
+
}
|
|
56
|
+
const incoming = join(base, '.seed-' + randomUUID()); await mkdir(incoming, { mode: 0o700 });
|
|
57
|
+
try {
|
|
58
|
+
await run('tar', ['-xzf', archive, '-C', incoming, '--no-same-owner', '--no-same-permissions']);
|
|
59
|
+
const helper = await import(pathToFileURL(join(incoming, 'node_modules/benchgo/shared/plugin-install.mjs')).href);
|
|
60
|
+
const profile = join(base, 'home/profiles/web');
|
|
61
|
+
if (!(await present(join(profile, 'package.json')))) {
|
|
62
|
+
const boot = await import(pathToFileURL(join(runtime, 'node_modules/@deepseek-ai/dsh-app-boot/lib/index.js')).href);
|
|
63
|
+
boot.initProfile(profile, boot.PROFILE_TEMPLATES.web.bundles);
|
|
64
|
+
}
|
|
65
|
+
const release = await helper.prepareRelease({ directory: incoming, root: join(profile, '.benchgo/releases'), version: manifest.pluginVersion, runtime });
|
|
66
|
+
const webPort = installed?.webPort ?? await freePort(), port = installed?.port ?? await freePort();
|
|
67
|
+
const dataDir = installed?.dataDir ?? join(base, 'data');
|
|
68
|
+
if (!installed) await atomic(join(profile, 'cordis.patch.yml'), ['- id: commander', ' config:', ' dataDir: ' + JSON.stringify(dataDir), '- id: commander-link', ' config:', ' directory: ' + JSON.stringify(join(dataDir, 'link')), '- id: session-telemetry-otel', ' disabled: true', ''].join('\n'));
|
|
69
|
+
if (installed) {
|
|
70
|
+
const active = await authorize(installed, false);
|
|
71
|
+
const state = await linkJson(installed, '/link/api/rpc', { action: 'state' }, active.token);
|
|
72
|
+
await ownerAdmin(installed, { action: 'revoke', id: active.deviceId });
|
|
73
|
+
if (state.sessions?.some(session => session.running)) throw new Error('服务器有任务运行中,请结束后再升级');
|
|
74
|
+
}
|
|
75
|
+
await helper.activateRelease({ profile, release, enabled: true, appVersion: null, migrateLegacy: true });
|
|
76
|
+
installed = { schemaVersion: 1, harnessVersion: manifest.harnessVersion, pluginVersion: manifest.pluginVersion, runtime, profile, dataDir, webPort, port };
|
|
77
|
+
await atomic(stateFile, JSON.stringify(installed));
|
|
78
|
+
const launcher = '(' + launchServer.toString() + ')(' + [process.execPath, runtime, ownerFile, base, webPort].map(value => JSON.stringify(value)).join(',') + ');';
|
|
79
|
+
await atomic(join(base, 'start.mjs'), launcher);
|
|
80
|
+
const units = join(homedir(), '.config/systemd/user'); await mkdir(units, { recursive: true, mode: 0o700 });
|
|
81
|
+
const quote = value => JSON.stringify(String(value)).replaceAll('%', '%%');
|
|
82
|
+
const unit = ['[Unit]', 'Description=BenchGo server', 'After=network-online.target', '', '[Service]', 'Type=simple', 'UMask=0077', 'WorkingDirectory=' + base, 'ExecStart=' + quote(process.execPath) + ' ' + quote(join(base, 'start.mjs')), 'Environment=' + quote('PATH=' + process.env.PATH), 'Restart=on-failure', 'RestartSec=3', '', '[Install]', 'WantedBy=default.target', ''].join('\n');
|
|
83
|
+
await atomic(join(units, 'benchgo.service'), unit);
|
|
84
|
+
await run('systemctl', ['--user', 'daemon-reload']); await run('systemctl', ['--user', 'enable', 'benchgo.service']);
|
|
85
|
+
await rm(ownerFile, { force: true }); await run('systemctl', ['--user', 'restart', 'benchgo.service']);
|
|
86
|
+
} finally { await rm(incoming, { recursive: true, force: true }); }
|
|
87
|
+
} else { process.stderr.write('复用已安装的 BenchGo…\n'); await run('systemctl', ['--user', 'start', 'benchgo.service']); }
|
|
88
|
+
if (operation === 'update') {
|
|
89
|
+
let ready = false;
|
|
90
|
+
for (let i = 0; i < 100; i++) { try { const status = await ownerAdmin(installed, { action: 'status' }); if (status.enabled && status.port === installed.port && status.host === '127.0.0.1') { ready = true; break; } } catch {} await wait(500); }
|
|
91
|
+
if (!ready) throw new Error('服务器升级后未正常就绪,请查看服务日志');
|
|
92
|
+
}
|
|
93
|
+
const result = operation === 'update' ? { port: installed.port, pin: createHash('sha256').update(new X509Certificate(JSON.parse(await readFile(join(installed.dataDir, 'link/tls.json'), 'utf8')).cert).publicKey.export({ type: 'spki', format: 'der' })).digest('base64') } : await authorize(installed, true);
|
|
94
|
+
const linger = await run('loginctl', ['show-user', String(process.getuid()), '--property=Linger', '--value']).catch(() => 'unknown');
|
|
95
|
+
process.stdout.write(JSON.stringify({ ...result, version: installed.pluginVersion, persistent: linger.trim() === 'yes', ...(linger.trim() !== 'yes' ? { warning: '系统尚未启用用户常驻服务,退出所有 SSH 会话后服务可能停止;维护者可执行 loginctl enable-linger 用户名' } : {}) }) + '\n');
|
|
96
|
+
} finally { await rm(lock, { recursive: true, force: true }); }
|
|
97
|
+
async function ownerAdmin(state, input) {
|
|
98
|
+
const owner = JSON.parse(await readFile(ownerFile, 'utf8')); const url = new URL(owner.url);
|
|
99
|
+
if (url.hostname !== '127.0.0.1' || Number(url.port) !== state.webPort) throw new Error('服务器启动授权地址无效');
|
|
100
|
+
const login = await fetch(url, { redirect: 'manual', signal: AbortSignal.timeout(5000) });
|
|
101
|
+
const cookie = login.headers.getSetCookie().map(value => value.split(';')[0]).join('; '); if (!cookie) throw new Error('服务器尚未准备好');
|
|
102
|
+
const response = await fetch(url.origin + '/api/commander-link', { method: 'POST', headers: { cookie, 'content-type': 'application/json', origin: url.origin }, body: JSON.stringify(input), signal: AbortSignal.timeout(10000) });
|
|
103
|
+
const value = await response.json(); if (!response.ok) throw new Error(value.error ?? '服务器初始化失败'); return value;
|
|
104
|
+
}
|
|
105
|
+
async function linkJson(state, path, value, token) {
|
|
106
|
+
const identity = JSON.parse(await readFile(join(state.dataDir, 'link/tls.json'), 'utf8'));
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const request = httpsRequest('https://127.0.0.1:' + state.port + path, { method: 'POST', ca: identity.cert, headers: { 'content-type': 'application/json', ...(token ? { authorization: 'Bearer ' + token } : {}) }, timeout: 10000 }, response => {
|
|
109
|
+
let data = ''; response.on('data', chunk => { data += chunk; }); response.on('end', () => { try { const result = JSON.parse(data); response.statusCode < 400 ? resolve(result) : reject(new Error(result.error ?? '服务器授权失败')); } catch (error) { reject(error); } });
|
|
110
|
+
}); request.on('error', reject); request.on('timeout', () => request.destroy(new Error('服务器授权超时'))); request.end(JSON.stringify(value));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function authorize(state, retry) {
|
|
114
|
+
let failure;
|
|
115
|
+
for (let i = 0; i < (retry ? 100 : 1); i++) {
|
|
116
|
+
try {
|
|
117
|
+
let status = await ownerAdmin(state, { action: 'status' });
|
|
118
|
+
if (!status.enabled) status = await ownerAdmin(state, { action: 'configure', enabled: true, host: '127.0.0.1', port: state.port, publicUrl: '' });
|
|
119
|
+
if (status.host !== '127.0.0.1' || status.port !== state.port) throw new Error('服务器 Link 监听配置已改变,请由维护者检查');
|
|
120
|
+
const pair = await ownerAdmin(state, { action: 'pairing', mode: 'lan' });
|
|
121
|
+
const pending = await linkJson(state, '/link/api/pair', { code: pair.code, name: 'SSH 设备-' + randomUUID().slice(0, 5) });
|
|
122
|
+
await ownerAdmin(state, { action: 'confirm', id: pending.requestId });
|
|
123
|
+
const approved = await linkJson(state, '/link/api/pair/status', pending);
|
|
124
|
+
const tls = JSON.parse(await readFile(join(state.dataDir, 'link/tls.json'), 'utf8'));
|
|
125
|
+
const pin = createHash('sha256').update(new X509Certificate(tls.cert).publicKey.export({ type: 'spki', format: 'der' })).digest('base64');
|
|
126
|
+
return { port: state.port, pin, token: approved.token, deviceId: approved.deviceId };
|
|
127
|
+
} catch (error) { failure = error; if (retry) await wait(500); }
|
|
128
|
+
}
|
|
129
|
+
throw new Error('服务器启动或授权失败:' + (failure?.message ?? '未知错误'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function launchServer(node, runtime, ownerFile, base, webPort) {
|
|
133
|
+
Promise.all([import('node:child_process'), import('node:fs'), import('node:path')]).then(([{ spawn }, { writeFileSync, renameSync }, { join }]) => {
|
|
134
|
+
const child = spawn(node, [join(runtime, 'node_modules/@deepseek-ai/dsh/lib/bin.js'), '--profile', 'web', '--host', '127.0.0.1', '--port', String(webPort), '--no-open'], { env: { ...process.env, DSH_HOME: join(base, 'home'), DSH_TELEMETRY_DISABLED: '1' }, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
135
|
+
let pending = '';
|
|
136
|
+
function log(data) {
|
|
137
|
+
pending = (pending + data.toString()).slice(-65536);
|
|
138
|
+
const match = pending.match(/dsh web: (http:\/\/127\.0\.0\.1:\d+\/\?token=[^\s]+)/);
|
|
139
|
+
if (match) { writeFileSync(ownerFile + '.tmp', JSON.stringify({ url: match[1] }), { mode: 0o600 }); renameSync(ownerFile + '.tmp', ownerFile); }
|
|
140
|
+
const lines = pending.split(/\r?\n/); pending = lines.pop();
|
|
141
|
+
for (const line of lines) process.stdout.write(line.replace(/([?&]token=)[^\s&]+/g, '$1[redacted]').replace(/Bearer\s+[A-Za-z0-9._-]+/g, 'Bearer [redacted]') + '\n');
|
|
142
|
+
}
|
|
143
|
+
child.stdout.on('data', log); child.stderr.on('data', log);
|
|
144
|
+
child.on('error', () => process.exit(1)); child.on('exit', code => process.exit(code ?? 1));
|
|
145
|
+
for (const signal of ['SIGTERM', 'SIGINT']) process.on(signal, () => child.kill(signal));
|
|
146
|
+
}).catch(() => process.exit(1));
|
|
147
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
umask 077
|
|
4
|
+
source_name=${1:-mirror}
|
|
5
|
+
operation=${2:-connect}
|
|
6
|
+
case "$source_name" in mirror|github) ;; *) echo '无效下载源' >&2; exit 1;; esac
|
|
7
|
+
case "$operation" in connect|update) ;; *) echo '无效安装操作' >&2; exit 1;; esac
|
|
8
|
+
case "$(uname -s)" in Linux) ;; *) echo '服务器首版仅支持 Linux' >&2; exit 1;; esac
|
|
9
|
+
case "$(uname -m)" in x86_64) node_arch=x64;; aarch64|arm64) node_arch=arm64;; *) echo '仅支持 x64 和 arm64' >&2; exit 1;; esac
|
|
10
|
+
if [ ! -f /etc/os-release ]; then echo '无法识别服务器发行版' >&2; exit 1; fi
|
|
11
|
+
. /etc/os-release
|
|
12
|
+
case " ${ID:-} ${ID_LIKE:-} " in *' debian '*|*' ubuntu '*) ;; *) echo '首版仅支持 Ubuntu / Debian 系统' >&2; exit 1;; esac
|
|
13
|
+
command -v systemctl >/dev/null || { echo '需要 systemd 用户服务' >&2; exit 1; }
|
|
14
|
+
export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
|
15
|
+
export DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS:-unix:path=$XDG_RUNTIME_DIR/bus}
|
|
16
|
+
systemctl --user show-environment >/dev/null 2>&1 || { echo 'systemd 用户服务不可用;请启用用户会话服务后重试' >&2; exit 1; }
|
|
17
|
+
node_root="$HOME/.local/share/benchgo/node"
|
|
18
|
+
node_binary=$(command -v node || true)
|
|
19
|
+
if [ -z "$node_binary" ] || ! "$node_binary" -e 'const [a,b,c]=process.versions.node.split(".").map(Number);process.exit(a>=24||(a===22&&(b>19||(b===19&&c>=0)))?0:1)' >/dev/null 2>&1; then
|
|
20
|
+
node_version=22.22.1
|
|
21
|
+
node_file="node-v$node_version-linux-$node_arch.tar.xz"
|
|
22
|
+
command -v curl >/dev/null || { echo '安装 Node.js 需要 curl' >&2; exit 1; }
|
|
23
|
+
command -v sha256sum >/dev/null || { echo '安装 Node.js 需要 sha256sum' >&2; exit 1; }
|
|
24
|
+
work=$(mktemp -d)
|
|
25
|
+
trap 'rm -rf -- "$work"' EXIT
|
|
26
|
+
node_url="https://nodejs.org/dist/v$node_version"
|
|
27
|
+
if [ "$source_name" = mirror ]; then node_url="https://npmmirror.com/mirrors/node/v$node_version"; fi
|
|
28
|
+
echo '下载并校验 Node.js…' >&2
|
|
29
|
+
curl --proto '=https' --tlsv1.2 -fsSL --connect-timeout 15 --max-time 300 "$node_url/$node_file" -o "$work/$node_file"
|
|
30
|
+
curl --proto '=https' --tlsv1.2 -fsSL --connect-timeout 15 --max-time 60 "https://nodejs.org/dist/v$node_version/SHASUMS256.txt" -o "$work/SHASUMS256.txt"
|
|
31
|
+
checksum=$(awk -v name="$node_file" '$2==name { print }' "$work/SHASUMS256.txt")
|
|
32
|
+
[ -n "$checksum" ] || { echo '未找到 Node.js 官方校验值' >&2; exit 1; }
|
|
33
|
+
(cd "$work" && echo "$checksum" | sha256sum --check --status)
|
|
34
|
+
mkdir -p -- "$node_root"
|
|
35
|
+
tar -xJf "$work/$node_file" -C "$node_root" --strip-components=1
|
|
36
|
+
node_binary="$node_root/bin/node"
|
|
37
|
+
fi
|
|
38
|
+
export PATH="$(dirname "$node_binary"):$PATH"
|
|
39
|
+
exec "$node_binary" "$(dirname "$0")/bootstrap.mjs" "$source_name" "$operation"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"harnessVersion": "0.2.1-alpha.3",
|
|
4
|
+
"harnessCommit": "571902028a75c64c1d767f4ae5fd099485f0ddf0",
|
|
5
|
+
"pluginVersion": "0.0.1",
|
|
6
|
+
"bundle": "@benchgo/desktop-ee51afda5e1e26e6",
|
|
7
|
+
"sha256": "8a2dae05b9cdadf4142d45b8b3c1153a8e25f2ce1ddea7ab3cff5dcc3bd32a44"
|
|
8
|
+
}
|
|
Binary file
|
package/locale/en.json
ADDED