webapp

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package webapp serves the bdrive web server: a browsable web view of synced files (file tree reconstructed from the journals, rendered markdown, downloads), browser uploads, and — in hub mode — the sync API that lets storage-blind client devices sync whole projects through this server.

Two modes:

  • single-volume: Source is set (a DirSource for a plain folder, or a RemoteSource in tests); the classic viewer.
  • hub: Root + Projects are set; the server hosts many projects, each a volume stored under <root>/<project-id>/ in the object store, managed by a file-backed project registry.

The client — browser or syncing device — is deliberately told nothing about the storage: no remote URL, bucket, or credentials ever appear in an API response.

Index

Constants

View Source
const DefaultUploadTTL = 15 * time.Minute

DefaultUploadTTL is used when UploadConfig.TTL is unset: long enough for a slow upload, short enough that a leaked URL goes stale quickly.

Variables

This section is empty.

Functions

func RenderMarkdown

func RenderMarkdown(src []byte) (string, error)

RenderMarkdown converts markdown to HTML (GFM + wikilinks). Raw HTML in the source is escaped by goldmark's safe default.

Types

type AuthProvider

type AuthProvider interface {
	// CLILoginPath is the page `bdrive login` opens in a browser. The CLI
	// appends ?redirect=http://127.0.0.1:<port>/callback&state=<nonce>.
	CLILoginPath() string
	// Authenticate resolves the request's Bearer token or session cookie.
	Authenticate(r *http.Request) (User, bool)
	// Register mounts the provider's own pages and endpoints (/auth/*,
	// /api/auth/*) on the server mux.
	Register(mux *http.ServeMux)
}

AuthProvider is the seam between the server and an identity system.

type BuiltinAuth

type BuiltinAuth struct {
	AllowSignup bool
	Mail        *Mailer // nil → reset links go to the server log
	// contains filtered or unexported fields
}

BuiltinAuth is the open-source identity provider: email + password + name accounts and long-lived device tokens, persisted in one JSON file (loaded at open, rewritten atomically on every change — same discipline as the project registry). It owns the /auth/* pages the browser sees and the /api/auth/* endpoints the CLI uses.

func OpenBuiltinAuth

func OpenBuiltinAuth(path string, allowSignup bool, mail *Mailer) (*BuiltinAuth, error)

OpenBuiltinAuth loads (or starts) the account registry at path.

func (*BuiltinAuth) Authenticate

func (a *BuiltinAuth) Authenticate(r *http.Request) (User, bool)

func (*BuiltinAuth) CLILoginPath

func (a *BuiltinAuth) CLILoginPath() string

func (*BuiltinAuth) Register

func (a *BuiltinAuth) Register(mux *http.ServeMux)

type DeviceInfo

type DeviceInfo struct {
	ID       string    `json:"id"`
	Name     string    `json:"name,omitempty"`
	OS       string    `json:"os,omitempty"`
	User     string    `json:"user,omitempty"` // account email last seen using this device
	IP       string    `json:"ip,omitempty"`   // as observed by the server
	LastSeen time.Time `json:"last_seen"`
}

DeviceInfo is what the server knows about one syncing device: self-reported name/OS (headers sent by the client), plus what the server itself observed (public IP of the last push, last activity, the signed-in account). History joins ops against this registry, so IPs are real — as the server saw them — and ops stay small.

type DeviceRegistry

type DeviceRegistry struct {
	// contains filtered or unexported fields
}

DeviceRegistry is the file-backed device table (devices.json), same load-at-open / atomic-rewrite discipline as the project registry.

func OpenDeviceRegistry

func OpenDeviceRegistry(path string) (*DeviceRegistry, error)

func (*DeviceRegistry) Get

func (r *DeviceRegistry) Get(id string) (DeviceInfo, bool)

func (*DeviceRegistry) Observe

func (r *DeviceRegistry) Observe(d DeviceInfo)

Observe merges what a request revealed about a device. Disk writes are throttled: identity changes persist immediately, bare last-seen bumps at most once a minute.

type DirSource

type DirSource struct {
	Root string
}

DirSource serves a plain local folder straight from disk — no bdrive remote or volume needed. Meant for debugging the webapp (and as a quick local markdown browser): the tree reflects the folder live, provenance is just file mtimes, and content streams from the filesystem.

func (*DirSource) Files

func (d *DirSource) Files(_ context.Context) (map[string]FileInfo, error)

func (*DirSource) Open

func (d *DirSource) Open(_ context.Context, path string, _ FileInfo) (io.ReadCloser, error)

Open streams a file from disk. Paths are only ever snapshot map keys (produced by Files above), so they cannot escape Root.

func (*DirSource) Upload

func (d *DirSource) Upload(_ context.Context, p string, src io.Reader, _ int64) error

Upload writes the file atomically under Root. There is no journal here; on a mounted folder the daemon scans, journals, and syncs it like any local edit.

type DirectUploader

type DirectUploader interface {
	Uploader
	SignBlobPut(ctx context.Context, blob string, size int64, ttl time.Duration) (*remote.SignedPut, error)
	HasBlob(ctx context.Context, blob string) (bool, error)
	Commit(ctx context.Context, path, blob string, size int64) error
}

DirectUploader is additionally implemented by sources whose storage can accept presigned direct uploads.

type FileInfo

type FileInfo struct {
	Blob   string
	Size   int64
	Time   time.Time
	Author string
	Device string
}

FileInfo is the resolved state of one path: content identity (Blob doubles as the ETag), plus provenance where the source knows it.

type HistoryEntry

type HistoryEntry struct {
	Time     string     `json:"time"`
	Kind     string     `json:"kind"` // put | delete
	Path     string     `json:"path"`
	Size     int64      `json:"size,omitempty"`
	Blob     string     `json:"blob,omitempty"` // sha256; fetch via the blob endpoint
	User     string     `json:"user,omitempty"`
	UserName string     `json:"user_name,omitempty"`
	Author   string     `json:"author,omitempty"` // offline/git fallback identity
	Device   DeviceInfo `json:"device"`
	Note     string     `json:"note,omitempty"`
}

HistoryEntry is one change as the history API reports it.

type Identity

type Identity struct {
	ID, Name, Author string
}

Identity is the device identity uploads are journaled under.

type Mailer

type Mailer struct {
	Host string // e.g. smtp.gmail.com
	Port int    // e.g. 587 (STARTTLS)
	User string
	Pass string
	From string // e.g. drive@example.com
}

Mailer sends plain-text mail over SMTP — the lowest-common-denominator transport a self-hoster can point at anything (Gmail app password, SES, Mailgun, a local relay). No SDK, stdlib only. A nil Mailer reports itself as unconfigured so callers can fall back to logging the message.

func (*Mailer) Send

func (m *Mailer) Send(to, subject, body string) error

type Node

type Node struct {
	Name     string    `json:"name"`
	Path     string    `json:"path"`
	Dir      bool      `json:"dir"`
	Size     int64     `json:"size,omitempty"`
	Time     time.Time `json:"time,omitzero"`
	Author   string    `json:"author,omitempty"`
	Device   string    `json:"device,omitempty"`
	Children []*Node   `json:"children,omitempty"`
}

Node is one entry of the file tree returned by the tree endpoint.

type Project

type Project struct {
	ID      string    `json:"id"`
	Name    string    `json:"name"`
	Created time.Time `json:"created"`
}

Project is one synced project hosted by this server. Its storage lives under <root>/<id>/ in the object store; the id is permanent, the name is a renameable label.

type ProjectDB

type ProjectDB struct {
	// contains filtered or unexported fields
}

ProjectDB is the server's project registry, persisted as a JSON file that is loaded on open and rewritten atomically on every change.

func OpenProjectDB

func OpenProjectDB(path string) (*ProjectDB, error)

OpenProjectDB loads the registry at path; a missing file is an empty registry.

func (*ProjectDB) Get

func (db *ProjectDB) Get(id string) (Project, bool)

func (*ProjectDB) GetOrCreate

func (db *ProjectDB) GetOrCreate(name string) (Project, bool, error)

GetOrCreate returns the project with the given name, creating it (with a fresh id) if none exists. Names are matched exactly.

func (*ProjectDB) List

func (db *ProjectDB) List() []Project

type RemoteSource

type RemoteSource struct {
	Backend remote.Backend
	// Device identifies this server in ops it journals for uploads. Required
	// for uploads; irrelevant for reading.
	Device Identity
	// contains filtered or unexported fields
}

RemoteSource reads a beardrive remote: it fetches every journal and folds the ops into the current volume state (same total order as journal.Replay, but keeping author/device/time of the winning op per path). With Device set it also accepts uploads, journaled under that identity.

func (*RemoteSource) Commit

func (r *RemoteSource) Commit(ctx context.Context, p, blob string, size int64) error

Commit appends a put op for path→blob to this server's own journal. It refuses if the blob is not in the store yet (a peer must never see an op whose content is missing). Only this server writes this journal key, so the read-modify-write below has a single writer; upmu serializes it across concurrent requests.

func (*RemoteSource) Files

func (r *RemoteSource) Files(ctx context.Context) (map[string]FileInfo, error)

func (*RemoteSource) HasBlob

func (r *RemoteSource) HasBlob(ctx context.Context, blob string) (bool, error)

func (*RemoteSource) Open

func (r *RemoteSource) Open(ctx context.Context, _ string, fi FileInfo) (io.ReadCloser, error)

func (*RemoteSource) SignBlobPut

func (r *RemoteSource) SignBlobPut(ctx context.Context, blob string, size int64, ttl time.Duration) (*remote.SignedPut, error)

SignBlobPut presigns a direct upload of the blob, if the backend can sign.

func (*RemoteSource) Upload

func (r *RemoteSource) Upload(ctx context.Context, p string, src io.Reader, _ int64) error

Upload stores content through the server: spool to disk while hashing, push the blob, then journal the op.

type Server

type Server struct {
	// Single-volume mode: serve exactly this source.
	Source Source
	Volume string // display only

	// Hub mode (when Root is set): many projects on one storage root.
	Root     remote.Backend
	Projects *ProjectDB

	// Device identifies this server in ops it journals for browser uploads.
	Device  Identity
	Refresh time.Duration
	Upload  UploadConfig
	// Auth, when set, gates the whole API behind sign-in. Nil means the
	// historical trusted-network behavior: no accounts, everyone welcome.
	Auth AuthProvider
	// Devices, when set, records what the server observes about syncing
	// devices (name, OS, public IP, last activity) for history.
	Devices *DeviceRegistry
	// Shares, when set, enables public share links (/s/<token>).
	Shares *ShareDB
	// contains filtered or unexported fields
}

Server renders volumes as a website and, in hub mode, brokers sync for client devices.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the HTTP handler: /api/* plus the embedded frontend.

type Share

type Share struct {
	Token   string    `json:"token"`
	Project string    `json:"project"`
	Path    string    `json:"path"`
	Creator string    `json:"creator,omitempty"` // account email
	Created time.Time `json:"created"`
	Expires time.Time `json:"expires,omitzero"` // zero = permanent until revoked
}

Share is one public link.

type ShareDB

type ShareDB struct {
	// contains filtered or unexported fields
}

ShareDB is the file-backed share registry (shares.json), same discipline as the project registry.

func OpenShareDB

func OpenShareDB(path string) (*ShareDB, error)

func (*ShareDB) Create

func (db *ShareDB) Create(project, p, creator string, ttl time.Duration) (Share, error)

Create returns a share for (project, path), reusing an existing live one so repeated shares of the same file hand out the same URL.

func (*ShareDB) Get

func (db *ShareDB) Get(token string) (Share, bool)

Get resolves a live (non-expired) share.

func (*ShareDB) List

func (db *ShareDB) List(project string) []Share

List returns a project's live shares.

func (*ShareDB) Revoke

func (db *ShareDB) Revoke(token string) bool

type Source

type Source interface {
	Files(ctx context.Context) (map[string]FileInfo, error)
	Open(ctx context.Context, path string, fi FileInfo) (io.ReadCloser, error)
}

Source supplies the file set and content of one volume. Implementations: RemoteSource (a beardrive remote) and DirSource (a plain local folder).

type UploadConfig

type UploadConfig struct {
	Enabled bool
	// TTL bounds the lifetime of presigned direct-upload URLs.
	TTL time.Duration
}

UploadConfig controls whether and how clients may write.

type Uploader

type Uploader interface {
	Upload(ctx context.Context, path string, r io.Reader, size int64) error
}

Uploader is implemented by sources that accept writes through the server.

type User

type User struct {
	ID    string `json:"id"`
	Email string `json:"email"`
	Name  string `json:"name"`
}

User is an authenticated account as the rest of the server sees it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL