@hakimedes/dsh-easyremote 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/android.js +50 -0
- package/dist/android.js.map +1 -0
- package/dist/autostart.js +104 -0
- package/dist/autostart.js.map +1 -0
- package/dist/cli-views.js +32 -0
- package/dist/cli-views.js.map +1 -0
- package/dist/cli.js +584 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloudflared.js +54 -0
- package/dist/cloudflared.js.map +1 -0
- package/dist/command-router.js +12 -0
- package/dist/command-router.js.map +1 -0
- package/dist/connector-install.js +42 -0
- package/dist/connector-install.js.map +1 -0
- package/dist/controller.js +206 -0
- package/dist/controller.js.map +1 -0
- package/dist/doctor.js +46 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain.js +33 -0
- package/dist/domain.js.map +1 -0
- package/dist/download.js +59 -0
- package/dist/download.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/install-state.js +48 -0
- package/dist/install-state.js.map +1 -0
- package/dist/local-runtime.js +115 -0
- package/dist/local-runtime.js.map +1 -0
- package/dist/maintenance.js +58 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/named-tunnel.js +49 -0
- package/dist/named-tunnel.js.map +1 -0
- package/dist/runtime.js +110 -0
- package/dist/runtime.js.map +1 -0
- package/dist/setup-progress.js +23 -0
- package/dist/setup-progress.js.map +1 -0
- package/dist/supervisor.js +81 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/wizard.js +251 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +56 -0
- package/runtime/connector/README.md +52 -0
- package/runtime/connector/cordis.patch.yml +3 -0
- package/runtime/connector/dsh.plugin.json +16 -0
- package/runtime/connector/lib/client.js +262 -0
- package/runtime/connector/lib/command-cache.d.ts +7 -0
- package/runtime/connector/lib/command-cache.js +37 -0
- package/runtime/connector/lib/command-cache.js.map +1 -0
- package/runtime/connector/lib/connector-config.d.ts +20 -0
- package/runtime/connector/lib/connector-config.js +74 -0
- package/runtime/connector/lib/connector-config.js.map +1 -0
- package/runtime/connector/lib/dsh-api.d.ts +38 -0
- package/runtime/connector/lib/dsh-api.js +76 -0
- package/runtime/connector/lib/dsh-api.js.map +1 -0
- package/runtime/connector/lib/index.d.ts +4 -0
- package/runtime/connector/lib/index.js +913 -0
- package/runtime/connector/lib/index.js.map +1 -0
- package/runtime/connector/lib/protocol.d.ts +15 -0
- package/runtime/connector/lib/protocol.js +96 -0
- package/runtime/connector/lib/protocol.js.map +1 -0
- package/runtime/connector/package.json +44 -0
- package/runtime/hub/database.js +239 -0
- package/runtime/hub/database.js.map +1 -0
- package/runtime/hub/index.js +1804 -0
- package/runtime/hub/index.js.map +1 -0
- package/runtime/hub/schema.js +111 -0
- package/runtime/hub/schema.js.map +1 -0
package/dist/wizard.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import QRCode from 'qrcode';
|
|
4
|
+
const SESSION_COOKIE = 'dsh_easyremote_session';
|
|
5
|
+
export function wizardListenOptions() {
|
|
6
|
+
return { host: '127.0.0.1', port: 0 };
|
|
7
|
+
}
|
|
8
|
+
export function createWizardSecrets() {
|
|
9
|
+
return {
|
|
10
|
+
sessionToken: randomBytes(32).toString('base64url'),
|
|
11
|
+
csrfToken: randomBytes(32).toString('base64url'),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function authorizeWizardRequest(input) {
|
|
15
|
+
if (input.host !== input.expectedHost)
|
|
16
|
+
return { ok: false, status: 403, message: 'Invalid Host header' };
|
|
17
|
+
const cookies = parseCookies(input.cookie);
|
|
18
|
+
if (cookies.get(SESSION_COOKIE) !== input.sessionToken) {
|
|
19
|
+
return { ok: false, status: 401, message: 'Wizard session required' };
|
|
20
|
+
}
|
|
21
|
+
if (!['GET', 'HEAD', 'OPTIONS'].includes(input.method.toUpperCase())) {
|
|
22
|
+
if (input.origin !== `http://${input.expectedHost}`) {
|
|
23
|
+
return { ok: false, status: 403, message: 'Invalid request origin' };
|
|
24
|
+
}
|
|
25
|
+
if (input.csrf !== input.csrfToken)
|
|
26
|
+
return { ok: false, status: 403, message: 'Invalid CSRF token' };
|
|
27
|
+
}
|
|
28
|
+
return { ok: true };
|
|
29
|
+
}
|
|
30
|
+
function parseCookies(header = '') {
|
|
31
|
+
const cookies = new Map();
|
|
32
|
+
for (const item of header.split(';')) {
|
|
33
|
+
const separator = item.indexOf('=');
|
|
34
|
+
if (separator <= 0)
|
|
35
|
+
continue;
|
|
36
|
+
cookies.set(item.slice(0, separator).trim(), item.slice(separator + 1).trim());
|
|
37
|
+
}
|
|
38
|
+
return cookies;
|
|
39
|
+
}
|
|
40
|
+
export class WizardActionGate {
|
|
41
|
+
used = new Set();
|
|
42
|
+
async run(requestId, task) {
|
|
43
|
+
if (!requestId || this.used.has(requestId))
|
|
44
|
+
throw new Error('Action request ID was already used');
|
|
45
|
+
this.used.add(requestId);
|
|
46
|
+
if (this.used.size > 256)
|
|
47
|
+
this.used.delete(this.used.values().next().value);
|
|
48
|
+
return task();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function renderWizardHtml(options) {
|
|
52
|
+
const csrf = JSON.stringify(options.csrfToken).replaceAll('<', '\\u003c');
|
|
53
|
+
const routeCards = options.controlMode ? `
|
|
54
|
+
<section class="modes" style="grid-template-columns:1fr">
|
|
55
|
+
<article class="mode"><div class="num">LOCAL / ONLINE</div><h2>本机控制台</h2><p>当前 Hub 与 Tunnel 状态会显示在下方。运行 setup 可重新进入配置或从快速模式升级到固定域名。</p></article>
|
|
56
|
+
</section>` : `
|
|
57
|
+
<section class="modes">
|
|
58
|
+
<article class="mode"><div class="num">ROUTE / 01</div><h2>快速启动</h2><p>无需账号与域名。本机 Hub 通过一次性 Cloudflare 临时隧道上线。</p><button class="button primary" data-action="quick">生成临时连接</button></article>
|
|
59
|
+
<article class="mode"><div class="num">ROUTE / 02</div><h2>深度配置</h2><p>填写固定域名,完成 Cloudflare 授权后自动创建 Named Tunnel 与 DNS。</p><button class="button" data-action="deep">配置固定域名</button></article>
|
|
60
|
+
</section>`;
|
|
61
|
+
const deepPanel = options.controlMode ? '' : `
|
|
62
|
+
<section class="deep" id="deep" hidden>
|
|
63
|
+
<div class="steps"><div class="step active">01 · 域名</div><div class="step">02 · Nameserver</div><div class="step">03 · 授权</div><div class="step">04 · 完成</div></div>
|
|
64
|
+
<div class="fields">
|
|
65
|
+
<label>根域名<input id="rootDomain" placeholder="example.com" autocomplete="off"></label>
|
|
66
|
+
<label>公开地址<input id="hostname" placeholder="dsh.example.com" autocomplete="off"></label>
|
|
67
|
+
<label>Cloudflare NS 1<input id="ns1" placeholder="name.ns.cloudflare.com" autocomplete="off"></label>
|
|
68
|
+
<label>Cloudflare NS 2<input id="ns2" placeholder="name.ns.cloudflare.com" autocomplete="off"></label>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="actions"><button class="button" data-action="save-domain">保存域名</button><button class="button" data-action="check-ns">检查 Nameserver</button><button class="button" data-action="authorize">Cloudflare 授权</button><button class="button primary" data-action="provision">创建固定连接</button></div>
|
|
71
|
+
</section>`;
|
|
72
|
+
const apkCard = options.apkUrl && options.apkQrSvg ? `
|
|
73
|
+
<section class="apk">
|
|
74
|
+
<div><div class="num">ANDROID / COMMUNITY</div><h2>Community APK</h2><p>扫码下载支持任意 HTTPS Hub 的开源版本;可与内部固定域名版本同时安装。</p><div class="actions"><a class="button" href="${escapeHtml(options.apkUrl)}" target="_blank" rel="noreferrer">下载 APK</a><button class="button" data-action="install-adb">ADB 一键安装</button></div></div>
|
|
75
|
+
<div class="apkqr" aria-label="Community APK download QR">${options.apkQrSvg}</div>
|
|
76
|
+
</section>` : '';
|
|
77
|
+
return `<!doctype html>
|
|
78
|
+
<html lang="zh-CN">
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="utf-8">
|
|
81
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
82
|
+
<meta name="color-scheme" content="dark">
|
|
83
|
+
<title>DSH EasyRemote · ${options.controlMode ? '本机控制台' : '本机引导'}</title>
|
|
84
|
+
<style>
|
|
85
|
+
:root{--ink:#050708;--panel:#0b0f12;--line:rgba(135,170,188,.18);--text:#edf7fb;--muted:#82949d;--cyan:#54d6ff;--blue:#4a78ff;--ok:#72e5bc;--danger:#ff7d79}
|
|
86
|
+
*{box-sizing:border-box}html{background:var(--ink)}body{margin:0;min-height:100vh;color:var(--text);font-family:"Avenir Next","DIN Alternate","Segoe UI",sans-serif;background:radial-gradient(circle at 70% 15%,rgba(36,113,145,.18),transparent 35%),linear-gradient(145deg,#030506 0%,#081015 54%,#030506 100%);overflow-x:hidden}
|
|
87
|
+
body:before{content:"";position:fixed;inset:0;pointer-events:none;opacity:.25;background-image:linear-gradient(rgba(93,179,215,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(93,179,215,.05) 1px,transparent 1px);background-size:44px 44px;mask-image:linear-gradient(to bottom,black,transparent 80%)}
|
|
88
|
+
.shell{width:min(1120px,calc(100% - 36px));margin:0 auto;padding:48px 0 70px}.mast{display:grid;grid-template-columns:1.1fr .9fr;gap:34px;align-items:center;min-height:310px;border-bottom:1px solid var(--line)}
|
|
89
|
+
.eyebrow{font:700 11px/1 ui-monospace,SFMono-Regular,monospace;letter-spacing:.24em;color:var(--cyan);text-transform:uppercase}.mast h1{font-size:clamp(42px,7vw,92px);line-height:.91;letter-spacing:-.055em;margin:20px 0 22px;max-width:720px}.mast p{max-width:620px;color:#a3b3ba;font-size:17px;line-height:1.75}
|
|
90
|
+
.sonar{width:min(310px,72vw);aspect-ratio:1;margin:auto;position:relative;display:grid;place-items:center}.ring{position:absolute;border:1px solid rgba(84,214,255,.26);border-radius:50%;animation:pulse 5s ease-in-out infinite}.ring:nth-child(1){inset:4%}.ring:nth-child(2){inset:17%;animation-delay:-1.7s}.ring:nth-child(3){inset:30%;animation-delay:-3.4s}.beam{position:absolute;inset:4%;border-radius:50%;background:conic-gradient(from 10deg,transparent 0 80%,rgba(84,214,255,.18) 96%,transparent);animation:sweep 8s linear infinite}.whale{position:relative;width:108px;height:74px;filter:drop-shadow(0 0 26px rgba(84,214,255,.3));animation:float 4s ease-in-out infinite}.whale:before{content:"";position:absolute;inset:13px 8px 9px 5px;background:#020303;border:2px solid var(--cyan);border-radius:62% 44% 57% 45%/62% 48% 52% 38%;transform:rotate(-7deg)}.whale:after{content:"";position:absolute;width:34px;height:36px;right:-12px;top:4px;border-top:3px solid var(--cyan);border-right:3px solid var(--cyan);transform:rotate(30deg) skew(-18deg);border-radius:0 80% 0 0}
|
|
91
|
+
.modes{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:30px}.mode{position:relative;min-height:260px;padding:30px;border:1px solid var(--line);background:linear-gradient(160deg,rgba(16,24,28,.94),rgba(7,10,12,.9));border-radius:24px;overflow:hidden;transition:.25s transform,.25s border-color}.mode:hover{transform:translateY(-3px);border-color:rgba(84,214,255,.55)}.mode:after{content:"";position:absolute;width:180px;height:180px;border:1px solid var(--line);border-radius:50%;right:-76px;bottom:-92px}.num{font:700 11px/1 ui-monospace,monospace;color:var(--muted);letter-spacing:.18em}.mode h2{font-size:30px;margin:52px 0 10px;letter-spacing:-.03em}.mode p{color:var(--muted);line-height:1.65;min-height:52px}.button{appearance:none;border:1px solid rgba(84,214,255,.45);border-radius:999px;color:var(--text);background:rgba(84,214,255,.08);padding:12px 18px;font:700 14px/1 inherit;cursor:pointer;transition:.2s}.button:hover{background:var(--cyan);color:#031015}.button.primary{background:linear-gradient(100deg,var(--blue),var(--cyan));color:#041014;border:0}.button:disabled{opacity:.45;cursor:wait}
|
|
92
|
+
.deep{margin-top:20px;border:1px solid var(--line);border-radius:24px;padding:28px;background:rgba(4,8,10,.82)}.deep[hidden]{display:none}.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:24px}.step{border-top:2px solid var(--line);padding-top:10px;color:var(--muted);font:650 12px/1.4 ui-monospace,monospace}.step.active{border-color:var(--cyan);color:var(--text)}.fields{display:grid;grid-template-columns:1fr 1fr;gap:14px}label{display:grid;gap:7px;color:#a9bac1;font-size:13px}input{width:100%;border:1px solid var(--line);border-radius:13px;background:#070b0d;color:var(--text);padding:13px 14px;font:500 15px/1.2 ui-monospace,monospace;outline:none}input:focus{border-color:var(--cyan);box-shadow:0 0 0 3px rgba(84,214,255,.08)}.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
|
|
93
|
+
.apk{display:grid;grid-template-columns:1fr 170px;gap:24px;align-items:center;margin-top:20px;border:1px solid var(--line);border-radius:24px;padding:28px;background:linear-gradient(120deg,rgba(74,120,255,.09),rgba(84,214,255,.025))}.apk h2{font-size:28px;margin:18px 0 8px}.apk p{color:var(--muted);line-height:1.6}.apkqr{background:#fff;border-radius:16px;padding:10px;line-height:0}.apkqr svg{width:100%;height:auto}.apk a{text-decoration:none;display:inline-flex;align-items:center}
|
|
94
|
+
.console{margin-top:20px;border-left:2px solid var(--cyan);padding:12px 16px;color:#a9bbc2;background:rgba(84,214,255,.035);font:500 12px/1.65 ui-monospace,SFMono-Regular,monospace;min-height:48px;white-space:pre-wrap}.footer{display:flex;justify-content:space-between;margin-top:24px;color:#60727a;font:600 11px/1.5 ui-monospace,monospace;letter-spacing:.08em}
|
|
95
|
+
@keyframes sweep{to{transform:rotate(360deg)}}@keyframes pulse{50%{transform:scale(1.025);border-color:rgba(84,214,255,.48)}}@keyframes float{50%{transform:translateY(-8px) rotate(-2deg)}}
|
|
96
|
+
@media(max-width:760px){.shell{padding-top:26px}.mast{grid-template-columns:1fr}.sonar{order:-1;width:210px}.modes,.fields,.apk{grid-template-columns:1fr}.apkqr{width:170px}.mode{min-height:220px}.steps{grid-template-columns:1fr 1fr}.mast h1{font-size:52px}}
|
|
97
|
+
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
|
98
|
+
</style>
|
|
99
|
+
</head>
|
|
100
|
+
<body>
|
|
101
|
+
<main class="shell">
|
|
102
|
+
<section class="mast">
|
|
103
|
+
<div><div class="eyebrow">Local control plane · v${escapeHtml(options.version)}</div><h1>探索未至之境</h1><p>${options.controlMode ? 'Hub 与 Tunnel 正在这台电脑上运行。关闭本页不会改变已启动的连接。' : 'Hub 始终留在这台电脑。选择临时声道即刻连接,或用固定域名建立一条长期航线。'}</p></div>
|
|
104
|
+
<div class="sonar" aria-label="DSH black whale sonar"><div class="ring"></div><div class="ring"></div><div class="ring"></div><div class="beam"></div><div class="whale"></div></div>
|
|
105
|
+
</section>
|
|
106
|
+
${routeCards}
|
|
107
|
+
${apkCard}
|
|
108
|
+
${deepPanel}
|
|
109
|
+
<div class="console" id="console" role="status">本机控制面已就绪。请选择连接路径。</div>
|
|
110
|
+
<div class="footer"><span>127.0.0.1 ONLY</span><span>NO SSH · NO REMOTE HUB · NO CLOUDFLARE PASSWORD</span></div>
|
|
111
|
+
</main>
|
|
112
|
+
<script>
|
|
113
|
+
const csrf=${csrf};const out=document.getElementById('console');const deep=document.getElementById('deep');
|
|
114
|
+
const values=()=>({rootDomain:document.getElementById('rootDomain')?.value||'',hostname:document.getElementById('hostname')?.value||'',nameservers:[document.getElementById('ns1')?.value||'',document.getElementById('ns2')?.value||'']});
|
|
115
|
+
async function call(action,body={}){const requestId=crypto.randomUUID();document.querySelectorAll('button').forEach(b=>b.disabled=true);out.textContent='执行 '+action+'…';try{const response=await fetch('/api/'+action,{method:'POST',credentials:'same-origin',headers:{'content-type':'application/json','x-dsh-csrf':csrf,'x-dsh-request-id':requestId},body:JSON.stringify(body)});const data=await response.json();if(!response.ok)throw new Error(data.error||('HTTP '+response.status));out.textContent=data.message||JSON.stringify(data,null,2);return data}catch(error){out.textContent='失败:'+error.message;throw error}finally{document.querySelectorAll('button').forEach(b=>b.disabled=false)}}
|
|
116
|
+
document.addEventListener('click',event=>{const button=event.target.closest('[data-action]');if(!button)return;const action=button.dataset.action;if(action==='deep'){deep.hidden=false;deep.scrollIntoView({behavior:'smooth'});return}if(action==='quick')call('quick');else if(action==='save-domain')call('named/domain',values());else if(action==='check-ns')call('named/check-ns',values());else if(action==='authorize')call('named/authorize',values());else if(action==='provision')call('named/provision',values());else if(action==='install-adb')call('apk/install-adb');});
|
|
117
|
+
fetch('/api/state',{credentials:'same-origin'}).then(r=>r.json()).then(data=>{if(deep&&data.progress?.mode==='named'){deep.hidden=false;for(const id of ['rootDomain','hostname'])if(data.progress[id])document.getElementById(id).value=data.progress[id];(data.progress.nameservers||[]).forEach((v,i)=>{const n=document.getElementById('ns'+(i+1));if(n)n.value=v});}if(data.message)out.textContent=data.message}).catch(()=>{});
|
|
118
|
+
</script>
|
|
119
|
+
</body></html>`;
|
|
120
|
+
}
|
|
121
|
+
function escapeHtml(value) {
|
|
122
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
|
123
|
+
}
|
|
124
|
+
export async function startWizardServer(options) {
|
|
125
|
+
const secrets = createWizardSecrets();
|
|
126
|
+
const gate = new WizardActionGate();
|
|
127
|
+
let bootstrapConsumed = false;
|
|
128
|
+
const apkUrl = options.apkUrl ?? 'https://github.com/hakimedes/dsh-easyremote/releases/latest/download/DSH-EasyRemote-Community.apk';
|
|
129
|
+
const apkQrSvg = await QRCode.toString(apkUrl, { type: 'svg', margin: 1, width: 180 });
|
|
130
|
+
let expectedHost = '';
|
|
131
|
+
const server = createServer(async (request, response) => {
|
|
132
|
+
try {
|
|
133
|
+
const url = new URL(request.url || '/', `http://${request.headers.host || '127.0.0.1'}`);
|
|
134
|
+
if (request.method === 'GET' && url.pathname === '/'
|
|
135
|
+
&& request.headers.host === expectedHost
|
|
136
|
+
&& !bootstrapConsumed
|
|
137
|
+
&& url.searchParams.get('session') === secrets.sessionToken) {
|
|
138
|
+
bootstrapConsumed = true;
|
|
139
|
+
response.writeHead(303, {
|
|
140
|
+
location: '/',
|
|
141
|
+
'set-cookie': `${SESSION_COOKIE}=${secrets.sessionToken}; HttpOnly; SameSite=Strict; Path=/`,
|
|
142
|
+
'cache-control': 'no-store',
|
|
143
|
+
'x-content-type-options': 'nosniff',
|
|
144
|
+
});
|
|
145
|
+
response.end();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const authorization = authorizeWizardRequest({
|
|
149
|
+
method: request.method || 'GET',
|
|
150
|
+
host: request.headers.host,
|
|
151
|
+
expectedHost,
|
|
152
|
+
sessionToken: secrets.sessionToken,
|
|
153
|
+
csrfToken: secrets.csrfToken,
|
|
154
|
+
cookie: request.headers.cookie,
|
|
155
|
+
csrf: headerValue(request, 'x-dsh-csrf'),
|
|
156
|
+
origin: request.headers.origin,
|
|
157
|
+
});
|
|
158
|
+
if (!authorization.ok)
|
|
159
|
+
return sendJson(response, authorization.status, { error: authorization.message });
|
|
160
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
161
|
+
response.writeHead(200, {
|
|
162
|
+
'content-type': 'text/html; charset=utf-8',
|
|
163
|
+
'cache-control': 'no-store',
|
|
164
|
+
'content-security-policy': "default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; img-src 'self'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'",
|
|
165
|
+
'referrer-policy': 'no-referrer',
|
|
166
|
+
'x-content-type-options': 'nosniff',
|
|
167
|
+
'x-frame-options': 'DENY',
|
|
168
|
+
});
|
|
169
|
+
response.end(renderWizardHtml({ csrfToken: secrets.csrfToken, version: options.version, apkUrl, apkQrSvg, controlMode: options.controlMode }));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (request.method === 'GET' && url.pathname === '/api/state') {
|
|
173
|
+
return sendJson(response, 200, await options.getState());
|
|
174
|
+
}
|
|
175
|
+
if (request.method === 'POST' && url.pathname.startsWith('/api/')) {
|
|
176
|
+
const actionName = url.pathname.slice('/api/'.length);
|
|
177
|
+
const action = options.actions[actionName];
|
|
178
|
+
if (!action)
|
|
179
|
+
return sendJson(response, 404, { error: 'Unknown wizard action' });
|
|
180
|
+
const requestId = headerValue(request, 'x-dsh-request-id');
|
|
181
|
+
if (!requestId)
|
|
182
|
+
return sendJson(response, 400, { error: 'Action request ID required' });
|
|
183
|
+
const body = await readJsonBody(request);
|
|
184
|
+
const result = await gate.run(requestId, () => action(body));
|
|
185
|
+
return sendJson(response, 200, result ?? { ok: true });
|
|
186
|
+
}
|
|
187
|
+
sendJson(response, 404, { error: 'Not found' });
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
sendJson(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
await new Promise((resolve, reject) => {
|
|
194
|
+
server.once('error', reject);
|
|
195
|
+
server.listen(wizardListenOptions(), () => resolve());
|
|
196
|
+
});
|
|
197
|
+
const address = server.address();
|
|
198
|
+
if (!address || typeof address === 'string')
|
|
199
|
+
throw new Error('Wizard failed to bind to loopback');
|
|
200
|
+
expectedHost = `127.0.0.1:${address.port}`;
|
|
201
|
+
const origin = `http://${expectedHost}`;
|
|
202
|
+
return {
|
|
203
|
+
origin,
|
|
204
|
+
launchUrl: `${origin}/?session=${encodeURIComponent(secrets.sessionToken)}`,
|
|
205
|
+
close: () => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function headerValue(request, name) {
|
|
209
|
+
const value = request.headers[name];
|
|
210
|
+
return Array.isArray(value) ? value[0] : value;
|
|
211
|
+
}
|
|
212
|
+
function sendJson(response, status, value) {
|
|
213
|
+
if (response.headersSent)
|
|
214
|
+
return;
|
|
215
|
+
response.writeHead(status, {
|
|
216
|
+
'content-type': 'application/json; charset=utf-8',
|
|
217
|
+
'cache-control': 'no-store',
|
|
218
|
+
'x-content-type-options': 'nosniff',
|
|
219
|
+
});
|
|
220
|
+
response.end(JSON.stringify(value));
|
|
221
|
+
}
|
|
222
|
+
function readJsonBody(request, maxBytes = 64 * 1024) {
|
|
223
|
+
return new Promise((resolve, reject) => {
|
|
224
|
+
const chunks = [];
|
|
225
|
+
let size = 0;
|
|
226
|
+
request.on('data', (chunk) => {
|
|
227
|
+
size += chunk.length;
|
|
228
|
+
if (size > maxBytes) {
|
|
229
|
+
reject(new Error('Request body is too large'));
|
|
230
|
+
request.destroy();
|
|
231
|
+
}
|
|
232
|
+
else
|
|
233
|
+
chunks.push(chunk);
|
|
234
|
+
});
|
|
235
|
+
request.once('error', reject);
|
|
236
|
+
request.once('end', () => {
|
|
237
|
+
try {
|
|
238
|
+
if (!chunks.length)
|
|
239
|
+
return resolve({});
|
|
240
|
+
const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
241
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
242
|
+
throw new Error('JSON body must be an object');
|
|
243
|
+
resolve(value);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
reject(error);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=wizard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../src/wizard.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hakimedes/dsh-easyremote",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Local-first DSH EasyRemote installer, Hub and Cloudflare Tunnel supervisor",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dsh-easyremote": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.js",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"runtime",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22.19.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@fastify/websocket": "^10.0.1",
|
|
25
|
+
"fastify": "^4.29.0",
|
|
26
|
+
"jose": "^5.9.6",
|
|
27
|
+
"qrcode": "^1.5.4",
|
|
28
|
+
"uuid": "^11.1.0",
|
|
29
|
+
"ws": "^8.18.0",
|
|
30
|
+
"zod": "^3.24.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.13.0",
|
|
34
|
+
"@types/qrcode": "^1.5.5",
|
|
35
|
+
"@types/ws": "^8.18.1",
|
|
36
|
+
"typescript": "^5.6.3",
|
|
37
|
+
"vitest": "^3.0.0"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/hakimedes/dsh-easyremote.git"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/hakimedes/dsh-easyremote#readme",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/hakimedes/dsh-easyremote/issues"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "pnpm -C ../hub build && pnpm -C ../dsh-plugin build && tsc -p tsconfig.json && node scripts/build-runtime.mjs",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# DSH Remote Hub Connector
|
|
2
|
+
|
|
3
|
+
Host-side plugin for DeepSeek Harness (`dsh`) 0.1.0-rc.6. It keeps one outbound
|
|
4
|
+
WebSocket connection to a DSH Remote Hub and exposes session list, snapshot,
|
|
5
|
+
create, follow-up, steer, stop, event streaming, and allow-once/deny approval.
|
|
6
|
+
|
|
7
|
+
The node credential is stored with owner-only permissions at:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
$DSH_HOME/remote-hub/node-identity.json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Set these variables in the environment that starts DSH:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export DSH_REMOTE_HUB_URL=https://dsh.example.com
|
|
19
|
+
export DSH_REMOTE_NODE_NAME='My workstation'
|
|
20
|
+
# Optional: working directory for sessions created from Mobile.
|
|
21
|
+
export DSH_REMOTE_DEFAULT_CWD=/absolute/path/to/project
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`DSH_REMOTE_HUB_URL` must be the same public HTTPS origin compiled into the
|
|
25
|
+
Mobile APK. Do not expose the local DSH Web port to the internet; this plugin
|
|
26
|
+
only needs outbound HTTPS/WSS access to Hub.
|
|
27
|
+
|
|
28
|
+
## Install into a DSH profile
|
|
29
|
+
|
|
30
|
+
Install the packaged `.tgz` from this repository:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
dsh plugin --profile web add /absolute/path/to/dsh-remote-hub-connector-0.1.1.tgz
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then add this entry to `$DSH_HOME/profiles/web/cordis.patch.yml`:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- insert:
|
|
40
|
+
- id: dsh-remote-hub-connector
|
|
41
|
+
name: '@dsh-remote/hub-connector'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Restart `dsh web`, open the URL printed by DSH, append
|
|
45
|
+
`/__dsh_remote_v1/pair`, and scan the QR with DSH Mobile.
|
|
46
|
+
|
|
47
|
+
If Mobile has lost its local credential while this node is still connected,
|
|
48
|
+
open the same page and select **Reconnect mobile**. The authenticated connector
|
|
49
|
+
will issue a one-time recovery QR without replacing or duplicating the node.
|
|
50
|
+
|
|
51
|
+
See the repository `docs/DEPLOYMENT.md` for complete deployment, upgrade, and
|
|
52
|
+
troubleshooting instructions.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hakimedes/dsh-easyremote-connector",
|
|
3
|
+
"description": "Connect DeepSeek Harness to DSH EasyRemote over an outbound WebSocket.",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"entry": {
|
|
6
|
+
"name": "@hakimedes/dsh-easyremote-connector",
|
|
7
|
+
"inject": [
|
|
8
|
+
"webServer",
|
|
9
|
+
"agents",
|
|
10
|
+
"sessionQuery",
|
|
11
|
+
"agentDefaultModel",
|
|
12
|
+
"approval",
|
|
13
|
+
"apiProxy"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|