Documentation
¶
Overview ¶
Package remote defines the transport interface for binpass synchronisation and provides built-in implementations for git, Google Drive, Yandex.Disk, WebDAV, and S3.
The Remote interface is the same for all transports; its Caps method advertises what the transport can do so that the sync engine can adapt its strategy (for example, adding verify-after-write when the transport lacks atomicity).
Index ¶
- Variables
- func CheckPath(p string) error
- func IsBlockContainer(p string) bool
- func IsStoreContent(p string) bool
- func IsTombContainer(p string) bool
- func RelativeTo(root, absPath string) string
- type Caps
- type File
- type GitOptions
- type GitRemote
- func (g *GitRemote) Caps() Caps
- func (g *GitRemote) Close() error
- func (g *GitRemote) Delete(ctx context.Context, path string, expectRev string) error
- func (g *GitRemote) Get(ctx context.Context, path string) (io.ReadCloser, string, error)
- func (g *GitRemote) List(ctx context.Context) ([]File, error)
- func (g *GitRemote) Lock(_ context.Context) (Unlock, error)
- func (g *GitRemote) Name() string
- func (g *GitRemote) Pull(ctx context.Context) error
- func (g *GitRemote) Push(ctx context.Context) error
- func (g *GitRemote) Put(ctx context.Context, path string, content io.Reader, expectRev string) (string, error)
- func (g *GitRemote) Rename(ctx context.Context, from, to string) error
- type MemOptions
- type MemRemote
- func (m *MemRemote) Caps() Caps
- func (m *MemRemote) Close() error
- func (m *MemRemote) Delete(_ context.Context, path string, expectRev string) error
- func (m *MemRemote) Get(_ context.Context, path string) (io.ReadCloser, string, error)
- func (m *MemRemote) List(_ context.Context) ([]File, error)
- func (m *MemRemote) Lock(_ context.Context) (Unlock, error)
- func (m *MemRemote) Name() string
- func (m *MemRemote) Put(_ context.Context, path string, content io.Reader, expectRev string) (string, error)
- func (m *MemRemote) Rename(_ context.Context, from, to string) error
- type NoopUnlock
- type RcloneOptions
- type RcloneRemote
- func (r *RcloneRemote) Caps() Caps
- func (r *RcloneRemote) Close() error
- func (r *RcloneRemote) Delete(ctx context.Context, path string, expectRev string) error
- func (r *RcloneRemote) Get(ctx context.Context, path string) (io.ReadCloser, string, error)
- func (r *RcloneRemote) List(ctx context.Context) ([]File, error)
- func (r *RcloneRemote) Lock(_ context.Context) (Unlock, error)
- func (r *RcloneRemote) Name() string
- func (r *RcloneRemote) Put(ctx context.Context, path string, content io.Reader, expectRev string) (string, error)
- func (r *RcloneRemote) Rename(ctx context.Context, from, to string) error
- type Remote
- type ResticOptions
- type ResticRemote
- func (r *ResticRemote) Caps() Caps
- func (r *ResticRemote) Close() error
- func (r *ResticRemote) Delete(ctx context.Context, path string, expectRev string) error
- func (r *ResticRemote) Forget(ctx context.Context, policyArgs ...string) error
- func (r *ResticRemote) Get(ctx context.Context, path string) (io.ReadCloser, string, error)
- func (r *ResticRemote) List(ctx context.Context) ([]File, error)
- func (r *ResticRemote) Lock(_ context.Context) (Unlock, error)
- func (r *ResticRemote) Name() string
- func (r *ResticRemote) Pull(_ context.Context) error
- func (r *ResticRemote) Push(ctx context.Context) error
- func (r *ResticRemote) Put(ctx context.Context, path string, content io.Reader, expectRev string) (string, error)
- func (r *ResticRemote) Rename(_ context.Context, from, to string) error
- func (r *ResticRemote) Restore(ctx context.Context, snapshotID, targetDir string) error
- func (r *ResticRemote) Snapshots(ctx context.Context) ([]ResticSnapshot, error)
- type ResticSnapshot
- type Unlock
Constants ¶
This section is empty.
Variables ¶
var ErrNoSnapshots = errors.New("remote/restic: repository holds no snapshots yet")
ErrNoSnapshots reports a repository that holds no snapshot yet.
It is a condition rather than a failure: a repository nobody has backed up to is the ordinary starting point, and the first sync is what creates the snapshot.
var ErrUnknownRemote = errUnknownRemote("")
ErrUnknownRemote is returned when the remote type is not recognised.
var ErrUnsafePath = errors.New("remote: unsafe path")
ErrUnsafePath reports a remote path that would resolve outside the store.
Functions ¶
func CheckPath ¶
CheckPath reports whether a path reported by a remote is safe to join onto the store root.
Every path in a listing comes from the other side of the wire: a git repository, an S3 bucket, an rclone backend. A path like "../../../.ssh/authorized_keys" joined onto the store root and written during a pull would let whoever controls the remote place a file anywhere the user can write. Transports call this before reporting a file, so a hostile listing is rejected at the boundary rather than deep inside the sync engine.
func IsBlockContainer ¶
IsBlockContainer reports whether a path is a container holding a filesystem image rather than an encrypted archive.
These are the ones a file-by-file transport cannot carry usefully: a LUKS image or a sparse bundle is one large opaque file that changes wholesale on every write, so a transport that stores each version separately grows without bound.
func IsStoreContent ¶
IsStoreContent reports whether a store-relative path is content that sync owns: an encrypted entry, a recipients file, or a tomb container.
Every transport asks this rather than testing extensions itself. Six copies of "does it end in .gpg or .age" is how the tomb containers came to be omitted from three of them and included in none.
func IsTombContainer ¶
IsTombContainer reports whether a path is a tomb container or its key.
func RelativeTo ¶
RelativeTo makes a path from a snapshot relative to the root it was taken from.
A path outside that root returns empty rather than being passed through: restic snapshots hold absolute paths, and treating a stray one as an entry name produced entries called "home/alice/.password-store/x.age" on a machine whose store was somewhere else.
Types ¶
type Caps ¶
type Caps struct {
// Atomic reports whether Put replaces the file atomically. S3 with
// If-Match is atomic; Drive and WebDAV are not.
Atomic bool
// WeakAtomic reports whether the transport provides a best-effort atomic
// Put that should be verified with a subsequent read (verify-after-write).
WeakAtomic bool
// History reports whether the transport keeps old revisions. Git and S3
// do; Drive does (through revision IDs).
History bool
// Locking reports whether the transport supports explicit advisory locks
// (e.g. a .binpass.lock file with TTL).
Locking bool
// Rename reports whether the transport can rename a file without
// re-uploading it. Git can; cloud storages generally cannot.
Rename bool
// Watch reports whether the transport can push change notifications
// instead of requiring polling.
Watch bool
}
Caps describes the capabilities of a Remote transport. The sync engine uses these to decide whether to add extra safety measures like verify-after-write or advisory locking.
type File ¶
type File struct {
// Path is the store-relative path, for example "github.com/alice.gpg".
Path string
// Size is the file size in bytes.
Size int64
// ModTime is the modification time, or the zero time if unavailable.
ModTime time.Time
// Rev is the transport-specific revision: a git SHA, an S3 ETag, a Drive
// revision, and so on. It is passed to Put/Delete to implement conditional
// writes.
Rev string
}
File is a file listed by a Remote transport.
type GitOptions ¶
type GitOptions struct {
// Name is the remote name, for example "origin".
Name string
// Dir is the password store directory (the git working tree).
Dir string
// URL is where that working tree pushes to. When set, the git remote is
// created or corrected to point at it, so that `binpass remote add`
// alone is enough to make sync work.
URL string
// GitPath overrides the git binary; empty means autodetect.
GitPath string
}
GitOptions configures a GitRemote.
type GitRemote ¶
type GitRemote struct {
// contains filtered or unexported fields
}
GitRemote is a Remote backed by a git repository. It uses the system git binary via exec so that credential helpers, ssh-agent, commit signing, and proxies all work automatically. The commit messages match pass exactly so that the history is indistinguishable from a native pass repository (§8.3).
For git, the store directory IS the git working tree. "Push" and "pull" are implicit: List/Get pull from the remote first, and Push flushes all local commits after applyActions.
func NewGitRemote ¶
func NewGitRemote(opts GitOptions) (*GitRemote, error)
NewGitRemote returns a GitRemote. It initialises the store as a git working tree if it is not one already, and points the named git remote at URL.
Both steps are done here because the alternative is asking the user to run `git init` and `git remote add` by hand after `binpass remote add` has apparently succeeded, and then watching sync silently do nothing.
func (*GitRemote) Caps ¶
Caps returns the git transport capabilities. Git provides atomic commits, full history, and rename support.
func (*GitRemote) Delete ¶
Delete removes the file and commits the deletion. The commit is local only; call Push after all operations to flush.
func (*GitRemote) Get ¶
Get returns the content of the file at path from the git working tree. It pulls from the remote first so the content is up to date.
func (*GitRemote) List ¶
List returns all tracked files in the working tree that match the store extensions. It pulls from the remote first so the working tree reflects the latest remote state.
func (*GitRemote) Pull ¶
Pull fetches and rebases from the configured remote. This is called automatically before List and Get so the working tree reflects the latest remote state.
func (*GitRemote) Push ¶
Push flushes all local commits to the configured remote. This should be called after all Put/Delete/Rename operations are complete.
func (*GitRemote) Put ¶
func (g *GitRemote) Put(ctx context.Context, path string, content io.Reader, expectRev string) (string, error)
Put stages the file and commits it. The commit message follows pass's exact format. The commit is local only; call Push after all operations to flush.
expectRev is checked against the last commit that modified this file (not HEAD) to allow concurrent modifications to different files.
type MemOptions ¶
MemOptions configures a MemRemote.
type MemRemote ¶
type MemRemote struct {
// contains filtered or unexported fields
}
MemRemote is an in-memory Remote implementation for testing. It stores files in a map and supports all Caps except Watch.
func NewMemRemote ¶
func NewMemRemote(opts MemOptions) *MemRemote
NewMemRemote returns a new in-memory remote for testing.
type NoopUnlock ¶
type NoopUnlock struct{}
NoopUnlock is a no-op lock release for transports without advisory locking.
type RcloneOptions ¶
type RcloneOptions struct {
// Name is the human-readable name for this remote (for display and logging).
Name string
// Remote is the rclone remote:path, e.g. "mybucket:password-store" or
// "gdrive:binpass". Must end with a colon if it refers to the root.
Remote string
}
RcloneOptions configures an RcloneRemote.
type RcloneRemote ¶
type RcloneRemote struct {
// contains filtered or unexported fields
}
RcloneRemote is a Remote backed by any storage that rclone supports. It delegates all I/O to the system rclone binary, so the user's rclone.conf (with OAuth tokens, encryption, etc.) is used automatically.
This single implementation serves S3, Google Drive, Yandex.Disk, and WebDAV by varying the remote type in the rclone config. The sync engine treats all rclone-backed remotes identically and adapts its behaviour using Caps.
func NewRcloneRemote ¶
func NewRcloneRemote(opts RcloneOptions) (*RcloneRemote, error)
NewRcloneRemote creates a new rclone-backed remote. It verifies that the rclone binary is available on PATH.
func (*RcloneRemote) Caps ¶
func (r *RcloneRemote) Caps() Caps
Caps returns the capabilities of rclone-backed storage. Rclone provides atomic writes through S3's If-Match when the backend supports it; for other backends we report weak atomicity and rely on verify-after-write.
func (*RcloneRemote) Get ¶
func (r *RcloneRemote) Get(ctx context.Context, path string) (io.ReadCloser, string, error)
Get downloads a file from the remote.
func (*RcloneRemote) List ¶
func (r *RcloneRemote) List(ctx context.Context) ([]File, error)
List returns all .gpg and .age files on the remote.
func (*RcloneRemote) Lock ¶
func (r *RcloneRemote) Lock(_ context.Context) (Unlock, error)
Lock returns a no-op unlock since rclone has no advisory locking.
func (*RcloneRemote) Name ¶
func (r *RcloneRemote) Name() string
Name returns the human-readable name.
func (*RcloneRemote) Put ¶
func (r *RcloneRemote) Put(ctx context.Context, path string, content io.Reader, expectRev string) (string, error)
Put uploads a file to the remote. If expectRev is non-empty, it first checks that the existing file matches (conditional write). For S3, rclone uses --s3-upload-cutoff and multipart uploads; conditional writes are not natively supported for all backends, so this is best-effort.
type Remote ¶
type Remote interface {
// Name returns a human-readable name for this remote, for example
// "origin" or "gdrive".
Name() string
// Caps returns the capabilities of this transport.
Caps() Caps
// List returns all files currently on the remote. The sync engine uses
// this to build a remote snapshot.
List(ctx context.Context) ([]File, error)
// Get downloads the file at path. It returns the content, the current
// revision, and any error.
Get(ctx context.Context, path string) (content io.ReadCloser, rev string, err error)
// Put uploads content to path. If expectRev is non-empty, the upload
// must fail if the current revision does not match (conditional write).
// It returns the new revision.
Put(ctx context.Context, path string, content io.Reader, expectRev string) (rev string, err error)
// Delete removes the file at path. If expectRev is non-empty, the delete
// must fail if the current revision does not match.
Delete(ctx context.Context, path string, expectRev string) error
// Rename moves a file from one path to another. Transports that do not
// support Rename should return an error; the sync engine will fall back
// to Get + Put + Delete.
Rename(ctx context.Context, from, to string) error
// Lock acquires an advisory lock on the remote store. If the transport
// does not support locking, it returns a no-op Unlock.
Lock(ctx context.Context) (Unlock, error)
// Close releases any resources held by the remote (open connections,
// temporary files, and so on).
Close() error
}
Remote is the transport interface for synchronisation (§8.2). One implementation serves git, Google Drive, Yandex.Disk, WebDAV, and S3; the sync engine treats them identically and adapts its behaviour using Caps.
type ResticOptions ¶
type ResticOptions struct {
// Name is the human-readable remote name, e.g. "backup-s3".
Name string
// Repo is the restic repository, e.g. "s3:s3.amazonaws.com/bucket/path"
// or "/mnt/backup" or "sftp:user@server:/backup".
Repo string
// Password is the repository password. Prefer PasswordCommand for
// production use; this field is for testing.
Password string
// PasswordCommand is a shell command that prints the password to stdout,
// passed as --password-command to restic.
PasswordCommand string
// StoreDir is the local password store directory (the backup source).
StoreDir string
// ResticPath overrides the restic binary; empty means autodetect.
ResticPath string
// ExtraArgs are additional flags passed to every restic invocation.
ExtraArgs []string
// ExtraEnv are extra environment variables for the restic process.
ExtraEnv []string
}
ResticOptions configures a ResticRemote.
type ResticRemote ¶
type ResticRemote struct {
// contains filtered or unexported fields
}
ResticRemote is a Remote backed by a restic repository. It delegates all I/O to the system restic binary so that the user's restic configuration (password command, S3 credentials, SFTP keys, etc.) is used automatically.
The model is similar to GitRemote: the password store directory IS the backup source. Mutations (Put, Delete, Rename) modify the local store; Push creates a new restic snapshot that captures the current state. List and Get read from the latest snapshot via "restic ls" and "restic dump".
Revision tracking uses the restic snapshot short ID (8 hex characters). Before Push, the engine checks that the latest snapshot ID matches the one observed during List; if it differs, another client has pushed and the operation is a conflict.
func NewResticRemote ¶
func NewResticRemote(opts ResticOptions) (*ResticRemote, error)
NewResticRemote creates a ResticRemote. It validates that the restic binary is available and that StoreDir exists.
func (*ResticRemote) Caps ¶
func (r *ResticRemote) Caps() Caps
Caps returns restic transport capabilities. Restic provides full history (snapshots), weak atomicity (append-only, verify via snapshot ID), and local rename support. It does not support atomic single-file Put or watch.
func (*ResticRemote) Close ¶
func (r *ResticRemote) Close() error
Close is a no-op for restic (no persistent connections).
func (*ResticRemote) Delete ¶
Delete removes the file from the local store directory. The deletion is captured by the next Push (restic backup).
func (*ResticRemote) Forget ¶
func (r *ResticRemote) Forget(ctx context.Context, policyArgs ...string) error
Forget removes old snapshots according to the given policy. At least one policy flag must be set (e.g. "--keep-last 10").
func (*ResticRemote) Get ¶
func (r *ResticRemote) Get(ctx context.Context, path string) (io.ReadCloser, string, error)
Get downloads a single file from the latest snapshot via "restic dump". It returns the file content, the snapshot ID as the revision, and any error.
func (*ResticRemote) List ¶
func (r *ResticRemote) List(ctx context.Context) ([]File, error)
List returns all .gpg and .age files in the latest snapshot. It queries the restic repository via "restic ls latest --json" and records the snapshot ID as the revision for conditional writes.
func (*ResticRemote) Lock ¶
func (r *ResticRemote) Lock(_ context.Context) (Unlock, error)
Lock returns a no-op unlock. Restic does not support advisory locking; the snapshot-based conditional write serves a similar purpose.
func (*ResticRemote) Pull ¶
func (r *ResticRemote) Pull(_ context.Context) error
Pull is a no-op for restic. The latest snapshot is read on demand by List and Get, so there is no separate fetch step.
func (*ResticRemote) Push ¶
func (r *ResticRemote) Push(ctx context.Context) error
Push creates a new restic snapshot if the local store has been modified. It checks that the latest snapshot ID matches the one observed during List; if another client has pushed in the meantime, it returns an error.
func (*ResticRemote) Put ¶
func (r *ResticRemote) Put(ctx context.Context, path string, content io.Reader, expectRev string) (string, error)
Put writes the content to the local store directory. The change is captured by the next Push (restic backup). The file is written to the working tree immediately so that it is visible to the store.
expectRev is checked against the latest snapshot ID for conditional writes.
func (*ResticRemote) Rename ¶
func (r *ResticRemote) Rename(_ context.Context, from, to string) error
Rename moves a file in the local store directory. The rename is captured by the next Push (restic backup).
func (*ResticRemote) Restore ¶
func (r *ResticRemote) Restore(ctx context.Context, snapshotID, targetDir string) error
Restore downloads an entire snapshot to the given target directory. Restic restores the full directory structure including the absolute path of the backup source, so the actual files end up under targetDir/storeDir/... This method handles the path translation and places files directly under targetDir in store-relative layout.
func (*ResticRemote) Snapshots ¶
func (r *ResticRemote) Snapshots(ctx context.Context) ([]ResticSnapshot, error)
Snapshots returns a list of snapshot IDs, newest first. This is exposed for point-in-time recovery and conflict inspection.
type ResticSnapshot ¶
type ResticSnapshot struct {
ID string `json:"id"`
ShortID string `json:"short_id"`
Time string `json:"time"`
Paths []string `json:"paths"`
Hostname string `json:"hostname"`
Username string `json:"username"`
Tags []string `json:"tags"`
}
ResticSnapshot represents a restic snapshot, returned by the Snapshots method and by "restic snapshots --json".