diff options
author | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-02-12 16:01:54 +0300 |
---|---|---|
committer | Kamil Trzcinski <ayufan@ayufan.eu> | 2016-02-12 16:01:54 +0300 |
commit | 34f19dff7a86564df6afe5fdf34a3ac94cc14b33 (patch) | |
tree | 16a8ab94132a9a46fc77f3e0eda075d699d4c073 | |
parent | 88e8fb2a91372ec8a64b82a976a0e51c8f925446 (diff) |
Allow to daemonize the app
-rw-r--r-- | app.go | 5 | ||||
-rw-r--r-- | daemon.go | 124 | ||||
-rw-r--r-- | main.go | 48 |
3 files changed, 126 insertions, 51 deletions
@@ -124,3 +124,8 @@ func (a *theApp) Run() { wg.Wait() } + +func runApp(config appConfig) { + a := theApp{appConfig: config} + a.Run() +} @@ -6,61 +6,147 @@ import ( "os/exec" "os/user" + "encoding/json" "fmt" "github.com/kardianos/osext" + "os/signal" "strconv" "syscall" ) -func daemonize() { - if *pagesUser == "" { +const daemonRunProgram = "daemon-run" + +func daemonMain() { + if os.Args[0] != daemonRunProgram { return } + fmt.Printf("Starting the daemon as unprivileged user...\n") + + // read the configuration from the pipe "ExtraFiles" + var config appConfig + if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&config); err != nil { + log.Fatalln(err) + } + runApp(config) + os.Exit(0) +} + +func daemonReexec(cmdUser string, args ...string) (cmd *exec.Cmd, err error) { path, err := osext.Executable() if err != nil { - log.Fatalln(err) + return } - u, err := user.Lookup(*pagesUser) + u, err := user.Lookup(cmdUser) if err != nil { - log.Fatalln(err) + return } uid, err := strconv.Atoi(u.Uid) if err != nil { - log.Fatalln(err) + return } gid, err := strconv.Atoi(u.Gid) if err != nil { - log.Fatalln(err) + return } - cmd := &exec.Cmd{ + cmd = &exec.Cmd{ Path: path, - Args: append(os.Args, "-pages-user", "", "-pages-root", "/"), + Args: args, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, SysProcAttr: &syscall.SysProcAttr{ - Chroot: *pagesRoot, Credential: &syscall.Credential{ Uid: uint32(uid), Gid: uint32(gid), }, - //Setsid: true, - Setpgid: true, + Setsid: true, }, } - //cmd.SysProcAttr = nil + return +} - fmt.Println("Deamonizing as", uid, "and", gid, "...") - err = cmd.Run() +func daemonUpdateFd(cmd *exec.Cmd, fd *uintptr) { + if *fd == 0 { + return + } + + file := os.NewFile(*fd, "[socket]") + // we add 3 since, we have a 3 predefined FDs + *fd = uintptr(3 + len(cmd.ExtraFiles)) + cmd.ExtraFiles = append(cmd.ExtraFiles, file) +} + +func killProcess(cmd *exec.Cmd) { + if cmd.Process != nil { + cmd.Process.Kill() + } + cmd.Wait() + for _, file := range cmd.ExtraFiles { + file.Close() + } +} + +func passSignals(cmd *exec.Cmd) { + s := make(chan os.Signal) + signal.Notify(s, syscall.SIGTERM) + + go func() { + for { + <-s + if cmd.Process != nil { + cmd.Process.Kill() + } + } + }() +} + +func daemonize(config appConfig, cmdUser string) { + var err error + defer func() { + if err != nil { + log.Fatalln(err) + } + }() + fmt.Printf("Running the daemon as unprivileged user: %v...\n", cmdUser) + + cmd, err := daemonReexec(cmdUser, daemonRunProgram) if err != nil { - log.Fatalln(err) - os.Exit(1) - } else { - os.Exit(0) + return } + defer killProcess(cmd) + + // Create a pipe to pass the configuration + configReader, configWriter, err := os.Pipe() + if err != nil { + return + } + defer configWriter.Close() + cmd.ExtraFiles = append(cmd.ExtraFiles, configReader) + + // Create a new file and store the FD + daemonUpdateFd(cmd, &config.ListenHTTP) + daemonUpdateFd(cmd, &config.ListenHTTPS) + daemonUpdateFd(cmd, &config.listenProxy) + + // Start the process + if err = cmd.Start(); err != nil { + return + } + + // Write the configuration + if err = json.NewEncoder(configWriter).Encode(config); err != nil { + return + } + configWriter.Close() + + // Pass through signals + passSignals(cmd) + + // Wait for process to exit + err = cmd.Wait() } @@ -16,6 +16,8 @@ var VERSION = "dev" var REVISION = "HEAD" func main() { + daemonMain() + var listenHTTP = flag.String("listen-http", ":80", "The address to listen for HTTP requests") var listenHTTPS = flag.String("listen-https", "", "The address to listen for HTTPS requests") var listenProxy = flag.String("listen-proxy", "", "The address to listen for proxy requests") @@ -36,58 +38,40 @@ func main() { log.Fatalln(err) } - var app theApp - app.Domain = strings.ToLower(*pagesDomain) - app.RedirectHTTP = *redirectHTTP - app.HTTP2 = *useHTTP2 + var config appConfig + config.Domain = strings.ToLower(*pagesDomain) + config.RedirectHTTP = *redirectHTTP + config.HTTP2 = *useHTTP2 if *pagesRootCert != "" { - app.RootCertificate = readFile(*pagesRootCert) + config.RootCertificate = readFile(*pagesRootCert) } if *pagesRootKey != "" { - app.RootKey = readFile(*pagesRootKey) - } - -<<<<<<< 9042f5171c4bddc3da330b0e236e5faa78e657c3 -======= - //daemonize() - - fmt.Println("Starting...") - - // We don't need root privileges any more - // if err := syscall.Setgid(33); err != nil { - // log.Fatalln("setgid:", err) - // } - if err := syscall.Setuid(33); err != nil { - log.Fatalln("setuid:", err) + config.RootKey = readFile(*pagesRootKey) } - err := syscall.Chroot(*pagesRoot) - if err != nil { - log.Fatalln("chroot:", err) - } - *pagesRoot = "/" - - // Listen for HTTP ->>>>>>> Daemonize if *listenHTTP != "" { var l net.Listener - l, app.ListenHTTP = createSocket(*listenHTTP) + l, config.ListenHTTP = createSocket(*listenHTTP) defer l.Close() } if *listenHTTPS != "" { var l net.Listener - l, app.ListenHTTPS = createSocket(*listenHTTPS) + l, config.ListenHTTPS = createSocket(*listenHTTPS) defer l.Close() } if *listenProxy != "" { var l net.Listener - l, app.ListenHTTPS = createSocket(*listenProxy) + l, config.ListenHTTPS = createSocket(*listenProxy) defer l.Close() } - app.Run() + if *pagesUser != "" { + daemonize(config, *pagesUser) + } else { + runApp(config) + } } |