Bundle
dsh-remote-tailscale
DSH plugin: open the local DSH web UI on your other Tailscale devices. Loopback-only, own HTTPS port, never funnel.
- Source
- hxt9805
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# dsh-remote-tailscale
[English](README.md) | [中文](README.zh.md)
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 serve` on **its own HTTPS port** (default **8455**, never 443) and only binds a loopback proxy on `127.0.0.1`. It **never** runs `tailscale funnel` and 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:
```bash
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 no `prepare` script** is needed or expected; do not run anything that would require `allowBuilds`.
- **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:
```bash
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:
```bash
dsh plugin --profile web remove dsh-remote-tailscale
```
Restart DSH again. On exit the plugin removes only its own Tailscale serve mapping.
## First use
1. 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.
2. Start DSH (`dsh web`). Remote access is **off** by default.
3. Open **Settings → Remote access** in the DSH web UI and flip the switch on.
4. 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
1. Starts a **loopback-only** reverse proxy in front of DSH web (`127.0.0.1`, never `0.0.0.0`). The proxy stays up even while remote access is off, so the status endpoint always answers.
2. Publishes via `tailscale serve` on **its own HTTPS port** (default **8455**, never 443, never Funnel) — but only when you turn it on.
3. 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`
```json
{
"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
```bash
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 status `lib/status.js`, prefs `lib/prefs.js`, RPC `lib/rpc.js`, built-in QR `lib/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.js` is the RPC contract shared by both halves; `client/i18n.js` is the zh/en dictionary + UI-language detection + host-error EN translation).
- `lib/client.js`: the build product (DSH `window.__ModuleLoader__.load` format), committed to the repo; after changing sources you must re-run `npm run build:client` (tests verify sync).
- QR codes are zero-dependency: `lib/qr.js` ports the qrcode-generator algorithm (byte mode + ECC M + v1..10); golden vectors in `test/qr-vectors.json`.
## License
[MIT](LICENSE) © 2026 hxt9805
Install
dsh plugin --profile web add github:hxt9805/dsh-remote-tailscale
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-remote-tailscale from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.