Files
Snow LeeandClaude Fable 5 623da892d2 fix(auth): first-account bootstrap — admin emails activate on signup; add CI
A fresh hub following docs/self-hosting.md was a locked room: invite-only
(the default) showed "Sign up disabled" with nobody to mint an invite, and
the approval-gated posture stranded the first admin as pending forever.

Emails on the config's admin list are operator-vetted, so they now
activate immediately on signup (any posture), and while the hub has zero
accounts they may sign up even on an invite-only hub. Strangers still
can't take the bootstrap slot, and the door closes after the first
account. Validated end to end from scratch: hub boot → admin signup →
device-code login × 2 devices → init → bidirectional sync → hooks install.

Also adds the missing GitHub Actions CI workflow (build/vet/test on
ubuntu + macos) — the repo previously had no CI at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 23:24:25 -07:00

7.8 KiB

Self-hosting a BearDrive hub in ~10 minutes

Until BearDrive Cloud launches, self-hosting is the way to run a team hub — and it stays a first-class, fully-supported path forever (AGPL, no features held back). One static Go binary, one config file, any object store (or a plain directory).

1. Install the binary

brew install runbear-io/tap/beardrive     # macOS / Linuxbrew
# or: go install github.com/runbear-io/beardrive/cmd/bdrive@latest
# or: grab a release tarball — https://github.com/runbear-io/beardrive/releases

2. Pick storage

Any of: s3://bucket/prefix, gs://bucket/prefix, an S3-compatible endpoint, or — simplest for a first run — a plain directory (file:///var/lib/bdrive/storage). Files and change journals live there; hub metadata (accounts, projects, orgs) lives in the database you pick in step 3. Clients never see this storage — they sync through the hub over HTTPS.

3. Write config.json

{
  "remote": "file:///var/lib/bdrive/storage",
  "addr": ":4173",
  "upload": true,
  "auth": {
    // Signup is invite-only by default — the safe posture for a public
    // URL. Bootstrap: while the hub has zero accounts, the emails listed
    // in "admins" can sign up directly; then invite teammates from the
    // web UI.
    "admins": ["you@example.com"],
    "users_db": "/var/lib/bdrive/auth.json"
  },
  "reads": { "enabled": true },              // agent read analytics (Insights)
  "database": { "driver": "sqlite", "dsn": "/var/lib/bdrive/hub.db" }
}

Full knob reference for auth and databases: the sections below; share-link rate limits and upload TTLs: the README's web-server section.

4. Run it

bdrive web -c config.json

Put TLS in front (Caddy/nginx/your platform's LB) — device tokens travel as bearer tokens. For containers, the repo ships a Dockerfile (distroless, CGO-free) with a Cloud Run recipe in deploy/ — any container platform works the same way.

5. First sign-in and first project

  1. Open https://your-hub/ → sign up with one of the admins emails from your config. On a brand-new hub these can always create the first account (whatever the signup posture); it activates immediately, and admin-listed emails skip the approval/verification gates on any posture.
  2. On any machine: bdrive login https://your-hub (browser flow), then in the folder you want synced: bdrive init --name wiki --yes — or --shared docs inside a repo to sync only that subfolder.
  3. Invite a teammate: sidebar footer → ManageNew invite — the join link both creates their account and adds them to your org.
  4. Connect agents: the project's home page in the web UI shows one-paste setup for Claude Code/Cowork, Hermes, and Codex — hub URL and project id already filled in. Teammates paste it into their own agent, which installs the CLI, keeps the beardrive skill (bdrive skill install), signs in, mounts the project, and registers the sync hooks.

Authentication reference

Hubs always require sign-in — every change is attributed to a real account. The whole API — web UI, uploads, project creation, device sync — needs a session; only /api/config and the auth pages stay open (the plain-folder viewer, bdrive web ./folder, remains auth-free). Accounts are email + password + name, kept in a file-backed registry (auth.json: bcrypt password hashes and SHA-256 token digests, atomically rewritten — no plaintext credentials ever touch disk).

Signup is invite-only by default — the safe posture for a hub on a public URL. New people get in only through an expiring invite link an owner mints; the link lets them create an account (bypassing the gates below) and join, in one step. (One exception so a fresh hub isn't a locked room: while zero accounts exist, the config's admins emails may sign up directly.) To allow self-service signup instead, set "allow_signup": true with a gate — the server refuses to start an open hub that has none, so a fake email can never just walk in. Three postures:

  • Invite-only (default): allow_signup unset/false. Only invite links create accounts.
  • Approval-gated: allow_signup: true + require_approval: true — anyone can sign up, but a hub admin approves each new account before it works (no SMTP needed).
  • Domain-restricted + verified: allow_signup: true + allowed_domains: ["you.com"] + require_verification: true (needs smtp) — only your company's addresses may sign up, each confirming an emailed link. Verification without SMTP is refused (the link would otherwise only reach the server log).

Admins tune verification/approval live from the web UI (Admin → Signup & access); allowed_domains, the admin list, and allow_signup are server-config-owned so a browser session can never widen who gets in.

bdrive login <url> on a client device opens the server's sign-in page in a browser (sign up right there if needed); when the user signs in, the page bounces a one-time code to the CLI's loopback listener and the terminal finishes on its own, storing a long-lived per-device token (revocable server-side). On headless/SSH machines, bdrive login --device prints a short code to approve from any signed-in browser instead. Every sync and every bdrive init then authenticates with that token.

"Forgot password" emails a one-hour reset link via the auth.smtp block — plain SMTP, so any provider works. With no SMTP configured, the link is printed to the server log so an admin can hand it over; reset is never fully broken.

Two notes: put a hub behind TLS (reverse proxy or tailscale) — bdrive login warns when signing in over plain http to a non-localhost address. Internally all of this sits behind an AuthProvider interface; the open-source server ships the built-in email/password provider, and alternative identity backends can be swapped in without touching the CLI or the API.

Choosing a database

A hub keeps a little metadata — accounts, projects, orgs, invites, shares, devices — separate from your files. (File content and the sync journals always live in the object store; the database never holds them.) You choose where that metadata lives with the database block:

"database": { "driver": "file" }                       // default — JSON under BDRIVE_HOME
"database": { "driver": "sqlite",   "dsn": "/var/lib/bdrive/hub.db" }
"database": { "driver": "postgres", "dsn": "postgres://…@…pooler.supabase.com:6543/postgres" }
  • file (default): zero dependencies, human-readable JSON, perfect for a laptop or a small self-hosted hub.
  • sqlite: one embedded database file — a real DB locally with no server to run.
  • postgres: a managed Postgres such as Supabase for production — just point dsn at its connection string (use the transaction pooler for many connections). Since Supabase is Postgres, this stays fully open-source with no managed-only lock-in.

file and sqlite are single-writer (run one hub instance); Postgres is transactional and can back more than one instance. Switching backends doesn't migrate existing data — pick one when you set the hub up. Both SQL drivers are pure Go, so the binary stays a CGO-free static build.

Upgrading

brew upgrade beardrive (clients and hub are the same binary — keep them roughly in step; the sync protocol is append-only journals + blobs, which old clients read forward). After upgrading a client, re-run bdrive hooks install once per project to pick up any hook improvements, and bdrive skill install once per machine to refresh the agent skill.

Backup

Everything irreplaceable is in two places: the storage root (blobs + journals — files and their entire history) and the metadata database (accounts/projects/orgs/shares). Snapshot both; restore is copy-back.