feat(gtm): round 3 — never default a stranger into a dead cloud

Reviewer round 3 (9/7/9/8/7/9) caught the sharpest truth bug yet: the
README quickstart's bare 'bdrive login' defaults to beardrive.ai, which
is waitlist-only and not accepting logins — a launch-day visitor's
first command would fail silently. Fixes:

- quickstart step 1 now routes to self-hosting ('bdrive login
  https://your-hub', ~10-minute guide linked) with Cloud honestly
  framed as waitlist; website closer promises 'Self-host in one
  binary' instead of 'under a minute, done'
- handoff gains a P0 gate: verify the FULL first run (login → init →
  edit → log) against a working hub before any launch; bare 'bdrive
  login' must not be advertised until Cloud accepts logins
- handoff: seed 3-5 good-first-issues (CONTRIBUTING promises them);
  replace the mailto waitlist with a real form before Product Hunt
- README trimmed 583→524 lines: the Authentication and Choosing-a-
  database operator reference moved verbatim into docs/self-hosting.md
  with a compact pointer left behind

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
This commit is contained in:
Snow Lee
2026-07-15 12:13:44 -07:00
co-authored by Claude Fable 5
parent 71e8a49666
commit 3422d3509f
7 changed files with 116 additions and 87 deletions
+14 -71
View File
@@ -82,9 +82,11 @@ go install github.com/runbear-io/beardrive/cmd/bdrive@latest
## Quick start
```sh
# 1. Sign this device in (once). Default server: beardrive.ai;
# self-hosters pass their own URL.
bdrive login
# 1. Sign this device in against your hub (once per device).
# Self-host a hub in ~10 minutes (docs/self-hosting.md), then:
bdrive login https://your-hub
# (BearDrive Cloud — zero-setup, bare `bdrive login` — is coming;
# join the waitlist at beardrive.ai. Self-host to try it today.)
# 2. Start syncing a project — interactive: create or connect a project,
# sync the whole folder or just ./shared. Re-run any time to resume.
@@ -360,77 +362,18 @@ distinct-reader counts, and last-read times — never who read what;
`?by=device` adds the agent-only per-device folder breakdown (device
identity is already public via history; human emails never appear).
### Authentication
### Authentication & database
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).
Hubs always require sign-in — every change is attributed to a real
account. **Signup is invite-only by default** (the safe posture for a
public URL); self-service signup opens only with a gate (admin approval,
or allowed domains + email verification). Hub metadata (accounts,
projects, orgs, shares) lives in a file-backed store by default, or
SQLite/Postgres (incl. Supabase) via the `database` config block.
**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. 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:
Full reference — the three signup postures, SMTP, admins, CLI device
sign-in, and database selection: **[docs/self-hosting.md](docs/self-hosting.md)**.
- **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:
```jsonc
"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.
### Uploads
+22 -9
View File
@@ -25,10 +25,21 @@ Draft copy is ready to paste; adjust voice as you like.
bdrive version`, and `go install
github.com/runbear-io/beardrive/cmd/bdrive@latest`. If the tap is
stale, cut a fresh `goreleaser release` first.
6. **Verify the full first run, not just the binary**: on a clean
machine, `bdrive login <a working hub> && bdrive init && edit a file
&& bdrive log` must succeed end to end. Until BearDrive Cloud is
live, every try-path (README, website, launch posts) must route
people to self-hosting — bare `bdrive login` defaults to
beardrive.ai and MUST NOT be advertised until that hub actually
accepts logins. Gate any launch on this check.
7. **Seed 35 `good first issue` / `help wanted` issues** from
ROADMAP.md (more agent platforms in `bdrive hooks install`; per-path
access scopes design discussion; demo assets; docs gaps) —
CONTRIBUTING.md promises curated starter issues; make it true.
## P1 — demo assets (an hour)
6. **Record a 4560s demo GIF/video** for the README hero and any
8. **Record a 4560s demo GIF/video** for the README hero and any
launch post. Script (uses two terminals + a browser):
- T1: `bdrive init --name demo --yes` in a folder with a few notes →
show `bdrive status` (daemon running).
@@ -40,29 +51,31 @@ Draft copy is ready to paste; adjust voice as you like.
- T1: `bdrive share wiki/findings.md` → open the public URL.
Tools: `vhs` (charmbracelet) or QuickTime + gifski. Put the result at
`docs/assets/demo.gif` and add it above the fold in README.
7. **Verify beardrive.ai serves the updated landing page** (the repo's
9. **Verify beardrive.ai serves the updated landing page** (the repo's
`website/` — including the new `assets/insights.png`) after the next
deploy.
## P2 — when you're ready to be seen (launch window)
8. **Show HN post** — full drafts in [launch-plan.md](launch-plan.md):
10. **Show HN post** — full drafts in [launch-plan.md](launch-plan.md):
title, body, and the first-comment founder note. Post from your
account; be present for the first 3 hours to answer everything.
9. **Product Hunt** — drafts in launch-plan.md. Schedule after HN, not
the same day.
10. **Claude Code plugin discoverability** — submit/announce the
11. **Product Hunt** — drafts in launch-plan.md. Schedule after HN, not
the same day. Before PH: replace the mailto waitlist on the site
with a real form (any hosted waitlist works) — email-composer
friction kills launch-day conversion.
12. **Claude Code plugin discoverability** — submit/announce the
marketplace entry wherever Anthropic surfaces community plugins
(Discord, awesome-lists PRs from your account).
## P3 — measurement plumbing (see metrics.md)
11. **Turn on GitHub traffic watching**: stars/clones/views are under
13. **Turn on GitHub traffic watching**: stars/clones/views are under
Insights → Traffic; consider a weekly note of the numbers in
Discussions → Announcements.
12. **Homebrew install counts**: `brew info --analytics
14. **Homebrew install counts**: `brew info --analytics
runbear-io/tap/beardrive` (public analytics take ~30 days to
appear).
13. **Decide on hub-side opt-in telemetry** (proposal in metrics.md —
15. **Decide on hub-side opt-in telemetry** (proposal in metrics.md —
requires your explicit approval before any implementation; nothing
is wired today).
+1
View File
@@ -67,6 +67,7 @@ fabricated proof) — record such rejections under Won't-fix with reasons.
|---|---|---|---|---|---|---|---|
| 1 (baseline) | 6 | 4 | 8 | 3 | 3 | 2 | 3 blockers, 3 majors (all fixed this round; screenshots captured from the live demo hub — real UI, not fabricated) |
| 2 | 8 | 7 | 9 | 8 | 8 | 8 | 1 major (residual dead-deployment-mode copy ×3 on the site — fixed) + 4 minors fixed; share.png re-captured with real rendered content |
| 3 | 9 | 7 | 9 | 8 | 7 | 9 | 1 major (quickstart defaulted into the not-yet-live beardrive.ai — quickstart/closer rewritten to self-host-first, first-run gate added to handoff) + README trimmed 583→524, starter-issues & waitlist handoffs added |
## Won't-fix / disputed
+1 -1
View File
@@ -39,7 +39,7 @@ CLAUDE.md's invariants. Answer every comment for the first 3 hours.
**Prep checklist:** install path verified from a clean machine
(`brew install runbear-io/tap/beardrive && bdrive version` — a 404 tap on
launch day is fatal), demo GIF live in README (handoff #6), Discussions
launch day is fatal), full first-run verified against a working hub (handoff #6), demo GIF live in README (handoff #8), Discussions
enabled, `docs/self-hosting.md` linked from README, hub demo instance
warm (expect self-host attempts within minutes).
+2 -2
View File
@@ -17,8 +17,8 @@ within a week. Everything else is upstream of it.
| # | Metric | Source | Stage |
|---|---|---|---|
| 1 | README/landing views → GitHub stars | GitHub Insights → Traffic (handoff #11) | attention |
| 2 | Installs: brew + `go install` | brew analytics (handoff #12); go proxy stats are noisy — treat as directional | acquisition |
| 1 | README/landing views → GitHub stars | GitHub Insights → Traffic (handoff #13) | attention |
| 2 | Installs: brew + `go install` | brew analytics (handoff #14); go proxy stats are noisy — treat as directional | acquisition |
| 3 | Activation: `bdrive init` → first successful sync | today: anecdotal/self-reported; future: opt-in telemetry (below) | activation |
| 4 | Team formation: project gains a 2nd member (invite redeemed) | hub data (self-hosted: invisible to us; beardrive.ai once live) | expansion |
| 5 | Wedge proof: first agent read-telemetry event in a project | hub read ledger (same visibility caveat) | wedge |
+74 -2
View File
@@ -41,8 +41,8 @@ hub over HTTPS.
}
```
Full knob reference (allowed domains, email verification via SMTP,
admin approval, Postgres/Supabase, share-link rate limits): the
Full knob reference for auth and databases: the sections below;
share-link rate limits and upload TTLs: the
[README's web-server section](../README.md#web-server).
## 4. Run it
@@ -70,6 +70,78 @@ as bearer tokens. For containers, the repo ships a `Dockerfile`
copy-paste setup for Claude Code/Cowork, Hermes, and Codex; or run
`bdrive hooks install` in the folder.
## 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. 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:
```jsonc
"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
+2 -2
View File
@@ -494,8 +494,8 @@ every tool works content-addressed blobs append-only, no locks, offl
<section class="closer">
<div class="wrap">
<h2>Share files. Share context.<br><span class="green">Set up in under a minute.</span></h2>
<p>brew install, bdrive init, done. Files travel to people as URLs; context travels to every agent on the team.</p>
<h2>Share files. Share context.<br><span class="green">Self-host in one binary.</span></h2>
<p>brew install, run one hub, bdrive init. Files travel to people as URLs; context travels to every agent on the team. Cloud — zero-setup — is on the waitlist.</p>
<div class="cta-row">
<a class="btn" href="https://github.com/runbear-io/beardrive">Start on GitHub</a>
<button class="install" id="install2" title="Copy">