dsh-service-portal

dsh-service-portal

One random domain + one random password to expose your local services on demand.

中文 · Installation · How it works · Service manifest · Changelog · Issues

npm version MIT license

**dsh-service-portal (Service Portal)** is a [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) plugin. It exposes **any local service** to the public internet on demand: `cloudflared` creates a **random domain**, a **password login** (password only, no username) guards the entrance, and a settings-page toggle shows the URL and the password. It originally lived inside `ld-rl-poker` (to put its training dashboard / model viewer online), but the portal itself has nothing to do with poker — **any local service** can use the same "one domain + one password + on-demand mount" pattern. ## Features - **One-click exposure** — the settings page starts the service → auth proxy → tunnel, no manual steps. - **Password only, no username** — visitors get a custom login page with a single password field; the browser's native username+password dialog is never triggered. - **Two password shapes** — (1) **random per start** (default: smallest leak window); (2) a **fixed password you set** (convenient, but once leaked it stays valid — the settings page says so). - **Random domain** — changes on every start, so the entrance itself is short-lived. - **Two shapes** — one random domain per service, or **one domain + path prefix** (`https:///train/`). - **Manifest-driven** — write `services.json` to expose any service without touching code; an empty manifest falls back to the built-in default (ld-rl-poker's two GUIs, backwards compatible). - **Control checks** (house rule) — 26 Python control-check scripts plus 8 end-to-end scripts, and 83 Node checks; **a missing dependency must "skip and return 2", never silently pass**. - **Host metrics** — CPU / memory / VRAM / GPU utilization; unreadable values render "—", never a fake 0. ## Screenshots The portal page (what you get after scanning the QR code — read-only: test + guide). Captured from a real portal process with headless Chrome via `tools/shoot_portal.py`, not design mockups. | Phone (light) | Phone (dark) | Wide | | :---: | :---: | :---: | | portal page, phone, light | portal page, phone, dark | portal page, wide | ## How it works ``` https://.trycloudflare.com (cloudflared quick tunnel, new domain every start) | v 127.0.0.1: tools/tunnel_auth.py (password login + session cookie, auth before forwarding) | v 127.0.0.1: your local service (GUI / Ollama / any HTTP service) ``` ## Installation ### Via npm (after publish) ```powershell $dsh = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd" & $dsh plugin --profile desktop add dsh-service-portal ``` ### Via link (during development) ```powershell $dsh = "$env:LOCALAPPDATA\Programs\DeepSeek Harness\resources\runtime\cli\bin\dsh.cmd" & $dsh plugin --profile desktop add "link:D:/DshProject/dsh-service-portal" ``` Restart DSH after installing (the bundle loads in the host process). ## Configuration (DSH settings page) | Field | Default | Description | | :--- | :--- | :--- | | `infraPath` | this repo root | Infrastructure repo: contains `tools/` and `runs/` (tunnel/proxy/credentials) | | `repoPath` | **empty** | Host repo root: where the exposed services live. **You must set this** | | `pythonPath` | empty | Interpreter for services; auto-selects `.venv313` → `.runtime` → PATH | | `passwordMode` | `random` | Password shape: `random` = new password each start / `fixed` = your fixed password | | `fixedPassword` | empty | Used when `passwordMode = fixed`, at least 12 chars (validated on save) | | `webEnabled` | `true` | Mount the `/service-portal/api` routes | All four are `.volatile()`: changes apply immediately without remounting the plugin. ## HTTP API Mounted on the host webServer (`prefix` routes under `/service-portal/api`): | Method | Path | Purpose | | :--- | :--- | :--- | | GET | `/service-portal/api/status` | Current state: running, mode, URL, password, password mode, endpoints | | GET | `/service-portal/api/settings` | Password-shape settings (mode + fixed password + min length) | | POST | `/service-portal/api/settings` | Save password shape (`{"passwordMode":"fixed","fixedPassword":"..."}`) | | GET | `/service-portal/api` | Same as `/status` | | POST | `/service-portal/api/start` | Start (body optional `{"singleDomain": true}`, default single-domain+paths) | | POST | `/service-portal/api/stop` | Stop | Unknown sub-paths return **404** (no status fallback — a mistyped path must not "look usable"). ## Service manifest With no configuration the built-in manifest is used (= ld-rl-poker's two GUIs). To expose other services, write `services.json`: ```json { "hostRepo": "D:\\DshProject\\ld-rl-poker", "services": [ {"key": "train", "script": "gui/server.py", "guiPort": 8810, "proxyPort": 8890, "name": "训练配置台", "mount": "train"}, {"key": "ollama", "script": "serve.py", "guiPort": 11434, "proxyPort": 11499, "name": "Ollama", "mount": "ollama"} ] } ``` Priority: `--services ` > `$SERVICE_PORTAL_SERVICES` > `./services.json` > built-in manifest. - `hostRepo`: host repo root the service scripts are relative to; - `script` / `guiPort`: the service to launch and its port; - `proxyPort`: per-service auth proxy port in multi-domain mode; - `mount`: path prefix in single-domain mode (`/train/`). ## Tools (usable without the plugin) | File | Purpose | | :--- | :--- | | `tools/start_public_gui.py` | One-click launcher: read manifest → start services → proxy → cloudflared tunnel | | `tools/tunnel_auth.py` | Password-login reverse proxy with multi-mount path routing (auth before forwarding) | ```powershell python -X utf8 tools/start_public_gui.py --single-domain # one domain + paths python -X utf8 tools/start_public_gui.py # two independent random domains python -X utf8 tools/start_public_gui.py --status / --stop ``` ## Control checks | Check | Count | Bug it catches | | :--- | ---: | :--- | | `tools/check_portal_integration.py` | — | A malformed Host counted as LAN (`garbage value` was password-free), double-slash console links | | `tools/check_portal_registry.py` | 88 | Duplicate/illegal mounts, key collisions, **changes not persisted** (lost on restart), crash on a corrupt file | | `tools/check_portal_console.py` | — | Disabled services still clickable, `