SHC is a Go CLI + daemon that manages Docker Compose deployments
across single or multiple nodes. The CLI (shc) and the daemon (shcd)
are two binaries built from cmd/shc/ and cmd/shcd/; the rest of the
code lives under internal/. This document defines the technical
choices for all aspects of the codebase.
Component Choice Notes Language Go 1.25+ Compiled binaries, pure Go (CGO_ENABLED=0) Build toolchain asdf + go make build produces bin/shc + bin/shcdBundled binaries rqlited Fetched to vendor/rqlite/ by make vendor/rqlite
Component Choice Notes CLI Framework spf13/cobra Standard Go command tree Terminal Output charmbracelet/lipgloss + bubbletea Styled text + Bubbletea TUI for shc hud Shell Completion cobra-generated Bash/Zsh/Fish completion support
Component Choice Notes HTTP Router go-chi Lightweight chi router + middleware HTTP Server net/http with chi UDS + TCP listeners served from one daemon Validation go-playground/validator/v10validate: struct tags + custom validators
Component Choice Notes Lifecycle Manager subsystems/lifecycleCentral orchestrator for all server subsystems Subsystem Protocol lifecycle.SubsystemUniform interface: Name(), Start(), Stop(), Health() Fail-Fast systemd Service crashes bring down server; systemd restarts Leader Election raft (leader_hub) The raft leader id (rqlited) drives the leader signal
See daemon-manager for details.
Component Choice Notes Database SQLite / rqlite Plain file + WAL at {state}/shc.db single-node; raft-replicated rqlite in clusters Driver modernc.org/sqlite (pure Go)Registered as the sqlite sql driver; the pool swaps to shc-rqlite at boot when {state}/engine.json exists Distributed rqlite (external rqlited process) Supervised by the daemon (core/rqlited); SQLite with Raft consensus Query layer ent ORM + per-module reposent-generated client (ent/) over the shared db pool; one *StackRepo / *NodeRepo per module wraps it Migrations Atlas (ariga/atlas) Versioned SQL under migrations/, integrity hash in atlas.sum
Database modes:
Single-node : plain SQLite (no raft, zero coordination)
Cluster : external rqlited raft engine (automatic promotion when the first shc node join enrolls)
Component Choice Notes Container Runtime Docker Industry standard Orchestration Docker Compose Simple multi-container apps Docker SDK github.com/docker/docker go SDKTalks to the host docker daemon Swarm Support Optional For multi-node clustering
│ ├── shc/main.go # CLI binary (cobra root)
│ └── shcd/main.go # daemon binary
│ ├── core/ # cross-module primitives
│ │ ├── coded/ # X######CCCC error machinery
│ │ ├── api/ # chi helpers, manifest, SSE
│ │ ├── db/ # *db.Pool, atlas migrations, scan helpers
│ │ ├── log/ # slog wrapper + audit
│ │ ├── paths/ # XDG-aware filesystem layout
│ │ └── registry/ # central router + command + error registries
│ ├── daemon/ # daemon boot + wiring
│ ├── modules/ # feature modules (one per domain)
│ └── subsystems/ # lifecycle-managed background workers
│ ├── lifecycle/ # supervisor + resolver
│ └── impl/ # rqlite_engine, collectors, queue, http, ...
├── apps/ # App catalog (sample apps)
├── migrations/ # Atlas SQL migrations + atlas.sum
├── tests/ # unit/, integration/, parity/, e2e/, proxmox/
└── docs/ # Documentation
Each module under modules/{module}/ (ce module; ee-only modules under modules/) follows a consistent
structure:
├── service.go # *Service struct + methods (business logic)
├── router.go # HTTP routes via chi
├── types.go # request/response Go structs + validate tags
├── errors.go # X######CCCCCC factories + Spec slice + init()
├── events.go # event handler registrations (where applicable)
├── register.go # init() side-effects (router builder, command builder, runtime singleton)
├── service_test.go # unit tests against fakes
└── commands/ # cobra subcommand surface (if module exposes CLI verbs)
Tool Purpose Config gofmt Code formatting (canonical) golangci-lint Lint suite (forbidigo, gocritic, staticcheck, errcheck, …) .golangci.ymlshfmt Shell-script formatting (apps, tests) make format
forbidigo rejects errors.New and fmt.Errorf outside test files —
every error path must surface a *coded.CodedError.
staticcheck, errcheck, unused, gocritic, goimports all on.
Tests are exempt from forbidigo’s stricter rules but not from
gofmt/unused/staticcheck.
Tool Purpose testing stdlibTest runner testing.TB.ContextPer-test cancellable context httptestIn-process HTTP server for handler tests Hand-rolled fakes Cross-module seam stand-ins
go test -coverprofile=coverage.out ./internal/...
go tool cover -html=coverage.out -o coverage.html
# Run in parallel (default: GOMAXPROCS) — disable with -p=1
# Race detector (integration suite turns this on by default)
go test -race ./internal/...
Tool Purpose Bats Bash Automated Testing System Proxmox Multi-node VM e2e
Surface Component Logs typed wrapper in core/log/ — WARN+ requires a *coded.CodedError (raw slog.Warn/slog.Error are lint-banned); structured JSON + audit class + event-bus fan-out Metrics OpenTelemetry meters + scoped registries (modules/metrics/) Tracing OpenTelemetry traces with W3C traceparent propagation Events Internal event bus (modules/events/) + SSE for clients
// modules/metrics/definitions.go — sample shapes
backupTotal := svc . RecordCounter ( ctx , " shc_backup_total " , 1 )
activeStacks := svc . RecordGauge ( ctx , " shc_active_stacks " , float64 ( n ))
opDuration := svc . RecordHistogram ( ctx , " shc_operation_duration_seconds " , elapsed . Seconds ())
Runtime dependencies (selected, see go.mod for the full list):
modernc.org/sqlite // pure-Go sqlite driver
github.com/spf13/cobra // CLI framework
github.com/go-chi/chi/v5 // HTTP router
github.com/go-playground/validator/v10 // input validation
go.opentelemetry.io/otel // metrics + tracing
github.com/charmbracelet/bubbletea // HUD TUI
github.com/charmbracelet/lipgloss // terminal styling
ariga.io/atlas // migrations
github.com/moby/docker // docker SDK
go.yaml.in/yaml/v3 // YAML parsing
Dev tooling lives in go.mod build constraints; gofmt,
golangci-lint, and bats are installed via asdf at make prepare.
Method Command Use case .deb/.rpm/.apkmake package && sudo {dpkg,rpm,apk} -i dist/shc_*_linux_amd64.<fmt>Production Direct Install make installDevelopment/testing
Component Path / approach Build System GoReleaser + nfpm (.goreleaser.yaml)Go binaries /usr/bin/shc, /usr/bin/shcd (static, CGO_ENABLED=0)Bundled rqlited /usr/lib/shc/bin/rqlited (spawned by the daemon in cluster mode)systemd unit /usr/lib/systemd/system/shc.service (static, FHS paths)Group/Dirs systemd-sysusers (shc.sysusers.conf), systemd-tmpfiles (shc.tmpfiles.conf)Scriptlets scripts/{postinstall,preremove,postremove}.sh
Legacy Python code has been removed from the repo — Go is the single
implementation.
These technologies are explicitly not used :
Technology Reason React/Vue/Svelte No web UI CSS/Tailwind No frontend JavaScript/TypeScript Pure Go project PostgreSQL/MySQL SQLite/rqlite is sufficient Redis Not needed for current scope Kubernetes Docker Compose focused GraphQL REST API is simpler Generated mocks Hand-rolled fakes are cheaper to maintain