The bootstrap admin password was shown exactly once, as one INFO
record among the roughly 45 fx lines a boot writes, and there was no
reset path at all: no subcommand, no forgot-password flow, no
override. Losing that line meant deleting the users row from
webhooker.db by hand so the next start would re-seed.
- internal/banner renders the one credential shown in the clear as a
ruled block written straight to standard output, so it does not read
as one more log line. The first boot emits the password there and
nowhere else, and the banner names the recovery command.
- `webhooker resetpw [-generate] <username>` sets an existing
account's password. It reads the password as one line from standard
input, or generates one with crypto/rand via the existing
GenerateRandomPassword; it is never an argv value, which /proc would
publish to every account on the host. Hashing goes through
database.HashPassword, so the Argon2id parameters cannot drift.
- It refuses to run against a DATA_DIR a live instance holds, by
taking the same exclusive flock internal/datadir gives the server,
and releases it when it finishes.
- It creates nothing. A missing DATA_DIR, a directory with no
webhooker.db, and an unknown username are each an error: datadir
.Acquire calls os.MkdirAll, so a mistyped path would otherwise be
built out and reported as a success. The existence checks therefore
run before the lock is taken.
- The account is resolved and the hash computed in full before the
single UPDATE that stores it, so any failure leaves the stored
credential untouched.
- database.Open exposes the connect-and-migrate path without fx and
without seeding; seeding moves to ensureAdminUser, which only a
server start calls.
- main gains subcommand dispatch. No arguments still runs the server
on the same path, with the DATA_DIR lock taken before the fx graph
is built and fx owning the non-zero exit; an unknown subcommand
exits 2 rather than starting a server.
Tests: reset then log in through the real form POST handler, the
generated password verifying against the stored hash, the refusal
against a held lock, both create-nothing cases, the unknown user, the
unusable passwords, and the first-boot banner carrying a password that
opens the account.
README documents the bootstrap banner and the recovery command,
including the container invocation and what resetpw will not do.