sisyphus

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 6 Imported by: 0

README

sisyphus

Sisyphus was condemned to roll a boulder uphill forever. This package bundles the boulder-chores an application repeats forever: loading configuration, persisting state, and making encrypted, key-escrowed backups.

sisyphus is a small, app-agnostic Go toolkit for the plumbing every CLI or service ends up rewriting. It carries no application-specific types or names — the caller owns its config struct, namespaces, filenames, and identifiers and passes them in. It was extracted from munin but depends on nothing munin-specific.

# private module: configure Go to fetch it directly (not via the public proxy)
go env -w 'GOPRIVATE=github.com/codyconfer/*'
go get github.com/codyconfer/sisyphus

Building also needs git credentials with read access to the repo. DuckDB-backed packages require CGO (via github.com/marcboeker/go-duckdb/v2).

Packages

Package Responsibility
sisyphus (root) Manager facade for config reconciliation + package-level Backup/Restore.
sisyphus/config Home-dir resolution and parsing a YAML/JSON file into your struct (with env overrides).
sisyphus/configdb Versioned, name-keyed blob store in DuckDB (store_current + store_history) — the source of truth for file-backed state.
sisyphus/kv Generic namespaced key/value store in DuckDB, with an optional TTL column.
sisyphus/journal Generic activity log in DuckDB: nested parent/child runs + records, each with a free-form string attribute map.
sisyphus/secret Key escrow via the Bitwarden (bw) or 1Password (op) CLI, or the OS keyring.
sisyphus/backup tar archive + AES-256-GCM encrypt/decrypt/restore.

Each sub-package is usable on its own. A nil *Store is a valid no-op across the DuckDB packages, so "disabled" and "open failed" behave uniformly.

App-agnostic by design

Everything application-specific is injected, never baked in:

  • Home dirconfig.Home(override, envVar, dirName) takes the env var and directory name.
  • Config file namesconfig.ReadFile(home, basenames...) (defaults to config.{yaml,yml,json} when none given).
  • Env prefixconfig.ParseInto(target, raw, format, envPrefix).
  • KV namespace — a parameter on every kv call.
  • Config DB filenameOptions.ConfigDBName (defaults to config.duckdb).
  • Keyring service namesecret.Resolve(backend, service) (defaults to "sisyphus"); backup threads it via BackupSpec.SecretService.
  • Backup file list + secret name — supplied on BackupSpec / RestoreSpec.

Usage

Config reconciliation

Manager makes DuckDB the source of truth for file-backed config, and never auto-imports — you decide via a Resolver when file and DB disagree.

m, err := sisyphus.Open(home, sisyphus.Options{}) // ModeBoth; ConfigDBName defaults to config.duckdb
if err != nil { /* ... */ }
defer m.Close()

raw, format, _ := config.ReadFile(home)
content, format, err := m.Reconcile("config", raw, format, len(raw) > 0, myResolver)
// then: config.ParseInto(&myCfg, content, format, "MYAPP_")

Reconcile returns the DB content when file and DB match, and otherwise calls Resolver.Resolve with an Action (ActionImport / ActionUseFile / ActionUseDB). Manager.Current/Import/History cover the common config-DB operations; DB() exposes the underlying *configdb.Store for anything more.

Encrypted, key-escrowed backups
sealed, store, err := sisyphus.Backup(sisyphus.BackupSpec{
    Files:         []string{cfgDB, dataDB},
    SecretBackend: "auto",       // bw → op → OS keyring
    SecretService: "myapp",      // keyring service name
    SecretName:    "backup-key", // key entry name
})
// ... write `sealed` somewhere ...

names, _, err := sisyphus.Restore(sisyphus.RestoreSpec{
    Sealed: sealed, SecretBackend: "auto", SecretService: "myapp",
    SecretName: "backup-key", DestDir: home,
})

The AES key is generated on first backup and escrowed in the secret manager; it never travels with the archive, and Backup/Restore are package functions independent of Manager so restore works even when the config DB is corrupt.

KV and journal
store, _ := kv.Open(filepath.Join(home, "tokens.duckdb"))
_ = store.Put("tokens", "github", jsonBlob, time.Time{}) // zero time = no expiry
entry, ok, _ := store.Get("tokens", "github")

log, _ := journal.Open(filepath.Join(home, "audit.duckdb"))
parent, _ := log.Begin("job", "nightly", map[string]string{"env": "prod"})
_, _ = log.Add(journal.Run{ParentID: parent, Kind: "step", Name: "sync", Count: 3}, records)
_ = log.RollUp(parent) // roll child counts up into the parent

Development

go build ./...
go vet ./...
go test ./...

Tests run offline. The secret backends shell out to bw/op only when present; their availability probes are stubbable, and the keyring path is tested with go-keyring's mock.

License

Released under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Backup

func Backup(spec BackupSpec) (sealed []byte, storeName string, err error)

func Restore

func Restore(spec RestoreSpec) (names []string, storeName string, err error)

Types

type Action

type Action int
const (
	ActionImport Action = iota
	ActionUseFile
	ActionUseDB
)

type BackupSpec

type BackupSpec struct {
	Files         []string
	SecretBackend string
	SecretName    string
	SecretService string
}

type Manager

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

func Open

func Open(home string, opts Options) (*Manager, error)

func (*Manager) Close

func (m *Manager) Close() error

func (*Manager) Current

func (m *Manager) Current(name string) (Version, bool, error)

func (*Manager) DB

func (m *Manager) DB() *configdb.Store

func (*Manager) History

func (m *Manager) History(name string, limit int) ([]Version, error)

func (*Manager) Home

func (m *Manager) Home() string

func (*Manager) Import

func (m *Manager) Import(name string, content []byte, format string) error

func (*Manager) Mode

func (m *Manager) Mode() Mode

func (*Manager) Reconcile

func (m *Manager) Reconcile(name string, fileContent []byte, format string, hasFile bool, r Resolver) (content []byte, effFormat string, err error)

type Mode

type Mode int
const (
	ModeBoth Mode = iota
	ModeFileStore
	ModeDuckDB
)

type Options

type Options struct {
	Mode         Mode
	ConfigDBName string
}

type Reconciliation

type Reconciliation struct {
	Name        string
	FileContent []byte
	FileFormat  string
	DB          Version
	HasDB       bool
}

type Resolver

type Resolver interface {
	Resolve(Reconciliation) (Action, error)
}

type RestoreSpec

type RestoreSpec struct {
	Sealed        []byte
	SecretBackend string
	SecretName    string
	SecretService string
	DestDir       string
}

type Version

type Version = configdb.Version

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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