PSW Documentation Site#

What Is It?#

PSW Documentation is the same set of concept docs you’re reading right now — every file under docs/concepts/ plus the project README — rendered as a real, searchable, browsable website and shipped with your self-hosted solution. It lives at https://docs.<your-domain>/ (e.g. https://docs.casaeureka.ca/) and is served by your own server, alongside Jellyfin , Grafana , and everything else.

So the next time you wonder “wait, what does idempotency mean again?” or “how do I expose an app remotely?”, you don’t need to fish for a GitHub tab — open docs.<your-domain>/ and search.

Why Does It Exist?#

The docs already existed as markdown in the repo. Reading markdown on GitHub is fine for a developer, but a self-hosted setup outlives the day you cloned the repo:

  • You might be offline. A power cut, an ISP blip, a flight — your platform keeps running, your docs should too.
  • You might be on a different machine that doesn’t have the repo checked out — your phone, your TV-room laptop, a friend’s tablet.
  • You shouldn’t have to leave the house’s network just to look up “how does the convergence timer work again?”.
  • The site has full-text search across every page; the GitHub markdown viewer does not.

PSW takes the position that documentation is part of the platform, not a side artefact. So the docs ship like any other app — built into a container, deployed on your server, behind the same HTTPS, listed on your homepage dashboard .

Where It Lives#

It’s the eighth core app — installed automatically during bootstrap on the bootstrap target , no extra step required:

QuestionAnswer
URLhttps://docs.<your-domain>/ (e.g. https://docs.casaeureka.ca/)
Login required?No. Docs are public — anyone who can reach the URL can read them. Same sso_type: none reasoning as Authelia : the docs page can’t gate itself behind a login when most readers are looking up how to log in
App folderpsw-apps/psw_apps/psw_docs/ — the meta + the deploy hook
Image buildpsw-docs/Containerfile + psw-docs/nginx.conf — the two-stage build (Hugo render → nginx serve)

You’ll find it on the Apps page in the operations dashboard under the Infrastructure category, with a link to its homepage tile too.

How It’s Built#

The docs site is a static Hugo site using the hugo-book theme. Two big choices keep it honest:

  1. One source of truth. The Hugo site under site/ doesn’t have its own copy of the docs. Hugo’s content adapters read straight from docs/concepts/ and README.md via mounts declared in site/hugo.toml . Edit a markdown file under docs/concepts/ and the next render picks it up — no copy step, no sync script, no two-places-to-update.
  2. Same render at home and in production. make docs runs the Hugo dev server with live reload on http://localhost:1313 for editing. make docs-build produces site/public/. The container build does the same hugo --gc --minify invocation. What you see locally is exactly what ships.

The container itself is a tiny nginx:alpine serving the rendered HTML on port 8112 (a free high port — 80/443 are owned by Traefik ). No database, no environment variables, no storage volumes. The whole site is baked into the image at build time.

How It Gets onto Your Server#

Like the PSW Dashboard , the docs image isn’t pulled from a public registry. It’s built on your workstation and shipped to your server as a tarball. The flow during bootstrap :

  1. Workstation builds the image. build_docs_image() runs podman build against the repo with site/ + docs/ + README.md + psw-docs/ in the build context. Hugo fetches the theme module, renders the static site, nginx packages it. Output: /tmp/psw-docs-image.tar.
  2. Tarball travels by SCP. The tarball is copied to the bootstrap target as part of the Prepare target phase — same channel that ships the dashboard image and the PSW source.
  3. Deploy hook loads the image. Right before the podman quadlet starts the container, the @pre_deploy("psw_docs") hook runs podman load from the tarball so Image=localhost/psw-docs:latest resolves locally instead of triggering a registry pull. The tarball is consumed (deleted) on success.
  4. Quadlet starts the container. Network=host, port 8112, no special caps. Traefik picks up the convention that turns the subdomain: docs field in meta.yml into the routing rule Host(\docs.`)`.

The build mechanics (build context staging, podman build/save, SCP transfer) live in psw_lib.convergence_agent.local_image and are shared with PSW Dashboard . The two image-build modules just declare what goes in the build context.

Updating the Docs#

Two paths, depending on whether you’re editing or just consuming:

  • Editing (you’re working on PSW itself). Run make docs to start the Hugo dev server on http://localhost:1313. Edit any file under docs/concepts/ or README.md, save, and the browser auto-reloads. When you’re happy, commit + push as usual.
  • Updating the version on your server. Doc updates ship the same way every other PSW change does: psw self-update pulls the latest PSW source, and the next bootstrap (or a manual psw deploy bootstrap --apps psw_docs) rebuilds the image and redeploys. Updates do NOT flow through your user project’s git push → convergence loop, because the docs are part of PSW itself, not part of your project.

What’s on the Site#

Whatever’s in the source tree at build time:

  • Home page — your project’s README , rendered at the root URL.
  • Concepts section — every file under docs/concepts/, in a left-hand sidebar with categories (Getting Started, Apps, Infrastructure…) and an inter-page table of contents.
  • Search — a search box in the top-right indexes every page; results show the matching section. Powered by the hugo-book theme’s Lunr -based client-side search — no Elasticsearch, no third-party service.
  • Light / dark theme — auto-follows your OS preference, with a toggle in the header.

Internal links between concept docs (like the [bootstrap](bootstrap.md) references all over this doc) are rewritten to clean URLs by Hugo, so they keep working in both the rendered site and on GitHub.

What It’s NOT#

  • Not editable from the site. The docs are a static render. You can’t write notes inside it, leave comments, or reorganise the sidebar from the browser. To change content, edit the markdown in the repo.
  • Not the operations dashboard. “Doc” sounds vague — the operations dashboard at dashboard.<your-domain> is where you operate the platform. The docs site at docs.<your-domain> is where you learn how it works. Different URLs, different jobs.
  • Not user-installable. You can’t psw app remove psw_docs and expect a meaningful outcome — it’s a core app , part of the platform’s foundation. Same status as PSW Dashboard, Traefik , or LLDAP .
  • Not your project’s docs. It always renders PSW’s docs, not anything you might write inside your user project . If you want notes about your setup, that’s a separate problem — most operators keep a README.md next to their network.yml and call it a day.

Key Ideas#

  • Eighth core app — deployed automatically by bootstrap , no extra config to enable it
  • Public, no login — anyone who can reach docs.<your-domain> can read; the docs explain how to log into everything else, so they can’t gate themselves
  • One source of truth — Hugo content adapters render docs/concepts/ and README.md directly from the repo; no copies, no sync scripts
  • Workstation-built, SCP-shipped — same pattern as PSW Dashboard; no public registry round-trip
  • Updates with PSW itselfpsw self-update + a redeploy, not via your project’s git push loop
  • Local edit loopmake docs for live-reload editing; what you see locally is exactly what ships

See Also#

  • Apps — the catalog and the difference between core and user apps
  • Bootstrap — the one-time process that deploys this doc site (and the seven other core apps )
  • Operations Mode — the other PSW-built web UI on your server, the one that lets you act on what these docs explain
  • Conventions — how subdomain: docs in meta.yml becomes a working HTTPS URL with no per-app code