Artifact dcab1b47b9558b1b6964387c1ba9283ab142c805b0754cd50c5e16801387e2ef:
- File
docs/site/content/docs/guides/production.md
— part of check-in
[93ad669695]
at
2026-10-23 20:53:50
on branch main
— docs: reorganise into get-started, concepts, guides, reference
Move the flat docs pages into sections, keep each old URL as an alias, split sparse cones from horizons, and add concept pages for artifacts, repository state, sync, sub-manifests and signing plus a settings reference. Audit the moved pages against the code and fix drift. (user: [email protected], size: 5509) [annotate] [blame] [check-ins using] [more...]
title: Production patterns weight: 10
aliases: ["/docs/production/"]
The Quickstart uses log.Fatal to keep the snippets short. This page shows the patterns you'll actually want when libfossil is embedded in a service.
Error handling
Don't log.Fatal from a long-running process — return errors up to a caller that can log, retry, or fail the request.
func openRepo(path string) (*libfossil.Repo, error) {
repo, err := libfossil.Open(path)
if err != nil {
return nil, fmt.Errorf("open %s: %w", path, err)
}
return repo, nil
}
The errors you get back are wrapped Go errors; use errors.Is and errors.As against the sentinel errors exported from the libfossil package when you need to branch on a specific failure (for example ErrVersionNotFound, ErrAmbiguousVersion or ErrArtifactNotFound).
Context cancellation
Sync, Pull, and Clone accept a context.Context. Pass a request-scoped context with a deadline so a stalled remote doesn't tie up your goroutine forever.
ctx, cancel := context.WithTimeout(req.Context(), 30*time.Second)
defer cancel()
if _, err := repo.Sync(ctx, transport, libfossil.SyncOpts{Pull: true}); err != nil {
return fmt.Errorf("sync: %w", err)
}
The HTTP transport honors the deadline at the network boundary; the rest of the sync state machine checks ctx.Done() between rounds.
Telemetry
Wire an observer to your Repo's operations to surface sync rounds, checkout events, and per-error events to your existing logging or metrics stack. See Extension points for the full interface, and observer/otel for an OpenTelemetry-ready implementation that emits spans + counters.
import (
libfossil "github.com/danmestas/go-libfossil"
otelobs "github.com/danmestas/go-libfossil/observer/otel"
)
obs := otelobs.NewSyncObserver()
_, err := repo.Sync(ctx, transport, libfossil.SyncOpts{
Pull: true,
Observer: obs,
})
observer/otel is a separate Go module so the OpenTelemetry SDK doesn't leak into consumers that don't want it.
Repo handle lifetime
A *Repo wraps a SQLite database connection pool. Open it once at service startup, keep it for the lifetime of the process, and Close() it during shutdown — don't open and close per request.
type service struct {
repo *libfossil.Repo
}
func newService(path string) (*service, error) {
repo, err := libfossil.Open(path)
if err != nil {
return nil, err
}
return &service{repo: repo}, nil
}
func (s *service) Shutdown() error {
return s.repo.Close()
}
Concurrent reads through one Repo are safe — SQLite handles the locking. Writes serialize at the database level (the drivers open write transactions with BEGIN IMMEDIATE, and a writer waits up to the 5-second busy timeout for the lock); if your service has heavy write traffic, queue write operations through a single goroutine rather than relying on lock contention to back-pressure.
Driver selection
The shipped cmd/libfossil CLI uses modernc by default; go build -tags test_ncruces ./cmd/libfossil selects ncruces.
For library embedders targeting Linux/macOS/Windows, the default modernc driver is the right choice — pure Go, static binaries, no surprises. Switch to ncruces only when you're shipping a WASI/browser binary or when you need ncruces-specific behavior. Blank-import exactly one driver:
import _ "github.com/danmestas/go-libfossil/db/driver/ncruces"
// Do not also import:
// _ "github.com/danmestas/go-libfossil/db/driver/modernc"
Drivers are mutually exclusive — a process can register exactly one. See db for the registry contract.
Signing check-ins
With the clearsign setting on, every check-in that Repo.Commit, Checkout.Checkin or repo branch new stores is signed, as fossil and zit sign them. Fossil's legacy omitsign setting, when on, turns signing off again. The signer is the pgp-command setting, fossil's contract:
- PGP: unset, it is
gpg --clearsign -o, run asCOMMAND OUTFILE INFILE. - SSH: a command whose name starts with
ssh, such asssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519, is run asCOMMAND INFILEand its detached signature is wrapped in fossil's SSH envelope. The namespace must befossilscm.
libfossil -R repo.fossil repo config set clearsign on
libfossil -R repo.fossil repo config set pgp-command "ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519"
The signed text is the artifact, so a signed check-in is named by the hash of the signed bytes. Stock fossil and zit clone and read signed check-ins. A signer that fails abandons the commit. CommitOpts.NoSign, CheckoutCommitOpts.NoSign and --nosign store one check-in unsigned. The command runs through the shell, so ~ and environment variables expand. It runs inside the commit's transaction, so a signer that waits on a passphrase prompt holds the repository's write lock until it returns or two minutes pass. On js/wasm there is no external signer.
The same settings sign the horizon seeds an origin mints. A clone verifies them against the horizon-seed-key setting, which repo clone --seed-key (or CloneOpts.SeedKey) sets. See Signing for the envelope, verification and seed details.