Operations & Maintenance
August 15, 2026 · View on GitHub
Everything that happens after the first pairing: CLI reference, updates, backups, a permanent URL, troubleshooting, and how plugins behave under remote access.
CLI reference
On Linux and macOS the command is dsh-remote (use sudo where noted); on Windows it is
dsh-remote.ps1 (run from an elevated PowerShell). All three platforms expose the same
operations; the table below shows the Linux/macOS spelling.
Connection & pairing
| Command | What it does |
|---|---|
sudo dsh-remote pair [--label NAME] [--qr] | Generate a pairing code (name the device; prints a QR when qrencode is installed) |
dsh-remote devices | List paired devices |
sudo dsh-remote devices revoke <id|name|all> | Revoke one device or all of them |
sudo dsh-remote revoke | Revoke every session (same as devices revoke all) |
dsh-remote url [--qr] | Show the current tunnel URL (auto-refreshes from the tunnel log) |
Pairing works two ways — a camera is never required:
- Scan — the phone camera scans the QR printed by
pair(Linux/macOS print it in the terminal; the Windows CLI draws its own QR). - Copy & paste — send the invite link (or the 8-character code) to the new device: opening the
link in any browser pairs it instantly; or type the code on the pairing page. The Devices page
(
/devices) has a one-click Copy invite link button, so you can share the link through any messaging app.
Status & diagnostics
| Command | What it does |
|---|---|
dsh-remote status | Overall status (services + tunnel + health) |
dsh-remote health | Four-way check: DSH + gateway + URL + pairing path |
sudo dsh-remote doctor | Full health check: versions / services / ports / URL / disk / audit |
dsh-remote logs [-n N] | DSH logs |
dsh-remote logs-tunnel | cloudflared logs |
Notifications
| Command | What it does |
|---|---|
sudo dsh-remote notify-config set --bark <URL> [--serverchan <KEY>] [--telegram-bot <T> --telegram-chat <ID>] [--webhook <URL>] | Configure push channels |
sudo dsh-remote notify-config show | Show the current config (masked) |
sudo dsh-remote notify test [channel] | Send a test notification (bark|serverchan|telegram|webhook; all by default) |
The companion plugin pushes to your phone when a task completes or gets blocked.
Updates & services
| Command | What it does |
|---|---|
sudo dsh-remote update --check | Show the version diff only |
sudo dsh-remote update | Upgrade DSH: auto backup → install latest → record previous → restart |
sudo dsh-remote update --version <v> | Upgrade to a specific version (backs up first) |
sudo dsh-remote rollback | Roll back to the pre-upgrade version |
sudo dsh-remote backup / backup --restore <file> | Backup/restore sessions + config + keys (keeps the latest 5) |
sudo dsh-remote update-cloudflared | Upgrade cloudflared and restart the tunnel |
sudo dsh-remote self-update <new install.sh> | Upgrade this suite (backup first, validate the script, then run) |
dsh-remote kit-version | Suite + DSH + Node + cloudflared + gateway versions |
sudo dsh-remote dsh-reload | Restart only the DSH core and health-check it (use after installing a new plugin) |
sudo dsh-remote restart / start / stop | Restart / start / stop the whole stack |
⚠️ Prefer
dsh-reloadoverrestartwhen you only need to pick up plugin changes:restartalso restarts the tunnel, and a quick tunnel gets a new URL on every restart, breaking saved bookmarks. (dsh-reloadnever touches the tunnel.)
Automation (all platforms)
- Supervision — every daemon auto-restarts on crash and at boot: systemd on Linux, launchd KeepAlive on macOS, Task Scheduler with restart settings on Windows.
- Watchdog — a 5-minute heartbeat checks DSH and the gateway independently and restarts the failing one, with a 5-minute anti-restart-storm guard.
- Log rotation — cloudflared logs daily (7 kept) and the audit log weekly (12 kept), rotated
with
copytruncateso nothing needs to restart (Linux; macOS/Windows rely on the same copytruncate-friendly append-only files).
Permanent URL (recommended for long-term use)
A quick tunnel gets a new URL whenever cloudflared restarts (run pair again to recover). For a
set-and-forget URL, use a Cloudflare named tunnel — a cheap domain plus a free Cloudflare account:
- Create a tunnel in the Cloudflare dashboard and copy its token.
- Linux:
sudo bash install.sh --tunnel-token <token> --public-url https://dsh.your-domain.com(macOS/Windows installers accept the same two options). - Optionally add a Cloudflare Access policy (email verification) as one more gate.
Troubleshooting
| Symptom | Fix |
|---|---|
| Phone scan won't open the page | On the host, dsh-remote health localizes it with the four-way check |
| Pairing code invalid | Expired or used: sudo dsh-remote pair again |
| Broken after a host reboot | The quick-tunnel URL changed: sudo dsh-remote pair (named tunnels are unaffected) |
| Suddenly back at the pairing page | Session revoked or expired: pair again |
| Broken after an upgrade | sudo dsh-remote rollback |
| Blank web page | dsh-remote logs; sudo dsh-remote dsh-reload |
| Not sure about anything | sudo dsh-remote doctor and fix the ✗ items |
| Slow/unstable from mainland China | The tunnel relays through Cloudflare; see "Self-hosted relay" below |
Plugins & remote access
Plugins installed on the server work for remote devices. DSH plugins come in two planes:
| Plugin kind | Runs on server | Usable remotely | When it takes effect |
|---|---|---|---|
| Host plane (events / tools / HTTP routes, no UI) | ✅ | ✅ automatic — independent of who connects (e.g. the bundled notification plugin pushes to your phone) | Add a row to $DSH_HOME/profiles/web/cordis.patch.yml — hot-applied (the file is HMR-watched, no restart) |
Client UI plugin (package declares dsh.client) | ✅ (builds a client bundle) | ✅ the gateway transparently proxies /plugins/* and the __DSH_BOOT__ injection — the remote UI is identical to local | The plugin set is scanned at startup: after dsh plugin --profile web add <pkg>, restart the DSH core; browsers then see the new UI on refresh |
| Plugin opening its own port (bundled MCP / admin UI) | ✅ | ❌ not behind the gateway — unreachable remotely | — |
Installing a new client UI plugin (server-side, verify on the phone)
# 1. Install (on the server; as root, make sure DSH_HOME points to the install dir)
sudo dsh plugin --profile web add <plugin-package>
# 2. Restart only the DSH core (the plugin set is scanned at startup; health-checks after)
sudo dsh-remote dsh-reload
# ⚠️ Do NOT use `dsh-remote restart`: it also restarts the tunnel, which changes a
# quick-tunnel URL and breaks the phone's bookmark
# 3. Confirm the plugin loaded
sudo dsh-remote logs | grep -i <plugin>
Then refresh the page on the phone and the new plugin is there (Settings → Plugins). No
re-pairing is needed: the pairing session lives on the gateway side, and restarting the DSH core
doesn't touch it. The gateway answers 502 for a few seconds during the restart — refresh after
health passes.
FAQ
- Is dsh-remote itself a DSH plugin? No. dsh-remote is a standalone companion app installed
next to DSH (gateway + tunnel + watchdog + CLI) — it is not loaded into DSH's profile tree.
It ships exactly one DSH plugin,
dsh-remote-plugin, which is installed with the officialdsh plugin --profile web addflow and handles task notifications. The repository still carries thedsh-plugintopic because it lives in the DSH ecosystem (like the other tunnel/auth companions on the awesome-dsh-plugin list). - Is a plugin usable remotely right after install? Host plane: appending the registration row
to
cordis.patch.ymlhot-applies it. Client UI plugins: install →dsh-reload→ refresh the remote browser. There is no update-without-refresh in production (that is a dev-mode client-HMR capability). - Can the remote UI differ from local? No. The UI is injected same-origin by the server, and the gateway forwards HTTP and WebSocket transparently; plugin bundle URLs carry content hashes, so a refresh always gets the latest.
- Can I install plugins remotely? No. Settings → Plugins is a read-only inventory; installation happens on the server CLI only.
- Gotchas — a plugin whose client code hardcodes
http://localhost:3080absolute URLs breaks remotely (well-behaved plugins use relative paths); "open local file / native command" buttons are hidden or degraded remotely (see SECURITY.md).
Self-hosted relay (advanced)
Cloudflare's edge can technically see decrypted traffic — that is true of every public tunnel
service. For sensitive code, replicate Paseo's relay architecture: rent a small VPS (~$5/month),
run frps as a TCP forwarder, and forward the gateway port 127.0.0.1:8080 out. Gateway, pairing,
and watchdog are all reused; only the transport layer is swapped. See also
getpaseo/paseo-relay for the original.
Directory layout
dsh-remote/
├── README.md / README.zh-CN.md Homepage (English / 简体中文)
├── LICENSE · SECURITY.md · CONTRIBUTING.md · CODE_OF_CONDUCT.md
├── .github/ Issue / PR templates and CI
├── server/ Linux host (full edition)
│ ├── install.sh One-command deployment (embeds every component)
│ ├── gateway.js Security gateway (pairing / devices / audit / proxy / PWA)
│ ├── dsh-remote Ops CLI (Linux)
│ ├── watchdog.sh Heartbeat watchdog (Linux)
│ └── dsh-web.service etc. systemd units
├── host/
│ ├── macos/install-macos.sh macOS host (full edition, launchd)
│ │ ├── dsh-remote.sh Ops CLI (macOS)
│ │ └── dsh-watch.sh Heartbeat watchdog (macOS)
│ └── windows/
│ ├── install-windows.ps1 Windows host (full edition, Task Scheduler)
│ ├── dsh-remote.ps1 Ops CLI (Windows, with QR codes)
│ └── dsh-watch.ps1 Heartbeat watchdog (Windows)
├── plugin/ Companion notification plugin (dsh-remote-plugin)
├── tools/inject.ps1 Authoritative sources → embedded copies in install.sh
├── docs/
│ ├── operations.md This document
│ └── migration/ Migration guides from other agent tools
└── client/ Per-platform client connection guides