Files
keyfunc/internal/cli/ssh/to.go
T
clawbot 1d1c8182be
check / check (push) Failing after 2s
Current templates: safe.directory, golangci-lint v2.14.0, fetch-depth 0, the policy's last stage (closes #50)
Brings keyfunc to the current sneak/prompts templates: REPO_POLICIES.md and .golangci.yml are the template copies, the lint phase runs golangci-lint v2.14.0 on the template digest (its one new finding fixed), and .dockerignore gains the template line for submodule configs. The stage that compiles keyfunc marks /src safe for git, so a context sent as a tar stream still stamps the tag or short commit. The last stage is now a development environment, as the policy asks of a non-server repo: run the tool as docker run IMAGE keyfunc .... The CI checkout fetches tags.

Deviation: .gitea/workflows/check.yml differs from the template copy by fetch-depth: 0, which REPO_POLICIES.md requires.

Model: opus-5-5
2026-10-04 08:59:08 +02:00

104 lines
2.6 KiB
Go

package ssh
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"slices"
"syscall"
"github.com/spf13/cobra"
)
// StatusError says the tool should end with the status ssh ended with.
// Only "ssh to" gives one back; every other error ends the tool with
// status 1.
type StatusError struct {
Status int
}
// Error says which status ssh ended with.
func (e StatusError) Error() string {
return fmt.Sprintf("ssh exited with status %d", e.Status)
}
// to returns the command that runs ssh with the derived key held by an
// agent of the tool's own.
func to() *cobra.Command {
cmd := &cobra.Command{
Use: "to <host> [ssh arguments...]",
Short: "run ssh with the derived key served from its own agent",
Long: "Serves the derived key from an SSH agent that runs " +
"inside the tool and points the system ssh at it. The host " +
"and everything after it are given to ssh unchanged, the " +
"tool ends with the status ssh ended with, and the key is " +
"never written to disk.",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
key, comment, err := derived(cmd)
if err != nil {
return err
}
served, err := key.Serve(cmd.Context(), comment)
if err != nil {
return err
}
defer served.Stop()
argv := slices.Concat([]string{
"-o", "IdentityAgent=" + served.Socket(),
}, args)
return connect(cmd.Context(), argv)
},
}
// Everything from the host onwards belongs to ssh, so flag
// reading stops at the first argument that is not a flag.
cmd.Flags().SetInterspersed(false)
addComment(cmd)
return cmd
}
// connect runs ssh on the terminal the tool was given and turns the
// status it ended with into the status the tool ends with.
func connect(ctx context.Context, argv []string) error {
//nolint:gosec // the arguments are the user's own, meant for ssh
command := exec.CommandContext(ctx, "ssh", argv...)
command.Env = childEnv()
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
// A cancelled context means a signal ended the tool. Send ssh a
// SIGTERM rather than the default kill, so it puts the terminal
// back the way it found it before it goes.
command.Cancel = func() error {
return command.Process.Signal(syscall.SIGTERM)
}
err := command.Run()
if err == nil {
return nil
}
if ended, ok := errors.AsType[*exec.ExitError](err); ok {
status := ended.ExitCode()
if status < 0 {
// A signal ended ssh, and a signal has no status of its
// own to pass on.
status = 1
}
return StatusError{Status: status}
}
return fmt.Errorf("running ssh: %w", err)
}