ssh-client keeps ~/.ssh to what it knows: sweep every other path into a kept folder (issue 1017, ADR 0415)
mesh/merge-gate head acf075fa63cc merged into trunk 9675babf528b — pass: builds ssh-client → ace, g14, novox, shanks; no bus step; every machine composes w…
mesh/repo-check head acf075fa63cc merged into trunk 9675babf528b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered

ssh-client owns ~/.ssh, but treated every file it did not place as found, so a
predecessor's drop-in and old copies stayed there for weeks. Its process now
sweeps at start and every ten minutes: keys at any depth (and links to them),
authorized_keys, authorized_keys2 and sockets stay, and a folder holding one is
gone into; everything else moves to ~/.local/state/ssh-client/retired/<UTC
time>/, a folder never shared with another sweep, with a list beside it, which
ssh_client_check shows. Nothing is deleted.
This commit is contained in:
2026-10-17 12:38:16 +00:00
parent a7d526838a
commit acf075fa63
6 changed files with 589 additions and 19 deletions
@@ -52,7 +52,8 @@ func tools(c *Client) []stdio.Tool {
{
Name: "ssh_client_check",
Description: "Everything about ~/.ssh worth a look: modes of the directory and every file, private keys with no passphrase or weak or old, the mesh's region first with its include, " +
"duplicate hosts, known_hosts entries for the mesh's machines that are missing or stale (scanned live unless scan is false), authorized keys without a comment, and debris. Says what it did not check.",
"duplicate hosts, known_hosts entries for the mesh's machines that are missing or stale (scanned live unless scan is false), authorized keys without a comment, " +
"and what the sweep moved out of ~/.ssh because this module does not know it, with where each lies now (ADR 0415). Says what it did not check.",
Input: map[string]any{"scan": map[string]any{"type": "boolean", "description": "ask each of the mesh's machines for its host key now (default true; 5 s each, in parallel)"}},
Run: func(args map[string]any) (any, error) { return c.Check(ctx, flag(args, "scan", true)) },
},
@@ -149,10 +150,20 @@ func startKeeper() {
if node == "" || err != nil || c.Home == "" {
return
}
go keepSwept(c)
k := &KeyKeeper{Home: c.Home, Node: node, Account: u.Username, Run: ExecRunner, State: keysState{}, Now: time.Now}
k.Keep(context.Background(), func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[ssh-client] "+f+"\n", a...) })
}
// keepSwept sweeps ~/.ssh (sweep.go) now and every KeyRound.
func keepSwept(c *Client) {
say := func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[ssh-client] "+f+"\n", a...) }
for {
c.SweepOnce(time.Now(), say)
time.Sleep(KeyRound)
}
}
type keysState struct{}
func (keysState) Put(key string, value any) error {