Compare commits

..

53 commits
0.0.1 ... main

Author SHA1 Message Date
45fa8a0124
add break 2024-09-19 11:05:25 -04:00
5827a7434b
add error message to failing commands *facepalm* 2024-09-19 11:03:10 -04:00
0466a423ec
add error message to failing commands *facepalm* 2024-09-19 10:57:27 -04:00
88c55d3d73
add error message to failing commands *facepalm* 2024-09-19 10:53:21 -04:00
8c22105cf2
temp remove executable check 2024-09-18 22:23:18 -04:00
2ced31effd Update linux/utils.go 2024-08-25 00:51:29 +02:00
13c3c6241c Update go.mod 2024-08-25 00:35:38 +02:00
b14e2ccfb1
fix signals 2024-07-11 17:27:44 -04:00
8959b13a0d
test stuff 2024-07-11 17:13:58 -04:00
c395f09556
fix sysprocattr 2024-07-11 16:57:03 -04:00
a693fb5cd6
fix sysprocattr 2024-07-11 16:45:10 -04:00
2b190eccb7
fix goroutine call for commands with no handlers 2024-07-11 16:34:08 -04:00
2a59a80b11
remove debug stuff 2024-07-11 15:54:59 -04:00
ad63e15564
debug 2024-07-10 21:49:07 -04:00
f9e5426b1b
debug 2024-07-10 21:48:37 -04:00
3e5b7f99fc
debug 2024-07-10 21:47:50 -04:00
f627fde186
debug 2024-07-10 21:45:07 -04:00
8a7fdf0f43
debug 2024-07-10 21:43:11 -04:00
060a6a49eb
debug 2024-07-10 21:42:20 -04:00
db8c33a526
debug 2024-07-10 21:41:36 -04:00
6cbbe69223
debug 2024-07-10 21:37:45 -04:00
751d90c840
debug 2024-07-10 21:36:57 -04:00
69b891d598
debug 2024-07-10 20:24:17 -04:00
def7d60825
debug 2024-07-10 20:23:27 -04:00
8390e16616
switch to whichOut var for executable check 2024-07-10 20:22:32 -04:00
c0fb972b5f
debug thing 2024-07-10 20:20:18 -04:00
c7db79fe13
remove sources from which command 2024-07-10 20:16:13 -04:00
0fb6fcc14a
panic thingy 2024-07-09 19:21:07 -04:00
182f75e37a
put code in right palce 2024-07-09 19:13:10 -04:00
991429a290
update which options 2024-07-09 18:53:22 -04:00
5d646427bd
add env and source options to commands 2024-07-09 18:44:28 -04:00
992dd14272
add env options 2024-07-09 17:45:33 -04:00
bde6ae681e
add exit event 2024-07-06 15:09:17 -04:00
750686fd1f
add ability for live output, add events system 2024-07-06 11:48:45 -04:00
4e2c314c2c
add pip command and fix error codes on all commands 2024-07-05 12:05:22 -04:00
e6ff94c45f
add python command 2024-07-05 11:33:12 -04:00
cafa0dcca1
add node command 2024-07-05 11:28:33 -04:00
c7b6273166
remove garbage file 2024-07-04 21:58:04 -04:00
ef7c4f26f7
added 'command' shell command 2024-07-04 21:57:46 -04:00
2cc3517f37
fixed permission checking on commands, added "which" command. 2024-07-04 20:37:59 -04:00
d127424b9f
add custom exit code errors 2024-07-04 19:40:33 -04:00
76f276f69e
add sources option 2024-07-04 18:08:45 -04:00
0c1ffa1cd9
add rcfile flag 2024-07-04 17:47:16 -04:00
d219f1b005 Merge pull request 'Update dependency go to v1.22.5' (#2) from renovate/go-1.x into main
Reviewed-on: https://git.shadowhosting.xyz/Eggactyl/shell/pulls/2
2024-07-04 22:33:44 +01:00
41a68740a2 Merge branch 'main' into renovate/go-1.x 2024-07-04 22:33:30 +01:00
942735b726
fix permissions error, fix cwd path 2024-07-04 17:27:41 -04:00
368b99ecfa
properly handle error 2024-07-04 17:16:13 -04:00
be8fd4ca55
remove test file 2024-07-04 17:14:44 -04:00
c10fdf32b2
add executable check for commands 2024-07-04 17:14:10 -04:00
3a9d03a46f
testing exit codes 2024-07-04 16:46:29 -04:00
f9f879e7ee
remove command check in runner 2024-07-04 16:44:26 -04:00
5bb2d6d1b4
new runner structure 2024-07-04 16:40:13 -04:00
Renovate Bot
278d21cc48
Update dependency go to v1.22.5 2024-07-04 19:35:25 +00:00
11 changed files with 569 additions and 179 deletions

28
cmd/command.go Normal file
View file

@ -0,0 +1,28 @@
package cmd
import (
"errors"
"fmt"
"os/exec"
"strings"
)
func Command(cmd string) (string, error) {
command := exec.Command("command", "-v", cmd)
outputBytes, err := command.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() == 1 {
return "", ErrNotFound
} else {
return "", fmt.Errorf("command error: %w", err)
}
}
}
return strings.Trim(string(outputBytes), "\n"), nil
}

38
cmd/node.go Normal file
View file

@ -0,0 +1,38 @@
package cmd
import (
"errors"
"fmt"
"os/exec"
"strings"
)
var NodeNotFound = errors.New("nodejs not found")
func Node(options BasicOptions, args ...string) (output string, err error) {
if _, err := Which("node", BasicOptions{
Env: options.Env,
Sources: options.Sources,
Cwd: options.Cwd,
}); err != nil {
if errors.Is(err, ErrNotFound) {
return "", NodeNotFound
} else {
return "", err
}
}
command := exec.Command("node", args...)
outputBytes, err := command.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return "", fmt.Errorf("command error: %w", err)
}
}
return strings.Trim(string(outputBytes), "\n"), nil
}

37
cmd/pip.go Normal file
View file

@ -0,0 +1,37 @@
package cmd
import (
"errors"
"fmt"
"os/exec"
"strings"
)
func Pip(options BasicOptions, args ...string) (output string, err error) {
if _, err := Which("python3", BasicOptions{
Env: options.Env,
Sources: options.Sources,
Cwd: options.Cwd,
}); err != nil {
if errors.Is(err, ErrNotFound) {
return "", PythonNotFound
} else {
return "", err
}
}
command := exec.Command("python3", "-m", "pip")
command.Args = append(command.Args, args...)
outputBytes, err := command.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return "", fmt.Errorf("command error: %w", err)
}
}
return strings.Trim(string(outputBytes), "\n"), nil
}

38
cmd/python.go Normal file
View file

@ -0,0 +1,38 @@
package cmd
import (
"errors"
"fmt"
"os/exec"
"strings"
)
var PythonNotFound = errors.New("python not found")
func Python(options BasicOptions, args ...string) (output string, err error) {
if _, err := Which("python3", BasicOptions{
Env: options.Env,
Sources: options.Sources,
Cwd: options.Cwd,
}); err != nil {
if errors.Is(err, ErrNotFound) {
return "", PythonNotFound
} else {
return "", err
}
}
command := exec.Command("python3", args...)
outputBytes, err := command.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
return "", fmt.Errorf("command error: %w", err)
}
}
return strings.Trim(string(outputBytes), "\n"), nil
}

49
cmd/which.go Normal file
View file

@ -0,0 +1,49 @@
package cmd
import (
"errors"
"fmt"
"os/exec"
"strings"
)
var ErrNotFound = errors.New("which: command not found")
type BasicOptions struct {
Env map[string]string
Sources []string
Cwd string
}
func Which(cmd string, options BasicOptions) (dir string, err error) {
var sourceCommand strings.Builder
for _, value := range options.Sources {
sourceCommand.WriteString(fmt.Sprintf("source %s && ", value))
}
command := exec.Command("which", cmd)
if options.Cwd != "" {
command.Dir = options.Cwd
}
for k, v := range options.Env {
command.Env = append(command.Env, fmt.Sprintf("%s=%s", k, v))
}
outputBytes, err := command.Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() == 1 {
return "", ErrNotFound
} else {
return "", fmt.Errorf("command error: %w", err)
}
}
}
return strings.Trim(string(outputBytes), "\n"), nil
}

4
go.mod
View file

@ -1,5 +1,5 @@
module git.shadowhosting.xyz/Eggactyl/shell
module git.eggactyl.cloud/Eggactyl/shell
go 1.22.4
go 1.22.5
require golang.org/x/sys v0.22.0

18
linux/events.go Normal file
View file

@ -0,0 +1,18 @@
package linux
const (
EventOutput = iota
EventExit
)
type EventOutputData struct {
Output string
CmdOptions CommandOptions
}
type EventExitData struct {
HasSucceeded bool
ExitCode int
CmdOptions CommandOptions
Error string
}

36
linux/interface.go Normal file
View file

@ -0,0 +1,36 @@
package linux
import (
"errors"
"io"
"sync"
)
type LinuxCommand struct {
Options CommandOptions
handlers map[int]interface{}
wg sync.WaitGroup
stdout io.ReadCloser
stderr io.ReadCloser
stdin io.WriteCloser
}
type CommandOptions struct {
Env map[string]string
Sources []string
Command string
Args []string
CustomErrors map[int8]error
Cwd string
Shell string
}
// Errors
var (
ErrFetchingCwd = errors.New("error fetching cwd")
ErrRunningCmd = errors.New("error running command")
ErrCommandNotFound = errors.New("error command not found")
ErrCommandNotExecutable = errors.New("error command not executable")
ErrInvalidHandler = errors.New("invalid handler")
ErrRunningEvt = errors.New("error running event")
)

248
linux/run.go Normal file
View file

@ -0,0 +1,248 @@
package linux
import (
"bufio"
"bytes"
"errors"
"fmt"
"golang.org/x/sys/unix"
"io"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
)
func NewCommand(options CommandOptions) (*LinuxCommand, error) {
if len(options.Shell) == 0 {
options.Shell = "/bin/bash"
}
if len(options.Cwd) == 0 {
cwd, err := os.Getwd()
if err != nil {
return nil, ErrFetchingCwd
}
options.Cwd = cwd
}
return &LinuxCommand{
Options: options,
handlers: make(map[int]interface{}),
}, nil
}
func (cmd *LinuxCommand) AddHandler(handler interface{}) error {
switch h := handler.(type) {
case func(data EventOutputData) error:
cmd.handlers[EventOutput] = h
break
case func(data EventExitData) error:
cmd.handlers[EventExit] = h
break
default:
return ErrInvalidHandler
}
return nil
}
func (cmd *LinuxCommand) Run() error {
//isCommandExecutable, err := cmd.isCommandExecutable(cmd.Options.Command)
//if err != nil {
// return err
//}
//
//if !isCommandExecutable {
// return ErrCommandNotExecutable
//}
var sourceCommand strings.Builder
for _, value := range cmd.Options.Sources {
sourceCommand.WriteString(fmt.Sprintf("source %s && ", value))
}
var commandOptions strings.Builder
commandOptions.WriteString(" ")
for index, arg := range cmd.Options.Args {
if len(cmd.Options.Args)-1 == index {
commandOptions.WriteString(fmt.Sprintf("%s", arg))
} else {
commandOptions.WriteString(fmt.Sprintf("%s ", arg))
}
}
command := exec.Command(cmd.Options.Shell, "-c", sourceCommand.String()+cmd.Options.Command+commandOptions.String())
command.SysProcAttr = &unix.SysProcAttr{Setsid: true}
command.Dir = cmd.Options.Cwd
for key, value := range cmd.Options.Env {
command.Env = append(command.Env, fmt.Sprintf("%s=%s", key, value))
}
var signalChannel chan os.Signal
signalChannel = make(chan os.Signal, 1)
signal.Notify(signalChannel, unix.SIGINT, unix.SIGTERM)
if len(cmd.handlers) != 0 {
var err error
cmd.stdout, err = command.StdoutPipe()
if err != nil {
return err
}
cmd.stdin, err = command.StdinPipe()
if err != nil {
return err
}
cmd.stderr, err = command.StderrPipe()
if err != nil {
return err
}
}
if err := command.Start(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() == 127 {
return ErrCommandNotFound
} else if _, ok := cmd.Options.CustomErrors[int8(exitErr.ExitCode())]; ok {
return cmd.Options.CustomErrors[int8(exitErr.ExitCode())]
} else {
return fmt.Errorf("%s: %w", ErrRunningCmd.Error(), err)
}
}
}
if len(cmd.handlers) != 0 {
cmd.wg.Add(2)
go func() {
defer cmd.wg.Done()
scanner := bufio.NewScanner(cmd.stderr)
for scanner.Scan() {
line := scanner.Text()
if h, ok := cmd.handlers[EventOutput]; ok {
if err := h.(func(data EventOutputData) error)(EventOutputData{
Output: line,
CmdOptions: cmd.Options,
}); err != nil {
return
}
}
}
}()
go func() {
defer cmd.wg.Done()
scanner := bufio.NewScanner(cmd.stdout)
for scanner.Scan() {
line := scanner.Text()
if h, ok := cmd.handlers[EventOutput]; ok {
if err := h.(func(data EventOutputData) error)(EventOutputData{
Output: line,
CmdOptions: cmd.Options,
}); err != nil {
return
}
}
}
}()
}
cmd.wg.Add(1)
go func() {
defer cmd.wg.Done()
select {
case _, ok := <-signalChannel:
if !ok {
return
}
if err := unix.Kill(-command.Process.Pid, syscall.SIGINT); err != nil {
return
}
}
}()
var exitInfo *EventExitData
if _, ok := cmd.handlers[EventExit]; ok {
exitInfo = &EventExitData{
HasSucceeded: true,
CmdOptions: cmd.Options,
}
}
if err := command.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.String() != "signal: interrupt" {
if exitErr.ExitCode() == 127 {
return ErrCommandNotFound
} else if _, ok := cmd.Options.CustomErrors[int8(exitErr.ExitCode())]; ok {
if h, ok := cmd.handlers[EventExit]; ok {
if exitInfo == nil {
return fmt.Errorf("%s: %w", ErrRunningCmd.Error(), err)
}
exitInfo.HasSucceeded = false
exitInfo.ExitCode = exitErr.ExitCode()
var stdoutData bytes.Buffer
if _, err := io.Copy(&stdoutData, cmd.stdout); err != nil {
return err
}
exitInfo.Error = stdoutData.String()
err := h.(func(data EventExitData) error)(*exitInfo)
if err != nil {
return fmt.Errorf("%s: %w", ErrRunningEvt.Error(), err)
}
}
return cmd.Options.CustomErrors[int8(exitErr.ExitCode())]
} else {
if h, ok := cmd.handlers[EventExit]; ok {
if exitInfo == nil {
return fmt.Errorf("%s: %w", ErrRunningEvt.Error(), err)
}
exitInfo.HasSucceeded = false
exitInfo.ExitCode = exitErr.ExitCode()
var stdoutData bytes.Buffer
if _, err := io.Copy(&stdoutData, cmd.stdout); err != nil {
return err
}
exitInfo.Error = stdoutData.String()
err := h.(func(data EventExitData) error)(*exitInfo)
if err != nil {
return fmt.Errorf("%s: %w", ErrRunningEvt.Error(), err)
}
}
return fmt.Errorf("%s: %w", ErrRunningCmd.Error(), err)
}
}
}
if h, ok := cmd.handlers[EventExit]; ok {
if exitInfo == nil {
return nil
}
exitInfo.ExitCode = 0
err := h.(func(data EventExitData) error)(*exitInfo)
if err != nil {
return fmt.Errorf("%s: %w", ErrRunningEvt.Error(), err)
}
}
close(signalChannel)
signal.Stop(signalChannel)
cmd.wg.Wait()
return nil
}

75
linux/utils.go Normal file
View file

@ -0,0 +1,75 @@
package linux
import (
"errors"
"fmt"
cmd2 "git.eggactyl.cloud/Eggactyl/shell/cmd"
"golang.org/x/sys/unix"
"io/fs"
"os"
"os/exec"
)
func (cmd *LinuxCommand) isCommandExecutable(command string) (bool, error) {
whichOut, err := cmd2.Which(command, cmd2.BasicOptions{
Env: cmd.Options.Env,
Sources: cmd.Options.Sources,
Cwd: cmd.Options.Cwd,
})
if err != nil {
if errors.Is(err, cmd2.ErrNotFound) {
if _, err := os.Stat(command); errors.Is(err, fs.ErrNotExist) {
return false, err
}
} else {
return false, err
}
}
if len(whichOut) == 0 {
return false, nil
}
if err := unix.Access(whichOut, unix.X_OK); err != nil {
if err == unix.EACCES {
return false, nil
} else {
fmt.Println(err)
return false, err
}
}
return true, nil
}
func (cmd *LinuxCommand) doesCommandExist(command string) (bool, error) {
shellCmd := exec.Command(cmd.Options.Shell, "-c", fmt.Sprintf("command -v %s", command))
if err := shellCmd.Start(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() == 1 {
return false, nil
} else {
return false, ErrRunningCmd
}
}
}
if err := shellCmd.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() == 1 {
return false, nil
} else {
return false, ErrRunningCmd
}
}
}
return true, nil
}

177
main.go
View file

@ -1,177 +0,0 @@
package shell
import (
"bytes"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"strings"
"golang.org/x/sys/unix"
)
var LinuxType string
var ErrCommandDoesNotExist = errors.New("command Doesn't Exist")
var ErrSourceDoesNotExist = errors.New("source Doesn't Exist")
func RunCommand(command string, args ...string) error {
if !DoesCommandExist(command) {
return ErrCommandDoesNotExist
}
var stderr bytes.Buffer
var mainCommand string
mainCommand = "/bin/bash"
bashCommand := exec.Command(mainCommand, "-c", command)
bashCommand.Args = append(bashCommand.Args, args...)
bashCommand.Stderr = &stderr
if err := bashCommand.Start(); err != nil {
return errors.New(stderr.String() + " " + err.Error())
}
if err := bashCommand.Wait(); err != nil {
return errors.New(stderr.String() + " " + err.Error())
}
return nil
}
func RunCommandWithSource(source string, command string, args ...string) error {
if !DoesSourceExist(source) {
return ErrSourceDoesNotExist
}
if !DoesCommandExistWithSource(source, command) {
return ErrCommandDoesNotExist
}
var stderr bytes.Buffer
var mainCommand string
mainCommand = "/bin/bash"
bashCommand := exec.Command(mainCommand, "-c", fmt.Sprintf("source %s; %s", source, command))
bashCommand.Args = append(bashCommand.Args, args...)
bashCommand.Stderr = &stderr
if err := bashCommand.Start(); err != nil {
return errors.New(stderr.String())
}
if err := bashCommand.Wait(); err != nil {
return errors.New(stderr.String())
}
return nil
}
func DoesCommandExist(command string) bool {
var stderr bytes.Buffer
var mainCommand string
mainCommand = "/bin/bash"
bashCommand := exec.Command(mainCommand, "-c", fmt.Sprintf("command -v %s", command))
bashCommand.Stderr = &stderr
if err := bashCommand.Start(); err != nil {
if strings.Contains(err.Error(), "1") {
return false
}
}
if err := bashCommand.Wait(); err != nil {
if strings.Contains(err.Error(), "1") {
return false
}
}
return true
}
func DoesSourceExist(source string) bool {
var stderr bytes.Buffer
var mainCommand string
mainCommand = "/bin/bash"
bashCommand := exec.Command(mainCommand, "-c", fmt.Sprintf("source %s", source))
bashCommand.Stderr = &stderr
if err := bashCommand.Start(); err != nil {
if strings.Contains(err.Error(), "1") {
return false
}
}
if err := bashCommand.Wait(); err != nil {
if strings.Contains(err.Error(), "1") {
return false
}
}
return true
}
func DoesCommandExistWithSource(source string, command string) bool {
if sourceCheck := DoesSourceExist(source); !sourceCheck {
return false
}
var stderr bytes.Buffer
var mainCommand string
mainCommand = "/bin/bash"
bashCommand := exec.Command(mainCommand, "-c", fmt.Sprintf("source %s && command -v %s", source, command))
bashCommand.Stderr = &stderr
if err := bashCommand.Start(); err != nil {
if strings.Contains(err.Error(), "1") {
return false
}
}
if err := bashCommand.Wait(); err != nil {
if strings.Contains(err.Error(), "1") {
return false
}
}
return true
}
func CanExecute(script string) bool {
if _, err := os.Stat(script); errors.Is(err, fs.ErrNotExist) {
return false
}
if err := unix.Access(script, unix.X_OK); err != nil {
return err == unix.EACCES
}
return true
}