From 8ffb937d433a9f722428eb9662f024d2daf5bbc0 Mon Sep 17 00:00:00 2001 From: yuweizzz Date: Mon, 22 May 2023 17:52:27 +0800 Subject: [PATCH] fix: no output when RunAsDaemon is false in windows --- Makefile | 7 +- cmd/sidecar/main.go | 74 +++----------------- daemon.go | 138 +++++++++++++++++++++--------------- daemon_windows.go | 165 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 123 deletions(-) create mode 100644 daemon_windows.go diff --git a/Makefile b/Makefile index 8bfa889..a62fed2 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ MAIN := main.go SIDECAR := sidecar +LD_FLAGS := "-s -w" CURDIR := $(shell pwd) OUTPUTDIR := build @@ -18,11 +19,11 @@ mac: clean build_mac copy_tpl .PHONY: build_linux build_windows build_mac copy_tpl build_linux: - GOARCH="amd64" GOOS="linux" go build -o $(OUTPUTDIR)/$(SIDECAR) $(SIDECAR_DIR)/$(MAIN) + GOARCH="amd64" GOOS="linux" go build -ldflags=$(LD_FLAGS) -o $(OUTPUTDIR)/$(SIDECAR) $(SIDECAR_DIR)/$(MAIN) build_windows: - GOARCH="amd64" GOOS="windows" go build -ldflags="-H windowsgui" -o $(OUTPUTDIR)/sidecar.exe $(SIDECAR_DIR)/$(MAIN) + GOARCH="amd64" GOOS="windows" go build -ldflags=$(LD_FLAGS) -o $(OUTPUTDIR)/sidecar.exe $(SIDECAR_DIR)/$(MAIN) build_mac: - GOARCH="amd64" GOOS="darwin" go build -o $(OUTPUTDIR)/$(SIDECAR) $(SIDECAR_DIR)/$(MAIN) + GOARCH="amd64" GOOS="darwin" go build -ldflags=$(LD_FLAGS) -o $(OUTPUTDIR)/$(SIDECAR) $(SIDECAR_DIR)/$(MAIN) copy_tpl: cp nginx_conf.tpl $(OUTPUTDIR)/nginx_conf.tpl cp config_toml.tpl $(OUTPUTDIR)/config.toml diff --git a/cmd/sidecar/main.go b/cmd/sidecar/main.go index 41d0ef1..b4dda84 100644 --- a/cmd/sidecar/main.go +++ b/cmd/sidecar/main.go @@ -4,11 +4,7 @@ import ( "flag" "fmt" "os" - "os/exec" "path" - "runtime" - "strconv" - "syscall" "github.com/yuweizzz/sidecar" ) @@ -35,7 +31,7 @@ func main() { configServerPath := serverCmd.String("conf", pwd+"/config.toml", "the path of sidecar conf") serverAction := serverCmd.String("action", "", "action must in ['start', 'stop', 'create-nginx-conf']") - // for run as daemon + // run as daemon, use function sidecar.StartDaemonProcess() to start if len(os.Args) < 2 { if os.Getenv("SPECIAL_MARK") == "ENABLED" { path := os.Getenv("CONF_PATH") @@ -60,36 +56,12 @@ func main() { switch *clientAction { case "start": if cfg.Client.RunAsDaemon { - cmd := &exec.Cmd{ - Path: os.Args[0], - Env: []string{"SPECIAL_MARK=ENABLED", "CONF_PATH=" + *configClientPath, "TYPE=client"}, - Stdout: os.Stdout, - Stderr: os.Stdout, - } - err := cmd.Start() - if err != nil { - panic(err) - } - os.Exit(0) + sidecar.StartDaemonProcess(*configClientPath, "client") } else { runClient(cfg) } case "stop": - lockPath := cfg.Client.WorkDir + "/sidecar.lock" - pid := sidecar.ReadLock(lockPath) - // if lock exist - if pid != 0 { - proc, _ := os.FindProcess(pid) - if runtime.GOOS == "windows" { - sidecar.RemoveLock(lockPath) - proc.Kill() - } else { - proc.Signal(syscall.SIGINT) - } - fmt.Println("Now Server is stopped.") - } else { - fmt.Println("Now sidecar.lock is not exist, server is stopped") - } + sidecar.StopDaemonProcess(cfg.Client.WorkDir) default: help(filename) } @@ -99,35 +71,12 @@ func main() { switch *serverAction { case "start": if cfg.Server.RunAsDaemon { - cmd := &exec.Cmd{ - Path: os.Args[0], - Env: []string{"SPECIAL_MARK=ENABLED", "CONF_PATH=" + *configServerPath, "TYPE=server"}, - Stdout: os.Stdout, - Stderr: os.Stdout, - } - err := cmd.Start() - if err != nil { - panic(err) - } - os.Exit(0) + sidecar.StartDaemonProcess(*configServerPath, "server") } else { runServer(cfg) } case "stop": - lockPath := cfg.Server.WorkDir + "/sidecar.lock" - pid := sidecar.ReadLock(lockPath) - if pid != 0 { - proc, _ := os.FindProcess(pid) - if runtime.GOOS == "windows" { - sidecar.RemoveLock(lockPath) - proc.Kill() - } else { - proc.Signal(syscall.SIGINT) - } - fmt.Println("Now Server is stopped.") - } else { - fmt.Println("Now sidecar.lock is not exist, server is stopped") - } + sidecar.StopDaemonProcess(cfg.Server.WorkDir) case "create-nginx-conf": sidecar.RenderTemplateByConfig(cfg.Server.WorkDir, cfg) default: @@ -147,24 +96,24 @@ func runClient(cfg *sidecar.Config) { LogLevel: cfg.Client.LogLevel, } daemon.Perpare(cfg.Client.RunAsDaemon) + remoteServer := cfg.Client.RemoteServers[0] switch cfg.Client.Mode { case "WSS": - pac := sidecar.NewPac(cfg.Client.RemoteServers[0], cfg.Client.GfwListUrl, cfg.Client.CustomProxyHosts) + pac := sidecar.NewPac(remoteServer, cfg.Client.GfwListUrl, cfg.Client.CustomProxyHosts) proxy := sidecar.NewProxyViaWss(daemon.Logger, pac, cfg.Client.OnlyListenIPv4, cfg.Client.ProxyPort, - cfg.Client.RemoteServers[0].Host, cfg.Client.RemoteServers[0].ComplexPath, cfg.Client.RemoteServers[0].CustomHeaders) + remoteServer.Host, remoteServer.ComplexPath, remoteServer.CustomHeaders) go proxy.Run() default: // HTTPS daemon.LoadCertAndPriKey() - pac := sidecar.NewPac(cfg.Client.RemoteServers[0], cfg.Client.GfwListUrl, cfg.Client.CustomProxyHosts) + pac := sidecar.NewPac(remoteServer, cfg.Client.GfwListUrl, cfg.Client.CustomProxyHosts) proxy := sidecar.NewProxyViaHttps(daemon.Logger, pac, cfg.Client.OnlyListenIPv4, cfg.Client.ProxyPort) cache := sidecar.NewCertLRU(daemon.Cert, daemon.PriKey) mitm := sidecar.NewMitMServer(proxy.Listener, cache, daemon.Logger, - cfg.Client.RemoteServers[0].Host, cfg.Client.RemoteServers[0].ComplexPath, cfg.Client.RemoteServers[0].CustomHeaders) + remoteServer.Host, remoteServer.ComplexPath, remoteServer.CustomHeaders) go proxy.Run() go mitm.Run() } sidecar.Info("Now Server is run as a Client.") - sidecar.Info("Now Server is running and pid is " + strconv.Itoa(daemon.Pid)) daemon.WatchSignal() } @@ -177,8 +126,6 @@ func runServer(cfg *sidecar.Config) { LogLevel: cfg.Server.LogLevel, } daemon.Perpare(cfg.Server.RunAsDaemon) - sidecar.Info("Server privatekey file: ", daemon.CertPath) - sidecar.Info("Server certificate file: ", daemon.PriKeyPath) switch cfg.Server.Mode { case "WSS": server := sidecar.NewRemoteServerWss(daemon.Logger, cfg.Server.ServerPort, cfg.Server.OnlyListenIPv4, @@ -190,6 +137,5 @@ func runServer(cfg *sidecar.Config) { go server.Run() } sidecar.Info("Now Server is run as a Server.") - sidecar.Info("Now Server is running and pid is " + strconv.Itoa(daemon.Pid)) daemon.WatchSignal() } diff --git a/daemon.go b/daemon.go index 8e12fb3..37e516d 100644 --- a/daemon.go +++ b/daemon.go @@ -1,3 +1,6 @@ +//go:build linux || darwin +// +build linux darwin + package sidecar import ( @@ -6,8 +9,8 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "os/signal" - "runtime" "strconv" "syscall" ) @@ -24,9 +27,47 @@ type Daemon struct { Cert *x509.Certificate } +func readLock(path string) (pid int) { + lock := DetectFile(path) + if lock == "" { + return 0 + } else { + bytes, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + pid, err = strconv.Atoi(string(bytes)) + if err != nil { + panic(err) + } + return + } +} + +func writeLock(pid int, path string) { + pid_str := strconv.Itoa(pid) + err := ioutil.WriteFile(path, []byte(pid_str), 0444) + if err != nil { + panic(err) + } +} + +func removeLock(path string) { + err := os.Remove(path) + if err != nil { + panic(err) + } + Info("Remove sidecar.lock done.") +} + +func exitWhenLocked(pid int) { + fmt.Println("Start Server failed because sidecar.lock exist, maybe Server is already running and pid is", pid) + fmt.Println("If you confirm Server is not running, remove sidecar.lock and retry.") + os.Exit(2) +} + func (d *Daemon) Perpare(backgroud bool) { if backgroud { - fmt.Println(d.WorkDir) log_fd := CreateFileIfNotExist(d.WorkDir + "/sidecar.log") if log_fd == nil { log_fd = OpenExistFile(d.WorkDir + "/sidecar.log") @@ -36,22 +77,18 @@ func (d *Daemon) Perpare(backgroud bool) { d.Logger = os.Stdout } Initial(d.LogLevel, d.Logger) - pid := ReadLock(d.LockFilePath) + pid := readLock(d.LockFilePath) Info("Detect if Server is running .....") // if lock exist if pid != 0 { - if runtime.GOOS == "linux" { - alive := DetectProcess(pid) - // if process alive - if alive { - exitWhenLocked(pid) - } else { - // if process not alive - Info("File sidecar.lock exist, file path is ", d.LockFilePath, ", but process is not running.....") - RemoveLock(d.LockFilePath) - } - } else { + alive := DetectProcess(pid) + // if process alive + if alive { exitWhenLocked(pid) + } else { + // if process not alive + Info("File sidecar.lock exist, file path is ", d.LockFilePath, ", but process is not running.....") + removeLock(d.LockFilePath) } } d.Pid = os.Getpid() @@ -77,44 +114,6 @@ func (d *Daemon) LoadCertAndPriKey() { } } -func ReadLock(path string) (pid int) { - lock := DetectFile(path) - if lock == "" { - return 0 - } else { - bytes, err := ioutil.ReadFile(path) - if err != nil { - panic(err) - } - pid, err = strconv.Atoi(string(bytes)) - if err != nil { - panic(err) - } - return - } -} - -func writeLock(pid int, path string) { - pid_str := strconv.Itoa(pid) - err := ioutil.WriteFile(path, []byte(pid_str), 0444) - if err != nil { - panic(err) - } -} - -func RemoveLock(path string) { - err := os.Remove(path) - if err != nil { - panic(err) - } -} - -func exitWhenLocked(pid int) { - fmt.Println("Start Server failed because sidecar.lock exist, maybe Server is already running and pid is ", pid) - fmt.Println("If you confirm Server is not running, remove sidecar.lock and retry.") - os.Exit(2) -} - func (d *Daemon) WatchSignal() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) @@ -123,9 +122,36 @@ func (d *Daemon) WatchSignal() { <-sigs done <- true }() - Info("Awaiting signal......") + Info("Now Server is running and pid is " + strconv.Itoa(d.Pid)) + Info("Awaiting signal ......") <-done - Info("Except signal, exiting......") - Info("Remove sidecar.lock......") - RemoveLock(d.LockFilePath) + Info("Except signal, exiting ......") + removeLock(d.LockFilePath) +} + +// run in backgroud +func StartDaemonProcess(configPath string, serviceType string) { + cmd := &exec.Cmd{ + Path: os.Args[0], + Env: []string{"SPECIAL_MARK=ENABLED", "CONF_PATH=" + configPath, "TYPE=" + serviceType}, + Stdout: os.Stdout, + Stderr: os.Stdout, + } + err := cmd.Start() + if err != nil { + panic(err) + } + os.Exit(0) +} + +func StopDaemonProcess(workDir string) { + lockPath := workDir + "/sidecar.lock" + pid := readLock(lockPath) + // if lock exist + if pid != 0 { + proc, _ := os.FindProcess(pid) + proc.Signal(syscall.SIGINT) + } else { + Error("Now sidecar.lock is not exist, server is stopped") + } } diff --git a/daemon_windows.go b/daemon_windows.go new file mode 100644 index 0000000..7f78c6c --- /dev/null +++ b/daemon_windows.go @@ -0,0 +1,165 @@ +package sidecar + +import ( + "crypto/rsa" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/signal" + "strconv" + "syscall" +) + +type Daemon struct { + Pid int + WorkDir string + CertPath string + PriKeyPath string + LockFilePath string + LogLevel string + Logger *os.File + PriKey *rsa.PrivateKey + Cert *x509.Certificate +} + +// show windows console +// https://stackoverflow.com/questions/23743217/printing-output-to-a-command-window-when-golang-application-is-compiled-with-ld +func console(show bool) { + getWin := syscall.NewLazyDLL("kernel32.dll").NewProc("GetConsoleWindow") + showWin := syscall.NewLazyDLL("user32.dll").NewProc("ShowWindow") + hwnd, _, _ := getWin.Call() + if hwnd == 0 { + return + } + if show { + var SW_RESTORE uintptr = 9 + showWin.Call(hwnd, SW_RESTORE) + } else { + var SW_HIDE uintptr = 0 + showWin.Call(hwnd, SW_HIDE) + } +} + +func readLock(path string) (pid int) { + lock := DetectFile(path) + if lock == "" { + return 0 + } else { + bytes, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + pid, err = strconv.Atoi(string(bytes)) + if err != nil { + panic(err) + } + return + } +} + +func writeLock(pid int, path string) { + pid_str := strconv.Itoa(pid) + err := ioutil.WriteFile(path, []byte(pid_str), 0444) + if err != nil { + panic(err) + } +} + +func removeLock(path string) { + err := os.Remove(path) + if err != nil { + panic(err) + } + Info("Remove sidecar.lock done.") +} + +func exitWhenLocked(pid int) { + fmt.Println("Start Server failed because sidecar.lock exist, maybe Server is already running and pid is ", pid) + fmt.Println("If you confirm Server is not running, remove sidecar.lock and retry.") + os.Exit(2) +} + +func (d *Daemon) Perpare(backgroud bool) { + console(!backgroud) + if backgroud { + log_fd := CreateFileIfNotExist(d.WorkDir + "/sidecar.log") + if log_fd == nil { + log_fd = OpenExistFile(d.WorkDir + "/sidecar.log") + } + d.Logger = log_fd + } else { + d.Logger = os.Stdout + } + Initial(d.LogLevel, d.Logger) + pid := readLock(d.LockFilePath) + Info("Detect if Server is running .....") + // if lock exist + if pid != 0 { + exitWhenLocked(pid) + } + d.Pid = os.Getpid() + writeLock(d.Pid, d.LockFilePath) +} + +func (d *Daemon) LoadCertAndPriKey() { + if pri_file_path := DetectFile(d.PriKeyPath); pri_file_path == "" { + pri_fd := CreateFileIfNotExist(d.PriKeyPath) + d.PriKey = GenAndSavePriKey(pri_fd) + Info("Generate new privatekey, privatekey file save to ", d.PriKeyPath) + } else { + d.PriKey = ReadPriKey(d.PriKeyPath) + Info("Use exist privatekey, file path is ", pri_file_path) + } + if crt_file_path := DetectFile(d.CertPath); crt_file_path == "" { + crt_fd := CreateFileIfNotExist(d.CertPath) + d.Cert = GenAndSaveRootCert(crt_fd, d.PriKey) + Info("Generate new certificate, certificate file save to ", d.CertPath) + } else { + d.Cert = ReadRootCert(d.CertPath) + Info("Use exist certificate, file path is ", crt_file_path) + } +} + +func (d *Daemon) WatchSignal() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + done := make(chan bool, 1) + go func() { + <-sigs + done <- true + }() + Info("Now Server is running and pid is " + strconv.Itoa(d.Pid)) + Info("Awaiting signal......") + <-done + Info("Except signal, exiting......") +} + +// run in backgroud +func StartDaemonProcess(configPath string, serviceType string) { + cmd := &exec.Cmd{ + Path: os.Args[0], + Env: []string{"SPECIAL_MARK=ENABLED", "CONF_PATH=" + configPath, "TYPE=" + serviceType}, + Stdout: os.Stdout, + Stderr: os.Stdout, + } + err := cmd.Start() + if err != nil { + panic(err) + } + os.Exit(0) +} + +func StopDaemonProcess(workDir string) { + lockPath := workDir + "/sidecar.lock" + pid := readLock(lockPath) + // if lock exist + if pid != 0 { + proc, _ := os.FindProcess(pid) + removeLock(lockPath) + proc.Kill() + } else { + Error("Now sidecar.lock is not exist, server is stopped") + } +}