Doc review: connect-to-postgres, create-release-artifact-workflow, deploy-k8s-service (#191)

## Summary

Review session covering 3 docs, plus a codebase-wide cleanup:

### Docs reviewed
- **connect-to-postgres** — verified end-to-end (psql connection tested), stamped
- **create-release-artifact-workflow** — clarified that `build-blumeops.yaml` is only a version bump example (not a packages API example)
- **deploy-k8s-service** — fixed stale repoURL (`indri:2200` → `forge.ops.eblu.me:2222`), wrong Caddy config keys (`upstream` → `backend`, added missing `host`), updated Homepage group to "Services", added Tailscale tag documentation

### Codebase cleanup
- Migrated all remaining `op item get --fields` calls to `op read` URI syntax across 7 files (docs, READMEs, YAML comments)
- Simplified the `op read` vs `op item get` guidance in CLAUDE.md

## Side findings (not addressed)
- New `immich-pg` CNPG cluster not yet documented in the postgresql reference card

## Test plan
- [x] `psql` connection to `pg.ops.eblu.me` verified
- [x] All pre-commit hooks pass
- [x] `docs-check-links`, `docs-check-index`, `docs-check-frontmatter` pass

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/191
This commit is contained in:
Erich Blume 2026-02-15 07:42:01 -08:00
commit 22f418d0dc
12 changed files with 43 additions and 25 deletions

View file

@ -96,4 +96,4 @@ mise run blumeops-tasks # fetch from Todoist, sorted by priority
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`.
Prefer `op read "op://vault/item/field"` over `op item get --fields` to avoid quoting issues with multi-line values.

View file

@ -8,8 +8,8 @@
# op inject -i argocd/manifests/teslamate/secret-db.yaml.tpl | kubectl apply -f -
#
# Then create the database:
# PGPASSWORD=$(op --vault blumeops item get <eblume-item-id> --fields password --reveal) \
# psql -h pg.tail8d86e.ts.net -U eblume -c "CREATE DATABASE teslamate OWNER teslamate;"
# PGPASSWORD=$(op read "op://blumeops/postgres/password") \
# psql -h pg.ops.eblu.me -U eblume -c "CREATE DATABASE teslamate OWNER teslamate;"
#
# After syncing, access the TeslaMate UI at https://tesla.tail8d86e.ts.net to complete
# Tesla API authentication via OAuth flow.

View file

@ -54,7 +54,7 @@ After the cluster is healthy:
psql -h k8s-pg.tail8d86e.ts.net -U eblume -W -d miniflux
# Or with password from 1Password
PGPASSWORD=$(op --vault blumeops item get guxu3j7ajhjyey6xxl2ovsl2ui --fields password --reveal) \
PGPASSWORD=$(op read "op://blumeops/guxu3j7ajhjyey6xxl2ovsl2ui/password") \
psql -h k8s-pg.tail8d86e.ts.net -U eblume -d miniflux
# Get miniflux app credentials (for applications)
@ -73,7 +73,7 @@ Alternative if Tailscale service is unavailable:
kubectl -n databases port-forward svc/blumeops-pg-rw 5432:5432
# Terminal 2: Connect as eblume
PGPASSWORD=$(op --vault blumeops item get guxu3j7ajhjyey6xxl2ovsl2ui --fields password --reveal) \
PGPASSWORD=$(op read "op://blumeops/guxu3j7ajhjyey6xxl2ovsl2ui/password") \
psql -h localhost -U eblume -d miniflux
```

View file

@ -36,8 +36,8 @@ op inject -i argocd/manifests/teslamate/secret-db.yaml.tpl | kubectl apply -f -
After the teslamate user exists in PostgreSQL (sync blumeops-pg first):
```bash
PGPASSWORD=$(op --vault blumeops item get <eblume-item-id> --fields password --reveal) \
psql -h pg.tail8d86e.ts.net -U eblume -c "CREATE DATABASE teslamate OWNER teslamate;"
PGPASSWORD=$(op read "op://blumeops/postgres/password") \
psql -h pg.ops.eblu.me -U eblume -c "CREATE DATABASE teslamate OWNER teslamate;"
```
## Deployment

View file

@ -0,0 +1 @@
Review connect-to-postgres, create-release-artifact-workflow, and deploy-k8s-service docs. Fix stale repoURL, incorrect Caddy config keys, add Tailscale tag documentation, and migrate remaining `op item get` calls to `op read`.

View file

@ -1,6 +1,7 @@
---
title: Connect to Postgres
modified: 2026-02-14
modified: 2026-02-15
last-reviewed: 2026-02-15
tags:
- how-to
- database

View file

@ -1,6 +1,7 @@
---
title: Create Release Artifact Workflow
modified: 2026-02-12
modified: 2026-02-15
last-reviewed: 2026-02-15
tags:
- how-to
- forgejo
@ -34,7 +35,7 @@ This is required because Forgejo's built-in `GITHUB_TOKEN` does not have permiss
## 2. Create the workflow
Create `.forgejo/workflows/<name>-release.yaml` with `workflow_dispatch` and a version input. Use the semver bump pattern (see `cv-release.yaml` or `build-blumeops.yaml` for examples).
Create `.forgejo/workflows/<name>-release.yaml` with `workflow_dispatch` and a version input. Use the semver bump pattern (see `cv-release.yaml` for the full upload flow, or `build-blumeops.yaml` for the version bump logic only — it uploads to Forgejo releases, not generic packages).
The upload step uses `FORGE_TOKEN`:

View file

@ -1,6 +1,7 @@
---
title: Deploy K8s Service
modified: 2026-02-11
modified: 2026-02-15
last-reviewed: 2026-02-15
tags:
- how-to
- kubernetes
@ -34,7 +35,7 @@ metadata:
spec:
project: default
source:
repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/<service>
destination:
@ -60,7 +61,7 @@ metadata:
tailscale.com/proxy-group: "ingress"
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "Service Name"
gethomepage.dev/group: "Apps"
gethomepage.dev/group: "Services"
gethomepage.dev/icon: "<service>.png"
gethomepage.dev/href: "https://<service>.ops.eblu.me"
gethomepage.dev/pod-selector: "app=<service>"
@ -80,6 +81,12 @@ Key points:
- **`proxy-group: "ingress"`** routes through the shared ProxyGroup instead of spawning a per-ingress proxy
- **Do not use `rules:` with `host:`** — the ProxyGroup proxy receives the FQDN as Host header (e.g. `<service>.tail8d86e.ts.net`), so a short `host: <service>` won't match. Use `defaultBackend` instead.
- **`tls.hosts`** sets the MagicDNS hostname (becomes `<service>.tail8d86e.ts.net`)
- **`gethomepage.dev/group`** — use one of the existing groups: "Services", "Content", or "Infrastructure"
- **`tailscale.com/tags`** is not needed in the default case — the ProxyGroup already applies `tag:k8s`. Only add this annotation when the service needs public internet access via the [[flyio-proxy]]. When you do, you must include both tags (setting tags overrides the ProxyGroup default):
```yaml
tailscale.com/tags: "tag:k8s,tag:flyio-target"
```
Then add a Caddy route and Fly.io proxy config per [[expose-service-publicly]].
## Add Caddy Route (if needed)
@ -88,7 +95,8 @@ If other pods need to access the service, add to `ansible/roles/caddy/defaults/m
```yaml
caddy_services:
- name: <service>
upstream: "https://<service>.tail8d86e.ts.net"
host: "<service>.{{ caddy_domain }}"
backend: "https://<service>.tail8d86e.ts.net"
```
Then: `mise run provision-indri -- --tags caddy`

View file

@ -32,7 +32,7 @@ Both tasks fetch the Gandi PAT from 1Password automatically.
To run Pulumi directly:
```bash
export GANDI_PERSONAL_ACCESS_TOKEN=$(op item get mco6ka3dc3rmw7zkg2dhia5d2m --field pat --reveal --vault vg6xf6vvfmoh5hqjjhlhbeoaie)
export GANDI_PERSONAL_ACCESS_TOKEN=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/mco6ka3dc3rmw7zkg2dhia5d2m/pat")
cd pulumi/gandi
pulumi preview
pulumi up --yes

View file

@ -69,7 +69,7 @@ kubectl --context=minikube-indri -n <namespace> get pods --field-selector=status
**ArgoCD login expired:**
```bash
argocd login argocd.ops.eblu.me --username admin --password "$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get srogeebssulhtb6tnqd7ls6qey --fields password --reveal)"
argocd login argocd.ops.eblu.me --username admin --password "$(op read 'op://vg6xf6vvfmoh5hqjjhlhbeoaie/srogeebssulhtb6tnqd7ls6qey/password')"
```
### kubectl connection refused

View file

@ -1,6 +1,6 @@
---
title: PostgreSQL
modified: 2026-02-07
modified: 2026-02-15
tags:
- service
- database
@ -8,7 +8,7 @@ tags:
# PostgreSQL
Database cluster via CloudNativePG operator.
Database clusters via CloudNativePG operator.
## Quick Reference
@ -17,15 +17,18 @@ Database cluster via CloudNativePG operator.
| **URL** | `tcp://pg.ops.eblu.me:5432` |
| **Metrics** | `http://cnpg-metrics.tail8d86e.ts.net:9187/metrics` |
| **Namespace** | `databases` |
| **Cluster** | `blumeops-pg` |
| **Clusters** | `blumeops-pg`, `immich-pg` |
| **Operator** | CloudNativePG |
## Databases
| Database | Owner | Purpose |
|----------|-------|---------|
| miniflux | miniflux | [[miniflux]] feed data |
| teslamate | teslamate | [[teslamate]] vehicle data |
| Database | Cluster | Owner | Purpose |
|----------|---------|-------|---------|
| miniflux | blumeops-pg | miniflux | [[miniflux]] feed data |
| teslamate | blumeops-pg | teslamate | [[teslamate]] vehicle data |
| immich | immich-pg | immich | [[immich]] photo management |
The `immich-pg` cluster uses a custom image (`cloudnative-vectorchord`) with vector search extensions (vector, vchord, cube, earthdistance).
## Users
@ -47,15 +50,19 @@ Backed up via [[borgmatic]] `postgresql_databases` hook. Streams `pg_dump` direc
- `guxu3j7ajhjyey6xxl2ovsl2ui` - eblume password
- `mw2bv5we7woicjza7hc6s44yvy` - borgmatic password
**CNPG-managed secrets:**
**CNPG-managed secrets (blumeops-pg):**
- `blumeops-pg-app` - miniflux user
- `blumeops-pg-eblume` - eblume superuser
- `blumeops-pg-borgmatic` - borgmatic backup user
- `blumeops-pg-teslamate` - teslamate user
**CNPG-managed secrets (immich-pg):**
- `immich-pg-app` - immich user
## Related
- [[connect-to-postgres]] - How to connect via psql
- [[miniflux]] - Feed reader database
- [[teslamate]] - Vehicle data database
- [[immich]] - Photo management database
- [[borgmatic]] - Database backup

View file

@ -70,7 +70,7 @@ mise run dns-preview # Preview only
Or manually:
```bash
export GANDI_PERSONAL_ACCESS_TOKEN=$(op item get mco6ka3dc3rmw7zkg2dhia5d2m --field pat --reveal --vault vg6xf6vvfmoh5hqjjhlhbeoaie)
export GANDI_PERSONAL_ACCESS_TOKEN=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/mco6ka3dc3rmw7zkg2dhia5d2m/pat")
pulumi up
```