cafs

package module
v0.0.0-...-92d5c11 Latest Latest
Warning

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

Go to latest
Published: Nov 30, 2025 License: Apache-2.0 Imports: 16 Imported by: 0

README

cafs

Content-Addressable File System with OCI registry sync.

Store files by content hash. Index them by any key. Compare directories instantly. Sync anywhere.

Note: This project is in early MVP stage. APIs may change. Use at your own risk.

Install

Library:

go get github.com/aweris/cafs

CLI:

go install github.com/aweris/cafs/cmd/cafs@latest

Docker:

docker pull ghcr.io/aweris/cafs:latest

Quick Start

fs, _ := cafs.Open("ttl.sh/myorg/cache:main")

// Store content
digest, _ := fs.Blobs().Put([]byte("hello world"))

// Index by key
fs.Index().Set("greeting.txt", digest)

// Retrieve
d, _ := fs.Index().Get("greeting.txt")
data, _ := fs.Blobs().Get(d)

// Compare directories instantly
if fs.Index().Hash("src/") != lastKnownHash {
    // something changed
}

// Sync to registry
fs.Push(ctx)
fs.Close()

CLI Usage

# Pull from registry
cafs pull ttl.sh/myorg/cache:main

# Push to registry
cafs push ttl.sh/myorg/cache:main

# Push to multiple tags
cafs push ttl.sh/myorg/cache:main latest v1.0

# List entries
cafs list ttl.sh/myorg/cache:main
cafs list ttl.sh/myorg/cache:main src/  # with prefix filter

Core Concepts

Blobs — Content-addressed storage. Same content = same digest = stored once.

Index — Maps keys to digests. Like git's index, but flat.

Merkle — Directory hashes computed on-demand from flat index:

Index (flat):                    Computed:
┌─────────────────────────┐     ┌─────────────────────────┐
│ foo/bar/a.go → abc123   │     │ foo/bar/   → hash(...)  │
│ foo/bar/b.go → def456   │ ──▶ │ foo/       → hash(...)  │
│ foo/x.go     → ghi789   │     │ (root)     → hash(...)  │
└─────────────────────────┘     └─────────────────────────┘

Sync — Push/pull to OCI registries with zstd compression and incremental updates.

Configuration

Options
fs, _ := cafs.Open("ttl.sh/myorg/cache:main",
    cafs.WithCacheDir("/tmp/my-cache"),     // custom cache location
    cafs.WithAutoPull(cafs.AutoPullAlways), // auto-pull on open
    cafs.WithConcurrency(8),                // parallel operations (default: 4)
)
Environment Variables
Variable Description
XDG_DATA_HOME Base for cache dir (default: ~/.local/share)
XDG_CONFIG_HOME Base for config dir (default: ~/.config)
CAFS_CACHE_DIR Override cache directory
CLI Config

Config file: ~/.config/cafs/config.yaml

cache_dir: /custom/cache/path

API Reference

FS Interface
type FS interface {
    Blobs() BlobStore
    Index() Index

    Sync() error                                    // persist locally
    Push(ctx context.Context, tags ...string) error // push to registry
    Pull(ctx context.Context) error                 // pull from registry
    Close() error                                   // calls Sync()

    Root() Digest  // root hash of entire index
    Dirty() bool   // true if unsaved changes
}
BlobStore Interface
type BlobStore interface {
    Put(data []byte) (Digest, error)
    Get(digest Digest) ([]byte, error)
    Stat(digest Digest) (size int64, exists bool)
    Path(digest Digest) string
}
Index Interface
type Index interface {
    Set(key string, digest Digest)
    Get(key string) (Digest, bool)
    Delete(key string)
    Entries() iter.Seq2[string, Digest]

    Hash(prefix string) Digest              // merkle hash of subtree
    List(prefix string) iter.Seq2[string, Digest]
}

Examples

See examples/ for complete working examples:

  • 01-quickstart — Basic blob storage and indexing
  • 02-directories — Merkle tree directory hashing
  • 03-remote — Push/pull to OCI registries
  • 04-gocacheprog — GOCACHEPROG implementation for Go build cache

Why CAFS?

  • Deduplication — Same content stored once, referenced many times
  • Instant diff — Compare directories by hash, not file-by-file
  • Portable — Push/pull snapshots to any OCI registry
  • Concurrent — All operations are lockless and thread-safe
  • Incremental — Only changed prefixes are uploaded

License

Apache 2.0

Documentation

Overview

Package cafs provides a content-addressable store with OCI registry sync and merkle tree semantics.

CAFS stores blobs by content digest and indexes them by string keys. Keys cannot be empty, exceed 1024 bytes, start with "_", or contain ".." or null bytes. Directory hashes are computed on-demand from the flat index, enabling instant comparison of subtrees without storing tree objects.

Basic usage (local only):

fs, _ := cafs.Open("myproject:main")

// Store content by key
fs.Put("src/main.go", data)

// Store with metadata
fs.Put("src/util.go", data, cafs.WithMeta(cafs.FileMeta{Mode: 0644}))

// Retrieve content
data, _ := fs.Get("src/main.go")

// Get entry info
info, ok := fs.Stat("src/main.go")
fmt.Println(info.Digest, info.Size)

// Check existence and count
if fs.Exists("src/main.go") { ... }
fmt.Println(fs.Len(), "entries")

// Compare directories
if fs.Hash("src/") != previousHash {
    // src/ changed
}

// Maintenance
stats := fs.Stats()           // entry count, blob count, total size
removed, _ := fs.GC()         // remove unreferenced blobs
fs.Clear()                    // remove all entries

With remote sync:

fs, _ := cafs.Open("myproject:main", cafs.WithRemote("ttl.sh/myorg/cache"))
fs.Push(ctx)
fs.Pull(ctx)
fmt.Println("remote:", fs.Ref())

Index

Constants

View Source
const (
	AutoPullNever   = "never"
	AutoPullAlways  = "always"
	AutoPullMissing = "missing"
)

AutoPull modes

Variables

View Source
var (
	ErrNotFound    = errors.New("cafs: not found")
	ErrNoRemote    = errors.New("cafs: no remote configured")
	ErrReservedKey = errors.New("cafs: key prefix '_' is reserved")
	ErrInvalidKey  = errors.New("cafs: invalid key")
)

Functions

This section is empty.

Types

type Authenticator

type Authenticator = remote.Authenticator

Authenticator provides credentials for remote registries.

type CAS

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

CAS is the main content-addressable storage implementation.

func (*CAS) Clear

func (s *CAS) Clear()

func (*CAS) Close

func (s *CAS) Close() error

func (*CAS) Delete

func (s *CAS) Delete(key string)

Delete removes an entry by key.

func (*CAS) Dirty

func (s *CAS) Dirty() bool

func (*CAS) Exists

func (s *CAS) Exists(key string) bool

func (*CAS) GC

func (s *CAS) GC() (int, error)

func (*CAS) Get

func (s *CAS) Get(key string) ([]byte, error)

Get retrieves data by key.

func (*CAS) Hash

func (s *CAS) Hash(prefix string) Digest

Hash computes merkle hash for prefix.

func (*CAS) Len

func (s *CAS) Len() int

func (*CAS) List

func (s *CAS) List(prefix string) iter.Seq2[string, Info]

List iterates entries matching prefix.

func (*CAS) Path

func (s *CAS) Path(digest Digest) string

Path returns the filesystem path for a digest (for advanced use cases).

func (*CAS) Pull

func (s *CAS) Pull(ctx context.Context) error

Pull downloads from remote.

func (*CAS) Push

func (s *CAS) Push(ctx context.Context, tags ...string) error

Push uploads to the specified tags.

func (*CAS) Put

func (s *CAS) Put(key string, data []byte, opts ...Option) error

Put stores data at key with optional metadata.

func (*CAS) Ref

func (s *CAS) Ref() string

func (*CAS) Root

func (s *CAS) Root() Digest

func (*CAS) Stat

func (s *CAS) Stat(key string) (Info, bool)

Stat returns metadata for key.

func (*CAS) Stats

func (s *CAS) Stats() Stats

func (*CAS) Sync

func (s *CAS) Sync() error

type Digest

type Digest string

Digest is an OCI content identifier (e.g., "sha256:abc123...").

type FileMeta

type FileMeta struct {
	Mode    os.FileMode `json:"mode,omitempty" mapstructure:"mode"`
	ModTime time.Time   `json:"mtime,omitempty" mapstructure:"mtime"`
}

FileMeta provides common file system metadata.

func FileMetaFrom

func FileMetaFrom(info os.FileInfo) FileMeta

FileMetaFrom creates FileMeta from os.FileInfo.

type Info

type Info struct {
	Digest Digest // content hash
	Size   int64  // content size
	Meta   any    // optional user-defined metadata
}

Info represents metadata about a stored entry.

func (Info) DecodeMeta

func (i Info) DecodeMeta(out any) error

DecodeMeta decodes the metadata into a typed struct using mapstructure.

type OpenOption

type OpenOption func(*OpenOptions)

OpenOption is a functional option for configuring Open.

func WithAuth

func WithAuth(auth Authenticator) OpenOption

WithAuth sets custom authentication for remote operations.

func WithAutoPull

func WithAutoPull(mode string) OpenOption

WithAutoPull enables automatic pulling from remote on Open.

func WithCacheDir

func WithCacheDir(dir string) OpenOption

WithCacheDir sets the local cache directory.

func WithConcurrency

func WithConcurrency(n int) OpenOption

WithConcurrency sets the number of parallel operations for push/pull.

func WithRemote

func WithRemote(imageRef string) OpenOption

WithRemote sets the OCI registry image ref for push/pull operations.

type OpenOptions

type OpenOptions struct {
	CacheDir    string
	Remote      string // OCI image ref for push/pull (optional)
	Auth        Authenticator
	AutoPull    string
	Concurrency int
}

OpenOptions configures a CAS store.

type Option

type Option func(*Info)

Option configures a Put operation.

func WithMeta

func WithMeta(v any) Option

WithMeta sets custom metadata on the entry.

type Stats

type Stats struct {
	Entries   int   // number of user entries
	Blobs     int   // number of unique blobs on disk
	TotalSize int64 // total size of all blobs
}

Stats contains storage statistics.

type Store

type Store interface {
	// Core operations
	Put(key string, data []byte, opts ...Option) error
	Get(key string) ([]byte, error)
	Stat(key string) (Info, bool)
	Delete(key string)
	Clear()

	// Iteration
	List(prefix string) iter.Seq2[string, Info]

	// Tree hash
	Hash(prefix string) Digest

	// Sync
	Sync() error
	Push(ctx context.Context, tags ...string) error
	Pull(ctx context.Context) error
	Close() error

	// Status
	Root() Digest
	Dirty() bool
	Len() int
	Ref() string
	Exists(key string) bool
	Stats() Stats

	// Maintenance
	GC() (removed int, err error)

	// Advanced
	Path(digest Digest) string
}

Store provides content-addressed storage with OCI sync.

func Open

func Open(namespace string, opts ...OpenOption) (Store, error)

Open creates or opens a store for the given namespace. Format: "namespace" or "namespace:tag" (default tag is "latest").

Directories

Path Synopsis
cmd
cafs command
internal

Jump to

Keyboard shortcuts

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