// Package remote abstracts the cloud object store a volume syncs through. // beardrive is provider-agnostic: any backend that can put/get/list immutable // objects works. Built-in schemes: // // file:///abs/path local or network-drive directory (also used in tests) // s3://bucket/prefix Amazon S3 (or S3-compatible via AWS_ENDPOINT_URL) // gs://bucket/prefix Google Cloud Storage // https://host:4173 a bdrive web server brokering one of the above — // the device needs no storage credentials at all // // Remote layout: blobs/ for content, journal/.jsonl for op // logs. Each device writes only its own journal, so there are no concurrent // writers per object and no server-side coordination is needed. package remote import ( "context" "fmt" "io" "net/url" "strings" "time" ) type Object struct { Key string Size int64 } // SignedPut is a presigned direct-upload request: whoever holds the URL can // PUT that one object until Expires, without ever seeing storage credentials. type SignedPut struct { URL string // upload here Method string // always "PUT" Headers map[string]string // headers that must be sent verbatim (they are signed) Expires time.Time } // PutSigner is implemented by backends that can mint presigned upload URLs // so clients write to storage directly. Backends without that capability // (file://) simply don't implement it, and callers fall back to uploading // through the server. type PutSigner interface { SignPut(ctx context.Context, key string, size int64, ttl time.Duration) (*SignedPut, error) } type Backend interface { Put(ctx context.Context, key string, r io.Reader, size int64) error Get(ctx context.Context, key string) (io.ReadCloser, error) List(ctx context.Context, prefix string) ([]Object, error) Exists(ctx context.Context, key string) (bool, error) Close() error } // ReadEvent is one agent file read reported to the hub for its read heatmap. type ReadEvent struct { Path string `json:"path"` Time time.Time `json:"time,omitzero"` } // ReadReporter is the optional read-telemetry capability, in the PutSigner // mold: backends that sync through a hub report the device's agent reads so // the heat view can split human from agent traffic. Object-store backends // simply don't implement it — there is no hub to tell. type ReadReporter interface { ReportReads(ctx context.Context, reads []ReadEvent) error } // Open creates a backend from a remote URL. func Open(ctx context.Context, raw string) (Backend, error) { u, err := url.Parse(raw) if err != nil { return nil, fmt.Errorf("invalid remote %q: %w", raw, err) } switch u.Scheme { case "file": return newLocal(u.Path) case "s3": return newS3(ctx, u.Host, strings.Trim(u.Path, "/")) case "gs": return newGCS(ctx, u.Host, strings.Trim(u.Path, "/")) case "http", "https": return newHTTPBackend(raw) default: return nil, fmt.Errorf("unsupported remote scheme %q (supported: file://, s3://, gs://, https://)", u.Scheme) } }