sisyphus

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 7 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.
sisyphus/store Ad-hoc DuckDB file queries (read-only at the app layer).
sisyphus/sealed Encrypted credential store (AES-GCM over kv; key in OS keyring).
sisyphus/auth OAuth loopback · device flow · RunTool CLI helper.
sisyphus/mode Operating modes + injectable auth gate hooks.
sisyphus/lifecycle Home-dir install / clean / nuke primitives.
sisyphus/daemon Streaming core: poll/fan-in/dedupe, sockets (pipe-prefix param on Windows), cursors.
sisyphus/daemon/service OS service install/start/stop wrapper.
sisyphus/daemon/ui System tray + desktop notifications.

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.

ctx := context.Background()
m, err := sisyphus.Open(ctx, 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(ctx, "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
ctx := context.Background()
sealed, store, err := sisyphus.Backup(ctx, 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(ctx, 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
ctx := context.Background()
store, _ := kv.Open(ctx, filepath.Join(home, "tokens.duckdb"))
_ = store.Put(ctx, "tokens", "github", jsonBlob, time.Time{}) // zero time = no expiry
entry, ok, _ := store.Get(ctx, "tokens", "github")

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

Development

make build          # go build ./...
make check          # build + fmt-check + lint + govulncheck + test (CI gate is `make ci`)
make test           # go test ./...

Linters live in the nested tools/ module (go tool -modfile=tools/go.mod) so they stay out of the consumer dependency graph.

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.

Local multi-repo development (go.work)

When editing sisyphus alongside munin/viewkit, use an uncommitted go.work in the consumer (typically munin) that uses the sibling checkouts. Do not commit go.work / go.work.sum (gitignored here) and do not add committed replace directives — CI and published consumers build against tagged pins.

License

Released under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Backup

func Backup(ctx context.Context, spec BackupSpec) (sealed []byte, storeName string, err error)

func Restore

func Restore(ctx context.Context, 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(ctx context.Context, home string, opts Options) (*Manager, error)

func (*Manager) Close

func (m *Manager) Close() error

func (*Manager) Current

func (m *Manager) Current(ctx context.Context, name string) (Version, bool, error)

func (*Manager) DB

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

func (*Manager) History

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

func (*Manager) Home

func (m *Manager) Home() string

func (*Manager) Import

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

func (*Manager) Mode

func (m *Manager) Mode() Mode

func (*Manager) Reconcile

func (m *Manager) Reconcile(ctx context.Context, 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
ui
internal

Jump to

Keyboard shortcuts

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