Files
agent-tools/README.md
T
unkin-agent 61bb464e32
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Add agentvault with a seed-outpost subcommand
Interactive agents are classifier-blocked from plumbing credentials through
a shell, so seeding an Authentik outpost token into Vault KV needs to happen
inside one binary invocation that never exposes the secret.

- Add cmd/agentvault, a fourth CLI sharing the agentpr Vault AppRole login
  (role_id only, VAULT_ADDR/AGENT_APPROLE_ROLE_ID defaults unchanged).
- Add `agentvault seed-outpost`: read the Authentik API token from
  kv/service/authentik/agent-api-token (field `token`, falling back to
  `api_token`), exact-match the outpost by name via the instances search,
  fetch its key from /api/v3/core/tokens/<identifier>/view_key/ and write it
  to --dest-path under --dest-key.
- Print only the outpost name, token identifier, dest path and new KV
  version; keep secret material out of results, errors and logs.
- Distinguish the failure stages (login, KV read denied, outpost missing,
  view_key, KV write denied) with ErrVaultDenied/ErrVaultNotFound/
  ErrOutpostNotFound sentinels and actionable messages.
- Add internal/agent vaultkv.go (AppRole-authenticated KV-v2 client) and
  authentik.go (outpost search + view_key) for reuse by future flows.
- Cover the happy path, idempotent re-run, field fallback and every failure
  mode with httptest servers, including a leak check on error strings.
- Wire agentvault into the Makefile, build-rpm.sh, nfpm contents, release
  cross-builds/assets, README and AGENTS.md.
2026-08-29 21:03:31 +10:00

6.6 KiB

agent-tools

Small Gitea-automation CLIs, shipped together in one RPM (agent-tools). They act as the unkin-agent user by minting a scoped Gitea token from Vault, so automated PRs, comments and pushes are attributed to the agent — not to whoever happens to run the command.

  • agentpr — create pull requests and post PR comments as unkin-agent.
  • watchpr — poll one or more PRs and exit when one changes in a way worth acting on.
  • agentws — manage per-branch git worktrees for unkin-agent, cloning into Ben's source checkout and isolating agent work under the XDG cache.
  • agentvault — run deterministic Vault flows in one invocation, so agents never plumb secret material through a shell.

How it gets a token

On first use each tool performs a Vault AppRole login (role_id only, no secret_id), then reads gitea/creds/unkin-agent to obtain a short-lived Gitea token, cached in-process for the run.

Everything is configured by environment variables, all with defaults:

Variable Default Purpose
VAULT_ADDR https://vault.service.consul:8200 Vault/OpenBao address
AGENT_APPROLE_ROLE_ID built-in default AppRole role_id (overridable)
GITEA_URL https://git.unkin.net Gitea base URL
AGENT_LOGIN unkin-agent login whose comments watchpr ignores
AGENTWS_SRC_ROOT ~/src/prodenv source-of-truth checkout root (agentws)
AGENTWS_ROOT ~/.cache/agentws worktree root (agentws)
AGENTWS_OWNER unkin Gitea org that owns the repos (agentws)
AUTHENTIK_URL https://identity.k8s.syd1.au.unkin.net Authentik base URL (agentvault)

agentpr

# Verify identity (should print: unkin-agent)
agentpr whoami

# Open a PR
agentpr pr create --repo unkin/argocd-apps \
  --base main --head benvin/my-change \
  --title "Add woodpecker SA" --body "Adds the ServiceAccount ..."
# prints: #<number> <html_url>

# Comment on a PR
agentpr pr comment --repo unkin/argocd-apps --pr 42 --body "Rebased, CI green."

agentpr --version
agentpr --help

Non-zero exit on any API error.

watchpr

Poll PRs and exit (reporting what changed) when a tracked PR merges/closes, gets a new comment from someone other than the agent, its CI fails (failure/error), or it loses mergeability (a conflict appears). Benign transitions — CI pendingsuccess, the agent's own comments — are ignored.

# Watch until something meaningful happens (default interval 60s)
watchpr unkin/argocd-apps#42

# Multiple PRs, custom interval; refs accept #N or :N
watchpr --interval 30s unkin/argocd-apps#42 unkin/terraform-vault:98

# One-shot: print current state and exit 0 (great for scripts)
watchpr --once unkin/argocd-apps#42
watchpr --once --json unkin/argocd-apps#42

On a meaningful change watchpr prints the reason and the PR's current state, then exits 0. Use --json for machine-readable output.

agentws

agentws gives an agent an isolated git worktree per branch without disturbing Ben's shared checkouts. Repos are cloned into the source root (~/src/prodenv/<repo>) so branches created here are visible in the main checkout too; the worktrees themselves live under the worktree root (~/.cache/agentws/<repo>__<branch>).

# Clone unkin/argocd-apps into ~/src/prodenv if missing, then add a worktree for
# a new branch off the remote default branch. Prints the worktree path.
agentws new argocd-apps --branch benvin/my-change

# Branch off a specific base instead of the remote default
agentws new argocd-apps --branch benvin/hotfix --from release-1.2

# List managed worktrees (repo, branch, path)
agentws list

# Remove a worktree (by path or branch); refreshes the source repo afterwards
agentws rm benvin/my-change
agentws rm ~/.cache/agentws/argocd-apps__benvin-my-change --delete-branch

# Remove every managed worktree and prune each source repo
agentws clean

# Print a fresh unkin-agent Gitea token
agentws token

Auth / credential-helper design

Gitea tokens minted from Vault are short-lived (~1h), so agentws never persists one in a remote URL or in git config. Instead it wires itself as an ephemeral git credential helper:

  • agentws token prints a fresh token to stdout (handy for scripts).
  • agentws credential get speaks the git credential protocol on stdin and, for the configured Gitea host only, emits username=unkin-agent + password=<fresh token>.

agentws new sets this up per worktree without touching the shared checkout: it enables extensions.worktreeConfig on the repo once, then writes user.name / user.email and credential.helper = !<agentws> credential to the per-worktree config. Clone/fetch use the same helper via a transient -c credential.helper=...; the shared origin URL is left clean. On worktree removal agentws fetches in ~/src/prodenv/<repo> so its default branch stays current.

agentvault

Deterministic Vault flows, each a single self-contained invocation: the tool reads and writes the secrets itself, and prints only identifiers.

seed-outpost

Copy an Authentik outpost's token into Vault KV-v2. agentvault reads the Authentik API token from kv/service/authentik/agent-api-token, resolves the named outpost's token_identifier, fetches its key via /api/v3/core/tokens/<identifier>/view_key/ and writes it to the destination KV path. The token value is never printed or logged.

agentvault seed-outpost \
  --outpost k8s-outpost \
  --dest-path kubernetes/namespace/authentik/default/outpost-token
outpost:          k8s-outpost
token_identifier: ak-outpost-k8s-outpost
dest:             kv/kubernetes/namespace/authentik/default/outpost-token
version:          3

Re-running is safe: it writes a new KV version. Flags: --outpost and --dest-path are required; --dest-key (default token), --kv-mount (default kv), --token-path (default service/authentik/agent-api-token) and --authentik-url override the rest.

Errors name the failing stage: AppRole login, KV read denied (policy not applied), outpost not found (terraform not applied), view_key failure, or KV write denied.

Build & package

make build                 # -> dist/agentpr, dist/watchpr, dist/agentws, dist/agentvault
make test                  # go test -race ./...
make rpm                   # build + package dist/agent-tools-<version>-1.x86_64.rpm

Release is tag-driven (v*) via Woodpecker: builds the RPM, PUTs it to the artifactapi rpm-internal yum repo, and cuts a Gitea release with cross-compiled binaries attached.

Version bump

make patch   # or: make minor / make major  — tags vX.Y.Z and pushes the tag