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.
108 lines
4.9 KiB
Markdown
108 lines
4.9 KiB
Markdown
# 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
|
|
|
|
```sh
|
|
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
|
|
|
|
```sh
|
|
CLOUDRON_TOKEN=... publishing/deploy.sh
|
|
```
|
|
|
|
Writes the build into the **docs Surfer app** (Cloudron app `docs.inference.coop`,
|
|
`io.cloudron.surfer`) at:
|
|
|
|
- `/app/data/public/` — served at **https://docs.inference.coop/**
|
|
|
|
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:
|
|
|
|
```sh
|
|
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
|