SPB Git

spb/drive Public

SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.

JavaScript 82.7% CSS 10.6% Nunjucks 3.6% Shell 1.8% SQL 1.3%
7.1 KB · 138 lines markdown
Rendered Raw Blame History
1<!--2  ─────────────────────────────────────────────3   SPB Drive — Personal Cloud Drive4  ─────────────────────────────────────────────5   Author  : Simon-Pierre Boucher6   Contact : contact@spboucher.ai7   File    : README.md8   Purpose : Project overview, ops runbook, restore procedure9   License : MIT © Simon-Pierre Boucher10  ─────────────────────────────────────────────11-->1213# SPB Drive1415![Node](https://img.shields.io/badge/node-%E2%89%A520-339933?logo=node.js&logoColor=white)16![Fastify](https://img.shields.io/badge/fastify-5-000000?logo=fastify)17![SQLite](https://img.shields.io/badge/sqlite-FTS5%20%2B%20WAL-003B57?logo=sqlite)18![Version](https://img.shields.io/badge/version-2.0.0-b48cff)19![Tests](https://img.shields.io/badge/tests-27%20passing-22d3aa)20![License](https://img.shields.io/badge/license-MIT-4f8cff)2122**Personal cloud drive of [Simon-Pierre Boucher](mailto:contact@spboucher.ai)** — a self-hosted23Google Drive / Dropbox replacement for one person. Private by default behind a password;24the only public surfaces are explicitly created share links.2526**Live:** https://drive.spboucher.ai (node m3u96b · ngrok custom domain)2728## Highlights2930- **Vault** — content-addressed blob store (sha256 CAS → free dedup), unlimited nested folders,31  chunked + resumable uploads (8 MB chunks), drag-drop of entire folder trees, paste-to-upload,32  trash with 30-day retention, tags, stars, folder colors/emoji.33- **Universal previewer** — images (zoom/pan/EXIF/GPS), video (Range streaming with instant seek;34  mkv/avi/hevc transcoded to H.264 in the background), audio (waveform + ID3 cover art), PDF35  (pdf.js with page rail, text selection + in-doc search), Office → PDF via LibreOffice headless,36  xlsx native table fast path, code (Shiki, 100+ languages), Markdown GFM, CSV (100k rows,37  sortable), JSON tree, ipynb, zip/tar browsing with inner-file preview, fonts, eml — and an38  elegant fallback card for everything else.39- **Sharing machine**`https://drive.spboucher.ai/s/<token>` (10-char base58, ≥58 bits),40  optional expiry / argon2 password / max downloads / preview-only, folder shares with41  ZIP-all streaming, Open Graph unfurls, server-side SVG QR codes, per-share visit/download log,42  instant revocation.43- **Search** — SQLite FTS5 over names, tags **and file contents** (pdftotext, office→text, code).44- **CLI**`spbdrive init/ls/up/down/mkdir/mv/rm/restore/share/shares/revoke/search/push/doctor`.4546### New in v2.04748- **File versioning** — replacing or editing a file archives the previous content49  (up to 20 versions per file, CAS-backed so identical bytes cost nothing). Browse,50  download, restore or delete versions from the Info panel / right-click → *Version history*.51  Upload conflicts now offer **Keep both / Replace (new version) / Skip**, batch-applicable.52- **In-browser text editor** — edit code / Markdown / JSON / CSV directly (⌘S saves as a new53  version), plus *New → Text file*. Keyboard shortcut `E`.54- **File requests**`https://drive.spboucher.ai/r/<token>` (12-char base58, ~70 bits) lets55  anyone **send** files straight into a folder of your choice without seeing its contents:56  optional expiry + max-file quota, QR code, instant close, everything logged in Activity.57- **Storage insights** — dedup savings, version-history footprint, trash weight, top-2058  largest files and duplicate-file groups with one-click cleanup (sidebar → *Storage*).5960## Layout6162```63src/         Fastify server (auth, storage, preview, shares, search, api, web)64cli/         spbdrive CLI (API-token auth, chunked uploads, push sync)65deploy/      setup-m3u96b.sh · ecosystem.config.cjs (pm2) · ngrok.yml · backup.sh · systemd unit66scripts/     check-headers · inject-headers · check-secrets · reset-password67test/        vitest unit + e2e (upload → thumb → protected share → download)68```6970Runtime data lives outside the repo at `~/srv/drive` on m3u96b (macOS seals `/srv`; on Linux use `/srv/drive` — the systemd unit keeps that path)71(`files/` CAS blobs · `db/drive.sqlite` · `cache/` thumbs+transcodes · `backups/` · `logs/`).7273## Development7475```bash76npm install77SPBDRIVE_DATA_DIR=./data SPBDRIVE_BOOTSTRAP_PASSWORD=devpass npm run dev78# → http://127.0.0.1:743079npm run check      # lint + headers + secrets + tests80```8182The bootstrap password is consumed once on first boot (argon2id → `data/auth.json`) and the83env var is ignored forever after. Break-glass reset: `node scripts/reset-password.mjs` (local TTY).8485## Deployment (m3u96b)8687```bash88rsync -a --exclude node_modules --exclude .git . m3u96b:~/spbdrive-src/89ssh m3u96b 'SPBDRIVE_BOOTSTRAP_PASSWORD=… bash ~/spbdrive-src/deploy/setup-m3u96b.sh ~/spbdrive-src'90```9192The script is idempotent: installs ffmpeg/LibreOffice/poppler/7z/ngrok/pm2, creates `~/srv/drive`93(700), syncs the app to `~/srv/drive/app`, `npm ci --omit=dev`, seeds auth on first boot, starts94`spbdrive-server` + `spbdrive-tunnel` under pm2 (`pm2 save` + `pm2 startup` for reboot survival),95health-checks `:7430/healthz`, and installs the nightly backup cron (02:30).9697DNS/ngrok (one-time): reserve `drive.spboucher.ai` in the ngrok dashboard and add the CNAME it98gives you at the spboucher.ai DNS provider. TLS terminates at ngrok; the app trusts99`x-forwarded-for` for lockout + share logs.100101## Backups & restore102103`deploy/backup.sh` (cron, 02:30) writes `~/srv/drive/backups/YYYY-MM-DD/`:104a SQLite `.backup` snapshot plus an rsync `--link-dest` hardlink copy of the blob store105(unchanged blobs cost zero bytes). Retention: 14 dailies + 8 Sunday weeklies. Each run verifies106`PRAGMA integrity_check` and blob counts.107108**Restore procedure** (tested):109110```bash111pm2 stop spbdrive-server112DRIVE=~/srv/drive113SNAP=$DRIVE/backups/<date>114cp "$SNAP/drive.sqlite" $DRIVE/db/drive.sqlite115rm -f $DRIVE/db/drive.sqlite-wal $DRIVE/db/drive.sqlite-shm116rsync -a --delete "$SNAP/files/" $DRIVE/files/117cp -p "$SNAP/auth.json" "$SNAP/keys.json" $DRIVE/118pm2 start spbdrive-server && curl -fsS http://127.0.0.1:7430/healthz119```120121## Security model122123- Single argon2id-hashed password; 5 failures → 15-min per-IP lockout + global backoff.124- Sessions: 256-bit ids stored **hashed**; cookies `httpOnly · Secure · SameSite=Lax`; sliding TTL.125- CSRF: double-submit token on form posts, custom header on JSON API writes.126- Share links: unguessable base58 tokens, constant-time compares, argon2 share passwords,127  signed gate cookies; expired/revoked pages leak nothing.128- Nodes are DB ids and blobs are hashes — client paths are never trusted; names validated129  against `^[^/\\\0]{1,255}$`.130- Uploaded HTML downloads as attachment; SVG previews render under `CSP: sandbox`; strict131  `default-src 'self'` CSP app-wide; no third-party requests anywhere (fonts/pdf.js/wavesurfer132  self-hosted).133- ffmpeg/LibreOffice run via `execFile` (no shell) with timeouts; pino redacts auth headers.134135## License136137MIT © Simon-Pierre Boucher — [contact@spboucher.ai](mailto:contact@spboucher.ai)138