Add teabot daemon implementation #1

Merged
benvin merged 3 commits from benvin/teabot-initial into main 2026-07-27 17:10:50 +10:00
Owner

Bootstraps the teabot daemon: a Go service (systemd user unit) that watches Gitea repos and dispatches one-shot Claude Code sessions in Docker containers to work issues and review pull requests, acting as configurable bot personalities. The container image reuses the existing agent-dev image (already bundles the claude CLI + dev toolchain), so no new image is built.

Because sessions run claude --dangerously-skip-permissions with a prompt built from event text, dispatch is gated by a fail-closed author allowlist so untrusted accounts cannot inject instructions into a permissionless session.

  • add the daemon core in internal/: config (config.yaml + per-personality tea-config parsing, defaults, validation), gitea (read-only REST client for issues/pulls/comments/diff), state (atomic JSON persistence of processed events), prompt (per-event prompt construction), dispatch (poll loop, loop prevention, first-contact seeding, follow-up routing, bounded concurrency), and docker (containerised claude --print execution behind a Runner interface)
  • gate dispatch on a fail-closed allowed_authors allowlist (with optional per-repo repo_allowed_authors override): only events authored by allowlisted logins spawn a container; others are recorded processed-only and logged; an empty allowlist dispatches nothing and warns at startup; comment follow-ups gate on the new comment's author, not merely the acted-on thread
  • expose a cobra CLI (internal/cli, main.go) with run (--once, --log-json, --log-level), config init/config show, and shell completions
  • prevent loops and re-triggers: skip events authored by any personality login (separate from and in addition to the allowlist), dedup issues/PRs/comments in the state store, and seed a baseline on first contact so a fresh install does not stampede
  • mount each personality's tea config + a private copy of the Claude credentials into the job container, with optional ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL injection and SELinux :z relabels
  • add unit tests across every package (config precedence/validation/tea parsing/allowlist precedence & fail-closed, state roundtrips/dedup, gitea client via httptest, prompt content, docker arg/mount/env construction, dispatch filtering/loop-prevention/seeding/follow-up routing/allowlist enforcement via fakes)
  • package release tooling: Makefile with make patch|minor|major, nfpm RPM spec (binary + completions + systemd user unit + example config), scripts/build-rpm.sh, and .woodpecker pipelines (build/test/pre-commit on PR; test + cross-platform build + RPM PUT to artifactapi + Gitea release on v* tags), every step setting k8s resource requests+limits
  • document the design in docs/ (architecture, configuration incl. the allowlist security rationale, run, config) linked from the README, plus AGENTS.md

https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv

Bootstraps the teabot daemon: a Go service (systemd user unit) that watches Gitea repos and dispatches one-shot Claude Code sessions in Docker containers to work issues and review pull requests, acting as configurable bot personalities. The container image reuses the existing `agent-dev` image (already bundles the claude CLI + dev toolchain), so no new image is built. Because sessions run `claude --dangerously-skip-permissions` with a prompt built from event text, dispatch is gated by a fail-closed author allowlist so untrusted accounts cannot inject instructions into a permissionless session. - add the daemon core in `internal/`: `config` (config.yaml + per-personality tea-config parsing, defaults, validation), `gitea` (read-only REST client for issues/pulls/comments/diff), `state` (atomic JSON persistence of processed events), `prompt` (per-event prompt construction), `dispatch` (poll loop, loop prevention, first-contact seeding, follow-up routing, bounded concurrency), and `docker` (containerised `claude --print` execution behind a `Runner` interface) - gate dispatch on a fail-closed `allowed_authors` allowlist (with optional per-repo `repo_allowed_authors` override): only events authored by allowlisted logins spawn a container; others are recorded processed-only and logged; an empty allowlist dispatches nothing and warns at startup; comment follow-ups gate on the new comment's author, not merely the acted-on thread - expose a cobra CLI (`internal/cli`, `main.go`) with `run` (`--once`, `--log-json`, `--log-level`), `config init`/`config show`, and shell completions - prevent loops and re-triggers: skip events authored by any personality login (separate from and in addition to the allowlist), dedup issues/PRs/comments in the state store, and seed a baseline on first contact so a fresh install does not stampede - mount each personality's tea config + a private copy of the Claude credentials into the job container, with optional `ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL` injection and SELinux `:z` relabels - add unit tests across every package (config precedence/validation/tea parsing/allowlist precedence & fail-closed, state roundtrips/dedup, gitea client via httptest, prompt content, docker arg/mount/env construction, dispatch filtering/loop-prevention/seeding/follow-up routing/allowlist enforcement via fakes) - package release tooling: Makefile with `make patch|minor|major`, nfpm RPM spec (binary + completions + systemd user unit + example config), `scripts/build-rpm.sh`, and `.woodpecker` pipelines (build/test/pre-commit on PR; test + cross-platform build + RPM PUT to artifactapi + Gitea release on `v*` tags), every step setting k8s resource requests+limits - document the design in `docs/` (architecture, configuration incl. the allowlist security rationale, run, config) linked from the README, plus `AGENTS.md` https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
unkinben added 1 commit 2026-07-26 23:36:42 +10:00
Add teabot daemon implementation
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
1b4448afb4
teabot watches Gitea repos and dispatches one-shot Claude Code sessions in
Docker containers to work issues and review PRs, acting as configurable bot
personalities.

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
unkinben added 1 commit 2026-07-26 23:43:40 +10:00
Fix errcheck lint findings in config, docker, and gitea
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
3be3f4cc46
Handle Fprintf/Close/RemoveAll return values so golangci-lint passes.

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
unkinben added 1 commit 2026-07-27 00:33:17 +10:00
Add fail-closed author allowlist gating job dispatch
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
748048be50
Sessions run claude with --dangerously-skip-permissions and a prompt built
from issue/PR/comment text, so only trusted authors may supply that text.
teabot now dispatches a job only when the triggering event's author login is
on an allowlist; an empty allowlist dispatches nothing (fail-closed).

Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
benvin merged commit 7c9c855a13 into main 2026-07-27 17:10:50 +10:00
benvin deleted branch benvin/teabot-initial 2026-07-27 17:10:50 +10:00
Sign in to join this conversation.