mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
- Fixed critical error checking (errcheck) for file operations, HTTP responses, JSON operations - Added proper JSON encoding error handling (errchkjson) in HTTP handlers - Fixed built-in redefinition by renaming max variable to maxETag - Optimized range loops to avoid copying large structs (gocritic) - Resolved variable shadowing issues in multiple functions (govet) - Improved code structure with nesting reduction (gocritic) - Enhanced test robustness with proper error handling Remaining issues are primarily style/documentation related (revive comments).
152 lines
3.6 KiB
Go
152 lines
3.6 KiB
Go
// Package ssh provides simple SSH operations used during device setup and migration.
|
|
package ssh
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// Client wraps an SSH client to perform operations on SoundTouch speakers.
|
|
type Client struct {
|
|
Host string
|
|
User string
|
|
}
|
|
|
|
// NewClient creates a new SSH client for the given host.
|
|
func NewClient(host string) *Client {
|
|
return &Client{
|
|
Host: host,
|
|
User: "root",
|
|
}
|
|
}
|
|
|
|
// getConfig returns the SSH client configuration.
|
|
func (c *Client) getConfig() *ssh.ClientConfig {
|
|
return &ssh.ClientConfig{
|
|
User: c.User,
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.Password(""), // Default password for SoundTouch root is often empty or not used with these settings
|
|
},
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: 10 * time.Second,
|
|
Config: ssh.Config{
|
|
KeyExchanges: []string{
|
|
"diffie-hellman-group1-sha1",
|
|
"diffie-hellman-group14-sha1",
|
|
"ecdh-sha2-nistp256",
|
|
"ecdh-sha2-nistp384",
|
|
"ecdh-sha2-nistp521",
|
|
"curve25519-sha256@libssh.org",
|
|
},
|
|
Ciphers: []string{
|
|
"aes128-ctr",
|
|
"aes192-ctr",
|
|
"aes256-ctr",
|
|
"aes128-cbc",
|
|
"3des-cbc",
|
|
"aes128-gcm@openssh.com",
|
|
"arcfour256",
|
|
"arcfour128",
|
|
},
|
|
},
|
|
HostKeyAlgorithms: []string{
|
|
ssh.KeyAlgoRSASHA256,
|
|
ssh.KeyAlgoRSASHA512,
|
|
ssh.KeyAlgoRSA,
|
|
ssh.KeyAlgoECDSA256,
|
|
ssh.KeyAlgoECDSA384,
|
|
ssh.KeyAlgoECDSA521,
|
|
ssh.KeyAlgoED25519,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Run executes a command on the remote host and returns the combined stdout and stderr.
|
|
func (c *Client) Run(command string) (string, error) {
|
|
config := c.getConfig()
|
|
|
|
client, err := ssh.Dial("tcp", c.Host+":22", config)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to dial: %w", err)
|
|
}
|
|
|
|
defer func() { _ = client.Close() }()
|
|
|
|
session, err := client.NewSession()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create session: %w", err)
|
|
}
|
|
|
|
defer func() { _ = session.Close() }()
|
|
|
|
output, err := session.CombinedOutput(command)
|
|
|
|
return string(output), err
|
|
}
|
|
|
|
// UploadContent uploads the given content to a file on the remote host.
|
|
// It uses a simple approach: echoing the content into a file.
|
|
// For larger files, a proper SCP or SFTP implementation would be better.
|
|
func (c *Client) UploadContent(content []byte, remotePath string) error {
|
|
config := c.getConfig()
|
|
|
|
client, err := ssh.Dial("tcp", c.Host+":22", config)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to dial: %w", err)
|
|
}
|
|
|
|
defer func() { _ = client.Close() }()
|
|
|
|
session, err := client.NewSession()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create session: %w", err)
|
|
}
|
|
|
|
defer func() { _ = session.Close() }()
|
|
|
|
// Use a pipe to write content to the remote command's stdin
|
|
stdin, err := session.StdinPipe()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get stdin pipe: %w", err)
|
|
}
|
|
|
|
// Capture stderr to get better error messages
|
|
stderr, err := session.StderrPipe()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get stderr pipe: %w", err)
|
|
}
|
|
|
|
// Read content from stdin and write to the remote file
|
|
cmd := fmt.Sprintf("cat > %s", remotePath)
|
|
|
|
// Start the command
|
|
startErr := session.Start(cmd)
|
|
if startErr != nil {
|
|
return fmt.Errorf("failed to start upload command: %w", startErr)
|
|
}
|
|
|
|
// Write content and close stdin
|
|
_, err = stdin.Write(content)
|
|
_ = stdin.Close()
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write content to stdin: %w", err)
|
|
}
|
|
|
|
// Read stderr in case of failure
|
|
stderrBuf := new(strings.Builder)
|
|
|
|
go func() { _, _ = io.Copy(stderrBuf, stderr) }()
|
|
|
|
// Wait for the command to finish
|
|
if err := session.Wait(); err != nil {
|
|
return fmt.Errorf("failed to finish upload: %w (stderr: %s)", err, stderrBuf.String())
|
|
}
|
|
|
|
return nil
|
|
}
|