mcp: project-plan — hush-herby (hh) — Guided Secret-Entry CLI
This commit is contained in:
parent
e584798cb6
commit
9eb48131a7
56
Sources/Homelab/hush-herby.md
Normal file
56
Sources/Homelab/hush-herby.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
created: '2026-07-09'
|
||||
path: Sources/Homelab
|
||||
project: hush-herby
|
||||
tags:
|
||||
- go
|
||||
- cli
|
||||
- secrets
|
||||
- security
|
||||
- automation
|
||||
type: 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.
|
||||
Loading…
Reference in New Issue
Block a user