Page template and Markdown reference#
This page is the specification and the demonstration at once. It is written in the same Markdown every other page uses and rendered by the same code, so nothing here can describe a feature the renderer does not have. Copy it when you start a page, delete what you do not need.
Front matter#
Every page opens with a fenced block of key: value lines. It is not YAML — the parser
is about forty lines in build.py and understands scalars,
[a, b] lists, integers and booleans, nothing more.
---
title: Firewall
description: Zone policy, rule sets, and the nftables they become.
order: 20
tags: [firewall, nftables]
updated: 2026-09-02
---| Key | Type | Default | What it does |
|---|---|---|---|
title | text | — | The <h1> in the browser tab, the navigation label, the search result. Required; the build fails without it. |
description | text | — | The <meta name="description"> and the search snippet. Omitting it is a warning. |
order | integer | 500 | Position among siblings in the navigation tree. Ties break alphabetically. |
nav_title | text | title | A shorter label for the tree, when the full title is too long for it. |
tags | list | [] | Shown at the foot of the page and weighted in search. |
checked | list | [] | The repository files this page was written from. Verified on every build; rendered nowhere. See below. |
updated | text | — | Printed at the foot. Write the date you checked the claims, not the date you touched a typo. |
layout | text | page | Adds layout-<name> to the <body> class. The sales site uses home. |
draft | bool | false | Skips the file entirely. It is not built, not linked, not indexed. |
noindex | bool | false | Builds the page but keeps it out of sitemap.xml and sets a noindex robots tag. |
nav | bool | true | false keeps the page out of the navigation tree, and therefore out of the previous/next chain. |
toc | bool | true | false suppresses the on-this-page column. |
breadcrumbs, prevnext | bool | true | false suppresses those. |
search | bool | true | false keeps the page out of the search index. |
redirect_to | link | — | Turns the page into a redirect stub. See below. |
Citing the source#
Every claim on a page has to be checkable against the code that makes it true. The
citation lives in front matter, under checked, and the build fails if any path in it
is not in the checkout — so moving a file breaks the documentation build instead of
quietly leaving a claim behind that nothing supports.
---
title: Firewall
description: Zone policy, rule sets, and the nftables they become.
updated: 2026-09-02
checked: [
agent/firewall.go,
ui/src/pages/Firewall.tsx,
docs/firewall.md,
]
---A list may run over several lines, as this one does, and may end with a trailing comma.
Nothing about checked reaches the page: a reader who is running a router gets the
behaviour, not a tour of the source tree. Sort the paths and do not repeat one — a
repeat is a warning.
The repo: link scheme still works and is verified the same way, but it renders a
visible link into a private forge. Keep it for the development section, where a link
into the source is what the reader came for. In getting-started, concepts, the manual,
the guides and about, a repo: link is a warning and fails --strict.
Moving a page#
A published path is a promise, and a static host cannot answer a 301. So a page that
moves leaves its old path behind as a stub: front matter with redirect_to and nothing
else.
---
title: Firewall rules
description: Moved to /manual/firewall/rules/.
redirect_to: firewall/rules.md
---The build emits a <meta http-equiv="refresh">, a canonical URL pointing at the new
home, a visible link for a reader whose browser blocks the refresh, and keeps the stub
out of the sitemap, the search index and the navigation tree. redirect_to takes the
same link forms as everything else, so it is checked like everything else.
Where the file goes#
The path under content/ is the URL. Relative links and images resolve against the
source file's directory, so a link written in the Markdown is the link that works.
Text#
Ordinary paragraphs. Bold for the load-bearing clause of a sentence, italic
sparingly, and inline code for anything the reader will type or that came off the
router: a flag like --api-key-file, a path like /config/wheelhouse/state.json, a
header like X-Wheelhouse-CSRF.
Two trailing spaces force a line break —
like that — which suits an address block and little else.
A literal asterisk is \*, and any HTML written in a Markdown file is escaped rather
than rendered: <script> in a sentence produces those characters, not markup. The
renderer is a scanner that escapes everything it does not recognise, rather than a set
of regular expressions run over a whole document.
Links#
| Written | Resolves to |
|---|---|
[Contributing](index.md) | the sibling page, /contributing/ |
[Firewall](../manual/firewall.md) | a page in another section |
[this section](#links) | an anchor on this page; the build checks the heading exists |
[the product site](www:/) | the sales site's front page |
[the manual](wiki:/) | the wiki's front page |
[downloads](releases:/latest/) | the release host |
[the source](forge:/) | the git forge |
[agent/license.go](repo:agent/license.go) | that file in the forge, and the build fails if it is not in the tree. Development section only — everywhere else, cite it in checked |
<https://vyos.io/> | an autolink, marked as leaving the site |
A link may carry one class from a short allowlist, which is how the sales site gets its
buttons: [Download](releases:/latest/){.button}. Unknown class names are dropped with
a warning rather than passed through into the HTML.
Lists#
Unordered, nested by indentation:
- The agent drives VyOS over its documented HTTP API.
- The config plane stages
setanddeletelines and commits them as one diff. - The Linux plane reaches beneath the config tree where the tree cannot express
something:
tcfor the full CAKE knob set,- companion daemons for split-horizon DNS and PXE.
- The config plane stages
- Everything out-of-tree is declared in one file and reconciled idempotently.
Ordered, with a nested unordered list and a code block inside an item:
- Sign in at
https://<router>:8443asadmin. - Change the password. The account is flagged must change until you do.
Turn on two-factor authentication, then issue a token for anything automated:
bashcurl -sk -X POST https://router:8443/api/tokens \ -H "X-Wheelhouse-CSRF: $CSRF" -b cookies.txt \ -d '{"name":"terraform","role":"operator"}'- Stage a change and look at the diff before you commit it.
Code#
A fenced block takes a language, and optionally a title that replaces the language label in the header bar. The copy button appears only when JavaScript is running.
```yaml title="/config/wheelhouse/agent.yaml"
qos:
wan:
iface: eth1
```Which renders as:
qos:
wan:
iface: eth1
egress: { qdisc: cake, bandwidth: 70mbit, docsis: true, ack-filter: true }With no title, the language is the label:
$ wheelhouse-agent plan --file /config/wheelhouse/agent.yamlThere is no syntax highlighting. It would cost a runtime dependency or a hand-written lexer per language, on a page that has to stay under 150 KB.
Tables#
Alignment markers work: :--- left, :---: centre, ---: right. Wide tables scroll
inside their own box rather than pushing the page sideways.
| Flag | Default | Meaning |
|---|---|---|
--addr | 127.0.0.1:8090 | Listen address. The shipped systemd unit overrides it to 0.0.0.0:8443. |
--data-dir | /config/wheelhouse | Persistent state. On VyOS, /config survives an image upgrade and /var/lib does not. |
--api-key-file | — | File holding the VyOS API key. Preferred over --api-key, which is visible in ps. |
--tls-self-signed | false | Generate a certificate in the data directory if none exists. |
Every row above is from agent/main.go. If you cannot point at
the line, do not write the row.
Blockquotes#
A quotation, for something said elsewhere that you are reporting rather than asserting.
It can hold more than one paragraph, and
code.
Admonitions#
Written as a ::: fence. The word after the colons is the type; anything after that is
an optional title replacing the default one.
| Type | Default title | Use it for |
|---|---|---|
note | Note | A fact worth pulling out of the prose. |
tip | Tip | Something that makes the reader's life easier. |
warning | Warning | This can cut you off from the router, erase a disk or expose the management UI. Use it sparingly. |
danger | Danger | It will break, and here is exactly how. |
roadmap | Not built yet | Planned, not shipped. The words are in the text, not only the styling. |
platform-gap (or gap) | Platform gap | VyOS itself cannot express this, and what Wheelhouse does about it. State the fact and the workaround, never a complaint about upstream. |
:::warning Read this before you commit
Body text, with the **full** Markdown available.
:::Horizontal rule#
Three or more dashes on their own line. Use it to separate an appendix from the body.
What the build checks for you#
- Every internal link resolves to a page that exists.
- Every
#fragmentnames a heading that is really on the target page. - Every
checkedpath and everyrepo:path exists in this checkout. - Every page has a title.
- Every page's total weight — HTML, CSS, JavaScript and the one self-hosted font — stays under the 150 KB budget. The build names the heaviest three pages per site.
Numbers 1 through 4 fail the build. Number 5 warns, and fails under --strict — as
does a repo: link on a page outside the development section.
Accessibility, briefly#
Headings descend by one level at a time; the anchor beside each one is a real link with a label. Tables have header cells. Images have alt text that says what the image shows, not "diagram". Colour is never the only signal — an admonition carries its type as a word, not just as a stripe. The page reads and navigates with no JavaScript at all; search is the single feature that needs it, and it says so when it is missing.