wiki-vault/Sources/Homelab/hush-herby.md

5.7 KiB

created path project tags type
2026-07-09 Sources/Homelab hush-herby
go
cli
secrets
security
automation
project-plan

A guided CLI (hh) that captures secrets and writes them to a chosen destination, driven by reusable secret-free profiles. Origin: hand-entering the FB/IG and basic-auth secrets during the wordpress-install staging deploy (Trellis #1000) — the friction of read -s, $-escaping, masked review, and consistent tagging is exactly what this productizes.

Goal

hh captures secrets and writes them to a chosen destination, driven by a saved profile that is the recipe — field names, destination, format, per-field transforms and validators — and never the secret values. So a setup can be recalled and repeated without re-storing secrets.

Done when: hh can create a profile, run it to securely capture then transform then write secrets to a KEY: val file and a .env, with disruptive versioning, forced-prompt-on-collision, atomic writes, masked confirm-back, and import-from-existing-file — shipped as a single static binary.

Locked Decisions

  • Keystone invariant: a profile NEVER contains secret values. The secret flows prompt then transform then destination only; it is zeroed from memory after write; never logged, never in shell history, never in the profile.
  • Stack: Go + Cobra + charmbracelet huh. Single static binary so hh runs on the tower / ThinkPad / proxmox / NAS with no per-machine runtime. Binary hh, repo hush-herby.
  • Profiles: ~/.config/hush-herby/profiles/*.toml — secret-free, git-able, portable.
  • Per-field spec: each field carries name, prompt label, required, validate (regex or named shape), transform (htpasswd-apr1 / htpasswd-bcrypt / compose-escape / base64 / url-encode / none), and target (write under a different key than the one prompted for). Every footgun becomes a declarative line instead of a bash incantation.
  • Destinations (v1): a KEY: val reference sheet (staging.md style, primary) plus .env (KEY=val). The profile names the path plus format.
  • Versioning is disruptive, forced, and clean. On a key collision hh HALTS and forces a decision — no silent append. The active/newest value takes the bare key (clean, copyable, consumable); the previous value is demoted to the next incremental tag KEY_v1 / KEY_v2 with a dated comment. The file is tidied — blanks and dupes removed — as part of the step. This directly targets the staging.md duplicate-key cruft.
  • Write mechanism: surgical parse then apply then atomic write. Parse the existing file into an ordered model that preserves comments, key order, headings, and version history; apply the change; serialize; write to a temp file in the same directory at 0600, fsync, then rename() over the original (atomic on POSIX, so a crash or Ctrl-C never leaves a half-written secrets file). Preserve mode and owner; optional .bak first.
  • Confirm-back masking: show first 2 plus last 4 characters plus length, e.g. EA...3xQz (214 chars); full-mask when the secret is under 12 characters so a short password is not exposed whole.
  • Output mode: a --stdout mode emits the transformed value for piping — masked by default, --reveal to actually print — covering the use-once-do-not-persist case.
  • Import mode: point hh at an existing file, infer keys, format, and path, then back-fill a profile. First-class, not deferred.
  • Scaffold: via the project-scaffolding skill (Go Cobra) into /opt/projects/homelab/hush-herby/, gitea petal-power/hush-herby. zero-check applies since it is Go.

Open Items

  • Profile schema specifics: exact TOML keys for per-field specs, and how validate shapes are named — raw regex versus named presets like jwt, eaa-token, numeric.
  • .env quoting rules for values containing spaces, quotes, or newlines.
  • ansible-vault destination mechanics — decrypt via a vault-id, edit in memory, re-encrypt (P5).
  • ssh-aware remote write, since staging.md lives on the control node not the tower (P5).
  • Whether hh offers to git-commit the profile directory after changes (profiles are secret-free, so safe to track).

Build Phases

  • P1 — Recipe engine. Go/Cobra scaffold; Profile and Field models; TOML load and save; hh new / hh list / hh edit / hh rm skeletons. No secrets yet — just profiles.
  • P2 — Guided capture plus write (MVP). huh masked forms; the KEY: val and .env writers with surgical parse, disruptive versioning (bare equals active, old demoted to _vN), forced-collision prompt, and file-clean; atomic 0600 write plus optional .bak; masked confirm-back (2 plus last 4). End-to-end hh run <profile> to a secure write.
  • P3 — Transforms and validators. compose-escape (double-dollar), htpasswd apr1 and bcrypt, shape validators (jwt / eaa / numeric / regex), base64 and url-encode; declarable per-field. The layer that beats read -s.
  • P4 — Import, output, polish. Import-existing-file to profile; --stdout and --reveal; --dry-run masked preview of the write; solid errors, help, UX.
  • P5 (later) — Extensions. ansible-vault destination; ssh-aware remote write; optional profile git-commit hook.

Notes

  • Origin and testbed: the wordpress-install staging deploy (Trellis #1000). Concrete footguns it must handle: compose $-truncation, apr1 htpasswd generation, JWT and EAA shape validation, duplicate-key cruft (the blank IG_BUSINESS_ACCOUNT_ID), and masked review.
  • Sibling precedent: sshkm (local SSH key manager) — same small-local-homelab-CLI shape.
  • Security posture IS the product: no echo, no history, no secret in the profile, atomic 0600 writes, memory zeroed. If any phase cannot hold that line, stop and reconsider rather than ship a leak.