Skip to content

feat(tuskd): headless rules daemon (#5)#9

Merged
Fraser-Isbester merged 1 commit into
mainfrom
feat/tuskd-daemon
Jul 7, 2026
Merged

feat(tuskd): headless rules daemon (#5)#9
Fraser-Isbester merged 1 commit into
mainfrom
feat/tuskd-daemon

Conversation

@Fraser-Isbester

Copy link
Copy Markdown
Owner

What

Adds cmd/tuskd, a standalone daemon that runs a profile's rules engine headlessly — no TUI (tview/tcell) linked into the binary.

  • Reuses the existing config, db, and rules packages: load profile → BuildRulesNewEngine → poll + Evaluate on a -i/--interval (default 2s).
  • Honors readonly/dry_run; requires the profile to have ≥1 rule.
  • Resilient loop: a single resource's fetch error is logged and skipped (no crash); clean shutdown on SIGINT/SIGTERM.
  • .goreleaser.yml ships tuskd alongside tusk; README gains a Daemon section (install, usage, systemd unit).

Why

Issue #5: "Tusk's Rules engine should be deployable as a daemon without all the TUI bloat." The engine was already TUI-free; this exposes it as a deployable binary.

Validation

  • go build ./..., go vet ./..., golangci-lint run ./... (0 issues), go test ./cmd/tuskd/... pass; go mod tidy clean.
  • Unit tests: evaluate() with a fake snapshotter including a partial fetch failure; runDaemon() context-cancel shutdown.
  • End-to-end against local Postgres: daemon detected a live pg_sleep(12) query each tick and fired the action; graceful SIGTERM shutdown (exit 0); clear errors for no-rules and non-positive interval.

Review guide

  • cmd/tuskd/main.go — CLI + poll loop (snapshotter interface is the test seam).
  • .goreleaser.yml, README.md — packaging + docs.

Closes #5

🤖 Generated with Claude Code

Add cmd/tuskd, a standalone binary that runs a profile's rules engine
without any TUI dependency (no tview/tcell linked), resolving issue #5's
"deployable as a daemon without all the TUI bloat".

It reuses the existing config, db, and rules packages: loads a profile,
compiles its rules, and evaluates them against a live snapshot on a
configurable interval (-i, default 2s), honoring readonly/dry_run. Fetch
errors for a single resource are logged and skipped rather than crashing
the loop, and it shuts down cleanly on SIGINT/SIGTERM.

- .goreleaser.yml: build and ship the tuskd binary alongside tusk
- README: Daemon section with install, usage, and a systemd unit
- tests: evaluate() with a fake snapshotter (incl. partial fetch failure)
  and runDaemon() context-cancel shutdown

Closes #5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@Fraser-Isbester Fraser-Isbester left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed for simplicity and failure behavior.

Appropriately small: the engine was already TUI-free, so this just gives it a main and a loop rather than adding machinery. The snapshotter interface is a clean seam that keeps the poll loop testable without a live DB. Failure handling suits a long-running process — a single resource's fetch error is logged and skipped instead of crashing, interval is validated, and shutdown is signal-driven and clean.

One non-blocking note: on a partial fetch failure the tick still evaluates with an empty slice for the failed resource, so its active violations get marked closed and then reopened on the next good tick. It can't cause a wrong action (a match needs present data) — it's just churn in the violation log. Fine to leave; worth a follow-up only if audit history needs to be precise across DB blips.

Looks good.

@Fraser-Isbester Fraser-Isbester merged commit 2fe98c8 into main Jul 7, 2026
6 checks passed
@Fraser-Isbester Fraser-Isbester deleted the feat/tuskd-daemon branch July 7, 2026 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Tusk Daemon

1 participant