check / check (push) Successful in 47s
Entry() now returns the exit code and only main calls os.Exit, so the deferred memguard.Purge() in Entry() runs on success and on error; before, os.Exit(1) skipped every deferred Destroy(). SIGINT and SIGTERM go through memguard's handler, which wipes every buffer and exits with status 1. The passphrase prompt turns terminal echo off until its read returns, and the handler exits before that, so the handler first restores the terminal settings saved at startup, but only when this process is in the terminal's foreground process group: a background process that changes the terminal is stopped instead of exiting. Model: opus-5-5
76 lines
2.0 KiB
Go
76 lines
2.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"os"
|
|
|
|
"git.eeqj.de/sneak/secret/internal/secret"
|
|
"github.com/awnumar/memguard"
|
|
"github.com/spf13/cobra"
|
|
"golang.org/x/sys/unix"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// Entry runs the secret CLI and returns the process exit code. It wipes
|
|
// every memguard buffer before it returns, so the caller must do nothing
|
|
// but exit with the code.
|
|
func Entry() int {
|
|
// On SIGINT or SIGTERM memguard runs this function, wipes every buffer
|
|
// and exits with status 1. The passphrase prompt turns terminal echo
|
|
// off until the read finishes, so a signal there would leave echo off.
|
|
// Only a process in the terminal's foreground process group may reset
|
|
// it: one in the background that tries is stopped instead of exiting.
|
|
terminalState, terminalErr := term.GetState(unix.Stdin)
|
|
|
|
memguard.CatchSignal(func(os.Signal) {
|
|
foreground, err := unix.IoctlGetInt(unix.Stdin, unix.TIOCGPGRP)
|
|
if terminalErr == nil && err == nil && foreground == unix.Getpgrp() {
|
|
_ = term.Restore(unix.Stdin, terminalState)
|
|
}
|
|
}, os.Interrupt, unix.SIGTERM)
|
|
|
|
defer memguard.Purge()
|
|
|
|
err := newRootCmd().Execute()
|
|
if err != nil {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func newRootCmd() *cobra.Command {
|
|
secret.Debug("newRootCmd starting")
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "secret",
|
|
Short: "A simple secrets manager",
|
|
Long: `A simple secrets manager to store and retrieve sensitive ` +
|
|
`information securely.`,
|
|
// Ensure usage is shown after errors
|
|
SilenceUsage: false,
|
|
SilenceErrors: false,
|
|
}
|
|
|
|
secret.Debug("Adding subcommands to root command")
|
|
// Add subcommands
|
|
cmd.AddCommand(NewInitCmd())
|
|
cmd.AddCommand(newGenerateCmd())
|
|
cmd.AddCommand(newVaultCmd())
|
|
cmd.AddCommand(newAddCmd())
|
|
cmd.AddCommand(newGetCmd())
|
|
cmd.AddCommand(newListCmd())
|
|
cmd.AddCommand(newRemoveCmd())
|
|
cmd.AddCommand(newMoveCmd())
|
|
cmd.AddCommand(newUnlockerCmd())
|
|
cmd.AddCommand(newImportCmd())
|
|
cmd.AddCommand(newEncryptCmd())
|
|
cmd.AddCommand(newDecryptCmd())
|
|
cmd.AddCommand(newVersionCmd())
|
|
cmd.AddCommand(newInfoCmd())
|
|
cmd.AddCommand(newCompletionCmd())
|
|
|
|
secret.Debug("newRootCmd completed")
|
|
|
|
return cmd
|
|
}
|