Releases: notpop/hearth
Hearth v0.2.2-alpha
Hearth v0.2.2-alpha
Patch release. Three improvements:
Structured logging on both sides
Worker side emits these slog events with worker, job, kind, attempt, and elapsed fields:
INFO task.received worker=mac-1 job=ab12 kind=img2pdf attempt=1
INFO task.started worker=mac-1 job=ab12 kind=img2pdf
INFO task.done worker=mac-1 job=ab12 kind=img2pdf elapsed=27.3s
WARN task.failed worker=mac-1 job=ab12 kind=img2pdf err="..." elapsed=...
Coordinator (host) side emits:
INFO job.submitted job=ab12 kind=img2pdf
INFO lease.granted job=ab12 kind=img2pdf worker=mac-1 attempt=1
INFO job.complete job=ab12 worker=mac-1 output_blobs=1
INFO job.failed job=ab12 worker=mac-1 err="..." will_retry=true next_run_at=...
INFO job.cancelled job=ab12
Operators can now answer "who picked up this job?" and "how long did it take?" by tailing logs. Switch to a JSON handler (runner.Options.Logger) for structured ingestion.
Pure Nix task runner (Justfile dropped)
Tasks moved from a Justfile to flake apps. Inside nix develop (or via direnv) you can just type build, test, enroll, etc. — shell functions are auto-defined. Outside the shell, use nix run .#<name>.
# In-shell (interactive)
nix develop
build
test
enroll --addr 192.168.1.10:7843 my-worker
# Out of shell (CI, scripts)
nix run .#build
nix run .#test
nix run .#release-build -- v0.2.2-alphapackages.default — consume Hearth from another flake
inputs.hearth.url = "github:notpop/hearth";
# then in your devShell.packages:
hearth.packages.${system}.defaultUseful if your project wants to standardize on Nix-based delivery rather than downloading release archives.
Verified
go vet ./...,go test ./...,go test -race ./...- Cross-compile to all 5 targets
License
MIT © 2026 notpop
Full Changelog: v0.2.1-alpha...v0.2.2-alpha
Full Changelog: v0.2.1-alpha...v0.2.2-alpha
v0.2.1-alpha
Hearth v0.2.1-alpha
Patch release. Two improvements that pave the way for "single-file worker deployment":
pkg/runner.RunFromBundleBytes
A new entry point that accepts the .hearth bundle as []byte rather than a filesystem path. Use it with go:embed to ship a single self-contained worker binary — no separate cert/key/addr file to manage on the target machine:
package main
import (
_ "embed"
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/notpop/hearth/pkg/runner"
)
//go:embed worker.hearth
var bundle []byte
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
log.Fatal(runner.RunFromBundleBytes(ctx, bundle, myHandler{}))
}runner.Run(ctx, runner.Options{...}) now requires exactly one of BundlePath or BundleBytes; passing both or neither returns a clear error.
Justfile
A conventional task surface lands at the repo root, designed to pair with the existing nix develop shell:
just # list tasks
just build # ./bin/hearth
just test # full suite
just test-race
just cover
just lint
just proto # regenerate gRPC stubs
just release-build v0.2.1-alphajust is now in flake.nix's devshell packages so anyone with Nix gets it for free.
Verified
go vet ./...go test ./...go test -race ./...- Cross-compile to all 5 targets
License
MIT © 2026 notpop
Full Changelog: v0.2.0-alpha...v0.2.1-alpha
Hearth v0.2.0-alpha
Hearth v0.2.0-alpha
Second alpha. Significant API and ergonomics improvements. Wire protocol and bundle format unchanged from v0.1.0-alpha so existing bundles still work.
Ergonomics
hearth coordinatoris now zero-config on first run. Auto-creates the CA at~/.hearth/ca/, issues a server cert, and writes a localadmin.hearthbundle to the data directory. The 6-step Quick Start collapses to 2.- CLI auto-discovers the admin bundle.
submit,status,cancel, andnodeswalk--bundleflag →$HEARTH_BUNDLE→./.hearth/admin.hearth→~/.hearth/admin.hearth. No--bundleneeded when running on the coordinator host. hearth submit --blob <path>uploads files as input blobs. Repeatable. Closes the gap that previously forced users to write Go code for any non-trivial submission.hearth cancel <job-id>for cancelling running or queued jobs. Workers receive the cancel signal via the next heartbeat and unwind their handler context.
New public API: pkg/client
External Go projects can now talk to a coordinator without shelling out to the CLI or hand-rolling protobuf:
c, _ := client.Connect(ctx, "/path/to/admin.hearth")
defer c.Close()
id, _ := c.Submit(ctx, job.Spec{Kind: "img2pdf", Blobs: []job.BlobRef{ref}})
_ = c.Watch(ctx, id, func(j job.Job) { /* ... */ })Sentinel errors are exposed for switch-on-error patterns:
errors.Is(err, client.ErrNotFound) // 404
errors.Is(err, client.ErrInvalidArgument) // 400
errors.Is(err, client.ErrInvalidTransition) // already terminal
errors.Is(err, client.ErrUnauthenticated) // bundle rejectedProgress reporting
Long-running handlers can report progress, which surfaces in hearth status --watch:
func (h myHandler) Handle(ctx context.Context, in worker.Input) (worker.Output, error) {
for i, page := range pages {
in.Report(float64(i)/float64(len(pages)), fmt.Sprintf("page %d/%d", i+1, len(pages)))
process(page)
}
return worker.Output{}, nil
}Reports go through an atomic and piggyback on the next heartbeat, so calling Report in a tight loop is safe and cheap.
Worker reliability
- Auto-reconnect with exponential backoff. Workers no longer exit on transient
Leasefailures; they back off (1s → 30s) and retry. A coordinator restart looks like a long delay followed by reconnection.
WatchJob is push-based
Replaced the 200ms server-side poll with an in-memory pub/sub. Updates are delivered in the same coordinator goroutine that wrote the state, so latency drops to "milliseconds" from "up to 200ms".
Programmer-intuitive defaults
Spec zero values now do what most callers expect:
| Field | Before | After |
|---|---|---|
MaxAttempts: 0 |
1 attempt | 3 attempts |
BackoffPolicy{} |
undefined | {1s, 60s, ×2, 0.1 jitter} |
Multiplier: 0 |
constant backoff | 2.0 (default) |
Each field's behaviour is documented in godoc on pkg/job.Spec.
Validation and error clarity
Handler.Kind()andSpec.Kindnow must match^[a-z0-9._-]+$(≤ 64 chars). Validation runs at handler registration and atSubmittime, with messages that tell users exactly what to fix. Prevents--kindsCSV parsing bugs and keeps room for future namespacing likemedia.transcode.Job.StategainsIsActive()andIsRetryable()predicates alongside the existingIsTerminal(). EachStateconst now has a doc comment.Job.Attemptis documented as 1-based (the first delivery hasAttempt == 1).
Documentation
- New
docs/USAGE.mdanddocs/USAGE.ja.mdcovering all four use cases (worker, submitter, mixed, CLI), handler contract, error patterns, blob I/O, progress, and troubleshooting. - READMEs trimmed to ~129 lines each, focused on "what is it / get running / build your own worker".
Verified
go vet ./...go test ./...(full suite)go test -race ./...staticcheck -checks=U1000(no unused code)- Cross-compile to darwin/{arm64,amd64}, linux/{arm64,amd64}, windows/amd64 (CGO-free, ~6–7 MB)
Coverage on new code:
pkg/job: 100%pkg/client: 83%pkg/runner: 86%internal/wire: 100%internal/app/coordinator: 90%internal/app/workerrt: 90%
Pre-release audit fixes
A fresh-context review surfaced and fixed:
- A broken no-op in
streamReader.Close(was assigning a method value instead of calling it) - A doc/code drift in
runner.Options.LeaseTTLdefaults - Dead code:
ServerOptions.WatchPoll,DialContextOption,Client.Conn() - Defensive checks for unset
Spec.LeaseTTLand unsetprogress_reported_at_nscolumns - CLI commands now honour
Ctrl-Cduring long blob uploads - Conformance test coverage for Progress storage paths
License
MIT © 2026 notpop
Full Changelog: v0.1.0-alpha...v0.2.0-alpha
Hearth v0.1.0-alpha
Hearth v0.1.0-alpha — initial alpha release
Distributed task runner for home networks. Single Go binary, mTLS by default, single-file worker enrollment.
This is an alpha release — wire protocol, public Go API, and bundle format may still change.
What's in this release
- Coordinator — durable SQLite-WAL queue, FS content-addressable blob store, mDNS service publication, mTLS gRPC server.
- Worker runtime — pluggable
Handlerinterface, lease + heartbeat protocol, automatic re-queue on lease expiry. - CLI (
hearth) —ca init,enroll,coordinator,worker,submit,status,nodes,version. - Single-file bundle —
<name>.hearth(~1 KB tar.gz) carries everything a new machine needs to join.
A reference handler (examples/img2pdf, PNG/JPEG → PDF) and a runnable worker
binary (examples/img2pdf/cmd/img2pdf-worker) live in the source tree to
demonstrate the pattern, but are not shipped as release binaries — build them
yourself with go build ./examples/img2pdf/cmd/img2pdf-worker if you want it.
Installing from binaries
Download the archive for your platform below, extract, and put hearth somewhere on your $PATH.
# example: macOS Apple Silicon
tar xzf hearth-v0.1.0-alpha-darwin-arm64.tar.gz
./hearth versionVerify with:
sha256sum -c checksums.txt # Linux
shasum -a 256 -c checksums.txt # macOSQuick start
hearth ca init
hearth enroll --addr coordinator.local:7843 my-laptop
hearth coordinator --listen 0.0.0.0:7843 --data ./.hearth # on the always-on hostSee the README for the full guide
(日本語).
Known limitations
CancelJobserver-side returnsUnimplemented(worker-side cancel via heartbeat hook is in place, the public RPC is not).WatchJobis implemented as server-side polling, not push-based.- CLI
submitdoes not yet accept blob-file inputs (use the Go SDK). - Worker reconnect on coordinator restart is not yet automated.
Verified
go test ./...(full suite, ~10 s)go test -race ./...go vet ./...- Cross-compile to darwin/{arm64,amd64}, linux/{arm64,amd64}, windows/amd64 (CGO-free, ~12–18 MB binaries)
- End-to-end gRPC over real TCP + mTLS with handshake verification
- CLI smoke test (CA init → enroll → coordinator → submit → status → nodes)
License
MIT © 2026 notpop
Full Changelog: https://github.com/notpop/hearth/commits/v0.1.0-alpha
Full Changelog: https://github.com/notpop/hearth/commits/v0.1.0-alpha