Files
LocalAGI/services/actions/shell.go
Ettore Di Giacinto e7111c6554 Rename
2025-04-08 22:18:32 +02:00

179 lines
4.3 KiB
Go

package actions
import (
"context"
"fmt"
"log"
"github.com/mudler/LocalAGI/core/types"
"github.com/mudler/LocalAGI/pkg/config"
"github.com/sashabaranov/go-openai/jsonschema"
"golang.org/x/crypto/ssh"
)
func NewShell(config map[string]string) *ShellAction {
return &ShellAction{
privateKey: config["privateKey"],
user: config["user"],
host: config["host"],
customName: config["customName"],
customDescription: config["customDescription"],
}
}
type ShellAction struct {
privateKey string
user, host string
customName string
customDescription string
}
func (a *ShellAction) Run(ctx context.Context, params types.ActionParams) (types.ActionResult, error) {
result := struct {
Command string `json:"command"`
Host string `json:"host"`
User string `json:"user"`
}{}
err := params.Unmarshal(&result)
if err != nil {
fmt.Printf("error: %v", err)
return types.ActionResult{}, err
}
if a.host != "" && a.user != "" {
result.Host = a.host
result.User = a.user
}
output, err := sshCommand(a.privateKey, result.Command, result.User, result.Host)
if err != nil {
return types.ActionResult{}, err
}
return types.ActionResult{Result: output}, nil
}
func (a *ShellAction) Definition() types.ActionDefinition {
name := "shell"
description := "Run a shell command on a remote server."
if a.customName != "" {
name = a.customName
}
if a.customDescription != "" {
description = a.customDescription
}
if a.host != "" && a.user != "" {
return types.ActionDefinition{
Name: types.ActionDefinitionName(name),
Description: description,
Properties: map[string]jsonschema.Definition{
"command": {
Type: jsonschema.String,
Description: "The command to run on the remote server.",
},
},
Required: []string{"command"},
}
}
return types.ActionDefinition{
Name: types.ActionDefinitionName(name),
Description: description,
Properties: map[string]jsonschema.Definition{
"command": {
Type: jsonschema.String,
Description: "The command to run on the remote server.",
},
"host": {
Type: jsonschema.String,
Description: "The host of the remote server. e.g. ip:port",
},
"user": {
Type: jsonschema.String,
Description: "The user to connect to the remote server.",
},
},
Required: []string{"command", "host", "user"},
}
}
// ShellConfigMeta returns the metadata for Shell action configuration fields
func ShellConfigMeta() []config.Field {
return []config.Field{
{
Name: "privateKey",
Label: "Private Key",
Type: config.FieldTypeTextarea,
Required: true,
HelpText: "SSH private key for connecting to remote servers",
},
{
Name: "user",
Label: "Default User",
Type: config.FieldTypeText,
HelpText: "Default SSH user for connecting to remote servers",
},
{
Name: "host",
Label: "Default Host",
Type: config.FieldTypeText,
HelpText: "Default host for SSH connections (e.g., hostname:port)",
},
{
Name: "customName",
Label: "Custom Action Name",
Type: config.FieldTypeText,
HelpText: "Custom name for this action",
},
{
Name: "customDescription",
Label: "Custom Description",
Type: config.FieldTypeTextarea,
HelpText: "Custom description for this action",
},
}
}
func sshCommand(privateKey, command, user, host string) (string, error) {
// Create signer from private key string
key, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
log.Fatalf("failed to parse private key: %v", err)
}
// SSH client configuration
config := &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// Connect to SSH server
client, err := ssh.Dial("tcp", host, config)
if err != nil {
return "", fmt.Errorf("failed to dial: %v", err)
}
defer client.Close()
// Open a new session
session, err := client.NewSession()
if err != nil {
return "", fmt.Errorf("failed to create session: %v", err)
}
defer session.Close()
// Run a command
output, err := session.CombinedOutput(command)
if err != nil {
return "", fmt.Errorf("failed to run: %v", err)
}
return string(output), nil
}
func (a *ShellAction) Plannable() bool {
return true
}