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 /daemon.go | |
parent | 88e8fb2a91372ec8a64b82a976a0e51c8f925446 (diff) |
Allow to daemonize the app
Diffstat (limited to 'daemon.go')
-rw-r--r-- | daemon.go | 124 |
1 files changed, 105 insertions, 19 deletions
@@ -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() } |