Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Agents instructions

## Modifications

For any modifications in this repo, ask the user whether to edit on the main branch without commiting or use git worktrees.
If using git worktrees:

- use the vibe:worktree skill; worktrees live under $VIBE_HOME/worktrees.
- create a PR using gh CLI when finished.

## Development environment

The root `flake.nix` provides the tool shell and pre-commit checks for the workbench apps.

- `nix develop` — tool shell (go 1.27, golangci-lint, gotools, gitleaks, uv). Entering it installs the prek hooks into `.git/hooks/pre-commit`.
- Hooks run at commit time: gitleaks (secrets), gofmt (all `workbench/**.go`), and per-app `golangci-lint run` + `go vet ./...` for each Go module under `workbench/`. Hook entries are absolute nix store paths, so they also work from environments without the dev shell on PATH (e.g. VS Code git).
- `nix flake check` — runs the full hook set over the repo; run it before pushing.
- After changing hooks in `flake.nix`, re-enter `nix develop` once to refresh the installed config.
- Staticcheck runs through golangci-lint's `staticcheck` linter (the standalone package is gone from nixpkgs).
- The Go hooks need network access to download modules; they work on macOS (relaxed sandbox) and at commit time, but would fail on a strictly-sandboxed Linux CI.

## Repo layout

```
argocd-apps/   ApplicationSet registering every deployed app
k8s-apps/      One Helm umbrella chart per app (<app>/Chart.yaml + values.yaml)
charts/        Shared Helm charts rendered as extra Application sources
workbench/     Go source code + Dockerfiles for self-built images
scripts/       Helper scripts (deployed-apps table, pv-diff)
docs/superpowers/  Design specs and implementation plans
terraform/     Infrastructure provisioning (secrets, external-secrets)
```

## Secrets

Secrets are provisioned by `terraform/secrets/` from the SOPS-encrypted
`secrets.yaml`. Keys follow `<namespace>.<secret-name>.<key>`; each
`<namespace>/<secret-name>` becomes one k8s Secret (write-only, values never
land in terraform state). Rules:

- Agents must never run sops operations themselves (decrypt, encrypt, rotate,
  edit). Write the terraform/chart wiring only; the user adds sops entries and
  applies with their key.
- To reuse an existing secret in another namespace, the user copies the values
  into new sops keys under the target namespace
  (`<namespace>.<secret-name>.<key>`) — the generic provisioner handles the
  rest. No per-secret terraform resources.
- Prefer creating the secret in the target namespace with terraform over
  external-secrets cross-namespace sharing. ES (ClusterSecretStore per source
  namespace + RBAC in `k8s-apps/external-secrets/values.yaml` +
  ExternalSecret in the consumer) is only warranted for wide distribution,
  e.g. `registry-pull-secret` fanned out to many namespaces.

## Deployed apps

An app is deployed if and only if `k8s-apps/<name>/appset.yaml` exists (the
ApplicationSet git generator matches `k8s-apps/*/appset.yaml`). That file also
carries per-app flags: `namespace`, `serverSideApply`, `serverSideDiff`,
`scaleToZero`. Undeployed charts live in `k8s-apps/archive/`.

New apps pulling from `registry.terence.cloud` need two things, both easy to
forget (missing either leaves the app in ImagePullBackOff): their namespace in
the `registry-pull-secret` ClusterExternalSecret's `spec.namespaces` in
`k8s-apps/registry/values.yaml` — the registry app distributes its own pull
secret — and `imagePullSecrets: [{name: registry-pull-secret}]` under
`app-template.defaultPodOptions` in the app's own values.yaml.

Tool/API documentation for workbench apps lives in each app's `README.md` — update it whenever an app's interface (tool arguments, endpoints, env vars) changes.

## Grafana dashboards

Dashboards are code: builders live in `workbench/grafana-dashboards/` (one
file per dashboard, registered in `main.go`). Add a builder, run `go run .`
from that directory, and commit the regenerated JSON in
`k8s-apps/kube-prometheus-stack/dashboards/` together with the code. That
app's README has the full workflow.

## Superpowers

Never git add things in docs/superpowers forcefully.

## Versioning

Never bump image tags in PRs. Tags are bumped by CI on main after merge:

1. A PR changes `workbench/<app>/` code; the image tag is not touched.
2. On merge, the `workbench-tag-bump` workflow diffs the merge against the
   previous tip and, for each changed app without a tag bump, commits
   `<app>: bump image tag to <n>` on main: `workbench/<app>/build.yaml` plus
   the image tag in the consuming `k8s-apps/<app>/values.yaml`. Sidecar
   consumers (rangemusique, apachewebdav, gitea-mirror-sync) are updated in
   the consuming app's values.yaml — the bump script finds all references.
3. The bump commit makes the `build-<app>` ArgoCD application run a Kaniko
   build in-cluster, pushing `registry.terence.cloud/<app>:<tag>` to the
   in-cluster registry.
4. ArgoCD syncs `k8s-apps/<app>` to the new tag and rolls the Deployment.

Caveats:

- Never reuse an old tag for new code — `pullPolicy: IfNotPresent` means nodes
  will keep serving the cached image. Always increment.
- Every app carries the `charts/image-wait` chart as a second Application
  source (wired in `argocd-apps/applicationset.yaml`); it renders PreSync hooks
  that hold the sync until the workbench image tag exists in the registry, so
  rollouts never hit ImagePullBackOff while the Kaniko build is still running.
- After merging, check the `build-<app>` application in ArgoCD: if the Kaniko
  build fails, the consuming app's sync fails at the hook and the old pods keep
  running.
- Kaniko rebuilds only happen when the tag changes: the Job name embeds the
  tag and completed Jobs are kept (ArgoCD prunes the old-tag Job on the next
  bump), so no same-tag rebuild ever re-runs.
- `Chart.yaml` version stays `0.1.0` (never bumped) — the image tag is the
  release mechanism.

## Golang

### Build artifacts

When running `go build`, always delete the generated binary afterwards.

### Linting

Always run `golangci-lint run --fix ./...` before considering Go work done.
Fix all reported issues. If a lint rule seems wrong, discuss with the user rather than disabling it.

### Static analysis

Always run `staticcheck ./...` after linting. Fix all reported issues.

### Code conventions

- Flat `package main` layout — no subpackages, no `internal/` directory.
- Module path: `github.com/cterence/homelab-gitops/workbench/<app>`.
- Go version: 1.27.1 (set in `go.mod`).
- `main()` calls a `run() error` function; on error print to stderr and `os.Exit(1)`.
- Imports grouped in three blocks separated by blank lines: stdlib, third-party, local (local not needed yet since everything is `package main`).
- Error handling: wrap with context using `fmt.Errorf("doing X: %w", err)`. Never both log and return an error — pick one.
- Logging: use `log/slog` with a JSON handler (`slog.NewJSONHandler(os.Stdout, nil)`). Structured key-value pairs only.
- `context.Context` as first parameter on all methods that do I/O or are long-running.
- Concurrency: prefer `errgroup.WithContext` with `g.SetLimit()` for bounded parallelism.
- Graceful shutdown: use `signal.NotifyContext` for signal handling.

### Tests

- Test files live alongside source as `<name>_test.go` in the same `package main`.
- Table-driven tests using anonymous structs with `t.Run(tt.name, ...)`.
- Tests cover pure functions (parsing, comparison, logic) — no integration tests.
- Run tests with `go test ./...` from the app directory.
- Use `t.Setenv` for environment-dependent tests.

### Dockerfiles

- Multi-stage build: `golang:1.27-alpine` (or pinned by digest) as build stage, `scratch` or `gcr.io/distroless/static-debian12:nonroot` as final.
- Build flags: `CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w"`.
- No Makefiles. Build is entirely Dockerfile-based; Kaniko builds in-cluster.

## Helm charts (k8s-apps/)

- Each app directory is an umbrella chart: `Chart.yaml` + `values.yaml`.
- `Chart.yaml`: `apiVersion: v2`, `version: 0.1.0` (never bumped), dependencies on upstream charts.
- Most common dependency: `app-template` (bjw-s-labs).
- Workbench app images reference the in-cluster registry: `registry.terence.cloud/<app>` with a `tag` matching `build.yaml`.
- Third-party images are pinned by digest (`tag@sha256:...`).

## Falco (k8s-apps/falco/)

- In Falco, a macro redefined in a later rules file overrides the earlier definition (unlike lists, which append). Never define the same macro name in more than one `customRules` entry — merge all branches into a single `condition` with `or`. A duplicate silently kills the earlier whitelist (this caused the kaniko "Read sensitive file untrusted" alert spam; see PR #1272).
- Validate changes with `scripts/validate-falco-rules.sh` (needs docker + yq).

## Comments

Do not write comments unless there is a sensible justification for one.

## Commit messages

Format: `<app-name>: <description>` — lowercase, short, imperative.
Example: `cnpg-restore-test: fix rbac`

## Renovate

Config is at `/renovate.json5`. Auto-merge is enabled for non-major updates and digests.
Renovate uses the `fix:` conventional-commit prefix.

## CI

- `update-deployed-apps.yaml` — regenerates the deployed apps table in README.md on push to main.
- `nix-cache.yaml` — builds every flake package per system and pushes to the niks3 binary cache on push to main touching the flake or `workbench/catbox/**`. On PRs, the `catbox-pr` job runs on every PR and builds `.#catbox`, `.#catbox-android-bin` and `.#catbox-android` (pushing them to the cache) when the PR touches catbox's Go module, the nix/android trees, the flake, or the workflow — this is the only place the go toolchain, vendorHash, and gradle set (AGP, compose-bom, verification metadata, FOD hash) are proven before merge.
- `pr-argo-diff.yaml` — runs `argocd app diff` per changed app on PRs touching `k8s-apps/**` and posts the diff as a PR comment.
- `workbench-ci.yaml` — on PRs touching `workbench/**`: runs `go vet`/`go test`/golangci-lint per changed Go app, pytest per changed Python app, and a no-push docker build mirroring the Kaniko context. A changed Go app with an `integration_test.go` also runs it gated with `<APP>_INTEGRATION=1` (helper-process e2e; needs the DERP network, available on hosted runners).
- `workbench-tag-bump.yaml` — on push to main touching `workbench/**`: bumps `build.yaml` (and the matching `k8s-apps` image tag) when a merge modified a workbench app without bumping its tag. Idempotent (any `+tag:` line in the merge window skips the bump), runs serialized, works with Renovate merges too.
- Image builds still happen in-cluster via Kaniko on merge to main; CI builds are validation only.
- Branch protection on main requires the `Workbench CI result` and `argocd_diff` checks before merging (both workflows run on every PR; the workbench one reports a stable aggregate because matrix job names vary per app). With `platformAutomerge`, Renovate's auto-merge only merges once these pass.