This repository builds cozystack.io with Hugo and the Docsy theme. It hosts the official Cozystack documentation across all supported versions. This guide explains where to put your change, how the versioned layout works, and how a release moves docs around.
If you just want to run the site locally:
npm install
make serve # http://localhost:1313/docsSee README.md for tool setup.
Docs are versioned per Cozystack minor release. Each minor version has its
own self-contained directory under content/en/docs/, and each is served under
its own URL prefix (/docs/v1.2/, /docs/v1.1/, …). This lets users on an
older release keep reading accurate docs for that release, while newer users
see current material.
content/en/docs/
├── _index.md ← version-agnostic landing page (/docs/)
├── next/ ← trunk for upcoming/unreleased docs (/docs/next/)
├── v1.2/ ← current stable
├── v1.1/
├── v1.0/
└── v0/ ← legacy (all 0.x)
A few rules follow from this layout:
- Each version directory is independent. Editing
v1.2/applications/tenant.mddoes not affectv1.1/. - There is no automatic backport between versions. If a fix needs to apply to multiple versions, it must be copied to each.
- Autogenerated pages (see below) must not be edited in place — edit the upstream source and re-sync.
Docs describing a feature that has not yet shipped cannot sit in any released version directory:
- They must not leak into
/docs/v1.2/— that's the current production docs, and v1.2 does not have the feature. - They must not pre-register as
/docs/v1.3/— we don't know yet whether the next release will actually bev1.3, or when it will ship.
content/en/docs/next/ solves this: it's a permanent, version-agnostic
trunk where upcoming-release docs accumulate. When a release is cut, the
contents of next/ are promoted to the concrete version directory
(v1.3/, v2.0/, …) and the internal links are rewritten.
Properties of next/:
- Always present in the repo; never deleted.
- Hidden in production: excluded from GitHub Pages builds by
config/production/hugo.yaml, and hidden from the version-switcher dropdown viahidden: trueinhugo.yaml. - Visible in local dev and Netlify deploy previews, so reviewers can see upcoming-release docs on a PR preview before they ship.
- Uses
/docs/next/...paths internally; these are rewritten to/docs/vX.Y/...at release time byhack/release_next.sh. - The landing page (
next/_index.md) carries a draft banner warning the reader that the content is unreleased.
Use this decision table before you start editing:
| Your change is… | Edit here |
|---|---|
| A doc for a feature that ships in a future release | content/en/docs/next/ |
| A bug fix or addition for the current release | content/en/docs/<latest_version_id>/ (v1.2) |
| A fix that also applies to an older supported version | Copy the change into each relevant vX.Y/ |
| Cross-version content (the landing page, overview) | content/en/docs/_index.md |
| A blog post | content/en/blog/ |
| A layout, shortcode, or site-wide asset change | layouts/, assets/, static/ |
Treat v0/ as legacy: it receives fixes only for correctness of historic
references. Do not add new content there.
Some pages under each version are synced from the upstream
cozystack/cozystack repo. These
include most of applications/, virtualization/, networking/, kubernetes/,
and operations/services/. They carry a footer:
Autogenerated content. Don't edit this file directly; edit sources instead.
If you need to change one of these pages:
- Change the prose (body) → edit the upstream README in
cozystack/cozystackunderpackages/apps/<app>/README.md(or the matchingpackages/extra/<svc>/), then re-run the corresponding sync target:make update-apps,make update-vms,make update-networking,make update-k8s, ormake update-services. By default,update-*targets the trunk (next/). PassRELEASE_TAG=vX.Y.Zto route to a released version for patch updates. - Change only front matter (title, weight, aliases) for a specific
version → edit the stub under
content/en/docs/<version>/<section>/_include/<app>.md. Theupdate-*targets merge these stubs with the upstream README, so front matter edits survive the next sync.make template-*scaffolds new stubs.
Everything else on the site is hand-authored and edited directly.
v1.2 already exists and is registered as a released version. The release
workflow (upstream cozystack/cozystack tags.yaml) calls:
make update-all RELEASE_TAG=v1.2.5RELEASE_TAG routing detects that content/en/docs/v1.2/ exists and is
non-hidden, so DOC_VERSION resolves to v1.2 and the update happens in
place in that directory. next/ is not touched.
The trunk gets promoted to a new version directory. The upstream workflow calls:
make release-next RELEASE_TAG=v1.3.0
make update-all RELEASE_TAG=v1.3.0-
make release-next RELEASE_TAG=v1.3.0invokeshack/release_next.sh, which:- Validates that
next/is non-empty andv1.3/doesn't already exist. - Copies
content/en/docs/next/→content/en/docs/v1.3/(cp -a). - Rewrites
/docs/next/→/docs/v1.3/across every.mdin the new directory. - Updates
v1.3/_index.md: sets the title to Cozystack v1.3 Documentation and strips the draft banner. - Calls
hack/register_version.sh --release v1.3, which registersv1.3inhugo.yaml, setslatest_version_id: "v1.3", and normalizes_index.mdweights across all non-hidden versions (new latest → 10, then 20, 30, 40, …) so sidebar ordering stays deterministic. - Leaves
next/untouched — it stays in place for the next release cycle.
- Validates that
-
make update-all RELEASE_TAG=v1.3.0then pulls fresh upstream READMEs pinned to the exact tag into the newv1.3/directory.
hack/release_next.sh rewrites /docs/next/ URL paths, but version
references in prose are promoted verbatim. Before cutting a new minor, do a
sweep over content/en/docs/next/ for any Cozystack v<prev>, @v<prev>.0,
pinned-image versions, or "since Cozystack v" phrasing that would be
factually wrong once the trunk becomes the new version. Search patterns that
typically catch the stragglers:
grep -rn 'Cozystack v[0-9]' content/en/docs/next/
grep -rn '@v[0-9]' content/en/docs/next/
grep -rn 'talos:v[0-9]' content/en/docs/next/release-next copies next/ → vX.Y/; it does not move. The trunk
therefore still holds the now-released content and must be reset before any
further work targeting the next cycle:
make init-nextinit-next wipes content/en/docs/next/ and re-initializes it from the new
latest version. Run this as the final step of every release — skipping it
leaves the trunk in a stale state where edits look like they're for an
upcoming release but are actually landing on top of already-released content.
| Scenario | Target directory |
|---|---|
make update-all (no tag) |
next/ |
make update-all RELEASE_TAG=v1.1.8 (v1.1 exists) |
v1.1/ |
make update-all RELEASE_TAG=v1.3.0-rc1 (v1.3 doesn't yet) |
next/ |
make update-all RELEASE_TAG=v1.3.0 (v1.3 doesn't yet) |
next/ |
make release-next RELEASE_TAG=v1.3.0 |
copies next/ → v1.3/ |
make update-all RELEASE_TAG=v1.3.1 (v1.3 now exists) |
v1.3/ |
Use make show-target [RELEASE_TAG=…] to print the resolved DOC_VERSION and
BRANCH without running anything.
# Local development
npm install
make serve # dev server at http://localhost:1313/docs
# Production-shape build (reproduces what GitHub Pages serves)
./hack/download_openapi.sh && HUGO_ENV=production hugo --gc --minify
# Pull upstream content (targets next/ by default; pass RELEASE_TAG to route)
make update-all # trunk from upstream main
make update-all RELEASE_TAG=v1.2.5 # routes to v1.2/ (patch release)
make update-apps # application docs only
make update-vms | update-networking | update-k8s | update-services
make update-oss-health # OSS health metrics (requires Python 3.12+)
# Scaffolding (stubs for new apps/services)
make template-apps # also template-vms / template-networking / …
# Version lifecycle
make release-next RELEASE_TAG=v1.3.0 # promote next/ → v1.3/
make init-next # (re)create next/ from latest released version
make init-version DOC_VERSION=v1.3 # low-level init of any version dir
# Debugging
make show-target RELEASE_TAG=v1.3.0 # prints resolved DOC_VERSION / BRANCHRequired tools: Hugo extended 0.160.1, Go 1.23+, Node 20+, yq v4+ (for the
version lifecycle targets and Makefile routing).
If you need to understand or change the versioning machinery itself:
hugo.yaml— site-wide config;params.versions[]is the list of registered versions (includingnextwithhidden: true), andparams.latest_version_idis the current stable.config/production/hugo.yaml— production-only overrides; excludesdocs/next/**from GitHub Pages builds. Netlify deploy previews setHUGO_ENV=previewso they skip this override.Makefile—RELEASE_TAGrouting, lifecycle targets.hack/release_next.sh— trunk → version promotion.hack/register_version.sh— manageshugo.yamlversion entries and normalizes_index.mdweights.hack/init_version.sh— initializes any version directory, with special handling fornext/(title, draft banner).hack/update_apps.sh,hack/fill_templates.sh— content sync from cozystack/cozystack.hack/download_openapi.sh— fetchesopenapi.jsonper version at build time (v1.1+; v0 and v1.0 ship theirapi.jsonin-tree).layouts/docs/docs-landing.html— the/docs/landing template; builds the version table fromparams.versions[], filtering out hidden entries.layouts/partials/version-switcher.html— top-navbar version dropdown; filters out hidden entries.layouts/404.html— client-side fallback redirect for/docs/v1/…short paths.