Nextcloud “Drive” with Tailscale identity — spec

Status

Design approved 2026-08-31. This is the design of record; implementation plan derived separately. Do not commit changes to the vault per owner preference.

Goal

A self-hosted Google-Drive replacement on telep-mainframe: file uploads, folders, sharing, previews, and in-browser Office editing — with identity provisioned from Tailscale (no passwords), and the CAD export directory surfaced as a first-class folder plus a Taildrive mount.

Decisions (locked)

  • Platform: Nextcloud (adopt, not build).
  • Identity: Tailscale headers only — auto-provisioned SSO for tailnet users; tailnet-only access.
  • Scope: consolidate into Nextcloud. The earlier separate cad:8080 type-aware browser is dropped; the CAD dir becomes a Nextcloud folder. 3D files deep-link to Online3DViewer.
  • Taildrive ACLs: already provisioned in the tailnet policy (drive:share / drive:access).

Components

Docker Compose stack at /home/levander/nextcloud/, data on the root LVM (/ has 2.8 TB free). Follows the existing per-service /home/levander/<service>/ convention.

ContainerPurpose
nextcloud (fpm or apache image)The Drive app
postgresDatabase
redisMemory cache + transactional file locking
onlyoffice/documentserverIn-browser docx/xlsx/pptx editing
reverse proxy (identity injector)Tailscale whois → Remote-User header

Resource budget: box has 62 GiB RAM (45 free), 20+ cores — comfortable for this stack.

Identity — Tailscale → auto-provisioned SSO (highest-risk piece)

  1. Dedicated Tailscale node drive (tailscaled-drive, matching the existing per-service tsnet sidecar pattern, e.g. board-tsnet, tailscaled-bambuddy), userspace, own socket/statedir.
  2. tailscale serve on that node fronts the stack.
  3. A thin reverse proxy resolves each request’s source tailnet IP → user via the Tailscale whois LocalAPI (/localapi/v0/whois?addr=) and injects Remote-User: <tailscale-login> (candidate implementations: caddy-tailscale, a tsnet Go proxy, or nginx + a whois sidecar — chosen during the build spike).
  4. Nextcloud consumes the header via the user_saml app in “environment variable” (SSO) mode (reads REMOTE_USER/mapped header) → tailnet users are auto-created + logged in on first request. A break-glass local admin is retained for config.

Validate first

This chain is the fiddly part. Build order stands it up and proves a real tailnet login before anything else is built.

CAD integration

  • /home/levander/freecad/exports/ mounted as Nextcloud External Storage (Local), read-write → a “CAD” folder. New exports (from the cad-designer pipeline) appear automatically; edits flow back to disk → visible via Taildrive + the Mac ~/cad-exports/ sync.
  • 3D (step/stl/3mf/obj/gltf) → deep-link to Online3DViewer (https://cad.taild4189d.ts.net:8090/o3dv/#model=…). Link-out, not an inline viewer (no good native STEP/STL preview exists).
  • Office (docx/xlsx/pptx) → OnlyOffice, edits save back.
  • Images / PDF / CSV / text / markdown → Nextcloud native previews.

Taildrive — the whole Drive

The entire Nextcloud storage is exposed via Taildrive, not just CAD — a raw-filesystem view of the whole Drive on the tailnet.

  • On the mainframe: tailscale drive share drive /home/levander/nextcloud/data (the Nextcloud data dir; per-user files live under <datadir>/<user>/files/…).
  • Access on the Mac: Finder → Connect to Server → http://100.100.100.100:8080/<tailnet>/telep-mainframe/drive.
  • Complementary to the Nextcloud web app (raw filesystem mount vs browser), same underlying storage.

Write-back consistency

Files written into the Nextcloud data dir out-of-band (via the Taildrive mount, or by the CAD pipeline) are invisible to Nextcloud until an occ files:scan. Plan: a lightweight periodic/inotify files:scan so Taildrive-side additions surface in the web UI. Ownership must match the Nextcloud runtime user (www-data).

CAD reconciliation

To keep CAD files inside the Taildrive-mounted Drive and preserve the existing freecad/exports pipeline (O3DV :8090, exports browser :8080, Mac ~/cad-exports/ sync all read that path), the default is:

  • Keep /home/levander/freecad/exports/ as the CAD source of truth, mounted into Nextcloud as External Storage (Local, read-write) → shows as a “CAD” folder in the Drive UI.
  • Add a second Taildrive share for it: tailscale drive share cad /home/levander/freecad/exports.
  • Net: the Drive (uploads) is Taildrive share drive; CAD is Taildrive share cad; both mount on the Mac. (Alternative considered: relocate CAD output into the Nextcloud data dir for a single share — rejected in v1 because it breaks the existing CAD consumers.)

Access

  • Primary: https://drive.taild4189d.ts.net (tailnet, via the drive node).
  • Optional LAN: drive.telep.lan Caddy vhost (split-horizon, like the other services).
  • Tailnet-only — identity depends on the tailnet source IP; no public/off-tailnet login.

Out of scope (v1) / follow-ups

  • Backups of the Nextcloud DB + data dir — flagged, not built in v1.
  • Off-tailnet access / Pocket-ID OIDC fallback.
  • Mobile/desktop sync-client rollout (works out of the box; not a build task).
  • Inline 3D preview inside Nextcloud.

Build order (risk-first)

  1. Identity spikedrive tsnet node + whois→header proxy + minimal Nextcloud + user_saml env mode. Gate: log in as a real tailnet user, auto-provisioned.
  2. Full stack — Postgres + Redis + Nextcloud tuning (cron, caching, trusted proxies/domains).
  3. OnlyOffice Document Server + Nextcloud connector.
  4. CAD external storage (read-write) + 3D deep-link action.
  5. Taildrive share + verify Mac mount.
  6. LAN vhost (optional) + docs.

Success criteria

  • A tailnet user hits drive.taild4189d.ts.net, is auto-logged-in as their Tailscale identity, no password.
  • Upload/download/share/folder operations work.
  • CAD folder shows live exports; a .step opens in O3DV; a .docx opens+edits+saves in OnlyOffice.
  • The drive Taildrive share mounts on the Mac and exposes the whole Nextcloud storage as a filesystem; the cad share exposes the CAD dir. A file dropped via the Taildrive mount appears in the Nextcloud web UI after a scan.