Nextcloud but better
Find a file
Cordy bf35fcda80
All checks were successful
ci / test-and-build (push) Successful in 19s
release / image (push) Successful in 15s
changelog: v0.3.1 — LDAP groups + per-user quotas + trash UI
2026-07-26 15:20:16 +00:00
.forgejo/workflows ci: remove one-shot ldapgroups-patch workflow 2026-07-26 15:08:26 +00:00
cmd/cairnd quota: per-user cap wiring (config + main + Write path) 2026-07-26 15:14:31 +00:00
deploy helm: keywords + kubeVersion metadata (also: webhook delivery test) 2026-07-20 04:14:47 +00:00
internal quota: per-user cap wiring (config + main + Write path) 2026-07-26 15:14:31 +00:00
web/static web: trash panel (list / restore / purge), feature-detected 2026-07-26 15:17:28 +00:00
.gitignore Add .gitignore 2026-07-18 20:15:11 +00:00
.ko.yaml release: add ko config for daemonless multi-arch image builds 2026-07-20 01:27:18 +00:00
ARCHITECTURE.md docs: correct PQ claim — age v1.3.0+ is post-quantum-capable (hybrid ML-KEM-768) 2026-07-25 17:46:51 +00:00
CHANGELOG.md changelog: v0.3.1 — LDAP groups + per-user quotas + trash UI 2026-07-26 15:20:16 +00:00
Dockerfile Add Dockerfile (multi-stage, scratch runtime) 2026-07-18 20:15:03 +00:00
ENCRYPTION-COMPARISON.md docs: reframe ENCRYPTION-COMPARISON as an open fork — correct PQ (age IS PQ-capable), split custody(A) vs access(B), OpenBao/credential-derived/PQ/Datentresor, per Manuel 2026-07-24 2026-07-25 17:45:28 +00:00
example.config.json config: example gains the shares block (long-standing TODO) + S3 example 2026-07-20 04:00:17 +00:00
go.mod chore: go mod tidy (runner workflow, ref feat/encryption) 2026-07-22 23:12:44 +00:00
go.sum chore: go mod tidy (runner workflow, ref feat/encryption) 2026-07-22 23:12:44 +00:00
LICENSE.md Add draft fair-code license 2026-07-18 17:59:30 +00:00
Makefile Add Makefile (build, release cross-compile, test) 2026-07-18 20:14:53 +00:00
OIDC-COMPARISON.md docs: record the OIDC build decision — library route chosen 2026-07-20 17:21:35 +00:00
README.md README: rewrite for v0.2.7 (was stale since v0.0) 2026-07-26 14:35:05 +00:00

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 with rsync, 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 driversposix (any mounted filesystem) or s3 (hand-rolled SigV4 client, works against Garage/MinIO/AWS).
  • Auth modeslocal (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 enforced maxFileBytes / maxTotalBytes / reserveBytes limits → 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 at deploy/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.