chendefine/dsh-sidebar-onlyoffice ↗★ 0
dsh-sidebar-onlyoffice
在侧边栏编辑器中通过自托管服务打开办公文档 适合需要自托管ONLYOFFICE并在侧边栏直接编辑文档的用户。
安装
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:chendefine/dsh-sidebar-onlyoffice说明文档
阅读完整 README ↗Configuration
All keys are optional; the profile's cordis.patch.yml layer carries them:
- id: dsh-sidebar-onlyoffice
config:
jwtSecret: "" # empty = unsigned (only for JWT_ENABLED=false servers)
# documentServerUrl: http://onlyoffice-documentserver # the DS entry dsh web reaches directly (its own nginx)
# internalBaseUrl: http://172.31.255.4:3080 # how the DS reaches dsh web; empty = auto-detect
# publicBaseUrl: https://work.example.com # browser-visible origin when a gateway rewrites Host; empty = request Origin
# defaultMode: edit # edit | view
# username: User # editor display username (top-right corner)
# fileLimitMb: 100
# tokenTtlSec: 600
| Field | Default | Description |
|---|---|---|
jwtSecret | '' | Shared secret with the Document Server (its JWT_SECRET). Editor configs and callbacks are JWT-signed/verified when set; empty only fits a DS running JWT_ENABLED=false. |
documentServerUrl | '' | The one Document Server address: the entry dsh web itself reaches directly — the DS container's own nginx on the docker network (http://onlyoffice-documentserver) or a published host port. Doubles as the reverse-proxy upstream and the base saved-document bytes are fetched through. |
internalBaseUrl | (auto-detect) | Base URL the DS uses to reach this dsh web server (document download + callbacks). Empty = auto-detect the first non-loopback IPv4 + the webserver port. |
publicBaseUrl | (request Origin) | Browser-visible origin (scheme://host[:port]), used when minting X-Forwarded-Host/Proto and matching DS-reported save URLs. Set it when a fronting gateway rewrites the Host header away from the browser-visible authority; empty = derive from each request's Origin. |
defaultMode | edit | Default mode for newly opened files (edit/view). |
username | User | Editor display username (editorConfig.user.name, the co-author name in the editor's top-right corner); empty reverts to User. Reopen files after a change. |
fileLimitMb | 100 | Max file size (MB) for serving and saving back. |
tokenTtlSec | 600 | Signed URL token lifetime in seconds. |
Legacy pre-0.2.0 keys (proxy, proxyUpstream, internalDocumentServerUrl, proxyPublicBase, documentServerPort) are mapped once at startup — proxyUpstream/internalDocumentServerUrl onto documentServerUrl, proxyPublicBase onto publicBaseUrl — with a warning; move to the new names.
Graphical configuration (settings card)
When the composition mounts the DSH settings service (the standard dsh web bundle does), the plugin registers a settings section under the dsh-sidebar-onlyoffice namespace and the web client's Settings → Plugins tab renders a configuration card for it — all eight fields, zh/en copy, staged edits with a save/discard footer. A committed change is written to the deployment's user settings (/settings.yaml), layered over the composition entry, and applies without a restart: both the route closures AND the reverse proxy read the live section (jwt/mode/username/limit/token/URLs take effect on the next editor-config fetch; a changed documentServerUrl swaps the proxy upstream at once — the discovered versioned prefix resets and re-probes, and an address that first resolves through the card claims the mount immediately). A deployment may therefore boot with an empty composition entry and be configured entirely through the card.
Precedence per field: card/user layer → the composition entry (cordis.patch.yml config: block) → schema defaults. Removing a card edit (the "Reset to default" control) reverts to the composition layer; hand-editing settings.yaml works too (the section re-resolves live).
Save-URL rewrite (in-network fetch)
Two directions are configured independently: DS → this server via internalBaseUrl (document download + callbacks), and this server → DS for the saved bytes its callbacks report. The DS mints those save URLs against the browser-visible base the proxy forwards (publicBaseUrl, else the request Origin) — behind a reverse proxy they would hairpin out through the public entry (DNS + TLS + terminator + gateway). The plugin rewrites the minted prefix onto documentServerUrl (e.g. http://onlyoffice-documentserver, the docker-network container name) — path suffix and query stay intact — for a direct container-to-container fetch.
Safety verified against Document Server 9.4: /cache/files auth is an nginx secure_link md5 over expires + request-path + server secret, where the request path is the one AFTER the proxy strips the sub-path prefix — host and stripped prefix are not signed material, so a rewritten URL still returns 200. A reported URL outside the public base is fetched as-is with a warn log.
Same-origin proxy
The plugin is proxy-only: it always reverse-proxies the Document Server's whole browser UI through the dsh web port at /sidebar/onlyoffice/ds. The config route hands out that mount's api.js URL, and everything the editor then loads — the iframe, sdkjs/web-apps statics, fonts, the co-authoring socket, exports and downloads — flows back through dsh web. The browser never needs to reach the DS at all; point documentServerUrl at the DS container entry and the DS can stay on a private network. This also removes the mixed-content pitfall: an https GUI proxies an http-only DS transparently.
How it cooperates with the DS (9.x; the same generation the plugin targets):
- api.js derives the DS base from its own script URL, and the editor derives the socket path from the iframe URL — both stay under the mount automatically.
- The proxy sends
X-Forwarded-Host: /sidebar/onlyoffice/ds(the official ONLYOFFICE "virtual path" shape), so the URLs the DS itself mints (the versioned script-caching 302s, save/download links) point back into the mount; absoluteLocationheaders are additionally rewritten to mount-relative form. The same headers ride the WebSocket upgrade leg, where the DS derives the/cache/filesdocument-data base from the connection. - The editor's socket.io path is
/~/doc//c, where `` is the plugin's own content-addressed document key. The proxy registers that exact WebSocket upgrade route just-in-time per opened document (disposed on the DS's status-4 close, LRU-capped, re-discovered when a DS upgrade moves the versioned prefix). A missed registration degrades softly: the editor falls back to socket.io polling over plain HTTP — editing keeps working. - Both proxy legs sit behind the same browser-trust fence as every other plugin route (same-origin editor passes; cross-site pages are refused).
Notes and limits:
- Requires a DS of the 9.x generation (sub-path URL minting).
- Every document save changes the key (content-addressed), so upgrade routes churn per save — disposed on close, capped, invisible in practice.
- If a gateway in front of dsh web rewrites
Host(e.g. to127.0.0.1:3080), URLs minted inside DS responses would carry that unreachable host: setpublicBaseUrlto the browser-visible origin. - Security trade-off: the DS UI runs same-origin with the DSH page, so a compromised DS could script the dsh origin. The fence still stops cross-site callers; keep the DS trusted.
- Static assets hop twice (browser → dsh web → DS); the versioned immutable caching survives the hop, but an outer gateway may need its cache map extended for
/sidebar/onlyoffice/ds/**for best performance.
Deployment shape
The canonical deployment: the Document Server container and dsh web share a docker network (DS container name resolvable, e.g. onlyoffice-documentserver — that is documentServerUrl), JWT enabled with a fixed secret, and ALLOW_PRIVATE_IP_ADDRESS=true on the DS container. The DS needs no published browser-facing port at all.
Graphical configuration (settings card)
When the composition mounts the DSH settings service (the standard dsh web bundle does), the plugin registers a settings section under the dsh-sidebar-onlyoffice namespace and the web client's Settings → Plugins tab renders a configuration card for it — all eight fields, zh/en copy, staged edits with a save/discard footer. A committed change is written to the deployment's user settings (/settings.yaml), layered over the composition entry, and applies without a restart: both the route closures AND the reverse proxy read the live section (jwt/mode/username/limit/token/URLs take effect on the next editor-config fetch; a changed documentServerUrl swaps the proxy upstream at once — the discovered versioned prefix resets and re-probes, and an address that first resolves through the card claims the mount immediately). A deployment may therefore boot with an empty composition entry and be configured entirely through the card.
Precedence per field: card/user layer → the composition entry (cordis.patch.yml config: block) → schema defaults. Removing a card edit (the "Reset to default" control) reverts to the composition layer; hand-editing settings.yaml works too (the section re-resolves live).