5.7 KiB
| created | path | project | tags | type | |||||
|---|---|---|---|---|---|---|---|---|---|
| 2026-07-09 | Sources/Homelab | hush-herby |
|
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 sohhruns on the tower / ThinkPad / proxmox / NAS with no per-machine runtime. Binaryhh, repohush-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), andtarget(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: valreference 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
hhHALTS 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 tagKEY_v1/KEY_v2with 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, thenrename()over the original (atomic on POSIX, so a crash or Ctrl-C never leaves a half-written secrets file). Preserve mode and owner; optional.bakfirst. - 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
--stdoutmode emits the transformed value for piping — masked by default,--revealto actually print — covering the use-once-do-not-persist case. - Import mode: point
hhat 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/, giteapetal-power/hush-herby. zero-check applies since it is Go.
Open Items
- Profile schema specifics: exact TOML keys for per-field specs, and how
validateshapes are named — raw regex versus named presets likejwt,eaa-token,numeric. .envquoting 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
hhoffers to git-commit the profile directory after changes (profiles are secret-free, so safe to track).
Build Phases
- P1 — Recipe engine. Go/Cobra scaffold;
ProfileandFieldmodels; TOML load and save;hh new/hh list/hh edit/hh rmskeletons. No secrets yet — just profiles. - P2 — Guided capture plus write (MVP).
huhmasked forms; theKEY: valand.envwriters with surgical parse, disruptive versioning (bare equals active, old demoted to_vN), forced-collision prompt, and file-clean; atomic0600write plus optional.bak; masked confirm-back (2 plus last 4). End-to-endhh 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 beatsread -s. - P4 — Import, output, polish. Import-existing-file to profile;
--stdoutand--reveal;--dry-runmasked 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 blankIG_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.