make fmt and make fmt-check cover Markdown with prettier (closes #39)
check / check (push) Successful in 2m37s

make fmt and make fmt-check now cover the Markdown files with prettier as well as the Go source: prettier 3.8.1 pinned in package.json and yarn.lock, four-space indents and proseWrap always in .prettierrc, all three copied unchanged from the sneak/prompts templates. script/bootstrap adds pinned node (through nvm from a hash-checked archive when none is installed), yarn and prettier after the pinned Go. README.md is reformatted by make fmt and its Entrypoints section says what each step needs.

Model: opus-5-5
This commit was merged in pull request #45.
This commit is contained in:
2026-10-04 05:42:46 +02:00
parent 7280ee35f4
commit 1ddc2c747a
7 changed files with 194 additions and 65 deletions
+69 -60
View File
@@ -31,8 +31,8 @@ make build
```
`make build` produces `./keyfunc`. Every deriving command needs a mnemonic; see
[Giving it the mnemonic](#giving-it-the-mnemonic) for where it is read from, then
for example:
[Giving it the mnemonic](#giving-it-the-mnemonic) for where it is read from,
then for example:
```
./keyfunc ssh pub -n 0 --mnemonic-command 'secret get foo'
@@ -105,11 +105,12 @@ The mnemonic itself is never a command-line argument. It is looked for in this
order; the first one found wins:
1. `--mnemonic-command <command>`: a shell command, run with `sh -c`, whose
standard output is the mnemonic. Example: `--mnemonic-command 'secret get
foo'`. Whitespace around the output is dropped. If the command exits with a
non-zero status, the tool prints its standard error and exits with status 1.
2. Environment variable `KEYFUNC_MNEMONIC_COMMAND`: the same, as a shell
command held in the environment.
standard output is the mnemonic. Example:
`--mnemonic-command 'secret get foo'`. Whitespace around the output is
dropped. If the command exits with a non-zero status, the tool prints its
standard error and exits with status 1.
2. Environment variable `KEYFUNC_MNEMONIC_COMMAND`: the same, as a shell command
held in the environment.
3. Environment variable `KEYFUNC_MNEMONIC`: the mnemonic itself.
4. A prompt on the terminal with echo turned off.
@@ -119,8 +120,7 @@ refused with a message saying so.
`KEYFUNC_MNEMONIC` and `KEYFUNC_MNEMONIC_COMMAND` are removed from the
environment before the system `ssh` (`keyfunc ssh to`) and `sftp`
(`keyfunc ssh install`) are started, so the mnemonic is never handed on to
them.
(`keyfunc ssh install`) are started, so the mnemonic is never handed on to them.
Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`.
`keyfunc --version` prints the version. `make build` stamps it; a binary
@@ -128,9 +128,8 @@ installed with `go install` reports the module version instead.
## SSH keys: `keyfunc ssh`
Only ed25519 keys are produced. The application number is `838372`, so the
path is `m/83696968'/838372'/<n>'`. The 32 bytes from step 4 are the ed25519
seed.
Only ed25519 keys are produced. The application number is `838372`, so the path
is `m/83696968'/838372'/<n>'`. The 32 bytes from step 4 are the ed25519 seed.
Test vector, mnemonic
`abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`:
@@ -160,24 +159,24 @@ same as for `pub`.
### `keyfunc ssh install <[user@]host> [-- sftp options...]`
Adds the `pub` line to `~/.ssh/authorized_keys` on the host. No command is run
on the host: the file is fetched, changed here, and written back with the
system `sftp` client in batch mode.
on the host: the file is fetched, changed here, and written back with the system
`sftp` client in batch mode.
The first connection lists `~/.ssh` and then fetches
`~/.ssh/authorized_keys` from it. The file reads as empty in two cases only:
`sftp` reported `~/.ssh` itself as not being there, or the listing came up and
the file was not in it. Any other outcome of that connection fails the run — a
`~/.ssh` that is there but cannot be entered, an `authorized_keys` that is there
but cannot be read, or a connection that did not come up — and the tool prints
what `sftp` said and exits with status 1 without writing anything, rather than
put a file back holding the new key alone. The listing is what tells a missing
directory from one shut to the user, which `sftp` reports on a fetch the same
way; the wording of a missing file elsewhere does not count either, since `ssh`
writes `No such file or directory` about an `-i` it cannot find on a session
that then authenticates through the agent. If an identical line is already in
the file, the tool prints `already present` and connects no further. Otherwise
the line is added (after a newline, if the file did not end with one) and a
second connection:
The first connection lists `~/.ssh` and then fetches `~/.ssh/authorized_keys`
from it. The file reads as empty in two cases only: `sftp` reported `~/.ssh`
itself as not being there, or the listing came up and the file was not in it.
Any other outcome of that connection fails the run — a `~/.ssh` that is there
but cannot be entered, an `authorized_keys` that is there but cannot be read, or
a connection that did not come up — and the tool prints what `sftp` said and
exits with status 1 without writing anything, rather than put a file back
holding the new key alone. The listing is what tells a missing directory from
one shut to the user, which `sftp` reports on a fetch the same way; the wording
of a missing file elsewhere does not count either, since `ssh` writes
`No such file or directory` about an `-i` it cannot find on a session that then
authenticates through the agent. If an identical line is already in the file,
the tool prints `already present` and connects no further. Otherwise the line is
added (after a newline, if the file did not end with one) and a second
connection:
- makes `~/.ssh` and sets it to mode `0700`, but only when the first connection
found none; a `~/.ssh` that was already there keeps the mode it had;
@@ -188,15 +187,14 @@ second connection:
The tool then prints `added`. So a run that adds a line connects twice. The
rename is the step that either happens or does not: the file on the host is
never half-written. `sftp` does it in one step against servers that offer
OpenSSH's POSIX rename extension, as OpenSSH's own server does; a server
without it may refuse to rename onto a file that is already there.
OpenSSH's POSIX rename extension, as OpenSSH's own server does; a server without
it may refuse to rename onto a file that is already there.
If a step fails, the tool prints what `sftp` said, removes nothing, and exits
with status 1. It names the uploaded file only when the step that failed was
the upload or one after it, which is where a file of that name can be on the
host; a failure before the upload names none. Everything `sftp`
writes goes to standard error, so the tool's own standard output is only
`added` or `already present`.
with status 1. It names the uploaded file only when the step that failed was the
upload or one after it, which is where a file of that name can be on the host; a
failure before the upload names none. Everything `sftp` writes goes to standard
error, so the tool's own standard output is only `added` or `already present`.
Anything after `--` is passed to `sftp` unchanged, which is where the port goes
(`-P 2222`, not `-p`). How the connection authenticates is up to the user's
@@ -216,10 +214,10 @@ and the tool then exits with status 1 unless `ssh` reported one of its own.
## age identities: `keyfunc age`
The application number is `657169`, path `m/83696968'/657169'/<n>'`. The 32
bytes from step 4 are clamped as X25519 requires and become an age identity,
the same steps `sneak/secret` takes in its `agehd` package. `secret` derives at
a vendor-specific path today; for its keys to equal this tool's it moves to
this path, which is a change in `secret`, not here.
bytes from step 4 are clamped as X25519 requires and become an age identity, the
same steps `sneak/secret` takes in its `agehd` package. `secret` derives at a
vendor-specific path today; for its keys to equal this tool's it moves to this
path, which is a change in `secret`, not here.
Test vectors, mnemonic
`abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`:
@@ -256,11 +254,11 @@ says so and exits with status 1.
### `keyfunc mnemonic [-n N] [--words 12|18|24]`
Prints a child mnemonic derived from the main one, using BIP-85's own mnemonic
application (number `39`, English, path
`m/83696968'/39'/0'/<words>'/<n>'`, entropy taken as the specification says,
not through step 4). Default 12 words. A child mnemonic is a full mnemonic in
its own right: it can seed another `keyfunc`, another wallet, or `secret`, and
it never has to be written down, since it can be derived again.
application (number `39`, English, path `m/83696968'/39'/0'/<words>'/<n>'`,
entropy taken as the specification says, not through step 4). Default 12 words.
A child mnemonic is a full mnemonic in its own right: it can seed another
`keyfunc`, another wallet, or `secret`, and it never has to be written down,
since it can be derived again.
Test vector: the child-mnemonic step is checked against BIP-85's own published
vectors, which derive from the specification's master key
@@ -273,23 +271,33 @@ girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose
## Errors
Errors go to standard error and the exit status is 1, except for `ssh to`,
which passes through `ssh`'s own exit status.
Errors go to standard error and the exit status is 1, except for `ssh to`, which
passes through `ssh`'s own exit status.
## Entrypoints
The repo adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
standard: most Makefile targets are thin shims over an executable in
`script/` (`build` and `clean` are the exceptions).
standard: most Makefile targets are thin shims over an executable in `script/`
(`build` and `clean` are the exceptions).
- `script/bootstrap` installs everything needed to build and develop,
idempotently: git and make from nix, apt, brew or apk, and Go, at the version
the `Dockerfile`'s Go image carries, from the official release archive
(checked against a sha256 in the script) into `~/.local/go`. `script/fmt`,
`script/fmt-check`, `script/precommit` and the `Makefile` put
`~/.local/go/bin` first on their `PATH`, so they use that Go. It does not
install the linter, which only runs inside Docker.
- `script/bootstrap` installs, idempotently, everything needed to build and
develop apart from Docker, which it only warns about when it is missing, and
the linter, which only runs inside Docker. In this order:
- git and make from nix, apt, brew or apk, and from there too curl and bash
when a later step needs them;
- Go at the version the `Dockerfile`'s Go image carries, from the official
release archive at go.dev (checked against a sha256 in the script) into
`~/.local/go`, unless the `go` first on the `PATH` already is that
version; then the Go modules. `script/bootstrap` itself, `script/fmt`,
`script/fmt-check`, `script/precommit` and the `Makefile` put
`~/.local/go/bin` first on their `PATH`, so they use that Go;
- node: an installed one is used as it is, otherwise a pinned one is
installed through nvm, which, when it is missing, comes from its release
archive, checked against a sha256;
- yarn: an installed one is used as it is, otherwise the pinned version
through corepack, or through npm where there is no corepack;
- the pinned prettier, through yarn.
- `script/setup` prepares a fresh clone: it runs `bootstrap`, then installs the
git pre-commit hook.
- `script/projectname` prints the project name; other scripts call it so they
@@ -300,9 +308,11 @@ standard: most Makefile targets are thin shims over an executable in
- `script/lint` builds the `lint` phase of the `Dockerfile` alone, uncached: the
linter, pinned by hash, runs inside the build, so a complaint fails it and
leaves no container behind.
- `script/fmt` formats the Go source in place.
- `script/fmt-check` checks that formatting without writing, failing if anything
is unformatted.
- `script/fmt` formats in place: the Go source with `go fmt`, then every
Markdown file with prettier (four-space indents, prose wrapped at 80 columns).
- `script/fmt-check` checks the same files the same way without writing, failing
if anything is unformatted. Both need the node, yarn and prettier that
`bootstrap` installs.
- `script/check` runs `test`, `lint` and `fmt-check` and changes no files.
- `script/docker` builds the Docker image, uncached, tagged with the project
name and stamped with the version `git describe` gives on the host. The image
@@ -319,7 +329,6 @@ standard: most Makefile targets are thin shims over an executable in
The open issues that stand between the tree and a 1.0 release:
- [#39 make fmt and make fmt-check cover Markdown with prettier](https://git.eeqj.de/sneak/keyfunc/issues/39)
- [#42 go-bip39 no longer exists upstream: keep it, or copy it into the repo?](https://git.eeqj.de/sneak/keyfunc/issues/42)
## License