mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 11:26:58 +00:00
- add WithNoHostKeyCheck option for ephemeral server IPs - upload binary to genesis then distribute to other nodes (faster) - improve provisioning error handling for cleanup on partial failure
105 lines
2.9 KiB
Go
105 lines
2.9 KiB
Go
package remotessh
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/inspector"
|
|
)
|
|
|
|
// SSHOption configures SSH command behavior.
|
|
type SSHOption func(*sshOptions)
|
|
|
|
type sshOptions struct {
|
|
agentForward bool
|
|
noHostKeyCheck bool
|
|
}
|
|
|
|
// WithAgentForward enables SSH agent forwarding (-A flag).
|
|
// Used by push fanout so the hub can reach targets via the forwarded agent.
|
|
func WithAgentForward() SSHOption {
|
|
return func(o *sshOptions) { o.agentForward = true }
|
|
}
|
|
|
|
// WithNoHostKeyCheck disables host key verification and uses /dev/null as known_hosts.
|
|
// Use for ephemeral servers (sandbox) where IPs are frequently recycled.
|
|
func WithNoHostKeyCheck() SSHOption {
|
|
return func(o *sshOptions) { o.noHostKeyCheck = true }
|
|
}
|
|
|
|
// UploadFile copies a local file to a remote host via SCP.
|
|
// Requires node.SSHKey to be set (via PrepareNodeKeys).
|
|
func UploadFile(node inspector.Node, localPath, remotePath string, opts ...SSHOption) error {
|
|
if node.SSHKey == "" {
|
|
return fmt.Errorf("no SSH key for %s (call PrepareNodeKeys first)", node.Name())
|
|
}
|
|
|
|
var cfg sshOptions
|
|
for _, o := range opts {
|
|
o(&cfg)
|
|
}
|
|
|
|
dest := fmt.Sprintf("%s@%s:%s", node.User, node.Host, remotePath)
|
|
|
|
args := []string{"-o", "ConnectTimeout=10", "-i", node.SSHKey}
|
|
if cfg.noHostKeyCheck {
|
|
args = append([]string{"-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"}, args...)
|
|
} else {
|
|
args = append([]string{"-o", "StrictHostKeyChecking=accept-new"}, args...)
|
|
}
|
|
args = append(args, localPath, dest)
|
|
|
|
cmd := exec.Command("scp", args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("SCP to %s failed: %w", node.Host, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RunSSHStreaming executes a command on a remote host via SSH,
|
|
// streaming stdout/stderr to the local terminal in real-time.
|
|
// Requires node.SSHKey to be set (via PrepareNodeKeys).
|
|
func RunSSHStreaming(node inspector.Node, command string, opts ...SSHOption) error {
|
|
if node.SSHKey == "" {
|
|
return fmt.Errorf("no SSH key for %s (call PrepareNodeKeys first)", node.Name())
|
|
}
|
|
|
|
var cfg sshOptions
|
|
for _, o := range opts {
|
|
o(&cfg)
|
|
}
|
|
|
|
args := []string{"-o", "ConnectTimeout=10", "-i", node.SSHKey}
|
|
if cfg.noHostKeyCheck {
|
|
args = append([]string{"-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"}, args...)
|
|
} else {
|
|
args = append([]string{"-o", "StrictHostKeyChecking=accept-new"}, args...)
|
|
}
|
|
if cfg.agentForward {
|
|
args = append(args, "-A")
|
|
}
|
|
args = append(args, fmt.Sprintf("%s@%s", node.User, node.Host), command)
|
|
|
|
cmd := exec.Command("ssh", args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("SSH to %s failed: %w", node.Host, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SudoPrefix returns "sudo " for non-root users, empty for root.
|
|
func SudoPrefix(node inspector.Node) string {
|
|
if node.User == "root" {
|
|
return ""
|
|
}
|
|
return "sudo "
|
|
}
|