Files
docs/publishing
inference-bot 9b66409343 docs site: make Figtree actually apply, and size headings like the website
Material emits its own font variables and defines --md-text-font-family on <body>,
so brand.css's :root value lost to it by DOM proximity -- the same failure mode as the
palette, and the reason the site rendered entirely in the system font stack with
Material's default 32px/300 headings.

theme.font: false stops Material supplying typography, and brand.css now declares the
font on body, the navigation, the header and every heading, with heading sizes closer to
the website. Figtree is still self-hosted from publishing/fonts.
2026-11-03 20:35:20 +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 Surfer app. 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/
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.

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 Surfer app (Cloudron app inference.coop) at:

Both are written so the site stays reachable either way; the hostname is the canonical one.

Automatic rebuilds (push to main)

Two mechanisms keep the site in step with main; both run on the agent host and neither adds a Cloudron app.

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