Public Enelix Documentation Publisher
Context and decision
The license portal already serves public static files and exposes its effective
catalog at GET /api/catalog. Documentation is therefore built as static HTML;
the existing portal process, authentication, database and payment code are not
modified. Generic Utils runtime code is not coupled to the documentation service.
Source ownership:
docs/public/*.mdin Enelix-EMS: installation, operator guides, FAQ and pricing explanation.docs/public/images/*.png: explicitly reviewed real Symcon screenshots, allowlisted inimages.mjs.- Explicit README/interface allowlists in
config.mjs: original EMS and Utils references. - Backend catalog: every monetary value, product name, SKU, term and effective VAT rate.
- This directory: rendering, navigation, publication and tests.
The default source branch is develop, visibly identified as Testing. The
publisher reads separate shallow bare mirrors; it never changes existing
developer checkouts. All sources are pinned to the commits captured at the start
of a successful run. Neither unpublished worktrees nor internal handoffs, open
issues, credentials, operational notes or arbitrary new files are published.
Alternative considered: adding server-side documentation routes. Not selected: the current static mount enables this rollout without a portal restart or a new public service. A separately maintained copy of prices was rejected because it would drift from checkout. The browser refreshes the same catalog endpoint every 30 seconds while visible, on reappearance and on manual refresh.
Public routes
/docs/index.html/docs/erste-schritte.html/docs/installation.html/docs/manager.html/docs/verbraucher.html/docs/utils.html/docs/faq.html/docs/preise.html/docs/ems/*.htmland/docs/utils/*.html/docs/manifest.json: published source commits and file mappings./docs-status.json: last attempted/successful synchronization, no internal errors.
The portal's anonymous login screen links to documentation, FAQ and prices. Authenticated sidebar navigation additionally links to documentation and prices.
Within the EMS references, Manager remains a direct link; consumer modules and technical interfaces are nested in native, keyboard-accessible disclosure groups. The current page's group opens on the server, also without JavaScript. Search expands matching groups and restores their previous state when cleared. All page URLs remain unchanged; new references without a group stay accessible.
assets/portal-links.css contains only the added navigation styling.
portal-navigation.patch records the exact additive portal HTML change; check
it against the current portal before applying it and copy the stylesheet to
public/docs-portal.css. Never replace the full portal HTML from another checkout.
Install and publish
Deploy the reviewed publisher files to /home/agent/services/public-docs as
the unprivileged agent user. Do not copy node_modules, state, preview, QA or
backups into a repository or the public directory. Install locked dependencies:
npm ci --ignore-scripts
npm test
For rendering/rollback checks without changing public pages:
node preview.mjs
node verify.mjs
node browser-test.mjs
Publication uses the existing public mount at
/home/agent/services/license/public. The portal container's UID must be able to
read generated directories and files. No credentials are included in the
publisher: Git uses the server's preconfigured access. Never put credentials in
repository URLs or public output.
Install the two supplied units through systemctl --user link, reload the user
manager, start enelix-public-docs.service once and enable
enelix-public-docs.timer. Only these user units are affected. The timer checks
Git five minutes after completion, with at most fifteen seconds of jitter.
The successful first run can take longer while fetching both repositories.
DOCS_BRANCH accepts develop, beta or main; reading a source branch does
not merge or modify it. DOCS_PUBLIC_DIR and DOCS_STATE_DIR support isolated
tests. Keep the chosen channel explicit in the service configuration.
Git-only operation and historical bootstrap
The production service does not enable DOCS_ALLOW_DRAFTS. All approved guides
and screenshots must exist in the selected Git branch. A missing source aborts
publication and preserves the last good release; deployment copies cannot
silently replace deleted Git content.
The initial 2026-10-06 deployment used explicitly labelled drafts while Git
publication awaited authorization. Daniel approved commit and push on the same
day. The bootstrap fallback remains available only through the explicit
DOCS_ALLOW_DRAFTS=true setting, including isolated previews. It reads missing
sources from the deployment-only guides/ directory and labels them as drafts.
Do not treat these bootstrap copies as a second long-term editing location.
When completing a bootstrap deployment, push the approved sources first, verify that publication uses their Git commits, then install the production unit without the draft setting and reload the user service manager.
Security and resilience
Markdown is rendered with pinned Marked and sanitized with sanitize-html. Raw HTML and remote images are disabled; link schemes and local mappings are restricted. Relative links to unpublished files are plain text. The existing portal CSP remains active; there is no inline JavaScript or browser-side Git credential. Every page is readable without JavaScript except the live price table, which shows an explicit notice and a catalog link in that case.
Only the exact local PNG paths in images.mjs render as screenshots. The
publisher reads binary files at the same EMS commit as the guides, checks regular
file type, PNG signature and dimensions, and publishes content-hashed filenames.
Image hashes, dimensions and source revisions are recorded in the manifest.
Missing or invalid images fail publication and preserve the last good release.
Draft bootstrap images follow the same explicit opt-in as the draft guides.
Screenshot refresh is intentionally reviewed, not a live camera into Symcon. Capture through the local-only web console in a separate browser session, without saving configuration or invoking regulation. Select useful crops, inspect them for license codes, installation IDs, accounts and private plant details, then replace the approved PNGs in Git and update the dated captions. Never publish unreviewed captures automatically. Current screenshots show the demo object tree and the manager basic settings in Symcon 8.0 on 2026-10-06. Test values are not recommended installation settings; reference READMEs may evolve faster than the dated screenshots.
Publication is a complete immutable release followed by an atomic docs
symlink switch. Failed fetches or rendering leave the previous release intact.
Public status reports failed or overdue checks. Stale prices are removed when
the catalog cannot be obtained or validated; no hardcoded fallback is used.
The sync lock prevents overlapping manual and timer runs. If the process is forcibly terminated, inspect process state before removing its stale lock from the publisher state directory. Generated releases are retained to permit rollback; disk usage should be monitored and old releases pruned only after checking that they are not current or needed for rollback.
Verification and rollback
npm test covers relative and cross-repository links, HTML sanitization,
allowlist isolation, headings/tables, price validation, VAT and periods.
node verify.mjs validates all published links and deliberately exercises a
source failure against an isolated mirror, proving last-good preservation.
node browser-test.mjs checks desktop, 390px and 320px layouts, search, automatic
catalog refresh, newly returned products, VAT toggle, API failure/recovery and
JavaScript-free documentation. Mocks affect only a fresh browser context, never
the production catalog. Screenshots remain private in qa/.
node verify.mjs live and node browser-test.mjs --live perform anonymous checks
on the actual domain. The tests must not log in or mutate live application data.
For rollback, first stop and disable only the documentation timer. Restore the previous documentation symlink atomically. For full removal, revert only the added portal navigation and stylesheet link after checking for concurrent edits; do not overwrite the portal with an older full backup if other edits occurred. No database, Nginx or IP-Symcon rollback is required because those were unchanged.
Initial deployment evidence
2026-10-06: 29 pages, 1,116 valid local links, 12 unit tests passed, isolated
failure recovery passed, desktop/mobile browser tests passed both in preview and
against the public domain. The initial sources were EMS
58f91e431936fedca3e59178037b939f420ce3e3 and Utils
82860c0eb938173841088f935a306729b6ce9a1c. These are dated evidence, not permanent
source pins. The price API initially returned 15 products and was accessed
without login.
Portal navigation backup:
/home/agent/services/public-docs/backups/2026-10-06T12-58-13-558Z/index.html.
The backup is private and must never be copied into public/.
Pull request summary
Add public documentation and FAQ from reviewed Git-backed sources, plus a live backend-driven price table. Preserve existing portal authentication and data; publish static releases atomically with a five-minute user timer. No schema or module migration and no beta/main changes. Include the end-user introduction, guided first steps, reviewed Symcon screenshots and grouped module navigation. Production publication requires versioned guides and screenshots, without draft fallback.