Signals end every command, not only ssh to and ssh install (closes #48)
check / check (push) Failing after 2s
check / check (push) Failing after 2s
SIGINT, SIGTERM and SIGHUP were caught for the whole run, so the mnemonic prompt waited for Enter and an interrupted `age encrypt -o` put the encryption of the cut-off input in place. Now they end the tool at once, except where a command cleans up first: `ssh to` and `ssh install` while ssh or sftp runs, and `age encrypt -o` and `age decrypt -o` while they write. A signal those two have received when their input ends removes the unfinished file and exits 1; a later one leaves the whole file in place. The tool stays on the main thread, where Linux delivers the signal first. Signals the tool was started ignoring stay ignored, so a run under nohup survives a hangup. Model: opus-5-5
This commit is contained in:
+20
-14
@@ -2,13 +2,11 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sneak.berlin/go/keyfunc/internal/cli/age"
|
||||
@@ -64,24 +62,32 @@ func Root() *cobra.Command {
|
||||
return root
|
||||
}
|
||||
|
||||
// init keeps the command on the main thread. Linux hands a signal sent
|
||||
// to the tool to that thread first, and a thread runs a pending signal
|
||||
// handler before its own code, so when "age encrypt -o" or "age
|
||||
// decrypt -o" checks for a signal as its input ends, one sent before
|
||||
// then, as by Ctrl-C on a pipeline, has been received.
|
||||
//
|
||||
//nolint:gochecknoinits // only an init can keep main on the main thread
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
// Main runs the tool and returns the status the process should exit
|
||||
// with. An error ends the tool with status 1, except when it carries a
|
||||
// status of its own, which "ssh to" uses to hand on the status ssh
|
||||
// ended with. ssh has already said whatever it had to say in that
|
||||
// case, so nothing more is printed.
|
||||
//
|
||||
// SIGINT, SIGTERM and SIGHUP cancel the command's context instead of
|
||||
// killing the process outright, so the child ssh or sftp ends and the
|
||||
// deferred cleanup that removes the agent socket and the install
|
||||
// working directory still runs.
|
||||
// SIGINT, SIGTERM and SIGHUP end the tool at once, as they end any Go
|
||||
// program, so a command waiting at the mnemonic prompt or reading what
|
||||
// it encrypts or decrypts goes no further. The exceptions catch the
|
||||
// signals to clean up first: "ssh to" and "ssh install" while they
|
||||
// have ssh or sftp running, so the child ends and their own cleanup
|
||||
// still runs, and "age encrypt -o" and "age decrypt -o" while they
|
||||
// write, so the unfinished file is removed.
|
||||
func Main() int {
|
||||
ctx, stop := signal.NotifyContext(
|
||||
context.Background(),
|
||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP,
|
||||
)
|
||||
defer stop()
|
||||
|
||||
err := Root().ExecuteContext(ctx)
|
||||
err := Root().Execute()
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user