Administrator Guide

Operating the kimidoc document-rendering service. For the REST API, see the web app's API docs view.

Install & first run

sudo snap install kimidoc
snap services kimidoc          # daemon should be active
curl http://127.0.0.1:8080/health

The service starts automatically and listens on port 8080, bound to localhost only until an API key is configured. Open http://<server>:8080/ in a browser for the web app: template management, an editor with live PDF preview, six built-in starter templates, stats, and API documentation. Dependencies (the Qt runtime content snap) install automatically.

Configuration

All configuration is via snap set; any change validates and restarts the service automatically:

sudo snap set kimidoc port=8080 api-key=CHANGE_ME pool=4
sudo snap set kimidoc allow-remote=true allowed-hosts=cdn.example.com,*.gstatic.com
snap get kimidoc               # view current configuration
KeyDefaultMeaning
port8080Listen port
bindautoExplicit bind address. Auto = 127.0.0.1 without an API key, 0.0.0.0 with one
api-keyunsetEnables auth on /v1/* (X-Api-Key / Bearer) and non-loopback binding
pool3Concurrent render pages (see Capacity)
webhook-allowed-hostsunsetCSV of allowed webhook destinations (*.suffix ok). Unset = any public host; internal/LAN receivers must be listed here explicitly (entries override the internal-range block)
chromium-flagsunsetExtra Chromium flags appended to the built-in set (--disable-gpu + a V8 heap cap that bounds render memory)
recycle-renders100Replace each render page after N documents (bounds Chromium's per-render memory growth during large batches; lower = flatter memory, slightly slower)
allow-remotefalseLet rendered content fetch http(s) resources; also gates /v1/render-url
allowed-hostsallCSV allowlist when remote is on; *.suffix supported
max-body8388608Request size cap in bytes (also caps asset uploads)
storage-quota4294967296Byte cap for stored blobs: uploaded assets and batch-job outputs (documents + ZIP; a 40k job needs ~2.5 GB). Asset uploads beyond it return 507; a job that hits it fails with retry instructions

Security model

  • Safe by default: without an API key the service refuses to bind beyond localhost. Set api-key before exposing it to a network.
  • Rendering sandbox: templates execute inside Chromium's sandbox (kept fully enabled; the service itself runs as the unprivileged snap_daemon user under strict snap confinement).
  • SSRF policy: remote fetches by rendered content are off by default. When enabled, requests to loopback, link-local (cloud metadata), and private address ranges are always blocked, and allowed-hosts restricts the rest. Blocked requests are logged ([ssrf] blocked …) and reported to API clients via X-Kimidoc-Blocked-Remote response headers.
  • Webhooks: outbound job-completion callbacks are SSRF-guarded — internal/loopback/link-local destinations are blocked (checked against resolved addresses at send time) unless explicitly allowlisted via webhook-allowed-hosts. Payloads are HMAC-signed when the submitter provides a secret.
  • Document encryption & stamping: output.encrypt (on synchronous renders and batch jobs alike) produces AES-256 PDFs with per-recipient passwords and optional permission restrictions (allowPrint/allowCopy/allowModify); output.stamp watermarks every page (DRAFT/COPY/VOID). Passwords are passed to the bundled qpdf through private argument files, never on process command lines; the server does not store resolved passwords — they exist only in the submitted document data and the job's stored configuration.
  • TLS: the service speaks plain HTTP — put a reverse proxy (nginx, Caddy) in front for TLS, rate limiting, and request logging at the edge if exposed beyond a trusted network.
  • Web app: static assets are served without auth (they contain no secrets); every API call the app makes is authenticated. API docs and this guide are intentionally readable without a key.

Fonts

kimidoc bundles the Noto families (including CJK and color emoji) so complex-script rendering works identically on every install. Host system fonts are deliberately not used — this keeps output deterministic across machines. To add fonts server-wide:

sudo cp CorpFont-*.ttf /var/snap/kimidoc/common/fonts/
sudo snap restart kimidoc

The directory survives upgrades. Per-template fonts can instead be embedded as @font-face data URIs (no admin access needed). At startup the service logs a warning if coverage for major writing systems is missing.

Templates

  • Built-ins: six starter templates (invoice, resume, report, certificate, letter, receipt) are synced from the snap at every start and marked built-in: read-only, cannot be deleted, refreshed on upgrades. Users customize them via "Use as base", which saves a copy.
  • User templates live in the service database with their sample data and render options.
  • Storage: everything is in a single SQLite file at /var/snap/kimidoc/<rev>/state/templates.db (see Backup).

Monitoring & logs

journalctl -u snap.kimidoc.kimidoc -f      # follow logs
curl -H "X-Api-Key: KEY" http://127.0.0.1:8080/v1/stats

Log line formats:

POST /v1/render -> 200 from 10.0.0.5          # access log, every request
render ok: 843 ms, 51234 bytes                # render detail
GET /wp-admin/x -> 404 (no route) from 1.2.3.4  # unmatched hits (scanners!)
[ssrf] blocked https://... - reason           # subresource policy
[page console] ...                            # JS errors from templates -
                                              #  first stop for "renders blank"

/v1/stats returns uptime, render counters (ok / failed / timedOut / rejectedBusy), average render time, pool utilization, and the active remote-fetch config. Expected benign startup warnings (harmless headless artifacts, do not chase): the org.freedesktop.portal D-Bus line, QRhiGles2 / Failed to create RHI GL-probe lines, and the udev monitor error.

Capacity & performance

  • Pool: pool render pages work concurrently; further requests queue up to 8× pool, beyond which the API returns 503 server busy — clients should retry with backoff. Size the pool roughly to CPU cores minus one for sustained load.
  • Memory: expect on the order of ~250 MB resident for the service with a pool of 3, growing modestly under load. Rendering is software-based by design (--disable-gpu) — no GPU required or used.
  • Timeouts: renders are bounded per request (timeoutMs, default 20 s) and surface as 504.
  • Simple renders complete in well under a second; remote assets, waitMs, and Paged.js add their own time.

Backup & restore

Three things hold all state:

/var/snap/kimidoc/current/state/templates.db     # templates + asset metadata
/var/snap/kimidoc/common/storage/          # uploaded asset files
/var/snap/kimidoc/common/fonts/            # operator-installed fonts
sudo snap stop kimidoc
sudo cp /var/snap/kimidoc/current/state/templates.db /backup/
sudo cp -r /var/snap/kimidoc/common/storage /backup/
sudo snap start kimidoc
# restore = copy the files back (same paths) and restart

Built-in templates need no backup — they re-sync from the snap. Configuration is snapshotted by snap save or re-applied with snap set.

Upgrades

Snaps auto-refresh; sudo snap refresh kimidoc forces it. On stop the service drains gracefully: it stops accepting connections, finishes in-flight renders (up to 30 s), then exits — API clients see completed responses, not dropped connections. After upgrade, the database schema migrates automatically and built-in templates refresh. Roll back with sudo snap revert kimidoc.

Troubleshooting

SymptomCheck
Service inactive after installPort conflict? journalctl -u snap.kimidoc.kimidoc; change with snap set kimidoc port=…
Template renders blank[page console] lines in the journal — usually a JS error in the template
Images/fonts missing from PDFsX-Kimidoc-Blocked-Remote header / [ssrf] blocked logs → enable allow-remote, extend allowed-hosts, or inline as data URIs
A script renders as boxes (tofu)Startup "font coverage missing" warning → add fonts to common/fonts
504 with waitForReadyThe template never calls window.pdfReady()
Frequent 503sRaise pool, add client backoff, or scale out behind a load balancer
Can't reach from another machineNo API key set → service binds localhost only by design