Ctrl K
ComunidadGuías…

README.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<div align="center">

# <sub><img src="src/internal/render/assets/favicon.svg" height="34" alt=""></sub> cairn

**The directory page for the people you host services _for_.**

[![Build](https://github.com/MorganKryze/cairn/actions/workflows/build.yml/badge.svg)](https://github.com/MorganKryze/cairn/actions/workflows/build.yml)
[![Security](https://github.com/MorganKryze/cairn/actions/workflows/security.yml/badge.svg)](https://github.com/MorganKryze/cairn/actions/workflows/security.yml)
[![Tests](https://raw.githubusercontent.com/MorganKryze/cairn/badges/tests.svg)](https://github.com/MorganKryze/cairn/actions/workflows/build.yml)
[![Coverage](https://raw.githubusercontent.com/MorganKryze/cairn/badges/coverage.svg)](https://github.com/MorganKryze/cairn/actions/workflows/build.yml)
[![Release](https://img.shields.io/github/v/release/MorganKryze/cairn?label=release&color=247b7b)](https://github.com/MorganKryze/cairn/releases)
[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-blue.svg)](LICENSE)

[![Image](https://img.shields.io/badge/image-5%20MB-2496ED?logo=docker&logoColor=white)](https://hub.docker.com/r/morgankryze/cairn/tags)
[![Docker pulls](https://img.shields.io/docker/pulls/morgankryze/cairn?label=docker%20pulls&color=2496ED&logo=docker&logoColor=white&cacheSeconds=3600)](https://hub.docker.com/r/morgankryze/cairn)
[![Go](https://img.shields.io/badge/Go-single%20static%20binary-00ADD8?logo=go&logoColor=white)](https://go.dev)
[![Helm](https://img.shields.io/badge/Helm-chart-0F1689?logo=helm&logoColor=white)](docs/deployment/helm.md)
[![Docker Compose](https://img.shields.io/badge/Compose-ready-2496ED?logo=docker&logoColor=white)](docs/deployment/docker-compose.md)
[![Kubernetes](https://img.shields.io/badge/Kubernetes-ready-326CE5?logo=kubernetes&logoColor=white)](docs/deployment/kubernetes.md)
[![Context7](https://img.shields.io/badge/Context7-ask%20your%20assistant-247b7b?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTEyIDJhMTAgMTAgMCAxIDAgMCAyMCAxMCAxMCAwIDAgMCAwLTIwem0wIDNhNyA3IDAgMSAxIDAgMTQgNyA3IDAgMCAxIDAtMTR6Ii8+PC9zdmc+)](https://context7.com/morgankryze/cairn)

</div>

> A cairn is a small stack of stones left by hikers who walked the trail
> before you, so you find your way without digging.

<div align="center">

<a href="https://cairn.libresoftware.cloud" title="Open the live demo">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="docs/assets/home-dark.png">
  <img src="docs/assets/home-light.png" alt="cairn home page: a welcome note, service cards grouped by category with live status pills, and a category trail in the margin" width="100%">
</picture>
</a>

</div>

---

Your family has no use for a dashboard, and neither do your clients or your
friends. They want to know what this place is, what each tool does and whether
it works right now. cairn is the page that tells them, written in their
language and readable without an account or a manual, and it is boring for you
to operate.

## Features

### What your visitors get

- 👋 **A welcome note in your words**: who hosts this, for whom, how to reach
  you. Dismissable, remembered for a year.
- 🗂️ **Tools grouped by need**, one plain sentence each, with a "Learn more"
  page for the curious.
- 🚦 **Live status pills** fed by the monitor you already run. Your server does
  the polling, never the visitor's browser.
- 🏷️ **A word for where a service stands**: coming soon, beta, new, deprecated,
  no longer available. The last two stop being links.
- 🌍 **Their language**: the server reads it from the browser, a switcher pins
  it. cairn ships [eight](docs/configuration/i18n.md), your own text goes inline.
- 🔒 **A lock on what needs an account**, right after the name, and a note on
  its page saying whom to ask for one.
- 🔍 **Search from anywhere**: start typing, or ⌘K, and Tab through what it
  finds. A name finds that one service, not everything that mentions it.
- 📱 **At home on a phone**: one-handed layout, and a header that steps aside as
  you scroll and returns the moment you head back up.
- ♿ **Built to stay readable**: WCAG AA contrast in both themes, a skip link,
  named landmarks, announced results, right-to-left the right way round.
- 🌗 **Calm typography, light and dark**, and every feature still works with
  JavaScript off.

> CI measures the contrast in a real browser on every pull request. No audit
> has covered cairn as a whole, and no screen reader user has tried it yet. If
> you use one, [tell us what breaks](https://github.com/MorganKryze/cairn/issues).

<div align="center">

<a href="https://cairn.libresoftware.cloud" title="Open the live demo"><img src="docs/assets/two-views.png" alt="Left: a service detail page, with the name beside its icon and a button opening the tool at the far end of the same row, then a live status pill, a paragraph explaining what the tool is for, and a screenshot with its caption. Right: the same directory on a phone, a row of category chips then cards each showing an icon, a sentence, a self-hosted flag and a status pill" width="100%"></a>

<em>Behind a card when a visitor wants more than one sentence, and the same page in a hand.</em>

<video src="https://github.com/MorganKryze/cairn/raw/main/docs/assets/demo.mp4" width="100%" autoplay loop muted playsinline></video>

<em>Seventeen seconds: a visitor lands, searches, opens a service page, switches language. <a href="https://cairn.libresoftware.cloud">Click it yourself</a> if the player stays blank.</em>

</div>

### What you get as the operator

- 📦 **One static binary, no database**, in an image of about 5 MB to pull.
- 📝 **YAML mounted read-only**, and cairn picks up an edit within seconds. A
  bad one names the file, the line and the shape it wanted, instead of taking
  the site down.
- 📜 **Legal pages served by cairn itself**, in ordinary
  [markdown](docs/configuration/text.md): the notice and privacy pages
  self-hosters never have anywhere to put.
- 🌐 **A domain, a subdomain, or a sub-path** of one you already use. cairn
  handles the prefix, so your
  [proxy](docs/deployment/reverse-proxies.md) needs no rewriting rule.
- 🗃️ **Or no server at all**: `cairn -export site.zip` writes the site as
  [static files](docs/deployment/static.md) for Cloudflare Pages, Netlify or
  an Apache host. The status pills are the one thing that stays behind.

## Status monitoring

**Whatever already tells you it is up.** Keep the monitor you run: cairn
reads it from your server, never from the visitor's browser.

<table>
<tr><td><strong>Self-hosted</strong></td><td>
<a href="https://github.com/TwiN/gatus">Gatus</a> ·
<a href="https://github.com/louislam/uptime-kuma">Uptime Kuma</a> ·
<a href="https://github.com/cachethq/cachet">Cachet</a> ·
<a href="https://github.com/statping-ng/statping-ng">Statping-ng</a> ·
<a href="https://upptime.js.org">Upptime</a>
</td></tr>
<tr><td><strong>Hosted</strong></td><td>
<a href="https://www.atlassian.com/software/statuspage">Atlassian Statuspage</a> ·
<a href="https://instatus.com">Instatus</a> ·
<a href="https://uptimerobot.com">UptimeRobot</a> ·
<a href="https://betterstack.com/uptime">Better Stack</a> ·
<a href="https://www.statuscake.com">StatusCake</a>
</td></tr>
</table>

Every one was read from a live instance, not from a manual. Anything else
publishing a list of names and states takes six lines of config, and
[which monitors cairn reads](docs/recipes/status.md#which-monitors-cairn-reads)
also says what cannot be read, and why.

<table>
<tr>
<td width="86" align="center" valign="middle">
<a href="https://github.com/TwiN/gatus"><img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gatus.svg" width="56" alt="Gatus"></a>
</td>
<td valign="middle">

**cairn knows [Gatus](https://github.com/TwiN/gatus) best.**
It is the only one cairn integrates with **both ways**, since `cairn -emit-gatus`
writes its endpoint config out of your services, and the only one whose pills
link to a page per service rather than to one page for everything. If you have
no monitor yet, start there.

</td>
</tr>
</table>

## Security

**Secure by subtraction.** cairn stores nothing and signs no one in, and it
takes no input it has to trust.

- 🪨 **`FROM scratch`, non-root**: no shell, no package manager, no libc in the
  image, so a compromised process has nothing to pivot into.
- 🛡️ **Runs locked down**: `read_only`, `cap_drop: ALL` and a self-probing
  healthcheck work out of the box.
  [Hardened compose](docs/deployment/docker-compose.md).
- 🧱 **A strict Content-Security-Policy** (`default-src 'none'`, inline
  fragments pinned by hash), with no third-party script or font to trust.
- 🔌 **No outbound requests of its own**, so it is air-gap friendly. The
  [demo](demo/README.md) runs on a network with no route out and still makes no
  third-party request.
- 🔬 **A watched supply chain**: `govulncheck`, a Trivy image scan and CodeQL on
  every pull request and weekly on a schedule, and every action pinned to a
  commit rather than a movable tag.
- 🧪 **More test than product**, and every check has to fail before it earns
  trust: patch the fix out, read the red. The badges above carry the live
  count.
- ✍️ **Artifacts you can check**: cosign signature, SLSA provenance and an SBOM
  on the image, an attestation on the binaries.
  [Two lines to verify](SECURITY.md#verifying-what-you-pulled).

## Quickstart

To look at it first, with nothing to write or mount:

```sh
docker run --rm -p 8080:8080 morgankryze/cairn:stable
```

That is a running cairn on <http://localhost:8080>, telling you what to feed
it. Feeding it takes two files and one command:

```yaml
# config/services.yaml
- id: pdf
  url: https://pdf.example.org
  icon: stirling-pdf
  name: PDF toolbox
  desc: Merge, split, compress your PDFs.
```

(Want two languages? `name: { fr: Boîte à outils PDF, en: PDF toolbox }`;
every text key works both ways. See [Languages](docs/configuration/i18n.md).)

```yaml
# compose.yaml
services:
  cairn:
    image: morgankryze/cairn:latest
    ports:
      - 8080:8080
    volumes:
      - ./config:/config:ro
```

```sh
docker compose up -d
```

<http://localhost:8080> now shows a finished page.

If you would rather not keep a server running, add
`url: https://tools.example.org` to a `config/site.yaml` and the same config
exports as static files for Cloudflare Pages, Netlify or any Apache host, OVH
shared hosting included:

```sh
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" \
  morgankryze/cairn:stable -config /work/config -export /work/site.zip
```

The status pills stay behind, since nothing polls. The rest, and a pipeline
that exports on every push, is in [Static hosting](docs/deployment/static.md).

Everything else
(title, languages, categories, status, theming) is one optional key at a
time, at your pace: follow [Getting started](docs/getting-started.md).

A public instance runs at **<https://cairn.libresoftware.cloud>** if you want
to see it live first, and the [demo stack](demo/README.md)
spins up your own copy, with a real Gatus and a handful of sample services,
one intentionally dead, in one command:

```sh
git clone https://github.com/MorganKryze/cairn.git && cd cairn/demo
docker compose up -d --build
```

## Documentation

Everything lives in [docs/](docs/README.md). Each page explains why a setting
exists before it shows how to set it. Start anywhere.

<table>
<tr><td><strong>Start</strong></td><td>
<a href="docs/getting-started.md">Getting started</a>, the five-minute path ·
<a href="docs/upgrading.md">Upgrading</a>, what a new version can refuse and how to fix it
</td></tr>
<tr><td><strong>Configure</strong></td><td>
<a href="docs/configuration/services.md">Services</a> ·
<a href="docs/configuration/site.md">Site</a> ·
<a href="docs/configuration/text.md">Text</a> ·
<a href="docs/configuration/theming.md">Theming</a> ·
<a href="docs/configuration/i18n.md">Languages</a>
</td></tr>
<tr><td><strong>Deploy</strong></td><td>
<a href="docs/deployment/docker-compose.md">Docker Compose</a> ·
<a href="docs/deployment/podman.md">Podman</a> ·
<a href="docs/deployment/binary.md">Bare binary</a> ·
<a href="docs/deployment/kubernetes.md">Kubernetes</a> ·
<a href="docs/deployment/helm.md">Helm</a> ·
<a href="docs/deployment/airgap.md">Air-gapped</a> ·
<a href="docs/deployment/static.md">Static hosting</a> ·
<a href="docs/deployment/reverse-proxies.md">Reverse proxies</a>
</td></tr>
<tr><td><strong>Recipes</strong></td><td>
<a href="docs/recipes/status.md">Status page</a> ·
<a href="docs/recipes/icons.md">Icons</a> ·
<a href="docs/recipes/multiple-files.md">Multiple files</a> ·
<a href="docs/recipes/migration.md">Migration</a>
</td></tr>
<tr><td><strong>Look up</strong></td><td>
<a href="docs/reference.md">Reference</a> ·
<a href="docs/faq.md">FAQ</a> ·
<a href="docs/comparison.md">Comparison</a>
</td></tr>
</table>

### For your AI assistant

Those pages are indexed on
**[Context7](https://context7.com/morgankryze/cairn)**, so an assistant with
the Context7 MCP server can pull cairn's real documentation instead of
inventing config keys that never existed. Ask it for `morgankryze/cairn` by
name.

## Scope

**Not a dashboard, on purpose.** cairn is a directory, with no auth, no widgets, no
Docker socket and no admin UI. If the audience is _you_, the admin,
[Homepage](https://github.com/gethomepage/homepage) or
[Homer](https://github.com/bastienwirtz/homer) will make you happier; the
[comparison](docs/comparison.md) is honest about it.

## Contributing

cairn is young and opinionated, and it needs other people's eyes. Ideas
and bugs in the [issues](https://github.com/MorganKryze/cairn/issues), code and
docs through [contributing](CONTRIBUTING.md), within the scope above. And if
cairn serves your people well, a [coffee](https://ko-fi.com/morgankryze) keeps
its maintainer walking the trail.

## Contributors

Following the [all-contributors](https://allcontributors.org) convention,
which counts every kind of contribution rather than only the commits: an
issue that names a real problem, a bug report with the screenshot that cracks
it, and a translation are all work.

<table>
<tr>
<td align="center" width="150">
<a href="https://github.com/MorganKryze"><img src="https://github.com/MorganKryze.png?size=100" width="80" alt=""><br><sub><b>MorganKryze</b></sub></a><br>
<a href="https://github.com/MorganKryze/cairn/commits?author=MorganKryze" title="Code">💻</a>
<a href="https://github.com/MorganKryze/cairn/tree/main/docs" title="Documentation">📖</a>
<span title="Design">🎨</span>
<span title="Ideas and planning">🤔</span>
<span title="Maintenance">🚧</span>
</td>
<td align="center" width="150">
<a href="https://github.com/AntonPalmqvist"><img src="https://github.com/AntonPalmqvist.png?size=100" width="80" alt=""><br><sub><b>AntonPalmqvist</b></sub></a><br>
<a href="https://github.com/MorganKryze/cairn/issues/33" title="Bug reports">🐛</a>
<a href="https://github.com/MorganKryze/cairn/issues?q=author%3AAntonPalmqvist" title="Ideas and planning">🤔</a>
<a href="https://github.com/MorganKryze/cairn/pull/72" title="Translation">🌍</a>
</td>
<td align="center" width="150">
<a href="https://github.com/rbourgeat"><img src="https://github.com/rbourgeat.png?size=100" width="80" alt=""><br><sub><b>rbourgeat</b></sub></a><br>
<a href="https://github.com/MorganKryze/cairn/pull/51" title="Code">💻</a>
<a href="https://github.com/MorganKryze/cairn/issues?q=author%3Arbourgeat" title="Ideas and planning">🤔</a>
</td>
<td align="center" width="150">
<a href="https://github.com/justinjakull"><img src="https://github.com/justinjakull.png?size=100" width="80" alt=""><br><sub><b>justinjakull</b></sub></a><br>
<a href="https://github.com/MorganKryze/cairn/issues?q=author%3Ajustinjakull" title="Ideas and planning">🤔</a>
</td>
</tr>
</table>

💻 code · 📖 documentation · 🌍 translation · 🎨 design · 🤔 ideas · 🐛 bug reports · 🚧 maintenance

The avatars come from GitHub itself rather than from a third-party image
service, for the reason the coverage badge is self-hosted: nothing about this
repository should depend on somebody else's uptime to render. Adding yourself
here belongs in the pull request itself.

## Colophon

A colophon tells how the book was made, so here is mine: Go, plain YAML,
[Fraunces](https://github.com/undercasetype/Fraunces) for the headings, and
[Claude Code](https://claude.com/claude-code) drafting at my side, never on
autopilot. The taste, the reviews and the final word stay mine; the tests,
the CI and the public history keep me honest.

## License

Free software under [GPL-3.0](LICENSE): use it, modify it, share it. What
you redistribute stays under the same license, source included. Hosting your
own instance is not distribution and asks nothing of you.