make fmt and make fmt-check cover Markdown with prettier (closes #39)
check / check (push) Failing after 5s
check / check (push) Failing after 5s
script/fmt now runs go fmt and then prettier --write on every Markdown file; script/fmt-check runs the gofmt check and then prettier --check. prettier is pinned in package.json and yarn.lock, configured in .prettierrc with four-space indents and prose wrapped at 80 columns, all copied from the sneak/prompts templates. script/bootstrap installs node through a hash-verified nvm archive when it is missing, yarn at a pinned version, and the yarn dependencies. README.md is reformatted by make fmt, and its Entrypoints section says what fmt, fmt-check and bootstrap now cover. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"proseWrap": "always"
|
||||
}
|
||||
@@ -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,19 +271,21 @@ 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 (git, make,
|
||||
Go), idempotently, from nix, apt, brew or apk; it does not install the linter,
|
||||
which only runs inside Docker.
|
||||
Go, node and yarn), idempotently, from nix, apt, brew or apk, with node at a
|
||||
pinned version through nvm when it is not already installed; it then installs
|
||||
the pinned prettier with yarn. It does not install the linter, which only runs
|
||||
inside Docker.
|
||||
- `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
|
||||
@@ -296,9 +296,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 and yarn 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
|
||||
@@ -315,7 +317,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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"prettier": "3.8.1"
|
||||
}
|
||||
}
|
||||
+80
-3
@@ -3,13 +3,23 @@
|
||||
# repo. Idempotent: every install is guarded by a check, so tools that
|
||||
# are already there are left alone. Base tooling comes from nix, apt,
|
||||
# brew, or apk, detected in that order, and nothing is assumed to be
|
||||
# present. The linter is not installed here: linting and testing run
|
||||
# only as phases of the Dockerfile, so Docker is what is needed for
|
||||
# them, and that is checked for rather than installed.
|
||||
# present. Node is used directly if installed; otherwise it is installed
|
||||
# at a pinned version via nvm (installing nvm itself first, from a
|
||||
# hash-verified release archive, never curl | sh). The linter is not
|
||||
# installed here: linting and testing run only as phases of the
|
||||
# Dockerfile, so Docker is what is needed for them, and that is checked
|
||||
# for rather than installed.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# Pinned versions, 2026-07-06
|
||||
NODE_VERSION="22.17.0"
|
||||
NVM_VERSION="0.40.3"
|
||||
# sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz
|
||||
NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0"
|
||||
YARN_VERSION="1.22.22"
|
||||
|
||||
PKGMGR=""
|
||||
SUDO=""
|
||||
|
||||
@@ -50,6 +60,69 @@ missing() {
|
||||
! command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# verify_sha256 <file> <expected-hash>
|
||||
verify_sha256() {
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
actual="$(sha256sum "$1" | cut -d' ' -f1)"
|
||||
else
|
||||
actual="$(shasum -a 256 "$1" | cut -d' ' -f1)"
|
||||
fi
|
||||
if [ "$actual" != "$2" ]; then
|
||||
echo "bootstrap: sha256 mismatch for $1" >&2
|
||||
echo " expected: $2" >&2
|
||||
echo " actual: $actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# nvm is a bash script; run a command in a bash with nvm loaded
|
||||
nvm_sh() {
|
||||
bash -c ". \"\$HOME/.nvm/nvm.sh\" && $*"
|
||||
}
|
||||
|
||||
ensure_nvm() {
|
||||
[ -s "$HOME/.nvm/nvm.sh" ] && return 0
|
||||
# nvm prerequisites; nvm itself requires bash
|
||||
if missing bash; then pkg_install bash bash bash bash; fi
|
||||
if missing curl; then pkg_install curl curl curl curl; fi
|
||||
if missing git; then pkg_install git git git git; fi
|
||||
tmp="$(mktemp -d)"
|
||||
curl -fsSL -o "$tmp/nvm.tar.gz" \
|
||||
"https://github.com/nvm-sh/nvm/archive/refs/tags/v${NVM_VERSION}.tar.gz"
|
||||
verify_sha256 "$tmp/nvm.tar.gz" "$NVM_SHA256"
|
||||
mkdir -p "$HOME/.nvm"
|
||||
tar -xzf "$tmp/nvm.tar.gz" -C "$HOME/.nvm" --strip-components=1
|
||||
rm -rf "$tmp"
|
||||
}
|
||||
|
||||
ensure_node() {
|
||||
if ! missing node; then return 0; fi
|
||||
ensure_nvm
|
||||
nvm_sh "nvm install $NODE_VERSION"
|
||||
}
|
||||
|
||||
ensure_yarn() {
|
||||
if ! missing yarn; then return 0; fi
|
||||
if ! missing corepack; then
|
||||
corepack enable
|
||||
corepack prepare "yarn@$YARN_VERSION" --activate
|
||||
elif [ -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
nvm_sh "nvm use $NODE_VERSION >/dev/null && corepack enable && \
|
||||
corepack prepare yarn@$YARN_VERSION --activate"
|
||||
else
|
||||
npm install -g "yarn@$YARN_VERSION"
|
||||
fi
|
||||
}
|
||||
|
||||
install_js_deps() {
|
||||
if missing yarn && [ -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
nvm_sh "nvm use $NODE_VERSION >/dev/null && cd \"$ROOT\" && \
|
||||
yarn install --frozen-lockfile"
|
||||
else
|
||||
yarn install --frozen-lockfile
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
|
||||
@@ -59,6 +132,10 @@ main() {
|
||||
|
||||
go mod download
|
||||
|
||||
ensure_node
|
||||
ensure_yarn
|
||||
install_js_deps
|
||||
|
||||
if missing docker; then
|
||||
echo "bootstrap: docker is not installed; make lint and make test need it" >&2
|
||||
fi
|
||||
|
||||
+22
-1
@@ -1,12 +1,33 @@
|
||||
#!/bin/sh
|
||||
# script/fmt: format all files (writes).
|
||||
# script/fmt: format all files (writes): the Go source with go fmt, then
|
||||
# the Markdown files with prettier.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# Must match the pin in script/bootstrap.
|
||||
NODE_VERSION="22.17.0"
|
||||
|
||||
# script/bootstrap installs node and yarn under nvm and leaves neither
|
||||
# on the PATH of the shell that called it, so resolve the pinned
|
||||
# toolchain here the way bootstrap's own install step does. nvm is a
|
||||
# bash script, hence the subshell.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
exec yarn "$@"
|
||||
fi
|
||||
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
echo "fmt: no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
||||
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
go fmt ./...
|
||||
run_yarn run prettier --write '**/*.md' --tab-width 4 --prose-wrap always
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -5,6 +5,25 @@ set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
# Must match the pin in script/bootstrap.
|
||||
NODE_VERSION="22.17.0"
|
||||
|
||||
# script/bootstrap installs node and yarn under nvm and leaves neither
|
||||
# on the PATH of the shell that called it, so resolve the pinned
|
||||
# toolchain here the way bootstrap's own install step does. nvm is a
|
||||
# bash script, hence the subshell.
|
||||
run_yarn() {
|
||||
if command -v yarn >/dev/null 2>&1; then
|
||||
exec yarn "$@"
|
||||
fi
|
||||
if [ ! -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
echo "fmt-check: no yarn; run script/bootstrap first" >&2
|
||||
exit 1
|
||||
fi
|
||||
exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null &&
|
||||
shift && exec yarn "$@"' bash "$NODE_VERSION" "$@"
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
if [ -n "$(gofmt -l .)" ]; then
|
||||
@@ -12,6 +31,7 @@ main() {
|
||||
gofmt -l .
|
||||
exit 1
|
||||
fi
|
||||
run_yarn run prettier --check '**/*.md' --tab-width 4 --prose-wrap always
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
prettier@3.8.1:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
|
||||
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
|
||||
Reference in New Issue
Block a user