The Cloudstic Go Client API provides a high-level interface for embedding backup functionality directly into your applications. All CLI operations are exposed as methods on the Client struct.
Installation
go get github.com/cloudstic/cli
Quick Start
package main
import (
"context"
"fmt"
"log"
cloudstic "github.com/cloudstic/cli"
"github.com/cloudstic/cli/pkg/store"
)
func main() {
ctx := context.Background()
// Create a storage backend
baseStore, err := store.NewLocalStore("/path/to/repository")
if err != nil {
log.Fatal(err)
}
// Create a client with encryption
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithKeyProvider(cloudstic.Credentials{
Password: "my-secure-password",
}),
)
if err != nil {
log.Fatal(err)
}
// Create a backup source
source, err := store.NewLocalSource("/path/to/data")
if err != nil {
log.Fatal(err)
}
// Run backup
result, err := client.Backup(ctx, source)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Backup complete: %s\n", result.SnapshotID)
fmt.Printf("Files: %d total, %d added\n", result.TotalFiles, result.FilesAdded)
fmt.Printf("Size: %d bytes added\n", result.BytesAddedStored)
}
Client Creation
NewClient
func NewClient(base store.ObjectStore, opts ...ClientOption) (*Client, error)
Creates a new Cloudstic client with the specified storage backend and options.
Parameters:
base - The underlying ObjectStore implementation (S3, B2, local, etc.)
opts - Optional configuration via ClientOption functions
Client Options
WithKeyProvider
func WithKeyProvider(kp KeyProvider) ClientOption
Sets a KeyProvider for automatic key resolution. During NewClient, the repository config is read from the store. If the repository is encrypted, ResolveKey is called to obtain the encryption key. If the repository is not encrypted, the provider is silently ignored.
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithKeyProvider(cloudstic.Credentials{
Password: "my-password",
}),
)
WithEncryptionKey
func WithEncryptionKey(key []byte) ClientOption
Directly sets the AES-256-GCM encryption key (32 bytes). This bypasses repository config detection and unconditionally applies encryption. The HMAC deduplication key is automatically derived from this key.
Use this for scenarios where the key is already resolved externally (e.g., SaaS products).
key := []byte("...32-byte-encryption-key...") // Must be exactly 32 bytes
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithEncryptionKey(key),
)
WithReporter
func WithReporter(r Reporter) ClientOption
Sets the progress reporter for the client. By default, a no-op reporter is used.
reporter := &MyCustomReporter{} // Implements ui.Reporter interface
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithReporter(reporter),
)
WithPackfile
func WithPackfile(enable bool) ClientOption
Enables or disables bundling small objects into 8MB packs to save API calls. Enabled by default.
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithPackfile(false), // Disable packfiles
)
Key Providers
Key providers resolve the encryption key for a repository. They implement the KeyProvider interface:
type KeyProvider interface {
ResolveKey(ctx context.Context, rawStore store.ObjectStore) ([]byte, error)
}
StaticKey
A KeyProvider that returns a pre-resolved encryption key. Use this when the key has already been unwrapped externally.
key := []byte("...32-byte-encryption-key...")
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithKeyProvider(cloudstic.StaticKey(key)),
)
Credentials
type Credentials struct {
PlatformKey []byte // Raw 32-byte platform key
Password string // Password for password-based slots
RecoveryMnemonic string // BIP39 24-word recovery phrase
KMSDecrypter crypto.KMSDecrypter // For kms-platform slots (optional)
PasswordPrompt func() (string, error) // Interactive password fallback (optional)
}
Resolves the encryption key by trying credentials against the repository’s stored key slots. The resolution order is:
- KMS (if
KMSDecrypter provided)
- Platform key (if
PlatformKey provided)
- Password (if
Password provided)
- Recovery mnemonic (if
RecoveryMnemonic provided)
- Password prompt (if
PasswordPrompt provided and password slot exists)
client, err := cloudstic.NewClient(baseStore,
cloudstic.WithKeyProvider(cloudstic.Credentials{
Password: "my-password",
RecoveryMnemonic: "word1 word2 ... word24",
PasswordPrompt: func() (string, error) {
fmt.Print("Enter password: ")
var pw string
fmt.Scanln(&pw)
return pw, nil
},
}),
)
Backup
Client.Backup
func (c *Client) Backup(ctx context.Context, src store.Source, opts ...BackupOption) (*BackupResult, error)
Creates a new backup snapshot from the specified source.
Parameters:
ctx - Context for cancellation
src - The backup source (implements store.Source interface)
opts - Optional backup configuration
Returns:
BackupResult containing snapshot ID, file counts, and byte statistics
source, _ := store.NewLocalSource("/path/to/data")
result, err := client.Backup(ctx, source,
cloudstic.WithTags(map[string]string{
"environment": "production",
"host": "web-server-1",
}),
cloudstic.WithVerbose(true),
)
Backup Options
WithVerbose(bool) - Enable verbose output
WithBackupDryRun(bool) - Simulate backup without writing data
WithTags(map[string]string) - Add custom tags to the snapshot
WithGenerator(string) - Set the generator field (e.g., “myapp/1.0”)
WithMeta(map[string]interface{}) - Add custom metadata
WithExcludeHash(bool) - Skip content-addressing (for testing)
BackupResult
type BackupResult struct {
SnapshotID string
TotalFiles int64
TotalBytes int64
FilesAdded int64
FilesModified int64
FilesUnchanged int64
FilesDeleted int64
BytesAddedRaw int64 // Before compression/encryption
BytesAddedStored int64 // After compression/encryption
Duration time.Duration
}
Restore
Client.Restore
func (c *Client) Restore(ctx context.Context, w io.Writer, snapshotRef string, opts ...RestoreOption) (*RestoreResult, error)
Writes the snapshot’s file tree as a ZIP archive to the provided writer.
Parameters:
ctx - Context for cancellation
w - Writer to receive the ZIP archive
snapshotRef - Snapshot reference: "", "latest", bare hash, or "snapshot/<hash>"
opts - Optional restore configuration
file, _ := os.Create("backup.zip")
defer file.Close()
result, err := client.Restore(ctx, file, "latest",
cloudstic.WithRestorePath("documents/"), // Only restore this path
cloudstic.WithRestoreVerbose(true),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Restored %d files, %d bytes\n", result.FilesRestored, result.BytesRestored)
Restore Options
WithRestoreDryRun(bool) - Simulate restore without writing data
WithRestoreVerbose(bool) - Enable verbose output
WithRestorePath(string) - Only restore files under this path
List Snapshots
Client.List
func (c *Client) List(ctx context.Context, opts ...ListOption) (*ListResult, error)
Lists all snapshots in the repository.
result, err := client.List(ctx, cloudstic.WithListVerbose(true))
if err != nil {
log.Fatal(err)
}
for _, snap := range result.Snapshots {
fmt.Printf("%s: %s (%d files, %d bytes)\n",
snap.ID[:8], snap.Time, snap.FileCount, snap.TotalBytes)
}
Client.LsSnapshot
func (c *Client) LsSnapshot(ctx context.Context, snapshotID string, opts ...LsSnapshotOption) (*LsSnapshotResult, error)
Lists files in a specific snapshot.
result, err := client.LsSnapshot(ctx, "snapshot-id", cloudstic.WithLsVerbose(true))
if err != nil {
log.Fatal(err)
}
for _, file := range result.Files {
fmt.Printf("%s\t%d\t%s\n", file.Type, file.Size, file.Path)
}
Maintenance
Client.Prune
func (c *Client) Prune(ctx context.Context, opts ...PruneOption) (*PruneResult, error)
Removes unreferenced objects from the repository.
result, err := client.Prune(ctx,
cloudstic.WithPruneDryRun(false),
cloudstic.WithPruneVerbose(true),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Pruned %d objects, freed %d bytes\n",
result.ObjectsDeleted, result.BytesFreed)
Client.Forget
func (c *Client) Forget(ctx context.Context, snapshotID string, opts ...ForgetOption) (*ForgetResult, error)
Removes a specific snapshot.
result, err := client.Forget(ctx, "snapshot-id",
cloudstic.WithPrune(true), // Also prune unreferenced objects
cloudstic.WithDryRun(false),
)
Client.ForgetPolicy
func (c *Client) ForgetPolicy(ctx context.Context, opts ...ForgetOption) (*PolicyResult, error)
Applies retention policies to remove old snapshots.
result, err := client.ForgetPolicy(ctx,
cloudstic.WithKeepLast(7),
cloudstic.WithKeepDaily(30),
cloudstic.WithKeepWeekly(12),
cloudstic.WithKeepMonthly(12),
cloudstic.WithKeepYearly(5),
cloudstic.WithPrune(true),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Removed %d snapshots\n", len(result.RemovedSnapshots))
Forget Options
WithKeepLast(int) - Keep the last N snapshots
WithKeepHourly(int) - Keep one snapshot per hour for N hours
WithKeepDaily(int) - Keep one snapshot per day for N days
WithKeepWeekly(int) - Keep one snapshot per week for N weeks
WithKeepMonthly(int) - Keep one snapshot per month for N months
WithKeepYearly(int) - Keep one snapshot per year for N years
WithGroupBy(string) - Group snapshots by field (comma-separated: “source,account,path”)
WithFilterTag(key, value) - Only consider snapshots with this tag
WithFilterSource(string) - Only consider snapshots from this source type
WithFilterAccount(string) - Only consider snapshots from this account
WithFilterPath(string) - Only consider snapshots from this path
WithPrune(bool) - Also run prune after forgetting
WithDryRun(bool) - Simulate the operation
Verification
Client.Check
func (c *Client) Check(ctx context.Context, opts ...CheckOption) (*CheckResult, error)
Verifies the integrity of the repository by walking the full reference chain (snapshots → HAMT nodes → filemeta → content → chunks) and checking that every referenced object can be read.
result, err := client.Check(ctx,
cloudstic.WithReadData(true), // Re-hash chunk data for byte-level verification
cloudstic.WithCheckVerbose(true),
)
if err != nil {
log.Fatal(err)
}
if len(result.Errors) > 0 {
fmt.Printf("Found %d integrity errors\n", len(result.Errors))
for _, e := range result.Errors {
fmt.Printf("- %s: %s\n", e.ObjectKey, e.Message)
}
}
Check Options
WithReadData(bool) - Re-hash chunk data for byte-level verification
WithCheckVerbose(bool) - Enable verbose output
WithSnapshotRef(string) - Only check a specific snapshot
Utilities
Client.Diff
func (c *Client) Diff(ctx context.Context, snap1, snap2 string, opts ...DiffOption) (*DiffResult, error)
Compares two snapshots and returns the differences.
result, err := client.Diff(ctx, "snapshot1-id", "snapshot2-id")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Added: %d files\n", len(result.Added))
fmt.Printf("Modified: %d files\n", len(result.Modified))
fmt.Printf("Deleted: %d files\n", len(result.Deleted))
Client.Cat
func (c *Client) Cat(ctx context.Context, keys ...string) ([]*CatResult, error)
Fetches the raw data for one or more object keys from the repository. Object keys can be snapshot/<hash>, filemeta/<hash>, content/<hash>, node/<hash>, chunk/<hash>, config, index/latest, keys/<slot>, etc.
This is useful for debugging, inspection, and understanding the internal structure of the repository.
results, err := client.Cat(ctx, "config", "index/latest")
if err != nil {
log.Fatal(err)
}
for _, result := range results {
fmt.Printf("%s: %s\n", result.Key, string(result.Data))
}
Client.BreakLock
func (c *Client) BreakLock(ctx context.Context) ([]*RepoLock, error)
Removes stale repository locks.
locks, err := client.BreakLock(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Removed %d stale locks\n", len(locks))
Client.Store
func (c *Client) Store() store.ObjectStore
Returns the underlying ObjectStore for advanced use cases.
store := client.Store()
data, err := store.Get(ctx, "snapshot/abc123...")
Error Handling
All client methods return errors that can be inspected for specific failure conditions:
result, err := client.Backup(ctx, source)
if err != nil {
if strings.Contains(err.Error(), "repository not initialized") {
fmt.Println("Run 'cloudstic init' first")
} else if strings.Contains(err.Error(), "no provided credential matches") {
fmt.Println("Invalid password or encryption key")
} else {
log.Fatal(err)
}
}
Thread Safety
The Client struct is safe for concurrent use. Multiple goroutines can call client methods simultaneously.
var wg sync.WaitGroup
sources := []store.Source{source1, source2, source3}
for _, src := range sources {
wg.Add(1)
go func(s store.Source) {
defer wg.Done()
result, err := client.Backup(ctx, s)
if err != nil {
log.Printf("Backup failed: %v", err)
return
}
log.Printf("Backup complete: %s", result.SnapshotID)
}(src)
}
wg.Wait()