Nextcloud but better
Find a file
Cordy 28cfebd577
All checks were successful
ci / test-and-build (push) Successful in 1m12s
release / image (push) Successful in 39s
Merge pull request '#717: orphan space folders gain Take over and deactivate' (#718) from feat-717 into main
2026-09-24 04:32:02 +00:00
.forgejo/workflows docs: changelog for v0.6.265 (#717 orphan take-over) 2026-09-24 04:27:40 +00:00
cmd/cairnd fix(#715): managed-by-IdP is a verified claim, orphan folders say so 2026-09-24 03:57:07 +00:00
deploy docs+packaging(#486): handbook page, EULA clause, compose/chart notes, changelog; CAIRN_OFFICE_WOPI_BASE env 2026-09-13 16:49:29 +00:00
docs Dialog canon completed (#693): fact-row destructive confirms, structure, dead trio removed 2026-09-23 22:53:38 +00:00
internal fix(#715): managed-by-IdP is a verified claim, orphan folders say so 2026-09-24 03:57:07 +00:00
proto/cairn/peer/v1 #244 Ship 2: space-recipient wire + delivery into space inbox (v0.6.27) (#246) 2026-08-15 17:29:08 +00:00
web fix(#717): orphan space folders gain Take over and deactivate 2026-09-24 04:25:15 +00:00
.gitignore chore: ignore built cairnd binary 2026-08-04 09:49:21 +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 sweep for v0.6.234-246: sharing.md gains the outage tri-state and residual-link/revoke-permanence facts, local-accounts.md the random-character password generator and the delete fact-dialog, deployment.md the {origin} logout placeholder, ARCHITECTURE the two converter dialects, and five feature-matrix rows catch up to shipped behaviour (origin sign-out, destination picker, double-click open, both converter dialects, departed-owners canon) 2026-09-23 00:38:50 +00:00
CHANGELOG.md docs: changelog for v0.6.265 (#717 orphan take-over) 2026-09-24 04:27:40 +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
EULA.md docs+packaging(#486): handbook page, EULA clause, compose/chart notes, changelog; CAIRN_OFFICE_WOPI_BASE env 2026-09-13 16:49:29 +00:00
example.config.json docs: alignment run for v0.6.170-178 2026-09-13 17:04:39 +00:00
go.mod chore(#543): go@patch + grpc v1.83.1 - clear reachable vulnerabilities (v0.6.207) 2026-09-16 01:29:33 +00:00
go.sum chore(#543): go@patch + grpc v1.83.1 - clear reachable vulnerabilities (v0.6.207) 2026-09-16 01:29:33 +00:00
IMPLEMENTED-PEERING.md docs(peering): A+C recipient authorization, correct the 10 build-order row, homes default (#123, #102) 2026-08-05 18:18:39 +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
OCM.md #117: OCM.md updated to approved mockup rev 3 (Shares/Requests/Contacts, federation-as-folder, explicit copy) 2026-09-07 21:49:17 +00:00
OIDC-COMPARISON.md docs: record the OIDC build decision — library route chosen 2026-07-20 17:21:35 +00:00
PEERING.md docs: record the peering decisions (#100) 2026-08-04 22:59:58 +00:00
README.md docs(#585): the handbook stops saying a share is a link 2026-09-18 01:18:39 +00:00
VERSIONING.md #541: file versioning v1 - mint-on-overwrite engine, guard, API, pane panel 2026-09-18 17:52:01 +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. Even search works this way: a bounded walk over the backend, no index. Since v0.6, instance state — settings, license, share links, legal holds, peering trust — lives with the backend too, encrypted under a reserved prefix: a fresh container against the same bucket is the same instance, and updates cannot lose it.
  • Identity is consumed, not implemented. Bring your own OIDC provider (Keycloak, Authentik, Authelia, Zitadel, ...) or LDAP directory (Active Directory, OpenLDAP, FreeIPA). Groups flow through both — shared spaces and even admin can be managed entirely in your directory. Local argon2id users work out of the box.
  • Small trust surface. The core file server carries five Go dependencies: x/crypto (argon2id), go-oidc + x/oauth2 (OIDC), go-ldap (LDAP), and filippo.io/age (at-rest encryption) — plus exactly one vendored browser asset, a pinned build of typage (the same author's TypeScript age) used by the share viewer, embedded in the binary rather than loaded from a CDN. Instance peering adds two more, google.golang.org/grpc and google.golang.org/protobuf, and is the one place that discipline was deliberately broken: it is opt-in, off unless a peering is configured, and carries only small control messages — the file bytes still move over stdlib HTTP. The reasoning is recorded in IMPLEMENTED-PEERING.md §7.

Status: v0.6, dogfooded daily on a k3s cluster (S3(Garage) + OIDC via Keycloak + at-rest encryption; client interop verified with Finder, rclone, and Cyberduck; the delete-the-volume recovery drill rehearsed on the live instance). Pre-1.0: minor versions may break config. See ARCHITECTURE.md for the full design, docs/handbook/ for operator and user guides, and CHANGELOG.md for what shipped when. Product site: www.swisscairn.ch.

Features

  • Web UI — sidebar views (Personal, group Spaces, Recents, Favourites, Shares, Transfers, Federated, Deleted files), sortable list and tile views with server-generated image thumbnails, per-browser view options (hidden files, extensions, page size, tile size), multi-select with bulk download / copy / move / favourites / delete, per-row actions (open, edit, download, share, copy link, send to peer, copy to, move, rename, create a space from the selection, lock, details), a New menu that creates folders and documents, search across your tree, a notification bell (received and sent transfers, expired locks — history retention-bounded), chevron breadcrumbs, branded login page, dark mode + accent themes, full UI in English, German, French, and Italian. 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, and real class-2 LOCK/UNLOCK so Office and Finder hold genuine locks while editing. Two-way desktop sync via rclone bisync: see docs/SYNC.md.
  • 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, resuming across page reloads.
  • Share links — optional public /s/ surface with expiry, password, and upload-drop support; admins can list and revoke all links.
  • Sharing with people and groups — address a file or folder to another account or a group on this instance: recipients sign in as themselves and find it under Shares Shared with me, reading the owner's file in place. Roles are view or edit; only the owner manages the grant list (recipients cannot re-share), and internal sharing has its own admin switch, separate from public links.
  • In-browser document editing (WOPI) — point office.url at a separately deployed Collabora Online (or any WOPI-compatible editor) and office documents open in a full-page editor: editable formats come from the editor's own discovery, Microsoft and ODF files are edited natively, the New menu creates documents from templates embedded in the binary, and locked / held / read-only files open honestly read-only with the reason named. Cairn ships none of the editor's code. See docs/handbook/document-editing.md.
  • Instance peering (federation v1) — two Cairn instances whose admins explicitly paired them can send files to each other's users: per-peer Ed25519 keys exchanged out of band (shown once; only a verifier is stored), separate send/receive allow-lists of users and IdP groups per peer, and delivery into the recipient's Personal/Inbox/<peer>/. Files are age-encrypted end to end for the receiving instance before they leave the sender, so a TLS-terminating proxy at either end sees ciphertext only. No discovery, no central broker, no identity federation — an explicit admin handshake or nothing. Runtime on/off switch, per-peer connection test, transfer notifications in the UI. Requires auth.perUserHomes (the default). See docs/handbook/peering.md.
  • Open Cloud Mesh (OCM) — federate with Nextcloud, ownCloud, OpenCloud, Seafile and CERNBox: standards-track OCM speaking both deployed dialects (modern 1.1 and the legacy Nextcloud/ownCloud form), RFC 9421 and draft-cavage request signatures, and live access grants instead of copies. Off by default, behind a server allow-list that actually gates both directions, per-server strictness ("invited contacts only" by default) and user-owned invites. Received shares live in a dedicated Federated view — never mounted into sync trees, never copied silently. See docs/handbook/federation-ocm.md.
  • Storage driversposix (any mounted filesystem) or s3 (hand-rolled SigV4 client, works against Garage/MinIO/AWS). An instance can also be deployed with storage.driver: "setup" and connected to its storage from the browser: a token-gated wizard probes the target with a real write, persists a local bootstrap pointer, and restarts into the normal boot path.
  • Auth modeslocal (config-file users with argon2id hashes, up to 50 runtime accounts managed from the dashboard, and a first-run wizard that mints the initial admin behind a one-time token from the logs), oidc (Authorization Code + PKCE against any OIDC IdP — local accounts and app passwords work alongside), or ldap (search-then-bind against AD / OpenLDAP / FreeIPA). See docs/handbook/local-accounts.md.
  • Authorization (auth.perUserHomes, on by default) — each user gets a private /home, plus shared /spaces/<group> mapped from OIDC group claims or LDAP memberOf; admins see the whole backend. Set it to false only if you deliberately want a single shared namespace: the Personal/Spaces split users expect is not truthful without it, because "Personal" would be the shared backend root. Spaces are self-service: users create a space from the UI and the owner manages members (read or read+write) — app-owned by default (membership lives with the encrypted instance state; no IdP write access needed), with an opt-in power mode that brokers spaces to the directory instead (ADR 0001). With the IdP read client configured, directory-group membership changes apply live (~1 minute, revocations included) instead of at next login. Spaces have a full lifecycle: deactivate (reversible, hidden from members) and then delete — contents go to trash for the retention window and the name stays reserved until final purge. Admin itself can be granted by directory group (auth.adminGroups) — with auth.admins as the break-glass username list.
  • Accounts & groups — the Users & access page manages local accounts and app-owned groups that work everywhere directory groups do (peering allow-lists, the admin group) — one namespace with the directory, collisions refused. Directory accounts and groups stay read-only: identity is consumed, not implemented. See docs/handbook/groups.md.
  • Trash (opt-in storage.trash.enabled) — deletes move to a per-user trash with restore/purge in the UI and API; deletions inside a shared space land in the space's own trash, visible to all members, restorable by write members. Admins govern the whole lifecycle from the Deleted files admin topic: the retention window, a deliberate never-delete-automatically mode (the default), instance-wide manual purge, and a purge log of every final deletion. File versioning is deliberately delegated to backend snapshots (ZFS, S3 versioning).
  • Data governance — append-only audit log (GET /api/v1/admin/audit), legal holds that block delete/overwrite/rename for everyone (admins included), path retention windows, a trash lifecycle (retention sweep and admin manual purge, both behind retention + hold guard rails, with a purge log), read-only spaces (a -ro directory group is mounted view-only), and per-file locks — user- or Office-held, folder locks for space owners, an optional instance-wide expiry cap, and a live admin Locks inventory with audited break-lock — plus optional download marking for leak tracing: authenticated downloads of PDF/Office/image files carry an invisible pseudonymous mark, so a leaked copy can be traced to the exact download it came from (evidence, not a verdict — and off by default, with the per-country legal homework shipped in the handbook). Admin-driven, with UI.
  • Quota — reporting (GET /api/v1/quota, WebDAV RFC 4331) plus enforced maxFileBytes / maxTotalBytes / reserveBytes / per-user perUserBytes limits → 507 on breach.
  • At-rest encryption — optional age-based content encryption with per-user and per-space keys held in the Keycloak profile, an OpenBao KV mount (uniform custody for AD deployments), or a single deployment key with per-instance domain separation. Always encrypts to a recovery recipient so key-loss ≠ data-loss (age -d works offline) — and the same offline drill opens the encrypted instance-state objects, so your instance's identity is as recoverable as its files. Optional post-quantum mode: hybrid ML-KEM-768+X25519 identities with automatic upgrade-on-touch migration. See docs/handbook/encryption.md and ENCRYPTION-COMPARISON.md.
  • Fair-code licensing, built to never hurt you — free for up to 50 users with no license key — and no key installed means no phone-home, ever. With a licence, above or below 50 users: Ed25519-signed keys verified fully offline, a monthly check-in carrying the licence id and nothing else, a 7-day grace window, and enforcement that only ever blocks new sign-ins over the cap — existing users and file access are never touched. Admin panel: paste-to-install, and one-click Remove licence for an instant, harmless return to the free tier.
  • 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" (or "setup"), shares, storage.quota, storage.trash, storage.encryption, auth.admins, auth.adminGroups, auth.perUserHomes, auth.oidcLogoutUrl, license, peering, preview, office. See example.config.json and the field comments in internal/config/config.go — the config structs are the reference documentation. Operator guides live in docs/handbook/.

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, CAIRN_LICENSE_KEY, and friends (env wins).

Non-goals

Deliberately out of scope, so Cairn stays one auditable binary: calendars, contacts, office suites (in-browser editing plugs in via WOPI from a separately deployed editor — Cairn ships none of its code), app stores and plugins, per-file ACLs, a metadata database, a search index, 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 — purchased at www.swisscairn.ch. See LICENSE.md. Cairn is not OSI open source and does not claim to be. Use of the software — both tiers — is additionally governed by the EULA: what data reaches the vendor (nothing without a licence key; the licence id only, with one), warranty, liability, and the operator's responsibility for lawful use of the optional monitoring features.