The ssh install command works over sftp (closes #10)
All checks were successful
check / check (push) Successful in 20s

The command no longer sends a shell script to the host. It fetches
~/.ssh/authorized_keys with the system sftp in batch mode, adds the key
line here, and writes the file back in a second session: mkdir and chmod
on ~/.ssh, put to authorized_keys.keyfunc-<random>, chmod 600, rename
over authorized_keys. Adding the line connects twice.

The file reads as empty only when sftp reported it as not there, in the
line naming that path; the same wording elsewhere -- ssh writes it about
an identity file it cannot find -- does not count, so a file that cannot
be read is never written over. A failed step removes nothing and names
the uploaded file once sftp's echo shows the put was reached.

Model: opus-5
This commit is contained in:
2026-09-08 04:14:46 +00:00
parent a2a0890ded
commit ae3b79ee9a
4 changed files with 659 additions and 120 deletions

View File

@@ -88,17 +88,49 @@ Prints the unencrypted private key in OpenSSH format (the
and nothing else, so it can be redirected into a file. The key's comment is the
same as for `pub`.
### `keyfunc ssh install <[user@]host> [-- ssh options...]`
### `keyfunc ssh install <[user@]host> [-- sftp options...]`
Runs the system `ssh` to the host and, on the host:
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.
- creates `~/.ssh` with mode `0700` if it is missing;
- creates `~/.ssh/authorized_keys` with mode `0600` if it is missing;
- appends the `pub` line only if an identical line is not already there.
The first connection fetches `~/.ssh/authorized_keys`. The file reads as empty
only when `sftp` reported that file as not being there — the one line naming
that path. The same wording anywhere else in the session does not count: `ssh`
writes `No such file or directory` about an `-i` it cannot find, on a session
that then authenticates through the agent. When `sftp` failed for any other
reason — the file is there and cannot be read, the connection did not come up —
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. What `sftp`
cannot tell apart is a missing file and one in a directory it cannot enter, so a
`~/.ssh` whose mode shuts the user out reads as a host with no file; the second
connection sets that mode to `0700` and writes, as on a host that has none. 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:
It then prints `added` or `already present`. How this `ssh` connection
authenticates is up to the user's normal `ssh` setup (existing keys, agent,
password). Anything after `--` is passed to `ssh` unchanged.
- creates `~/.ssh` and sets it to mode `0700`;
- uploads the new file as `~/.ssh/authorized_keys.keyfunc-<random>` and sets it
to mode `0600`;
- renames that file over `~/.ssh/authorized_keys`.
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.
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`.
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
normal `ssh` setup, except that batch mode does not prompt: a key or an agent
has to do it, not a typed password.
### `keyfunc ssh to <host> [ssh arguments...]`