diff --git a/main.go b/main.go index 19d0545..fb91914 100644 --- a/main.go +++ b/main.go @@ -1,36 +1,78 @@ package main import ( + "bytes" "fmt" "net" "os" "os/exec" + "strconv" ) var outPort int func init() { var err error - outPort, err = getFreePort() + + outPort, err = checkExistingProcess() + if err == nil && isPortListening(outPort) { + fmt.Printf("Process is already running on port %d\n", outPort) + return + } + + // No existing process found or not listening, start a new one + outPort, err := getFreePort() if err != nil { panic("Can't get free port") } + + args := os.Args + bg := os.Getenv("NOHUP") == "1" + // Check if there are additional arguments - if len(os.Args) > 1 { - cmd := exec.Command(os.Args[1], os.Args[2:]...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, fmt.Sprintf("PORT=%d", outPort)) + if len(args) > 1 { + if bg { + var out bytes.Buffer + var stderr bytes.Buffer + cmdd := generateBgCmd(args[1], args[2:]...) + cmd := exec.Command("bash", "-c", cmdd) + cmd.Stdout = &out + cmd.Stderr = &stderr + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("PORT=%d", outPort)) + err = cmd.Run() + if err != nil { + fmt.Printf("Error starting command: %v\n", err) + os.Exit(1) + } + pid, err := strconv.Atoi(out.String()) + if nil != err { + fmt.Printf("Error starting command: %v\n", err) + os.Exit(1) + } + if pid == 0 { + fmt.Printf("invalid PID of 0") + os.Exit(1) + } + writePidPortFile(pid, outPort) + fmt.Printf("Started process %s with PID %d in background\n", args[1], pid) + } else { + cmd := exec.Command(args[1], args[2:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, fmt.Sprintf("PORT=%d", outPort)) - // Start the specified command - err := cmd.Start() - if err != nil { - fmt.Printf("Error starting command: %v\n", err) - os.Exit(1) + // Start the specified command + err := cmd.Start() + if err != nil { + fmt.Printf("Error starting command: %v\n", err) + os.Exit(1) + } + pid := cmd.Process.Pid + fmt.Printf("Started process %s with PID %d\n", args[1], pid) } - fmt.Printf("Started process %s with PID %d\n", os.Args[1], cmd.Process.Pid) } } diff --git a/util.go b/util.go index e54e46f..98309db 100644 --- a/util.go +++ b/util.go @@ -1,9 +1,15 @@ package main import ( + "fmt" "net" "net/http" + "os" + "path/filepath" "regexp" + "strconv" + "strings" + "syscall" "time" ) @@ -31,3 +37,67 @@ func filterInvalidHeaders(headers http.Header) { } } } + +func getPidFile() string { + return filepath.Join(os.Getenv("HOME"), "tmp", "app.pid") +} + +func checkExistingProcess() (int, error) { + data, err := os.ReadFile(getPidFile()) + if err != nil { + return 0, err + } + + fields := strings.Split(string(data), ":") + if len(fields) != 2 { + return 0, fmt.Errorf("invalid pid/port file format") + } + + pid, err := strconv.Atoi(fields[0]) + if err != nil { + return 0, fmt.Errorf("invalid PID format: %v", err) + } + + port, err := strconv.Atoi(fields[1]) + if err != nil { + return 0, fmt.Errorf("invalid port format: %v", err) + } + + if processExists(pid) { + return port, nil + } + return 0, fmt.Errorf("no running process found") +} + +func processExists(pid int) bool { + process, err := os.FindProcess(pid) + if err != nil { + return false + } + err = process.Signal(syscall.Signal(0)) + return err == nil +} + +func isPortListening(port int) bool { + conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + return false + } + conn.Close() + return true +} + +func writePidPortFile(pid, port int) { + err := os.WriteFile(getPidFile(), []byte(fmt.Sprintf("%d:%d", pid, port)), 0644) + if err != nil { + fmt.Printf("Failed to write PID/port file: %v\n", err) + } +} + +func generateBgCmd(name string, arg ...string) string { + return fmt.Sprintf( + "nohup %s %s < /dev/null &>$HOME/tmp/app.log & echo -n $! | awk '/[0-9]+$/{ printf $0 }'", + name, + strings.Join(arg, " "), + ) +}