| .forgejo/workflows | ||
| cmd/cairnd | ||
| deploy | ||
| internal | ||
| web/static | ||
| .gitignore | ||
| .ko.yaml | ||
| ARCHITECTURE.md | ||
| CHANGELOG.md | ||
| Dockerfile | ||
| ENCRYPTION-COMPARISON.md | ||
| example.config.json | ||
| go.mod | ||
| go.sum | ||
| LICENSE.md | ||
| Makefile | ||
| OIDC-COMPARISON.md | ||
| README.md | ||
Cairn
Self-hosted file storage. Files, and nothing else.
Cairn is a single-binary file server: a clean web interface, WebDAV for every sync client, resumable uploads, share links, pluggable storage (POSIX/NFS or any S3-compatible object store), and pluggable identity (local, OIDC, or LDAP). It is what remains of a "Nextcloud" after you remove everything that is not file storage.
- One container is the whole stack. No PHP, no Redis, no required database, no cron sidecar. One Go binary, one config file.
- Your storage backend is the source of truth. On the POSIX driver the
directory tree on disk is the data model — browse it with
ls, back it up withrsync, snapshot it with ZFS. On the S3 driver your bucket is the data model. No opaque blobs, no schema lock-in, no database index to corrupt. - Identity is consumed, not implemented. Bring your own OIDC provider (Keycloak, Authentik, Authelia, Zitadel, ...) or LDAP directory (Active Directory, OpenLDAP, FreeIPA). Local argon2id users work out of the box.
- Small trust surface. Four third-party dependencies total:
x/crypto(argon2id),go-oidc+x/oauth2(OIDC),go-ldap(LDAP),filippo.io/age(at-rest encryption).
Status: v0.2.x, dogfooded daily on a k3s cluster (POSIX and S3(Garage) deployments, OIDC via Keycloak, client interop verified with Finder, rclone, and Cyberduck). Pre-1.0: minor versions may break config. See ARCHITECTURE.md for the full design and CHANGELOG.md for what shipped when.
Features
- Web UI — browse, upload (resumable with progress for large files), download, rename, delete, folders. Embedded in the binary; no separate frontend to deploy.
- WebDAV at
/dav/— mount as a network drive from Finder, Windows Explorer, rclone, Cyberduck. RFC 4331 quota properties so clients show free space. - Resumable uploads (TUS 1.0.0) at
/api/v1/tus/— interrupted uploads survive dropped connections and server restarts; the web UI uses this automatically for files ≥16 MiB. - Share links — optional public
/s/surface with expiry, password, and upload-drop support; admins can list and revoke all links. - Storage drivers —
posix(any mounted filesystem) ors3(hand-rolled SigV4 client, works against Garage/MinIO/AWS). - Auth modes —
local(argon2id hashes in config),oidc(Authorization Code + PKCE; per-device app passwords bridge WebDAV clients),ldap(search-then-bind; WebDAV clients use directory credentials directly). - Authorization (opt-in
auth.perUserHomes) — each user gets a private/home, plus shared/spaces/<group>mapped from OIDC group claims; admins see the whole backend. - Trash (opt-in
storage.trash.enabled) — deletes move to a per-user trash with restore/purge via/api/v1/trash; file versioning is deliberately delegated to backend snapshots (ZFS, S3 versioning). - Quota — reporting (
GET /api/v1/quota, WebDAV RFC 4331) plus enforcedmaxFileBytes/maxTotalBytes/reserveByteslimits → 507 on breach. - At-rest encryption (probe) — optional age-based content encryption with per-user keys held in the Keycloak profile (or a single deployment key), and a recovery recipient so key-loss ≠ data-loss. Design fork documented in ENCRYPTION-COMPARISON.md.
- Ops — Prometheus
/metrics,/healthz, graceful shutdown, admin API (/api/v1/admin/*), structured logs. Multi-arch images (amd64/arm64) built by CI via ko; Helm chart atdeploy/chart.
Quick start
make build
cp example.config.json config.json
echo -n 'yourpassword' | ./bin/cairnd hash-password # paste into config.json
./bin/cairnd -config config.json
Open http://localhost:7420 — or mount http://localhost:7420/dav/ as a
network drive with any WebDAV client.
Docker:
docker compose -f deploy/docker-compose.example.yml up -d
Kubernetes (Helm):
helm install cairn deploy/chart -f your-values.yaml
Configuration
Minimal (local auth, POSIX storage):
{
"listen": ":7420",
"storage": { "driver": "posix", "root": "/data" },
"auth": {
"mode": "local",
"users": [{ "username": "admin", "passwordHash": "<cairnd hash-password output>" }]
},
"enableWebdav": true
}
Everything else is opt-in blocks on top of this: auth.mode: "oidc" or
"ldap" with their settings, storage.driver: "s3", shares,
storage.quota, storage.trash, storage.encryption, auth.admins,
auth.perUserHomes. See example.config.json and the
field comments in internal/config/config.go — the config structs are the
reference documentation.
Secrets can come from env instead of the file: CAIRN_S3_SECRET_KEY,
CAIRN_OIDC_CLIENT_SECRET, CAIRN_LDAP_BIND_PASSWORD,
CAIRN_ENC_RECOVERY_IDENTITY, and friends (env wins).
Non-goals
Deliberately out of scope, so Cairn stays one auditable binary: calendars, contacts, office suites, app stores and plugins, per-file ACLs, a metadata database, and app-layer file versioning (that's your filesystem's or object store's job). See ARCHITECTURE.md §2.
License
Fair-code / source-available: free for personal use and organizations up to 50 users; commercial license required above that or to offer Cairn as a hosted service. See LICENSE.md. Cairn is not OSI open source and does not claim to be.