From 79d349f289ce48e3dae3a5ae15ab3c37ce942e07 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sat, 23 Sep 2023 09:33:58 +1000 Subject: [PATCH] Add support for running sub-processes under a PTY This is really nice for running programs that output colour, such as coloured log lines, etc. I defaulted it to off because some applications when run under a PTY will attempt to move the cursor around, clear lines, and so on, so it should be up to the user to decide whether to enable it. --- go.mod | 5 ++++- go.sum | 2 ++ main.go | 3 +++ proc.go | 10 ++++++++-- proc_posix.go | 22 ++++++++++++++++++++++ proc_windows.go | 7 +++++++ 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8e84165..11eb30a 100644 --- a/go.mod +++ b/go.mod @@ -9,4 +9,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require github.com/mattn/go-isatty v0.0.17 // indirect +require ( + github.com/creack/pty v1.1.18 + github.com/mattn/go-isatty v0.0.17 // indirect +) diff --git a/go.sum b/go.sum index cd3a846..6379bc3 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= diff --git a/main.go b/main.go index d1459b2..db50446 100644 --- a/main.go +++ b/main.go @@ -98,6 +98,9 @@ var exitOnStop = flag.Bool("exit-on-stop", true, "Exit goreman if all subprocess // show timestamp in log var logTime = flag.Bool("logtime", true, "show timestamp in log") +// use a PTY for all subprocesses +var usePty = flag.Bool("pty", false, "use a PTY for all subprocesses (noop on Windows)") + var maxProcNameLength = 0 var re = regexp.MustCompile(`\$([a-zA-Z]+[a-zA-Z0-9_]+)`) diff --git a/proc.go b/proc.go index 01b4c70..378f72e 100644 --- a/proc.go +++ b/proc.go @@ -17,10 +17,16 @@ func spawnProc(name string, errCh chan<- error) { cs := append(cmdStart, proc.cmdline) cmd := exec.Command(cs[0], cs[1:]...) cmd.Stdin = nil - cmd.Stdout = logger - cmd.Stderr = logger cmd.SysProcAttr = procAttrs + if err := startPTY(logger, cmd); err != nil { + select { + case errCh <- err: + default: + } + fmt.Fprintf(logger, "Failed to open pty for %s: %s\n", name, err) + return + } if proc.setPort { cmd.Env = append(os.Environ(), fmt.Sprintf("PORT=%d", proc.port)) fmt.Fprintf(logger, "Starting %s on port %d\n", name, proc.port) diff --git a/proc_posix.go b/proc_posix.go index 7dbd375..2311bee 100644 --- a/proc_posix.go +++ b/proc_posix.go @@ -4,9 +4,13 @@ package main import ( + "fmt" + "io" "os" + "os/exec" "os/signal" + "github.com/creack/pty" "golang.org/x/sys/unix" ) @@ -51,3 +55,21 @@ func notifyCh() <-chan os.Signal { signal.Notify(sc, sigterm, sigint, sighup) return sc } + +func startPTY(logger *clogger, cmd *exec.Cmd) error { + if *usePty { + p, t, err := pty.Open() + if err != nil { + return fmt.Errorf("failed to open PTY: %w", err) + } + defer p.Close() + defer t.Close() + cmd.Stdout = t + cmd.Stderr = t + go io.Copy(logger, p) + } else { + cmd.Stdout = logger + cmd.Stderr = logger + } + return nil +} diff --git a/proc_windows.go b/proc_windows.go index 6b0d2d6..fe3bfea 100644 --- a/proc_windows.go +++ b/proc_windows.go @@ -2,6 +2,7 @@ package main import ( "os" + "os/exec" "os/signal" "syscall" @@ -63,3 +64,9 @@ func notifyCh() <-chan os.Signal { signal.Notify(sc, os.Interrupt) return sc } + +// This is a no-op on Windows. +func startPTY(logger *clogger, cmd *exec.Cmd) error { + cmd.Stdout = logger + cmd.Stderr = logger +}