Signals end every command, not only ssh to and ssh install (closes #48)
check / check (push) Successful in 2m20s

SIGINT, SIGTERM and SIGHUP were caught for the whole run, but only the
ssh and sftp children acted on them: the mnemonic prompt waited for
Enter, and an interrupted `age encrypt -o` went on to put the
encryption of the cut-off input in place. Now `ssh to` and
`ssh install` catch them from once the mnemonic is read until their
cleanup has run, and `age encrypt -o` and `age decrypt -o` catch them
while they write, to remove the unfinished file and exit with status 1;
everywhere else they end the tool at once. Tests cover an interrupted
`age encrypt -o` and `age decrypt -o` and the install working directory
on a signal.

Model: opus-5-5
This commit is contained in:
2026-10-04 05:32:51 +00:00
parent d4fbcbc83d
commit a31a03b2c3
7 changed files with 210 additions and 23 deletions
+25 -1
View File
@@ -6,7 +6,9 @@ import (
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"syscall"
"github.com/spf13/cobra"
"sneak.berlin/go/keyfunc/internal/agekey"
@@ -185,16 +187,38 @@ func output(cmd *cobra.Command) (io.Writer, func(error) error, error) {
}, nil
}
// From before the new file is made until it is renamed or removed,
// a signal removes it and ends the tool with status 1, even while
// the work is blocked reading its input, so an interrupted run
// leaves no file.
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
// The file is made in the same directory so that putting it in
// place is a rename and never a copy, and it is readable only by
// its owner, which is the mode it keeps once renamed.
file, err := os.CreateTemp(filepath.Dir(name), filepath.Base(name)+".")
if err != nil {
signal.Stop(signals)
return nil, nil, fmt.Errorf("creating a file beside %s: %w", name, err)
}
go func() {
if _, received := <-signals; received {
_ = os.Remove(file.Name())
os.Exit(1)
}
}()
return file, func(failed error) error {
return finish(file, name, failed)
finished := finish(file, name, failed)
signal.Stop(signals)
close(signals)
return finished
}, nil
}