Files
docs/publishing
docs-agent 4f07c2d3ac docs site: content-hash the site's own stylesheets (cache-proof URLs)
brand.css and typography.css are referenced at fixed URLs via extra_css, so a
warm browser cache could keep serving the previous stylesheet after a deploy.
Add on_post_build to publishing/hooks.py: at build time copy each extra_css
file to <name>.<sha256-8>.css in the output and rewrite the <link> in the built
HTML. Any content change now yields a new URL. Sources keep their names; hashed
copies exist only in the build output and stale ones are pruned on rebuild.
2026-11-04 00:23:28 +00:00
..

Publishing the documentation site

The Markdown in this repository is the single source of truth. This directory is the publishing layer that turns it into a member-readable static site and puts it on the co-op's docs Surfer app at https://docs.inference.coop/. Documents are never edited here — a change to a document is a normal commit or pull request against the Markdown at the repository root.

What is built

File Purpose
../mkdocs.yml Site configuration (MkDocs + Material for MkDocs)
brand.css Co-op palette + self-hosted Figtree webfont (SIL OFL 1.1)
fonts/ Figtree woff2 files and the OFL licence text
logo.svg Co-op logo (shared with the website repo)
build.sh Stage the Markdown into .site-src/, render to .site-build/
hooks.py MkDocs hooks: directory-link rewriting + content-hashed stylesheet URLs
deploy.sh Push the build into the Surfer app container
poll-watcher.sh Polls Gitea main every 60s; deploys on change (agent host)
docs-deploy-poll.service systemd unit for the poller
webhook-receiver.py Gitea webhook → pull → build → deploy (agent host)
webhook-receiver.service systemd unit for the receiver
requirements.txt Pinned Python build toolchain

Nothing external is loaded by the site: no CDN, no third-party font service. Fonts are served from this repository's own output.

Cache-proof stylesheets

brand.css and typography.css are listed under extra_css, so at a fixed URL they would be vulnerable to a stale browser cache after a deploy — a warm cache keeps serving the old file because the URL never changes. hooks.py's on_post_build fixes this at build time: it copies each stylesheet to <name>.<sha256-8>.css in the output and rewrites the <link> in every built page to the hashed name. A change to a stylesheet's content therefore always produces a new URL. The authored sources keep their names and locations; the hashed copies exist only in the build output. The hashed copy sits in the same directory as the original, so the @font-face url("fonts/…") references keep resolving. The deploy is unchanged.

Build

publishing/build.sh          # creates .venv-docs on first run

Output lands in .site-build/ (gitignored). mkdocs build --strict is what CI-style runs should use, so broken links fail the build.

Deploy

CLOUDRON_TOKEN=... publishing/deploy.sh

Writes the build into the docs Surfer app (Cloudron app docs.inference.coop, io.cloudron.surfer) at:

That is now the one canonical location. The docs used to be a pair of directories inside the marketing Surfer app (inference.coop) as well, at /app/data/public-docs/ and /app/data/public/docs/; both were deleted when this app took over, so https://inference.coop/docs/ no longer serves the docs (nothing on the marketing site linked to it).

Override the destination with SURFER_APP (the app's Cloudron location) and DOCS_DEST (path inside the container) if you ever need to.

Automatic rebuilds (push to main)

Two mechanisms keep the site in step with main; both run on the agent host and deploy into the docs Cloudron app above.

Polling (running now). docs-deploy-poll.service polls Gitea every 60 seconds and deploys whenever main moves. A push appears on the site within about a minute, with no human action. The Gitea webhook alone was not enough because the agent host's cloud firewall only admits inbound SSH — a webhook POST is silently dropped — while outbound access to Gitea works.

The service builds from a dedicated clean clone at /home/ubuntu/.docs-publisher, not from anyone's working copy, so uncommitted edits in a working clone can never block a deploy. Edit, commit, push — that is the whole deployment story.

Webhook (wired, waiting on one port). A Gitea webhook is registered on co-op/docs (push events, branch main) pointing at the receiver below. The moment the Infomaniak panel allows inbound TCP to the agent host — port 8787 from the Cloudron box 179.237.86.116 is enough — start the receiver and pushes deploy instantly instead of within a minute:

sudo systemctl enable --now docs-deploy-webhook
tail -f /home/ubuntu/docs-deploy.log        # what both mechanisms are doing

The receiver authenticates webhooks with an HMAC-SHA256 shared secret (DEPLOY_WEBHOOK_SECRET in the agent host's env file) and only accepts pushes to main of co-op/docs from the Cloudron box. Woodpecker CI (Cloudron catalog) could do the same job inside Cloudron, at the cost of running another app; the agent-host mechanisms were chosen because they add no new Cloudron app.

Each deploy writes meta.json into the site with the commit it was built from, so a deploy can be verified from the served site itself: https://docs.inference.coop/meta.json