Merge pull request #9 from runbear-io/feat/logout-and-remote-help

feat(cli)!: hub-only — add `bdrive logout`, remove `bdrive remote`
This commit is contained in:
Snow W. Lee (Sungwon)
2026-07-10 09:56:17 -07:00
committed by GitHub
6 changed files with 89 additions and 93 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this is
**BearDrive** is the product name; **`bdrive`** is its CLI binary (file conventions: the `.bdrive/` settings directory and `.bdriveignore` at the project root, `~/.bdrive` home, `BDRIVE_HOME`). BearDrive is a Go CLI that mounts any folder as a synced volume: contents sync across devices through cloud object storage (S3, GCS, S3-compatible, or a plain directory), with per-file change history and offline support. No server required — devices converge through append-only journals in a dumb object store; an optional `bdrive web` server can front the store as a sync hub for storage-blind client devices.
**BearDrive** is the product name; **`bdrive`** is its CLI binary (file conventions: the `.bdrive/` settings directory and `.bdriveignore` at the project root, `~/.bdrive` home, `BDRIVE_HOME`). BearDrive is a Go CLI that mounts any folder as a synced volume: contents sync across devices and teammates through a **`bdrive web` hub**, with accounts, per-file change history, and offline support. Clients are storage-blind — they sync through the hub over `https://` and never hold storage credentials; the hub owns the object store (S3, GCS, S3-compatible, or a plain directory) and devices converge through append-only journals in it. (Direct client-to-bucket sync without a hub is no longer supported; the object-storage backends exist only as the hub's own storage.)
The repo ships one binary: `cmd/bdrive` — the CLI, the sync daemon, and the web server (`bdrive web`: viewer, uploads, multi-project sync hub).
@@ -41,7 +41,7 @@ Package roles (`internal/`):
- **`config`** — global state under `$BDRIVE_HOME` (default `~/.bdrive`): device identity (`device.json`), settings (`settings.json`: default server + device token + signed-in account), and the mount registry (`mounts.json`, keyed by **stable mount id**, holding only each mount's last-known path). The per-folder `.bdrive/` directory (`project.go`) holds `config.json` with the mount id + volume/remote/include; **nothing is keyed by the folder path**, so renames/moves are free — `ResolveMount` self-heals the registry path, and the volume store lives at `~/.bdrive/volumes/<mount-id>/`. `.bdrive/` is never synced and holds no credentials.
- **`webapp`** — the `bdrive web` server, in two modes. Single-volume: `Source` is a `DirSource` (plain folder from disk) or `RemoteSource` (folds journals into a file tree with per-file provenance). Hub: `Root` + `Projects` host many projects on one storage root, each under `<root>/<project-id>/` via `remote.Prefixed`; `ProjectDB` (`projects.go`) is a file-backed registry (JSON, loaded at open, rewritten atomically per change) with create-or-join-by-name semantics, name-scoped per organization. Orgs (`orgs.go`, file-backed `orgs.json`) wall projects by membership (email → owner|member): every per-project route — viewer APIs, uploads, history, shares management, the `/store/*` sync proxy — 403s for non-members, `/api/projects` lists only your orgs' projects, owners mint expiring multi-use invite links (`/join/<token>`), and a pre-org hub migrates all projects into a "default" org (all existing accounts join, oldest owns) at startup. `QuotaProvider` (`quota.go`) is the plan-enforcement seam mirroring `AuthProvider` — CheckWrite/RecordUsage on every write path, CheckSeat on invite redemption; OSS ships only `UnlimitedQuota`, managed deployments swap the provider. Renders markdown (goldmark + Obsidian `[[wikilinks]]`). With `--upload` it accepts writes: browser uploads (`upload.go` — direct-to-storage via presigned URLs when the backend implements `remote.PutSigner`, relayed otherwise; ops journaled under the server's own device) and the per-project `/api/p/<id>/store/*` proxy (`store.go`) that whole devices sync through — the `https://` remote backend (`remote/http.go`) is its client; journals are never presigned, only immutable blobs. Frontend is dependency-free vanilla JS embedded via `go:embed static`; it learns everything from `/api/config` (+ `/api/projects` in hub mode) and never sees storage info or credentials. It uses native History-API path routing (`/<project-id>/<path>` in hub mode, `/<path>` in volume mode, `/join/<token>` for invites — no `#`, slashes stay literal); `Server.frontend` serves `index.html` as the SPA fallback for any non-asset, non-API/auth/share route so deep links and refreshes resolve, and all client API/asset URLs are root-absolute so a deep path doesn't break relative resolution. **Hub metadata persistence** (accounts, projects, orgs+invites, shares, devices — never blobs or journals) sits behind a pluggable `MetaStore` of typed repos (`db.go`): the service structs (`BuiltinAuth`, `OrgDB`, `ProjectDB`, `ShareDB`, `DeviceRegistry`) keep their in-memory maps + logic and persist each change as one record through a repo. Two backends — `db_file.go` (the historical JSON files, still the zero-dep default, reached via the `Open*(path)` constructors) and `db_sql.go` (one `database/sql` impl over pure-Go drivers: `modernc.org/sqlite` locally, `jackc/pgx` for Postgres/Supabase, portable schema + idempotent migrations + transactional multi-row writes). `web.go`'s `database` config (`{driver:file|sqlite|postgres, dsn}`) selects it; file is default and untouched. `db_conformance_test.go` runs the same service ops against every backend.
`cmd/bdrive/` is a thin cobra CLI over these packages (`login`, `init`, `stop`, `sync`, `status`, `log`, `remote`, `web`, `whoami`, `daemon`, `version``mnt`/`umnt` are gone; `init` is the front door and `stop` pauses). `bdrive login` signs the device in (bare form uses the remembered server or `config.DefaultServer` = beardrive.ai; loopback-callback browser flow in `login.go`, `--device` for headless) and stores server+token+account in `settings.json`. `bdrive init` is interactive on a TTY (survey menus: create-new vs connect-existing with a project list; whole-folder vs `--shared <dir>`, which becomes the include list) with full flag bypass (`--name/--project/--shared/--yes`) and never prompts without a TTY; it runs the login flow first when there is no session, writes `.bdrive/config.json`, seeds `.bdriveignore`, and starts sync via `startSync`; re-running it resumes — including after a folder move. `bdrive web -c config.json` configures the server from a file, explicit flags winning.
`cmd/bdrive/` is a thin cobra CLI over these packages (`login`, `logout`, `init`, `stop`, `sync`, `status`, `log`, `web`, `whoami`, `daemon`, `version``mnt`/`umnt`/`remote` are gone; `init` is the front door and `stop` pauses). `bdrive login` signs the device in (bare form uses the remembered server or `config.DefaultServer` = beardrive.ai; loopback-callback browser flow in `login.go`, `--device` for headless) and stores server+token+account in `settings.json`; `bdrive logout` clears the saved token+account (keeps the remembered server unless `--forget`). Switching hubs is `bdrive login <new-url>` then re-`init``init` is the only thing that writes a folder's remote (always a hub, `server + "/p/" + id`); there is no client command to point a folder at a raw bucket. `bdrive init` is interactive on a TTY (survey menus: create-new vs connect-existing with a project list; whole-folder vs `--shared <dir>`, which becomes the include list) with full flag bypass (`--name/--project/--shared/--yes`) and never prompts without a TTY; it runs the login flow first when there is no session, writes `.bdrive/config.json`, seeds `.bdriveignore`, and starts sync via `startSync`; re-running it resumes — including after a folder move. `bdrive web -c config.json` configures the server from a file, explicit flags winning.
Authentication (`webapp/auth.go`, `authlocal.go`, `mail.go`) is **mandatory in hub mode** — the config's `auth` block tunes `users_db`/`allow_signup`/`allowed_domains`/`require_verification`/`require_approval`/`admins`/`smtp`; the plain-folder viewer stays auth-free — and sits behind the `AuthProvider` interface — the OSS server ships only `BuiltinAuth` (email+password accounts and device tokens in a file-backed `auth.json`; bcrypt for passwords, SHA-256 digests for tokens, plaintext never stored; server-owned `/auth/*` pages; one-time codes for the CLI callback and device flows; SMTP reset mail with a log-link fallback). **Signup is invite-only by default** (`allow_signup` defaults false): a valid org invite bootstraps an account even when self-signup is closed — `BuiltinAuth.InviteValid` (wired to `OrgDB.ValidInvite`) lets `pageSignup`/`pageLogin` offer account creation for a `/join/<token>` target, and `signupInvited` skips the domain/verification/approval gates and activates immediately (the invite is the vetting). `BuiltinAuth.ValidateSignupPolicy` (called at hub startup, `web.go`) refuses an ungated open hub and email-verification-without-SMTP rather than silently leaving the door open. The three postures: invite-only (default), approval-gated (`require_approval`), and domain-restricted+verified (`allowed_domains`+`require_verification`+`smtp`); `allow_signup`/`allowed_domains`/`admins` stay server-config-owned so a browser session can't widen access. A managed deployment can swap in a different provider (e.g. PropelAuth) without touching the CLI or API — keep provider-specific code out of this repo. The sync client picks up its token from `BDRIVE_TOKEN` or `settings.json` and sends `X-Bdrive-Device{,-Name,-Os}` headers (`remote/http.go`); the hub's file-backed device registry (`webapp/devices.go`) records per-device name/OS/account/server-observed IP. Journal ops carry the signed-in account (`Op.User`/`UserName` from `Session.Account`; `Author` remains the git/OS fallback). History (`webapp/history.go`): `GET /api/p/<id>/history?path=|prefix=` (newest first, device-registry join) and `GET /api/p/<id>/blob?sha=` stream any exact version — blobs are retained forever, so the future revert phase is just re-putting an old blob as a new op. Share links (`webapp/shares.go`, file-backed `shares.json`): any signed-in member mints `/s/<token>` public URLs (`bdrive share`, or the UI's Share button) serving the file's LATEST content until revoked (optional expiry); `/s/*` responses are sandboxed (CSP `sandbox allow-scripts`, no auth cookies) so shared HTML can't attack hub sessions — keep that header on any change; `/s/*` also sits behind a per-IP token bucket (`ratelimit.go`, `share_rpm` config), and markdown share pages get a "Shared with BearDrive" footer (raw HTML is never injected into).
+18 -12
View File
@@ -1,9 +1,11 @@
# BearDrive — Google Drive for AI agents
**BearDrive** mounts any folder as a synced volume: its contents stay
synchronized across all your devices through cloud object storage, every
change is tracked (who, when, on which device), and everything keeps
working offline. The CLI is `bdrive`.
synchronized across all your devices and teammates through a BearDrive
**hub**, every change is tracked (who, when, on which device), and
everything keeps working offline. The CLI is `bdrive`; a hub is a
`bdrive web` server you (or we) run on an object store — clients sync
through it over HTTPS and never touch the storage directly.
Two things it's for: **sharing files with people** — any synced file
becomes a public URL that renders as a page — and **sharing context across
@@ -33,16 +35,17 @@ $ bdrive login && cd ~/workspace && bdrive init
project. Files are *real files on disk*: every tool, editor, and agent can
use them with zero integration work. Rename or move the folder freely —
state is keyed by a stable id, never the path.
- **Multi-device sync** — devices converge through a shared remote. Each
device only writes its own append-only journal, so no locking service or
server is needed any object store works.
- **Multi-device sync** — devices converge through a shared hub. Each
device only writes its own append-only journal, so no locking service is
needed; the hub can be backed by any object store.
- **Change tracking** — `bdrive log` and the web UI's History view show
which account changed which file, when, from which device (name, OS, IP).
Content is stored content-addressed, so every version is retained — view
or download any point in a file's history.
- **Cloud-provider agnostic** — Amazon S3 (`s3://`), Google Cloud Storage
(`gs://`), any S3-compatible store (MinIO, Cloudflare R2 via
`AWS_ENDPOINT_URL`), or a plain shared directory (`file://`, e.g. a NAS).
- **Cloud-provider agnostic** — a hub can store on Amazon S3 (`s3://`),
Google Cloud Storage (`gs://`), any S3-compatible store (MinIO, Cloudflare
R2 via `AWS_ENDPOINT_URL`), or a plain shared directory (`file://`, e.g. a
NAS). Clients never see it.
- **Offline-first** — the working folder is always fully usable with no
network. Changes are journaled locally and pushed when the remote becomes
reachable again.
@@ -113,14 +116,14 @@ beardrive uses each provider's standard credential chain — nothing beardrive-s
| Command | Description |
|---|---|
| `bdrive login [server-url]` | Sign this device in (browser flow; `--device` for headless; default server beardrive.ai) |
| `bdrive login [server-url]` | Sign this device in (browser flow; `--device` for headless; default server beardrive.ai). Switch hubs with `bdrive login <new-url>` |
| `bdrive logout` | Sign this device out — clear the saved token/account (`--forget` also drops the remembered server) |
| `bdrive init [folder]` | Create/connect a project and start syncing — interactive on a TTY, flags (`--name/--project/--shared/--yes`) for scripts; re-run to resume |
| `bdrive stop [folder]` | Stop syncing (files stay; `bdrive init` resumes) |
| `bdrive share <file>` | Public URL for a synced file (`--list`, `--revoke`, `--expires`) |
| `bdrive sync [folder]` | Run one sync cycle now |
| `bdrive status [folder]` | Projects, daemon state, pending changes |
| `bdrive log [folder] [-p path] [-n N]` | Change history: account, device, time, file |
| `bdrive remote [folder]` / `bdrive remote set <folder> <url>` | Show / set the remote (advanced, incl. direct-to-bucket) |
| `bdrive web [folder \| storage-root-url]` | Web server: viewer (rendered markdown, downloads, history), uploads, multi-project sync hub |
| `bdrive whoami` | Device identity used in change tracking |
@@ -232,7 +235,10 @@ cd ~/some-project && bdrive init # once per project
`bdrive login` signs the device in and remembers the server (`settings.json`
under the bdrive home; bare `bdrive login` defaults to beardrive.ai —
`--status` shows the current server and account). `bdrive init` then, per
`--status` shows the current server and account). To move to a **different
hub**, run `bdrive login <new-url>` and then re-run `bdrive init` in each
folder to connect it to a project there; `bdrive logout` signs out entirely.
`bdrive init` then, per
project, walks you through it on a terminal: **create a new project or
connect an existing one** (picked from the server's list), and **sync the
whole folder or only a shared subfolder** (e.g. `./shared`). Every question
-57
View File
@@ -2,7 +2,6 @@ package main
import (
"fmt"
"net/url"
"time"
"github.com/spf13/cobra"
@@ -174,62 +173,6 @@ func logCmd() *cobra.Command {
return c
}
func remoteCmd() *cobra.Command {
c := &cobra.Command{
Use: "remote [folder]",
Short: "Show or set the cloud remote of a mounted folder",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
folder, err := absFolder(args)
if err != nil {
return err
}
proj, err := mustProject(folder)
if err != nil {
return err
}
if proj.Remote == "" {
fmt.Println("(none)")
} else {
fmt.Println(proj.Remote)
}
return nil
},
}
set := &cobra.Command{
Use: "set <folder> <url>",
Short: "Set the remote (s3://bucket/prefix, gs://bucket/prefix, file:///path, https://bdrive-server)",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
folder, err := absFolder(args[:1])
if err != nil {
return err
}
raw := args[1]
u, err := url.Parse(raw)
if err != nil || (u.Scheme != "s3" && u.Scheme != "gs" && u.Scheme != "file" && u.Scheme != "http" && u.Scheme != "https") {
return fmt.Errorf("invalid remote %q (want s3://bucket/prefix, gs://bucket/prefix, file:///path, or https://bdrive-server)", raw)
}
proj, err := mustProject(folder)
if err != nil {
return err
}
proj.Remote = raw
if proj, err = config.SaveProject(folder, proj); err != nil {
return err
}
if _, _, err := config.ResolveMount(folder); err != nil { // sync the registry
return err
}
fmt.Printf("remote of %s set to %s\n", folder, raw)
fmt.Println("run `bdrive sync` to sync now (a running daemon picks it up automatically)")
return nil
},
}
c.AddCommand(set)
return c
}
func daemonCmd() *cobra.Command {
c := &cobra.Command{
Use: "daemon",
+50
View File
@@ -106,6 +106,56 @@ With no argument the remembered server is used, or ` + config.DefaultServer + `.
return c
}
func logoutCmd() *cobra.Command {
var forget bool
c := &cobra.Command{
Use: "logout",
Short: "Sign this device out (clear the saved token)",
Long: `Clear this device's saved sign-in the token and account so it is no
longer authenticated to the bdrive server. The remembered server is kept so
"bdrive login" re-authenticates to it; pass --forget to clear that too.
To switch to a different server, just run "bdrive login <new-server-url>".
Your synced folders are untouched; this only affects this device's session.`,
Example: ` bdrive logout # sign out, keep the server remembered
bdrive logout --forget # sign out and forget the server`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
settings, err := config.LoadSettings()
if err != nil {
return err
}
if settings.Token == "" && settings.Email == "" && !(forget && settings.Server != "") {
fmt.Println("already signed out")
return nil
}
who, server := settings.Email, settings.Server
settings.Token, settings.Email, settings.Name = "", "", ""
if forget {
settings.Server = ""
}
if err := config.SaveSettings(settings); err != nil {
return err
}
switch {
case who != "" && server != "" && !forget:
fmt.Printf("signed out %s from %s\n", who, server)
case who != "":
fmt.Printf("signed out %s\n", who)
default:
fmt.Println("signed out")
}
if forget {
fmt.Println("forgot the remembered server (run `bdrive login <url>` to set a new one)")
}
fmt.Println("note: the device token stays valid on the server until it expires — revoke it from the hub's device list if needed")
return nil
},
}
c.Flags().BoolVar(&forget, "forget", false, "also forget the remembered server")
return c
}
// runLogin executes the sign-in flow against a server known to require auth
// and persists server + token + account to settings.
func runLogin(server string, cfg serverConfig, useDevice bool) error {
+1 -1
View File
@@ -31,13 +31,13 @@ everything keeps working offline; changes sync when the remote is reachable.`,
}
root.AddCommand(
loginCmd(),
logoutCmd(),
initCmd(),
shareCmd(),
stopCmd(),
syncCmd(),
statusCmd(),
logCmd(),
remoteCmd(),
webCmd(),
whoamiCmd(),
daemonCmd(),
+18 -21
View File
@@ -1,13 +1,13 @@
---
name: beardrive
description: Use BearDrive — a synced file system for AI agents and teams. Start syncing any project folder (bdrive init) and it stays in sync across devices and teammates through a BearDrive server or object storage, with accounts, per-file change history, public share links, and offline support. Use when the user wants to "set up beardrive", "sync this folder", "mount a folder", "share this file by URL", "start/stop syncing", "connect to a beardrive server", "check bdrive status", "see what changed", "who changed this file?", or troubleshoot a stuck sync.
description: Use BearDrive — a synced file system for AI agents and teams. Start syncing any project folder (bdrive init) and it stays in sync across devices and teammates through a BearDrive hub, with accounts, per-file change history, public share links, and offline support. Use when the user wants to "set up beardrive", "sync this folder", "share this file by URL", "start/stop syncing", "connect to a beardrive server", "switch to a different hub", "check bdrive status", "see what changed", "who changed this file?", or troubleshoot a stuck sync.
---
# BearDrive — synced file system for AI agents
**BearDrive** (CLI: `bdrive`) turns any folder into a synced project: a background daemon per project scans for local changes and exchanges with the server (or object store). Files on disk are always real files — every tool, editor, and agent works on them with no integration.
**BearDrive** (CLI: `bdrive`) turns any folder into a synced project: a background daemon per project scans for local changes and exchanges them with a **hub** (a `bdrive web` server). Files on disk are always real files — every tool, editor, and agent works on them with no integration.
Use this skill whenever the user is working with the `bdrive` CLI: initializing or stopping projects, syncing, sharing files by URL, configuring a remote, inspecting state, reading change history, or debugging. ("Mount" in older docs = today's `bdrive init`.)
Use this skill whenever the user is working with the `bdrive` CLI: initializing or stopping projects, syncing, sharing files by URL, signing in / switching hubs, inspecting state, reading change history, or debugging. ("Mount" in older docs = today's `bdrive init`.)
## Command map
@@ -19,9 +19,9 @@ Use this skill whenever the user is working with the `bdrive` CLI: initializing
| One sync cycle now | `bdrive sync [<folder>]` |
| Mounts + daemon + pending state | `bdrive status [<folder>]` |
| Change history | `bdrive log [<folder>] [-p path] [-n N]` |
| Show / set remote | `bdrive remote [<folder>]` · `bdrive remote set <folder> <url>` |
| This device's identity | `bdrive whoami` |
| Sign this device in (once per device) | `bdrive login [url]` — bare form uses the remembered server or beardrive.ai. Opens the sign-in page in a browser (sign-up available there); the terminal completes on its own and stores a per-device token. `--device` prints a code to approve from any browser (SSH/headless); `--status` shows server + account. Password reset: "Forgot password?" on the sign-in page (emailed via the server's SMTP config, or the link appears in the server log). |
| Sign this device in (once per device) | `bdrive login [url]` — bare form uses the remembered server or beardrive.ai. Opens the sign-in page in a browser (sign-up available there); the terminal completes on its own and stores a per-device token. `--device` prints a code to approve from any browser (SSH/headless); `--status` shows server + account. Password reset: "Forgot password?" on the sign-in page (emailed via the server's SMTP config, or the link appears in the server log). **Switch hubs** with `bdrive login <new-url>`, then re-run `bdrive init` in each folder. |
| Sign this device out | `bdrive logout` — clears the saved token + account (folders untouched); `--forget` also drops the remembered server. The device token stays valid server-side until it expires — revoke it from the hub's device list to be sure. |
| Share a synced file publicly by URL | `bdrive share <file>` — prints a link anyone can open (HTML renders as a page, markdown rendered, PDFs inline; sandboxed; always the latest content; no account needed). `--expires 24h` for self-destructing links; `--list` / `--revoke <token-or-url>` to manage. Put generated reports in the shared folder, sync, then share. |
| Set up a project for a Claude Code team | `/beardrive:install` — installs the CLI, signs in, runs init (whole/shared folder), offers a CLAUDE.md section about the shared folder, and registers project-level hooks (blocking pull at prompt-submit, async push after Write/Edit) in `.claude/settings.json` |
| Per-file / folder change history in the web UI | History button (file versions or project feed) and per-folder ⌚ — each entry: account, time, device (name/OS/IP), view/download of that exact version. API: `GET /api/p/<id>/history?path=\|prefix=`, `GET /api/p/<id>/blob?sha=` |
@@ -91,7 +91,7 @@ bdrive login https://drive.example.com:4173
cd ~/agent-workspace && bdrive init --name agent-workspace
```
Devices connecting the same project (by name or id) converge through the hub. Direct-to-bucket setups (no hub) remain possible via `bdrive remote set <folder> s3://…` after an offline init.
Devices connecting the same project (by name or id) converge through the hub. A hub is required — clients always sync through a `bdrive web` server and never talk to the object store directly.
Hub projects belong to an **organization**: only members of the project's org can see or sync it (project names are scoped per org too). Your first `bdrive init` creates your org automatically. **Hubs are invite-only by default** — the safe posture for a public URL. To give a teammate access, an org **owner** opens the web UI and clicks **Invite** in the sidebar footer — it mints an expiring join link (`…/join/<token>`); the teammate opens it and creates an account through the link (invites bootstrap signup even when public self-signup is closed), and is in. An admin can instead open self-service signup with a gate (admin approval, or allowed-domains + email verification) under **Admin → Signup & access** / the config's `auth` block.
@@ -139,9 +139,9 @@ bdrive stop ./notes --forget
---
## 2. Cloud storage setup
## 2. Storing a hub's data (object storage)
beardrive uses each provider's standard credential chain — nothing beardrive-specific.
This section is about the **hub** (`bdrive web <storage-root>`), which keeps its blobs + journals in an object store you point it at — **clients never touch storage**; they sync through the hub over `https://`. The credentials below live on the hub's machine, not on client devices. beardrive uses each provider's standard credential chain — nothing beardrive-specific.
### Supported URL schemes
@@ -152,22 +152,19 @@ beardrive uses each provider's standard credential chain — nothing beardrive-s
| `file:///abs/path` | Plain directory (local, NAS, Dropbox folder, …) | `file:///Volumes/nas/beardrive/notes` |
| `https://host:port/p/<project-id>` | One project on a `bdrive web` hub — the client holds **no storage credentials**; the server device owns the bucket config. Server must run with `--upload` for clients to push. Set up with `bdrive init` (never hand-write the `/p/<id>` URL) | `https://drive.example.com:4173/p/p-7f3a2c91` |
`bdrive remote set` validates the scheme and rejects anything else. The prefix can be multi-segment (`s3://bucket/team/agent/workspace`); beardrive writes `blobs/` and `journal/` underneath it.
The `s3://`/`gs://`/`file://` URL is the **hub's** storage root; the prefix can be multi-segment (`s3://bucket/team/agent/workspace`), and beardrive writes `blobs/` and `journal/` underneath it. The `https://…/p/<id>` scheme is what a **client** folder uses, wired automatically by `bdrive init` (never hand-written).
### Setting the remote
### Pointing a hub at storage (server-side)
```sh
# At mount time
bdrive init ./workspace --remote s3://acme-beardrive/workspace
# Start a hub on an S3 bucket; clients then `bdrive login` + `bdrive init`
bdrive web s3://acme-beardrive/root --upload
# After mounting
bdrive remote set ./workspace s3://acme-beardrive/workspace
# Inspect
bdrive remote ./workspace
# Or from a config file
bdrive web -c config.json # { "remote": "s3://acme-beardrive/root", "upload": true, … }
```
After `remote set`, run `bdrive sync ./workspace` to push immediately. A running daemon picks up the change on its next interval.
Clients never set storage: they run `bdrive login <hub-url>` once, then `bdrive init` per folder.
### Amazon S3 (`s3://`)
@@ -206,14 +203,14 @@ export AWS_ENDPOINT_URL=https://<accountid>.r2.cloudflarestorage.com
export AWS_REGION=auto
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
bdrive init ./workspace --remote s3://my-r2-bucket/workspace
bdrive web s3://my-r2-bucket/workspace --upload
# MinIO
export AWS_ENDPOINT_URL=http://minio.local:9000
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=minioadmin
export AWS_SECRET_ACCESS_KEY=minioadmin
bdrive init ./workspace --remote s3://beardrive/workspace
bdrive web s3://beardrive/workspace --upload
```
Persist these in the user's shell rc or a systemd/launchd unit so the daemon also has them.
@@ -229,7 +226,7 @@ gcloud auth application-default login
# Service account
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
bdrive init ./workspace --remote gs://acme-beardrive/workspace
bdrive web gs://acme-beardrive/workspace --upload
```
Service account needs `storage.objects.{get,list,create,delete}` on the bucket (`roles/storage.objectAdmin` bucket-scoped works).