SSH Remote Runner
A tool that connects to a remote host via SSH using key-based authentication, runs system commands (hostname, disk usage, uptime), streams the output, and uploads a configuration file via SFTP, all without shelling out to the ssh binary.
package main
import (
"bytes"
"fmt"
"io"
"log"
"os"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
func main() {
host := os.Getenv("SSH_HOST") // e.g., "10.0.1.50"
user := os.Getenv("SSH_USER") // e.g., "deploy"
keyPath := os.Getenv("SSH_KEY") // e.g., "/home/deploy/.ssh/id_ed25519"
if host == "" || user == "" || keyPath == "" {
log.Fatal("set SSH_HOST, SSH_USER, SSH_KEY env vars")
}
// Build config
key, err := os.ReadFile(keyPath)
if err != nil {
log.Fatalf("read key: %v", err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatalf("parse key: %v", err)
}
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 10 * time.Second,
}
// Connect
client, err := ssh.Dial("tcp", host+":22", config)
if err != nil {
log.Fatalf("dial: %v", err)
}
defer client.Close()
fmt.Printf("✓ Connected to %s\n", host)
// Run commands
commands := []string{
"hostname",
"df -h /",
"uptime",
}
for _, cmd := range commands {
session, err := client.NewSession()
if err != nil {
log.Fatalf("session: %v", err)
}
var buf bytes.Buffer
session.Stdout = &buf
if err := session.Run(cmd); err != nil {
log.Printf("command %q failed: %v", cmd, err)
} else {
fmt.Printf("$ %s\n%s\n", cmd, buf.String())
}
session.Close()
}
// Upload a file via SFTP
sftpClient, err := sftp.NewClient(client)
if err != nil {
log.Fatalf("sftp: %v", err)
}
defer sftpClient.Close()
configContent := []byte("max_connections: 100\nlog_level: info\n")
dstFile, err := sftpClient.Create("/tmp/app.conf")
if err != nil {
log.Fatalf("create remote file: %v", err)
}
if _, err := io.Copy(dstFile, bytes.NewReader(configContent)); err != nil {
log.Fatalf("write remote file: %v", err)
}
dstFile.Close()
fmt.Println("✓ Uploaded /tmp/app.conf")
}