dsh-remote-tailscale
A DSH plugin that publishes the full local DSH web UI to your other devices — phone, tablet, or another computer — over your Tailscale tailnet.
No shared LAN or Wi-Fi is needed. The two devices do not have to be on the same network: Tailscale builds the network, and signing in to the same Tailscale account is both the connectivity and the authentication. If you don't have Tailscale yet, install it on this computer and on the other device, and sign in with the same account.
Security scope — read this first
- Never Funnel, never public. This plugin only uses
tailscale serveon its own HTTPS port (default 8455, never 443) and only binds a loopback proxy on127.0.0.1. It never runstailscale funneland never puts DSH on the public internet. - Do not enable Funnel, and do not forward or publish port 8455 / DSH by any other public means. If you enable Funnel or any other public exposure yourself, that is your own action outside this plugin — the plugin cannot manage or undo it, and the risk is yours.
- Who gets in: devices signed in to the same Tailscale account (your tailnet). They see the full DSH web UI — same live page, WebSocket included. There is no second password: your Tailscale account is the authentication.
- Off by default. Publishing is opt-in: nothing is served until you flip the switch in Settings → Remote access (or check the auto-start box). When DSH stops, the plugin removes its own serve mapping and nothing else.
Install
On a machine that already has DSH and Tailscale:
dsh plugin --profile web add github:hxt9805/dsh-remote-tailscale
Then restart DSH (dsh web) so the plugin loads.
Notes for humans and AI agents alike:
- The install ships the committed
lib/bundle — no build step and nopreparescript is needed or expected; do not run anything that would requireallowBuilds. - Do not run
tailscale funnel, do not publish port 8455 or DSH to the public internet, and do not change anything on port 443 — that is Tailscale's default HTTPS root and may be owned by another local app.
Or install from a local checkout:
dsh plugin --profile web add /absolute/path/to/dsh-remote-tailscale
A local-path install links this directory; do not move or delete it after install.
Uninstall:
dsh plugin --profile web remove dsh-remote-tailscale
Restart DSH again. On exit the plugin removes only its own Tailscale serve mapping.
First use
- On this computer and on the other device, sign in to the same Tailscale account. They do not need to be on the same Wi-Fi or LAN — that is the point of Tailscale.
- Start DSH (
dsh web). Remote access is off by default. - Open Settings → Remote access in the DSH web UI and flip the switch on.
- On the other device, open the URL the page shows, e.g.
https://<machine>.<tailnet>.ts.net:8455/— or just scan the QR code.
Windows: allow incoming connections for Tailscale (Windows Firewall / the Tailscale client), otherwise tailnet devices cannot connect. Tailscale not installed, not running, or signed out is reported honestly on the settings page — the plugin never pretends to be ready.
What it does
- Starts a loopback-only reverse proxy in front of DSH web (
127.0.0.1, never0.0.0.0). The proxy stays up even while remote access is off, so the status endpoint always answers. - Publishes via
tailscale serveon its own HTTPS port (default 8455, never 443, never Funnel) — but only when you turn it on. - Exposes a local status endpoint so other tools on the same machine can see whether it is ready and what URL to open.
While the switch is on, the plugin probes Tailscale every few seconds (backend liveness first, then its own serve port). If Tailscale drops, it immediately reports not-ready, clears the URL and QR code, and shows the reason; when Tailscale comes back and the switch is still on, it republishes once with backoff (5/15/30/60 s) — it never hammers serve enable. If you flip the switch off, it never re-enables by itself; turning off only removes the mapping on its own port — never serve reset, never 443.
It does not do LAN passwords, public tunnels, desktop auto-update, or HTML injection.
The settings page (the only entry)
Remote access has exactly one entry: Settings → Remote access, a top-level settings page (same level as General / Models / Plugins). It carries:
- the on/off switch, with live status (Ready / Off / Not ready / Switching) and the honest failure reason plus a Retry button when on-but-failed;
- the full
https://…ts.net:8455/URL, one-click Copy URL, and a QR code; - the Auto-enable when DSH starts checkbox (off by default, persisted in
$DSH_HOME/dsh-tailscale/prefs.json); - a short Never Funnel reminder — this plugin only serves your private tailnet.
The page follows the DSH UI language (Chinese and English are both built in; it tracks <html lang>, with the browser language as fallback). A former sidebar-footer entry and overlay were removed (2026-08): the footer row was crowding the Settings button.
Ports
| What | Default | Notes |
|---|---|---|
| Loopback proxy | 127.0.0.1:3091 |
Override with DSH_TS_PORT |
| Device HTTPS | 8455 |
Override with DSH_TS_SERVE_PORT. 443 is refused so this plugin never takes Tailscale's default HTTPS root. |
WSL: the plugin looks for Windows tailscale.exe (or DSH_TS_TAILSCALE). Mirror networking is required so Windows loopback reaches the WSL DSH process.
Local status (optional)
GET http://127.0.0.1:3091/__dsh_tailscale_status
{
"ok": true,
"ready": true,
"url": "https://machine.tailnet.ts.net:8455/",
"error": "",
"proxyPort": 3091,
"servePort": 8455,
"dshPort": 3080,
"ts": 1760000000000
}
A copy is also written to $DSH_HOME/dsh-tailscale/status.json (default ~/.dsh/dsh-tailscale/status.json) for humans. The folder and HTTP path keep the dsh-tailscale names so existing local tools keep working. Preferences (autoEnable, default false) live next to it in prefs.json. The settings UI talks to the host over a loopback Connection RPC on channel /dsh-remote-tailscale (tailscale.status / tailscale.setEnabled / tailscale.setAutoEnable); desired / autoEnable / busy / qr only exist on the RPC, not in the file or HTTP endpoint.
Develop
npm test # pure functions + fake processes/ctx; never touches real Tailscale / real DSH
npm run build:client # rebuild lib/client.js after changing client/ sources (esbuild, devDependency)
Structure:
lib/: the host half (proxy, tailscale commands, single-source statuslib/status.js, prefslib/prefs.js, RPClib/rpc.js, built-in QRlib/qr.js). Host-side error copy is Chinese and asserted by tests.client/: the web half sources (the settings page, the only entry;client/api.jsis the RPC contract shared by both halves;client/i18n.jsis the zh/en dictionary + UI-language detection + host-error EN translation).lib/client.js: the build product (DSHwindow.__ModuleLoader__.loadformat), committed to the repo; after changing sources you must re-runnpm run build:client(tests verify sync).- QR codes are zero-dependency:
lib/qr.jsports the qrcode-generator algorithm (byte mode + ECC M + v1..10); golden vectors intest/qr-vectors.json.
License
MIT © 2026 hxt9805
No comments yet. Be the first to write one.