# Site
`site.yaml` describes everything around the cards. The file is optional, and
so is every key in it.
## Keys
| Key | Default | What it is |
| --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | `cairn` | Site name: header, `
`. Translatable like every text key. |
| `tagline` | empty | One sentence about the site, translatable. Opens the home page and feeds the meta description search engines and link previews show. |
| `url` | none | Public base URL, **the domain alone**: `https://tools.example.org`, never `https://example.org/cairn`. Enables canonical and hreflang links, absolute sitemap URLs and the preview image. See [below](#the-domain-and-nothing-else). |
| `logo` | none | Image in the header, and the picture on a shared link. A URL or an [`/assets` path](../recipes/icons.md#your-own-files); [it has to be a raster](#the-logo-and-the-picture-on-a-shared-link). Takes `{light, dark}` when one image cannot serve [both themes](theming.md#artwork-that-cannot-serve-both-themes). |
| `favicon` | cairn's | The tab icon: a URL or an `/assets` path, or `{light, dark}` for [two themes](theming.md#artwork-that-cannot-serve-both-themes). See [the icon set](#the-icon-set) for what replacing it costs. |
| `index` | `true` | `index: false` asks search engines to stay away: `robots.txt` disallows everything, every page carries `noindex`, the sitemap turns off. For directories meant for ten people, not the whole web. |
| `search` | `true` | The box in the header. `search: false` removes it, the disabled copy every other page carries so the header keeps its shape, the script behind it and the `Cmd+K` shortcut. Worth it on a directory small enough to read at a glance, where the box is a row spent on nothing. |
| `locales` | `[en]` | Languages served. First entry is the default and the fallback; see [Languages](i18n.md). |
| `theme.accent` | `#247b7b` | Hex color for links, focus rings, buttons; see [Theming](theming.md). |
| `theme.font` | none | Body font: `family`, a CSS font stack; `file`, a font in the config directory's `fonts/` folder, served by cairn itself so the page makes no external font request. See [Theming](theming.md). |
| `about` | empty | A welcome note under the hero, translatable, folded to one row on a phone; visitors can dismiss it (cookie, one year). Long text: paragraphs and [CommonMark, minus raw HTML](text.md). |
| `links` | `[]` | Header navigation links, with optional icons; see below. |
| `footer` | `[]` | Links at the bottom of every page. |
| `pages` | `[]` | Pages cairn serves itself (legal notice, privacy…); see below. Bodies accept [CommonMark, minus raw HTML](text.md). |
| `credit` | `true` | The small "powered by cairn" in the footer. `credit: false` removes it. |
| `show_version` | `false` | Prints the running cairn version beside that credit: the release number for a tagged build, the commit for a build off `main`. Handy when you report a bug, or run several instances. |
| `strings` | built-ins | UI text overrides; see [Languages](i18n.md#ui-strings). |
| `security` | none | `contact`, and optionally `policy` and `encryption`. Setting `contact` makes cairn serve [`/.well-known/security.txt`](#telling-researchers-where-to-write). |
| `hosting_flag` | none | Where the self-hosted and external [flags](services.md#making-the-flag-lead-somewhere) lead: `self` and `external`, each a page id, a path or a URL. Omitted, the flags stay plain text. |
| `service_links` | none | How the links that lead to a service behave: `new_tab`, and `confirm` for a dialog naming the destination. Only links that leave the site are touched. See [below](#leaving-the-site). |
| `status` | none | Live status pills fed by your monitor (`status.gatus`, or `status.url` with `status.provider` and, per monitor, `status.slug` or `status.map`; then `status.page`, `status.interval`, `status.linked`, `status.insecure`, `status.ca`); see [Status page](../recipes/status.md). |
## Full example
```yaml
title: Libre Internet
tagline:
fr: Des outils libres, simples, pour tout le monde.
en: Free, simple tools for everyone.
logo: /assets/logo.png
locales: [fr, en]
theme:
accent: "#247b7b"
about:
fr: |
Bienvenue ! Voici les services que j'héberge pour vous.
en: |
Welcome! These are the services I host for you.
links:
- label: Wiki
url: https://wiki.example.org
icon: book
- label: GitHub
url: https://github.com/you
icon: github
footer:
- label: { fr: Statut des services, en: Service status }
url: https://status.example.org
- label: Contact
url: mailto:admin@example.org
pages:
- id: legal
title: { fr: Mentions légales, en: Legal notice }
body:
fr: |
Éditeur : Prénom Nom, contact@exemple.org
Hébergement : ce site est auto-hébergé par son éditeur.
en: |
Publisher: First Last, contact@example.org
Hosting: this site is self-hosted by its publisher.
```
A typo in a key name is an error, not a silent no-op: cairn names the file
and the unknown key in the log.
## Header links
The header has two rows: your identity (logo, title, languages), then a
navigation row holding the `links` on the left and the search on the right.
Each link takes an optional `icon`. Name one of the built-in glyphs, drawn
inline so they cost no external request:
`announce` · `book` · `calendar` · `chat` · `cloud` · `code` · `coffee` ·
`community` · `download` · `file` · `github` · `globe` · `heart` · `help` ·
`home` · `key` · `legal` · `location` · `lock` · `mail` · `news` · `photos` ·
`portfolio` · `privacy` · `rss` · `social` · `status` · `support` · `user` ·
`video`
Or pass a URL or an `/assets` path for your own image. An unknown glyph name
is a config error that lists the valid ones.
## The logo, and the picture on a shared link
`logo` puts an image in the header, before the title, sized by the stylesheet
to the height of the text. It is optional: without one the header carries the
title alone.
It has a second job that is easy to miss. When `url` is also set, a **raster**
logo becomes `og:image`, the picture Mastodon, Slack, Signal or a chat client
shows on the card when someone shares a link to your site.
| Logo | Header | Preview card |
| ----------------------------------- | ---------------------- | ------------ |
| none | title alone | no image |
| `logo.svg` | fine, scales perfectly | **no image** |
| `logo.png`, `.jpg`, `.webp`, `.gif` | fine | your image |
The svg row is the one that surprises. A vector logo looks right everywhere on
the page, and most platforms simply ignore svg for a preview card, so your
links go out bare. `cairn -check` says so rather than leaving you to discover
it from a shared link:
```console
$ cairn -check -config ./config
warning: site.yaml logo "/assets/logo.svg" is not a raster image: links to the site preview with no image (og:image wants a png, jpg, webp or gif)
```
Nothing stops you keeping both: a vector `favicon` for the tab, where it scales
to every size a browser asks for, and a raster `logo` for the header and the
card. That is what the demo does.
On size: platforms crop a large card around 1200×630, so an image drawn for
that gets a full-width banner. A square logo gets a small square thumbnail
beside the text, which is perfectly respectable and what most self-hosted sites
want. cairn does not resize anything, it serves the file you point at, so pick
the shape you want to see.
Two things cairn deliberately will not do: fall back to its own mark when you
have no logo, because that would put cairn's stones on your link card, and
derive the tab icon from the logo, which is the separate `favicon` key.
A logo drawn in one colour disappears in the theme it was not drawn for. Give
`logo` two images instead of one and cairn swaps them with the theme; the
preview card still takes the light one, since a card in someone else's chat
window has no theme to follow. See
[artwork that cannot serve both themes](theming.md#artwork-that-cannot-serve-both-themes).
## The domain and nothing else
`url` is the base of your domain, not the address of the page:
```yaml
url: https://tools.example.org # yes
url: https://example.org/cairn # no, even if that is where visitors land
```
If cairn is served under a sub-path, that prefix comes from `-base-path`, and
cairn adds it to every link itself. Writing it in `url` as well makes it land
twice: canonical links, hreflang alternates and every entry of the sitemap
would point at `example.org/cairn/cairn/`, which nothing serves. Search engines
act on those, so cairn treats it as a config error rather than emit them. It
does not exit: a fresh start then serves the getting-started page with the
reason in the log, and a reload keeps the previous pages serving.
```console
config: site.yaml: url "https://example.org/cairn" must be the domain alone, with no path
(serving under example.org/cairn is what -base-path is for, and cairn adds that prefix itself)
```
More on the sub-path in [Reverse proxies](../deployment/reverse-proxies.md#under-a-sub-path).
## Links a browser will not follow
Every field of this file that becomes a link accepts `https://`, `http://`, an
absolute path, and for `links` and `footer` entries `mailto:`. Anything else is
a config error:
```console
config: site.yaml: footer url "tel:+33123456789" uses the tel: scheme, which cairn does not emit: the link would render dead and nothing would say why (expected https://…, mailto:… or an absolute path)
```
That list is not a matter of taste. `html/template`, which writes every page,
puts exactly those schemes in an `href` or a `src` and replaces every other one
with a placeholder that goes nowhere. So a `tel:` link and a `javascript:` link
fail in precisely the same way, and always have: the link renders in the footer
of every page, it looks live, clicking it does nothing, and no log line
anywhere mentions it. The only difference between the two is intent.
This is why the rule is a list of what is accepted rather than a list of what
is dangerous. A list of dangerous schemes is only ever as current as the last
time somebody thought about it, and the question that decides this one is not
which schemes are hostile but which ones cairn can actually write.
**If you were linking a phone number**, put it in the label and point the link
at a page: `{label: "Support: +33 1 23 45 67 89", url: /contact}`. A hosted page
is one entry in `pages`.
The rest of the file was already answered elsewhere: a service `url` has to be
a URL or a path, an unknown `links` icon is met with the list of glyphs. These
fields are the ones where nothing else would have spoken. `logo` and `favicon`
are still checked by nothing at boot beyond their scheme, since a missing file
there is a plausible typo and earns a `cairn -check` warning rather than a
refusal.
The favicon is the sharpest of them, and the reason this is an error rather
than a warning: it is the one value that escapes the template entirely, since
it reaches the web manifest as JSON, which no template escaping covers, and
comes back from `/favicon.ico` as a `Location` header.
`security.contact`, `security.policy` and `security.encryption` are the
exception, and they keep `tel:`. That file is plain text rather than markup, so
nothing blanks anything on the way out, and
[RFC 9116](https://www.rfc-editor.org/rfc/rfc9116) names `tel:` itself. Only
`javascript:`, `vbscript:` and `data:` are refused there, on the grounds that
no researcher can act on one.
The value is read the way a browser reads it, not the way it is written: a
browser drops tabs and newlines from a URL and strips leading control
characters before it looks at the scheme. `JavaScript:`, a leading space and a
tab inside the word are all the same URL as the plain one, so they all get the
same answer.
## Leaving the site
Two links lead a visitor to a service: the name on a card, and the button on
its detail page. `service_links` says how they behave. Both keys are off, so a
site that says nothing keeps the pages it has today.
```yaml
service_links:
new_tab: true
confirm: external
```
**`new_tab`** opens the link in a new tab, carrying `rel="noopener noreferrer"`,
so the directory stays where the visitor left it instead of being one back
button, or a chain of them, away.
**`confirm`** puts a dialog in front of the link, naming where it goes and
offering to copy the address before the visitor follows it. It takes which
links to guard rather than a yes:
| Value | Guards |
| ---------- | ------------------------------------------------------------------------- |
| `external` | only the services flagged [`selfhosted: false`](services.md#hosting-flag) |
| `all` | every service link that leaves the site (`true` means the same) |
| absent | nothing, which is the default |
`external` is the useful one for most directories: a visitor being sent to
somebody else's site is the case worth a sentence, while being sent to your own
Nextcloud is not. A service with **no** `selfhosted` flag never counts as
external. That is the literal reading and the safe one, since silence is what a
config says before you have thought about it, not a claim about who runs
something.
**Neither key touches a link cairn serves itself.** A "Learn more" page, the
language switcher, a footer entry, or a service whose `url` is a path on this
site: all of those navigate in place as they always have. What is being
described is leaving, not being a link.
**Both degrade.** The dialog is a native `