blumeops/CLAUDE.md
Erich Blume 0d5f48e2c2 Document op read vs op item get convention (#143)
## Summary
- Adds guidance to CLAUDE.md: use `op read` for secret values, `op item get` only for metadata
- Fixes the argocd login example which used `op item get --fields`
- `op item get --fields` wraps multi-line values in quotes, which corrupts keys and other secrets

Discovered while verifying the sifaka borg repokey in 1Password — hashes didn't match until we switched to `op read`.

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/143
2026-02-10 13:09:55 -08:00

99 lines
4 KiB
Markdown

# CLAUDE.md
Guidance for Claude Code working in this repository. See also [[ai-assistance-guide]].
## Overview
blumeops is Erich Blume's GitOps repository for personal infrastructure, orchestrated via tailnet `tail8d86e.ts.net`.
**CRITICAL: Public repo at github.com/eblume/blumeops - never commit secrets!**
## Rules
1. **Always run `mise run zk-docs -- --style=header --color=never --decorations=always` at session start**
This will refresh your context with important information you will be assumed to know and follow.
2. **Always use `--context=minikube-indri` with kubectl** - work contexts must never be touched
3. **Feature branches only** - checkout main, pull, create branch, commit often
4. **Create PRs via `tea pr create`** - user reviews before deploy, merges after
5. **Check PR comments with `mise run pr-comments <pr_number>`** before proceeding
6. **Add changelog fragments** - `docs/changelog.d/<branch>.<type>.md`
Types: `feature`, `bugfix`, `infra`, `doc`, `ai`, `misc`
7. **Test before applying** - dry runs (`--check --diff`), syntax checks, `ssh indri '...'`
8. **Wait for user review before deploying**
9. **Never merge PRs or push to main without explicit request**
10. **Verify deployments** - `mise run services-check`
## Project Structure
```
./docs/ # documentation (Diataxis, Quartz)
./docs/changelog.d/ # towncrier fragments
./mise-tasks/ # scripts via `mise run`
./ansible/playbooks/ # ansible (indri.yml primary)
./ansible/roles/ # indri service roles
./argocd/apps/ # ArgoCD Application definitions
./argocd/manifests/ # k8s manifests per service
./pulumi/ # Pulumi IaC (tailnet ACLs, cloud)
~/code/personal/ # user's projects
~/code/3rd/ # mirrored external projects
~/code/work # FORBIDDEN
```
## Service Deployment
### Kubernetes (ArgoCD)
Most services run in minikube on indri via ArgoCD (app-of-apps, manual sync).
**PR workflow:**
1. Create branch, modify `argocd/manifests/<service>/`
2. Push, then `argocd app sync apps`
3. Test on branch: `argocd app set <service> --revision <branch> && argocd app sync <service>`
4. After merge: `argocd app set <service> --revision main && argocd app sync <service>`
**Commands:** `argocd app list|get|diff|sync <app>`
**Login:** `argocd login argocd.ops.eblu.me --username admin --password "$(op read 'op://vg6xf6vvfmoh5hqjjhlhbeoaie/srogeebssulhtb6tnqd7ls6qey/password')"`
### Indri (Ansible)
Native services: Forgejo, Zot, Caddy, Borgmatic, Alloy
```fish
mise run provision-indri # full
mise run provision-indri -- --tags <role> # specific
mise run provision-indri -- --check --diff # dry run
```
### Routing
| Domain | Mechanism | Reachable from |
|--------|-----------|----------------|
| `*.eblu.me` | Fly.io proxy (Tailscale tunnel) | public internet |
| `*.ops.eblu.me` | Caddy on indri | k8s pods, containers, tailnet |
| `*.tail8d86e.ts.net` | Tailscale MagicDNS | tailnet clients only |
Check tailscale serve: `ssh indri 'tailscale serve status --json'`
## Container Releases
```fish
mise run container-list # show images/tags
mise run container-release <name> <version> # tag and build
```
## Third-Party Projects
Ask user to mirror on forge first, then clone to `~/code/3rd/<project>/`.
## Task Discovery
```fish
mise run blumeops-tasks # fetch from Todoist, sorted by priority
```
## Credentials
Root store is 1Password. Never grab directly - use existing patterns (ansible pre_tasks, external-secrets, scripts with `op` CLI). Warn user before any credential access.
**`op read` vs `op item get`:** Always use `op read "op://vault/item/field"` to retrieve secret values. `op item get --fields` wraps multi-line values in quotes, corrupting them. Use `op item get` only for listing item metadata (title, vault, field names), never for reading actual secret values in scripts or IaC. Look for existing uses of `op item get --fields` in Ansible/scripts and suggest replacing with `op read`.