think
16px
820px

Full-fidelity office editing (OnlyOffice module)

Obscura can edit Word / Excel / PowerPoint documents in the browser with real
Microsoft-format fidelity
— page layout, spacing, tables, styles — and save
the result back as a new document version. This is an optional, self-hosted,
free
module built on OnlyOffice Docs Community
(AGPLv3). It is distinct from the lightweight Convert to editable action
(mammoth → TipTap), which turns a .docx into a native Obscura rich-text
document and deliberately keeps meaning, not layout.

What the user sees

A .docx/.xlsx/.pptx document — plus the legacy binary (.doc/.xls/.ppt) and
OpenDocument (.odt/.ods/.odp) formats — gets a top-level Edit content button
(only when the module is configured). It opens the OnlyOffice editor inside the
app — the full ribbon (Home / Insert / Layout / References / …), fonts, tables,
track-changes — showing the actual document. Edits autosave; when the last
editor closes, a new version is written with the changes, the prior content
intact and versioned as usual.

How it works (and why it needs no extra hostname or cert)

browser ──https──▶ host edge nginx ──http──▶ web (nginx) ──┬─▶ SPA / /api → obscura
                                                            └─▶ /web-apps,/sdkjs,/cache,/doc,… → onlyoffice
obscura ──http(compose net)──▶ onlyoffice        (JWT-signed editor config)
onlyoffice ──http(compose net)──▶ obscura        (fetch document, POST save callback)
  • Same origin. The doc-server's fixed top-level paths (/web-apps, /sdkjs,
    /fonts, /cache, /coauthoring, /doc, …, plus its build-versioned
    /<ver>-<hash>/… asset root) are proxied to the onlyoffice container by the
    web edge. No office.* subdomain, no second TLS cert — it reuses the app's
    origin. (valbox has no wildcard DNS and no certbot in the sudo grant, so this
    was a hard requirement.)
  • Trust model. The browser never holds a doc-server credential. Obscura issues
    a short-lived HS256 "office token" embedded in the content/callback URLs, so
    the otherwise-unauthenticated doc-server can only touch the one authorized
    document + version. The editor config and the doc-server's save callback are
    authenticated by OnlyOffice's own JWT (shared ONLYOFFICE_JWT_SECRET), so a
    forged callback is rejected. Server-to-server URLs use compose service names
    (http://obscura:8080, http://onlyoffice).
  • Editable ⇔ access. The config is built server-side; it is only editable when
    the caller is a content admin or has AccessReadWrite on the document.

Enabling it

Off by default. office is a licensed module (listed in Admin → Licensing like
the other premium modules) that additionally requires the doc-server to be
configured — activation is the AND of both:

  • license includes office (licensegen -modules …,office; installable live via
    Admin → Licensing → Upload license file), and
  • deployment runs the doc-server with the shared secret set.

Licensed-but-unconfigured shows as "Office Editing (not configured)" in the
Licensing tab. If either is missing, /me reports office_edit: false, the Edit
button never renders, and (because deploy/update.sh only rebuilds obscura+web)
the onlyoffice container is never even started.

To configure the deployment side:

  1. Set a strong shared secret in the deploy env (deploy/mekari.env):
    ONLYOFFICE_JWT_SECRET=<openssl rand -hex 32>
  2. Start the doc-server (this is the explicit opt-in; restart: unless-stopped
    keeps it up across reboots):
    docker compose -f deploy/docker-compose.yml --env-file deploy/mekari.env up -d onlyoffice
  3. Recreate obscura so it picks up the secret (deploy/update.sh, or
    up -d obscura).

onlyoffice/documentserver is ~2.5 GB and idles at ~1 GB RAM, so it is left out
of the default deploy path on purpose — a stack that doesn't opt in pays nothing.

Custom fonts (Admin → Office fonts)

Documents that reference a font the doc-server doesn't have (e.g. a corporate
Helvetica Neue) render with a substitute in both the editor and the published
PDF. Admins can install the real fonts under Admin → Office fonts (the nav item
appears only when the office module is on): upload .ttf/.otf/.ttc, list, delete.
Requires license.admin.

How it works:

  • The backend writes uploads to OFFICE_FONTS_DIR, a directory bind-mounted into the
    doc-server under its core-fonts tree
    (/var/www/onlyoffice/documentserver/core-fonts/obscura-custom).
    This path matters: the stock documentserver-generate-allfonts.sh scans only
    core-fonts (with --use-system-user-fonts="false") — fonts placed under
    /usr/share/fonts are seen by fc-list but ignored by the converter/editor.
  • The custom doc-server image (deploy/onlyoffice/) runs a supervised font-watcher
    that regenerates the font index and restarts ds:converter + ds:docservice
    whenever the drop dir changes — no docker socket / host access. New fonts apply within
    ~a minute; already-open editors must be reopened, and a document's PDF is refreshed by
    re-Publishing it.

Enabling it (in addition to the office ONLYOFFICE_JWT_SECRET above):

  1. Set OFFICE_FONTS_DIR=/data/office-fonts on the obscura service (the compose file does
    this) and mount the shared host dir into both services (obscura at
    /data/office-fonts, onlyoffice at .../core-fonts/obscura-custom).
  2. Create the host dir writable by obscura's nonroot uid: mkdir -p deploy/office-fonts && chown 65532:65532 deploy/office-fonts (or chmod 0777).
  3. Because the doc-server image is now built (not stock), start it with
    docker compose … up -d --build onlyoffice.

Fonts you don't have redistribution rights to (Helvetica Neue is proprietary) must be
supplied by the customer from their own licensed copies — Obscura ships none.

Could we use Microsoft's editor instead? (researched)

Short answer: not for a free, self-hosted, air-gap-capable product. Microsoft
offers two in-browser editors and neither fits:

Option Self-hostable? Cost Verdict
Office for the web (the editor inside Microsoft 365) No — cloud only Requires an M365 tenant; third-party embedding needs the Cloud Storage Partner Program (approval-gated, aimed at cloud storage vendors) Documents must live in OneDrive/SharePoint. Can't run on-prem or air-gapped. Not available to a self-hosted DMS.
Office Online Server (OOS) — the on-prem WOPI editor Yes Not free: the installer is on the Volume Licensing Service Center, but using it (esp. editing, vs view-only) requires each user to hold a qualifying Office volume license or Microsoft 365 Apps subscription. No standalone list price. Only makes sense if the customer already has an Office VL / M365 E3/E5. Heavy to run, and every shipped customer would need their own OOS + Office licenses + WOPI wiring. Impractical for an on-prem product we ship.

So Microsoft's path is either cloud-locked (Office for the web) or
license-gated and operationally heavy (Office Online Server). OnlyOffice Docs
Community is OOXML-native (opens/saves real .docx/.xlsx/.pptx), free
(AGPLv3)
, self-hosted, and works offline/air-gapped — which is why it's the
default here. (Collabora Online / LibreOffice Online is the other free option;
OnlyOffice was chosen for stronger .docx layout fidelity.)

If a specific customer does have an Office VL and wants Microsoft's own
renderer, OOS speaks the same WOPI pattern we already implement here, so swapping
it in for that deployment is feasible — but it stays their licensing burden, not
ours.

Files

  • Backend: go/internal/httpapi/handlers_office.go (config / content / callback,
    hand-rolled HS256), routes in server.go, config in
    go/internal/platform/config/config.go, /me flag in handlers_auth.go.
  • Web: web/src/features/documents/OfficeEditorView.tsx,
    web/src/api/office.ts, the Edit button in DocumentDetailView.tsx, the
    same-origin proxy in web/nginx.conf.
  • Deploy: the onlyoffice service + ONLYOFFICE_JWT_SECRET in
    deploy/docker-compose.yml.