check / check (push) Failing after 2s
age encrypt -o and age decrypt -o always renamed a new file over the named path, replacing a symlink, a named pipe or a device such as /dev/null with a regular file. A path that is the same file as the tool's standard output or standard error, under any name, is now written to that stream, so the file it is redirected to keeps its contents. Any other path is looked at without following a final symlink: nothing there or a regular file is replaced by rename as before, a symlink gets the same treatment for what it points at and is refused if it points at nothing, and anything else is written to directly, without catching signals. The README says so, and that a replaced file has mode 0600. Model: opus-5-5
104 lines
3.2 KiB
Go
104 lines
3.2 KiB
Go
// Package cli builds the command tree and runs it.
|
|
package cli
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"runtime/debug"
|
|
|
|
"github.com/spf13/cobra"
|
|
"sneak.berlin/go/keyfunc/internal/cli/age"
|
|
"sneak.berlin/go/keyfunc/internal/cli/mnemonic"
|
|
"sneak.berlin/go/keyfunc/internal/cli/options"
|
|
"sneak.berlin/go/keyfunc/internal/cli/ssh"
|
|
)
|
|
|
|
// devVersion is what Version holds until a build stamps a real one.
|
|
const devVersion = "dev"
|
|
|
|
// Version is what --version prints. make build stamps it with -ldflags.
|
|
//
|
|
//nolint:gochecknoglobals // set at build time with -ldflags
|
|
var Version = devVersion
|
|
|
|
// resolveVersion chooses what --version reports. A value stamped at
|
|
// build time wins. Otherwise, for a binary from go install, the module
|
|
// version recorded in the build info is used, unless that is empty or
|
|
// the "(devel)" of a local build. When neither names a version, the
|
|
// "dev" fallback stays.
|
|
func resolveVersion(stamped string, info *debug.BuildInfo) string {
|
|
if stamped != devVersion {
|
|
return stamped
|
|
}
|
|
|
|
if info != nil && info.Main.Version != "" &&
|
|
info.Main.Version != "(devel)" {
|
|
return info.Main.Version
|
|
}
|
|
|
|
return devVersion
|
|
}
|
|
|
|
// Root returns the whole command tree.
|
|
func Root() *cobra.Command {
|
|
info, _ := debug.ReadBuildInfo()
|
|
|
|
root := &cobra.Command{
|
|
Use: "keyfunc",
|
|
Short: "derive key pairs from a BIP-39 mnemonic",
|
|
Long: "keyfunc turns a BIP-39 mnemonic into key pairs that can " +
|
|
"be recreated from that mnemonic at any time. The same " +
|
|
"mnemonic, key type and index always give the same key.",
|
|
Version: resolveVersion(Version, info),
|
|
SilenceUsage: true,
|
|
SilenceErrors: true,
|
|
}
|
|
|
|
options.Add(root)
|
|
root.AddCommand(ssh.Command(), age.Command(), mnemonic.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 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 a new file to rename over the named one, so the unfinished file
|
|
// is removed.
|
|
func Main() int {
|
|
err := Root().Execute()
|
|
if err == nil {
|
|
return 0
|
|
}
|
|
|
|
if passed, ok := errors.AsType[ssh.StatusError](err); ok {
|
|
return passed.Status
|
|
}
|
|
|
|
fmt.Fprintln(os.Stderr, "keyfunc: "+err.Error())
|
|
|
|
return 1
|
|
}
|