feat: ssh as shell command (#67)

* feat(ssh): add action to run shell commands over a remote server

Signed-off-by: mudler <mudler@localai.io>

* fix(github): correctly get content

Signed-off-by: mudler <mudler@localai.io>

---------

Signed-off-by: mudler <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2025-03-20 18:55:19 +01:00
committed by GitHub
parent d54abc3ed0
commit 1b187444fc
4 changed files with 113 additions and 6 deletions

View File

@@ -60,9 +60,9 @@ func (g *GithubRepositoryGetContent) Run(ctx context.Context, params action.Acti
return action.ActionResult{Result: resultString}, err
}
var content string
if fileContent.Content != nil {
content = *fileContent.Content
content, err := fileContent.GetContent()
if err != nil {
return action.ActionResult{}, err
}
return action.ActionResult{Result: fmt.Sprintf("File %s\nContent:%s\n", result.Path, content)}, err

View File

@@ -43,9 +43,9 @@ func (g *GithubRepositoryREADME) Run(ctx context.Context, params action.ActionPa
return action.ActionResult{Result: resultString}, err
}
var content string
if fileContent.Content != nil {
content = *fileContent.Content
content, err := fileContent.GetContent()
if err != nil {
return action.ActionResult{}, err
}
return action.ActionResult{Result: content}, err

103
services/actions/shell.go Normal file
View File

@@ -0,0 +1,103 @@
package actions
import (
"context"
"fmt"
"log"
"github.com/mudler/LocalAgent/core/action"
"github.com/sashabaranov/go-openai/jsonschema"
"golang.org/x/crypto/ssh"
)
func NewShell(config map[string]string) *ShellAction {
return &ShellAction{
privateKey: config["privateKey"],
}
}
type ShellAction struct {
privateKey string
}
func (a *ShellAction) Run(ctx context.Context, params action.ActionParams) (action.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 action.ActionResult{}, err
}
output, err := sshCommand(a.privateKey, result.Command, result.User, result.Host)
if err != nil {
return action.ActionResult{}, err
}
return action.ActionResult{Result: output}, nil
}
func (a *ShellAction) Definition() action.ActionDefinition {
return action.ActionDefinition{
Name: "shell",
Description: "Run a shell command on a remote server.",
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"},
}
}
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
}