dsh-plugin-approval-alert
doncelee229-cmyk
DeepSeek Harness 审批/选择方案系统级通知提醒,显示工作区名、点击跳转、多语言。Approval & decision alerts with native notifications for DeepSeek Harnes…
evanfang0054/dsh-tailscale-console
为 DeepSeek Harness 提供基于 Tailscale 的安全远程访问运营面板:一键健康检查、HTTPS 入口开关、macOS 代理绕过、中继服务器运维、ACL 生成。Tailscale remote-access control panel for DeepSeek Harness: health checks, HTTPS serve toggle, proxy bypass, relay ops, ACL helper.
PROJECT TOPICS
PROJECT README
A control panel for the DeepSeek Harness web GUI that operates secure remote access over Tailscale: one-click health checks, HTTPS entry (Tailscale Serve) toggle, macOS proxy bypass repair, relay-server ops, and an ACL snippet generator.
UI language: Chinese. Docs: English · 中文
| Card | Description |
|---|---|
| ① Devices & online alerts | Per-device online/offline state; auto-alerts when an offline device comes online |
| ② Health check | One-click verification of HTTPS entry, page, /api session list, /sidebar/api, server direct path, proxy bypass |
| ③ HTTPS entry | Tailscale Serve status / URL + on/off toggle (with verification & retry) |
| ④ Proxy bypass | Checks *.ts.net / 100.64.0.0/10 in the macOS system proxy bypass list; one-click re-apply (macOS only) |
| ⑤ Relay server | Tailnet status of your relay server + enable Peer Relay + ping verify |
| ⑥ Access | Remote HTTPS URL, local URL, and the exact dsh web start command |
| ⑦ ACL snippet | Generates the tailscale.com/cap/relay grants JSON from live device IPs |
Target architecture: the server joins the tailnet as a Peer Relay; the local machine exposes HTTPS via Tailscale Serve; DSH keeps listening on 127.0.0.1 only.
tailscale CLI installed and logged in~/.dsh/profiles/web) — requires dsh ≥ 0.1.0-rc.6 (for --trusted-host and the webRuntime service)pnpm-lock.yaml is v9; the default pnpm 8 under Node 24 will fail. Verified with Node 20.19.2 + pnpm 10.27.0)How to find your tailnet values (the
<...>placeholders below):
- Local tailnet IP:
tailscale ip -4- All devices:
tailscale status- MagicDNS name (
<hostname>.<tailnet>.ts.net):tailscale status --json | python3 -c "import json,sys; print(json.load(sys.stdin)['Self']['DNSName'].rstrip('.'))", or read it from the Tailscale admin console → Machines, or from the output oftailscale serve statusonce serve is up (section 2.1)- Server tailnet IP:
tailscale statuson the Mac (the Linux peer)Tip: you can bring the panel up locally first with
dsh web(loopback default) — card ⑥ then shows the real remote URL, start command and ACL JSON, which you can copy back into the steps below.
1.1 Install & authenticate Tailscale
ssh <server> 'curl -fsSL https://tailscale.com/install.sh | sh'
ssh <server> 'nohup tailscale up >/tmp/ts-up.log 2>&1 & sleep 4; cat /tmp/ts-up.log'
# Open the https://login.tailscale.com/a/xxxx link printed, authorize with your account.
# The server comes online automatically; `tailscale status` on the Mac should list it.
1.2 Enable the Peer Relay port + firewall
ssh <server> 'tailscale set --relay-server-port=40000 && ufw allow 40000/udp && ufw allow 41641/udp'
# 41641 = WireGuard direct port; 40000 = Peer Relay port
⚠️ Your cloud security group must also allow inbound UDP 40000 and 41641 (source 0.0.0.0/0) — the guest ufw cannot protect the cloud edge.
1.3 Verify the direct path (run on the Mac)
tailscale ping -c 3 <server-tailnet-ip> # expect "via <public-ip>:41641" (tens of ms); DERP means 41641 is blocked
2.1 HTTPS entry: Tailscale Serve (the core)
tailscale serve --bg 3080 # https://<hostname>.<tailnet>.ts.net → 127.0.0.1:3080, tailnet-only
tailscale serve status # inspect; `tailscale serve reset` to disable
Why HTTPS is mandatory: browsers expose
crypto.randomUUIDonly in secure contexts (HTTPS or localhost), and every DSH client RPC depends on it. The GUI athttp://<tailnet-ip>:3080will never work — debug only.
2.2 Proxy bypass (required if you run Clash-family proxies on macOS)
A local Clash proxy hijacks the private ts.net domain and breaks HTTPS in the browser. Add *.ts.net and 100.64.0.0/10 to the bypass list in Clash Verge → Settings → System proxy; or apply immediately:
networksetup -getproxybypassdomains "Wi-Fi" # first: back up the current list
networksetup -setproxybypassdomains "Wi-Fi" "*.ts.net" "100.64.0.0/10" "*.local" "<local>" "localhost" "127.0.0.1" "192.168.0.0/16" "10.0.0.0/8" "172.16.0.0/12"
⚠️
-setproxybypassdomainsreplaces the whole exception list of that service — back it up first with-getproxybypassdomains. Non-macOS: the panel hides card ④ automatically; for Linux/Windows set the equivalent bypass, e.g.export NO_PROXY="*.ts.net,100.64.0.0/10"in your proxy tool or environment.
2.3 (Optional) socat debug forward
brew install socat
socat TCP-LISTEN:3080,bind=<local-tailnet-ip>,reuseaddr,fork TCP:127.0.0.1:3080
# curl/API debugging only; the GUI is unusable over plain HTTP (non-secure context)
3.1 Start command (use this every time)
dsh web --trusted-host <local-tailnet-ip>:3080 --trusted-host <hostname>.<tailnet>.ts.net
⚠️ Every change below (cordis.patch.yml, plugin code, better-sidebar patch) requires restarting
dsh web, which interrupts the currently running session — save/finish your work first. The web profile has HMR disabled.
3.2 Static trustedHosts (belt & braces)
Append to ~/.dsh/profiles/web/cordis.patch.yml:
- id: connection
config:
trustedHosts: ['<local-tailnet-ip>:3080', '<hostname>.<tailnet>.ts.net']
3.3 Known third-party issue: dsh-better-sidebar
If your profile installs dsh-better-sidebar, its /sidebar/api fence has a bug in version 0.10.3 (the latest published): trustedHostsOf() matches entry.options.name against "connection", but name is the package name, so the trust list is always empty and /sidebar/api/* is loopback-only (403 remotely — the sidebar breaks). No fixed upstream version exists yet.
Patch trustedHostsOf() in node_modules/dsh-better-sidebar/lib/index.js (back up the file first) to read the webRuntime service:
function trustedHostsOf(ctx) {
const hosts = []
const runtime = ctx.get("webRuntime")
if (runtime !== void 0 && Array.isArray(runtime.trustedHosts)) hosts.push(...runtime.trustedHosts)
for (const entry of ctx.loader.entries()) if (entry.options.id === "connection") {
const cfg = entry.options.config?.trustedHosts
if (Array.isArray(cfg)) hosts.push(...cfg)
}
return [...new Set(hosts)]
}
⚠️ The patch lives in
node_modulesand is lost on the nextpnpm install— re-apply after every reinstall (check withgrep -c 'options.id === "connection"').
console.tailscale.com/admin/acls → JSON editor, add to grants:
{
"grants": [
{
"src": ["<device-A-ip>", "<device-B-ip>"],
"dst": ["<server-tailnet-ip>"],
"app": { "tailscale.com/cap/relay": [] }
}
]
}
(src = devices allowed to relay, dst = the relay server. Without it, phone traffic falls back to official DERP, 400ms+. Card ⑦ generates this for you.)
Installed from npm: dsh-tailscale-console. For local development (editing this repo in place), use the
file:reference instead.
cd ~/.dsh/profiles/web
# Node 20.x + pnpm >= 9 required (lockfile v9). Verified: Node 20.19.2 + pnpm 10.27.0.
pnpm add dsh-tailscale-console
# 本地开发:pnpm add "dsh-tailscale-console@file:./packages/dsh-tailscale-console"
Append to cordis.patch.yml:
- insert:
- id: tailscale-console
name: 'dsh-tailscale-console'
config:
sshAlias: my-server # optional: ssh alias for server-side mutations (install/relay)
Restart dsh web → Settings → Tailscale Console.
# HTTPS entry
curl -s -o /dev/null -w "%{http_code}\n" --noproxy '*' https://<hostname>.<tailnet>.ts.net/ # 200
# /api fence
curl -s --noproxy '*' -X POST https://<hostname>.<tailnet>.ts.net/api/session.list \
-H 'content-type: application/json' \
-d '{"type":"client-request","rpcId":"t-1","method":"session.list","payload":{}}' | head -c 120 # ok:true
# panel host route
curl -s -o /dev/null -w "%{http_code}\n" --noproxy '*' -X POST \
https://<hostname>.<tailnet>.ts.net/tsctl/api/config -H 'content-type: application/json' -d '{}' # 200
# direct path
tailscale ping -c 3 <server-tailnet-ip>
# Final: open https://<hostname>.<tailnet>.ts.net on any tailnet device — the sidebar lists all sessions
All values come from the plugin Config; the code contains no personal identifiers. MagicDNS URLs and tailnet IPs are auto-derived from tailscale status --json when not configured.
| Key | Type | Default | Meaning |
|---|---|---|---|
dshPort |
number | 3080 |
DSH web GUI port |
relayPort |
number | 40000 |
Relay server's Peer Relay UDP port |
tailnetUrl |
string | auto | HTTPS base, e.g. https://myhost.tail1234.ts.net |
sshAlias |
string | — | SSH alias for server-side mutations (install / relay) |
serverPeerPattern |
string | first Linux peer | Regex to identify the relay server peer |
proxyServices |
string[] | macOS set | Network services for the bypass apply |
danger-full-access sandbox policy (same as the model's bash tool session policy). The default workspace-write confinement kills the detached child of tailscale serve --bg, breaking the HTTPS "on" toggle. Blast radius stays bounded by the allowlist./tsctl/api prefix accepts only loopback Hosts or hosts in dsh's --trusted-host list (same fence as dsh's own /api).| Symptom | Cause | Fix |
|---|---|---|
| Panel missing after restart, host route 200 | exports lacks "./package.json" — client scan (require.resolve('<pkg>/package.json')) is blocked by Node exports encapsulation |
add "./package.json": "./package.json" to exports; restart |
| Stale code served after editing sources | pnpm file: deps are hard-linked copies |
re-symlink: ln -s ../packages/dsh-tailscale-console node_modules/dsh-tailscale-console |
| HTTPS "on" fails silently | sandbox kills serve --bg's daemon |
fixed in code (explicit policy + verify/retry) |
| "On" button dead after HTTPS entry is off | chicken-and-egg: the HTTPS page itself is unreachable once serve is off (connection refused) | open the panel at http://127.0.0.1:<port> on the local machine and toggle on (remote page shows a hint) |
| Settings/credentials page 403 remotely | dsh PRIVILEGED_METHODS design limit |
operate locally; do not relax |
| Phone access is slow | ACL grants not saved | see section 4 |
MIT. The browser-trust fence mirrors dsh's api-request-trust logic (see @deepseek-ai/dsh-client-connection).
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。