Full Code of hypersleep/easyssh for AI

master 70879c819ea1 cached
6 files
9.1 KB
2.7k tokens
10 symbols
1 requests
Download .txt
Repository: hypersleep/easyssh
Branch: master
Commit: 70879c819ea1
Files: 6
Total size: 9.1 KB

Directory structure:
gitextract_a379wuus/

├── .gitignore
├── README.md
├── easyssh.go
├── easyssh_test.go
└── example/
    ├── run.go
    └── scp.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof


================================================
FILE: README.md
================================================
# easyssh

## Description

Package easyssh provides a simple implementation of some SSH protocol features in Go.
You can simply run command on remote server or upload a file even simple than native console SSH client.
Do not need to think about Dials, sessions, defers and public keys...Let easyssh will be think about it!

## So easy to use!

[Run a command on remote server and get STDOUT output](https://github.com/hypersleep/easyssh/blob/master/example/run.go)

[Upload a file to remote server](https://github.com/hypersleep/easyssh/blob/master/example/scp.go)


================================================
FILE: easyssh.go
================================================
// Package easyssh provides a simple implementation of some SSH protocol
// features in Go. You can simply run a command on a remote server or get a file
// even simpler than native console SSH client. You don't need to think about
// Dials, sessions, defers, or public keys... Let easyssh think about it!
package easyssh

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"path/filepath"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Contains main authority information.
// User field should be a name of user on remote server (ex. john in ssh john@example.com).
// Server field should be a remote machine address (ex. example.com in ssh john@example.com)
// Key is a path to private key on your local machine.
// Port is SSH server port on remote machine.
// Note: easyssh looking for private key in user's home directory (ex. /home/john + Key).
// Then ensure your Key begins from '/' (ex. /.ssh/id_rsa)
type MakeConfig struct {
	User     string
	Server   string
	Key      string
	Port     string
	Password string
}

// returns ssh.Signer from user you running app home path + cutted key path.
// (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") )
func getKeyFile(keypath string) (ssh.Signer, error) {
	usr, err := user.Current()
	if err != nil {
		return nil, err
	}

	file := usr.HomeDir + keypath
	buf, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}

	pubkey, err := ssh.ParsePrivateKey(buf)
	if err != nil {
		return nil, err
	}

	return pubkey, nil
}

// connects to remote server using MakeConfig struct and returns *ssh.Session
func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
	// auths holds the detected ssh auth methods
	auths := []ssh.AuthMethod{}

	// figure out what auths are requested, what is supported
	if ssh_conf.Password != "" {
		auths = append(auths, ssh.Password(ssh_conf.Password))
	}

	if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
		auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers))
		defer sshAgent.Close()
	}

	if pubkey, err := getKeyFile(ssh_conf.Key); err == nil {
		auths = append(auths, ssh.PublicKeys(pubkey))
	}

	config := &ssh.ClientConfig{
		User: ssh_conf.User,
		Auth: auths,
	}

	client, err := ssh.Dial("tcp", ssh_conf.Server+":"+ssh_conf.Port, config)
	if err != nil {
		return nil, err
	}

	session, err := client.NewSession()
	if err != nil {
		return nil, err
	}

	return session, nil
}

// Stream returns one channel that combines the stdout and stderr of the command
// as it is run on the remote machine, and another that sends true when the
// command is done. The sessions and channels will then be closed.
func (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdout chan string, stderr chan string, done chan bool, err error) {
	// connect to remote host
	session, err := ssh_conf.connect()
	if err != nil {
		return stdout, stderr, done, err
	}
	// connect to both outputs (they are of type io.Reader)
	outReader, err := session.StdoutPipe()
	if err != nil {
		return stdout, stderr, done, err
	}
	errReader, err := session.StderrPipe()
	if err != nil {
		return stdout, stderr, done, err
	}
	// combine outputs, create a line-by-line scanner
	stdoutReader := io.MultiReader(outReader)
	stderrReader := io.MultiReader(errReader)
	err = session.Start(command)
	stdoutScanner := bufio.NewScanner(stdoutReader)
	stderrScanner := bufio.NewScanner(stderrReader)
	// continuously send the command's output over the channel
	stdoutChan := make(chan string)
	stderrChan := make(chan string)
	done = make(chan bool)

	go func(stdoutScanner, stderrScanner *bufio.Scanner, stdoutChan, stderrChan chan string, done chan bool) {
		defer close(stdoutChan)
		defer close(stderrChan)
		defer close(done)

		timeoutChan := time.After(time.Duration(timeout) * time.Second)
		res := make(chan bool, 1)

		go func() {
			for stdoutScanner.Scan() {
				stdoutChan <- stdoutScanner.Text()
			}
			for stderrScanner.Scan() {
				stderrChan <- stderrScanner.Text()
			}
			// close all of our open resources
			res <- true
		}()

		select {
		case <-res:
			stdoutChan <- ""
			stderrChan <- ""
			done <- true
		case <-timeoutChan:
			stdoutChan <- ""
			stderrChan <- "Run Command Timeout!"
			done <- false
		}

		session.Close()
	}(stdoutScanner, stderrScanner, stdoutChan, stderrChan, done)
	return stdoutChan, stderrChan, done, err
}

// Runs command on remote machine and returns its stdout as a string
func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr string, errStr string, isTimeout bool, err error) {
	stdoutChan, stderrChan, doneChan, err := ssh_conf.Stream(command, timeout)
	if err != nil {
		return outStr, errStr, isTimeout, err
	}
	// read from the output channel until the done signal is passed
	stillGoing := true
	for stillGoing {
		select {
		case isTimeout = <-doneChan:
			stillGoing = false
		case outline := <-stdoutChan:
			outStr += outline + "\n"
		case errline := <-stderrChan:
			errStr += errline + "\n"
		}
	}
	// return the concatenation of all signals from the output channel
	return outStr, errStr, isTimeout, err
}

// Scp uploads sourceFile to remote machine like native scp console app.
func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string) error {
	session, err := ssh_conf.connect()

	if err != nil {
		return err
	}
	defer session.Close()

	targetFile := filepath.Base(etargetFile)

	src, srcErr := os.Open(sourceFile)

	if srcErr != nil {
		return srcErr
	}

	srcStat, statErr := src.Stat()

	if statErr != nil {
		return statErr
	}

	go func() {
		w, _ := session.StdinPipe()

		fmt.Fprintln(w, "C0644", srcStat.Size(), targetFile)

		if srcStat.Size() > 0 {
			io.Copy(w, src)
			fmt.Fprint(w, "\x00")
			w.Close()
		} else {
			fmt.Fprint(w, "\x00")
			w.Close()
		}
	}()

	if err := session.Run(fmt.Sprintf("scp -tr %s", etargetFile)); err != nil {
		return err
	}

	return nil
}


================================================
FILE: easyssh_test.go
================================================
package easyssh

import (
	"testing"
)

var sshConfig = &MakeConfig{
	User:     "root",
	Server:   "172.30.19.2",
	Password: "password",
	//Key:  "/.ssh/id_rsa",
	Port: "22",
}

func TestStream(t *testing.T) {
	t.Parallel()
	// input command/output string pairs
	testCases := [][]string{
		{`for i in $(seq 1 5); do echo "$i"; done`, "12345"},
		{`echo "test"`, "test"},
	}
	for _, testCase := range testCases {
		outChannel, errChannel, done, err := sshConfig.Stream(testCase[0], 10)
		if err != nil {
			t.Errorf("Stream failed: %s", err)
		}
		stillGoing := true
		stdout := ""
		stderr := ""
		for stillGoing {
			select {
			case <-done:
				stillGoing = false
			case line := <-outChannel:
				stdout += line
			case line := <-errChannel:
				stderr += line
			}
		}
		if stdout != testCase[1] {
			t.Error("Output didn't match expected: %s,%s", stdout, stderr)
		}
	}
}

func TestRun(t *testing.T) {
	t.Parallel()
	commands := []string{
		"echo test", `for i in $(ls); do echo "$i"; done`, "ls",
	}
	for _, cmd := range commands {
		stdout, stderr, istimeout, err := sshConfig.Run(cmd, 10)
		if err != nil {
			t.Errorf("Run failed: %s", err)
		}
		if stdout == "" {
			t.Errorf("Output was empty for command: %s,%s,%s", cmd, stdout, stderr, istimeout)
		}
	}
}


================================================
FILE: example/run.go
================================================
package main

import (
	"fmt"

	"github.com/hypersleep/easyssh"
)

func main() {
	// Create MakeConfig instance with remote username, server address and path to private key.
	ssh := &easyssh.MakeConfig{
		User:   "john",
		Server: "example.com",
		// Optional key or Password without either we try to contact your agent SOCKET
		//Password: "password",
		Key:  "/.ssh/id_rsa",
		Port: "22",
	}

	// Call Run method with command you want to run on remote server.
	stdout, stderr, done, err := ssh.Run("ps ax", 60)
	// Handle errors
	if err != nil {
		panic("Can't run remote command: " + err.Error())
	} else {
		fmt.Println("don is :", done, "stdout is :", stdout, ";   stderr is :", stderr)
	}

}


================================================
FILE: example/scp.go
================================================
package main

import (
	"fmt"
	"github.com/hypersleep/easyssh"
)

func main() {
	// Create MakeConfig instance with remote username, server address and path to private key.
	ssh := &easyssh.MakeConfig{
		User:     "root",
		Server:   "example.com",
		Password: "123qwe",
		Port:     "22",
	}

	// Call Scp method with file you want to upload to remote server.
	err := ssh.Scp("/root/source.csv", "/tmp/target.csv")

	// Handle errors
	if err != nil {
		panic("Can't run remote command: " + err.Error())
	} else {
		fmt.Println("success")

	}
}
Download .txt
gitextract_a379wuus/

├── .gitignore
├── README.md
├── easyssh.go
├── easyssh_test.go
└── example/
    ├── run.go
    └── scp.go
Download .txt
SYMBOL INDEX (10 symbols across 4 files)

FILE: easyssh.go
  type MakeConfig (line 29) | type MakeConfig struct
    method connect (line 60) | func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) {
    method Stream (line 99) | func (ssh_conf *MakeConfig) Stream(command string, timeout int) (stdou...
    method Run (line 161) | func (ssh_conf *MakeConfig) Run(command string, timeout int) (outStr s...
    method Scp (line 183) | func (ssh_conf *MakeConfig) Scp(sourceFile string, etargetFile string)...
  function getKeyFile (line 39) | func getKeyFile(keypath string) (ssh.Signer, error) {

FILE: easyssh_test.go
  function TestStream (line 15) | func TestStream(t *testing.T) {
  function TestRun (line 46) | func TestRun(t *testing.T) {

FILE: example/run.go
  function main (line 9) | func main() {

FILE: example/scp.go
  function main (line 8) | func main() {
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (10K chars).
[
  {
    "path": ".gitignore",
    "chars": 266,
    "preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
  },
  {
    "path": "README.md",
    "chars": 565,
    "preview": "# easyssh\n\n## Description\n\nPackage easyssh provides a simple implementation of some SSH protocol features in Go.\nYou can"
  },
  {
    "path": "easyssh.go",
    "chars": 5948,
    "preview": "// Package easyssh provides a simple implementation of some SSH protocol\n// features in Go. You can simply run a command"
  },
  {
    "path": "easyssh_test.go",
    "chars": 1269,
    "preview": "package easyssh\n\nimport (\n\t\"testing\"\n)\n\nvar sshConfig = &MakeConfig{\n\tUser:     \"root\",\n\tServer:   \"172.30.19.2\",\n\tPassw"
  },
  {
    "path": "example/run.go",
    "chars": 698,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/hypersleep/easyssh\"\n)\n\nfunc main() {\n\t// Create MakeConfig instance with rem"
  },
  {
    "path": "example/scp.go",
    "chars": 544,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/hypersleep/easyssh\"\n)\n\nfunc main() {\n\t// Create MakeConfig instance with remo"
  }
]

About this extraction

This page contains the full source code of the hypersleep/easyssh GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (9.1 KB), approximately 2.7k tokens, and a symbol index with 10 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!