Implement daemon mode (#119)
* add daemon process * add daemon stop command * add daemon log to log file * check for running daemons * minor changes
This commit is contained in:
parent
42d344b4b5
commit
f63efc5f51
11 changed files with 170 additions and 7 deletions
|
@ -15,3 +15,6 @@ http-port: 80
|
|||
http-password:
|
||||
database-driver: internal
|
||||
database-dsn: root:photoprism@tcp(localhost:4000)/photoprism?parseTime=true
|
||||
daemon-pid-path: /srv/photoprism/photoprism.pid
|
||||
daemon-log-path: /srv/photoprism/photoprism.log
|
||||
daemon-mode: false
|
||||
|
|
|
@ -23,6 +23,7 @@ func main() {
|
|||
app.Commands = []cli.Command{
|
||||
commands.ConfigCommand,
|
||||
commands.StartCommand,
|
||||
commands.StopCommand,
|
||||
commands.MigrateCommand,
|
||||
commands.ImportCommand,
|
||||
commands.IndexCommand,
|
||||
|
|
2
go.mod
2
go.mod
|
@ -36,10 +36,12 @@ require (
|
|||
github.com/pingcap/tidb-tools v2.1.3-0.20190116051332-34c808eef588+incompatible
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829
|
||||
github.com/prometheus/common v0.2.0
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sevlyar/go-daemon v0.1.5
|
||||
github.com/simplereach/timeutils v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.2.0
|
||||
github.com/soheilhy/cmux v0.1.4 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -257,6 +257,8 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0
|
|||
github.com/rwcarlsen/goexif v0.0.0-20190501182100-9e8deecbddbd4989a3e8d003684b783412b41e7a/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk=
|
||||
github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
|
||||
github.com/simplereach/timeutils v1.2.0 h1:btgOAlu9RW6de2r2qQiONhjgxdAG7BL6je0G6J/yPnA=
|
||||
github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8=
|
||||
github.com/sirupsen/logrus v0.0.0-20170323161349-3bcb09397d6d/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
|
|
|
@ -4,13 +4,15 @@ import (
|
|||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/server"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
daemon "github.com/sevlyar/go-daemon"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
|
@ -41,6 +43,12 @@ var startFlags = []cli.Flag{
|
|||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_HTTP_MODE",
|
||||
},
|
||||
|
||||
cli.BoolFlag{
|
||||
Name: "daemonize, d",
|
||||
Usage: "run Photoprism as Daemon",
|
||||
EnvVar: "PHOTOPRISM_DAEMON_MODE",
|
||||
},
|
||||
}
|
||||
|
||||
func startAction(ctx *cli.Context) error {
|
||||
|
@ -60,6 +68,33 @@ func startAction(ctx *cli.Context) error {
|
|||
}
|
||||
conf.MigrateDb()
|
||||
|
||||
dctx := new(daemon.Context)
|
||||
dctx.LogFileName = conf.DaemonLogPath()
|
||||
dctx.PidFileName = conf.DaemonPIDPath()
|
||||
dctx.Args = ctx.Args()
|
||||
if !daemon.WasReborn() && conf.ShouldDaemonize() {
|
||||
conf.Shutdown()
|
||||
cancel()
|
||||
|
||||
if pid, ok := childAlreadyRunning(conf.DaemonPIDPath()); ok {
|
||||
log.Infof("Daemon already running with PID[%v]\n", pid)
|
||||
return nil
|
||||
}
|
||||
|
||||
child, err := dctx.Reborn()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if child != nil {
|
||||
if !util.Overwrite(conf.DaemonPIDPath(), []byte(strconv.Itoa(child.Pid))) {
|
||||
log.Fatal("failed to write PID to file")
|
||||
}
|
||||
|
||||
log.Infof("Daemon started with PID: %v\n", child.Pid)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
log.Infof("starting web server at %s:%d", conf.HttpServerHost(), conf.HttpServerPort())
|
||||
go server.Start(cctx, conf)
|
||||
|
||||
|
@ -70,6 +105,28 @@ func startAction(ctx *cli.Context) error {
|
|||
log.Info("Shutting down...")
|
||||
conf.Shutdown()
|
||||
cancel()
|
||||
err := dctx.Release()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func childAlreadyRunning(filePath string) (pid int, running bool) {
|
||||
if !util.Exists(filePath) {
|
||||
return pid, false
|
||||
}
|
||||
|
||||
pid, err := daemon.ReadPidFile(filePath)
|
||||
if err != nil {
|
||||
return pid, false
|
||||
}
|
||||
|
||||
process, err := os.FindProcess(int(pid))
|
||||
if err != nil {
|
||||
return pid, false
|
||||
}
|
||||
|
||||
return pid, process.Signal(syscall.Signal(0)) == nil
|
||||
}
|
||||
|
|
42
internal/commands/stop.go
Normal file
42
internal/commands/stop.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/prometheus/common/log"
|
||||
daemon "github.com/sevlyar/go-daemon"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// StopCommand stops the daemon if any.
|
||||
var StopCommand = cli.Command{
|
||||
Name: "stop",
|
||||
Usage: "Stops daemon",
|
||||
Action: stopAction,
|
||||
}
|
||||
|
||||
func stopAction(ctx *cli.Context) error {
|
||||
conf := config.NewConfig(ctx)
|
||||
log.Infof("Looking for PID from file: %v\n", conf.DaemonPIDPath())
|
||||
dcxt := new(daemon.Context)
|
||||
dcxt.PidFileName = conf.DaemonPIDPath()
|
||||
child, err := dcxt.Search()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = child.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
st, err := child.Wait()
|
||||
if err != nil {
|
||||
log.Info("Daemon exited successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Daemon[%v] exited[%v]? successfully[%v]?\n", st.Pid(), st.Exited(), st.Success())
|
||||
return nil
|
||||
}
|
|
@ -218,6 +218,29 @@ func (c *Config) ConfigPath() string {
|
|||
return c.config.ConfigPath
|
||||
}
|
||||
|
||||
// DaemonPIDPath returns the filepath of the pid.
|
||||
func (c *Config) DaemonPIDPath() string {
|
||||
if c.config.DaemonPIDPath == "" {
|
||||
return c.AssetsPath() + "/photoprism.pid"
|
||||
}
|
||||
|
||||
return c.config.DaemonPIDPath
|
||||
}
|
||||
|
||||
// DaemonLogPath returns the filepath of the log.
|
||||
func (c *Config) DaemonLogPath() string {
|
||||
if c.config.DaemonLogPath == "" {
|
||||
return c.AssetsPath() + "/photoprism.log"
|
||||
}
|
||||
|
||||
return c.config.DaemonLogPath
|
||||
}
|
||||
|
||||
// ShouldDaemonize returns true if daemon mode is set to true.
|
||||
func (c *Config) ShouldDaemonize() bool {
|
||||
return c.config.DaemonMode
|
||||
}
|
||||
|
||||
// SqlServerHost returns the built-in SQL server host name or IP address (empty for all interfaces).
|
||||
func (c *Config) SqlServerHost() string {
|
||||
return c.config.SqlServerHost
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package config
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Global CLI flags
|
||||
var GlobalFlags = []cli.Flag{
|
||||
|
@ -150,4 +152,21 @@ var GlobalFlags = []cli.Flag{
|
|||
Value: "heif-convert",
|
||||
EnvVar: "PHOTOPRISM_HEIFCONVERT_BIN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "daemon-pid-path",
|
||||
Usage: "File path to store daemon PID",
|
||||
EnvVar: "PHOTOPRISM_DAEMON_PID_PATH",
|
||||
Value: "/srv/photoprism/photoprism.pid",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "daemon-log-path",
|
||||
Usage: "File path for daemon logs.",
|
||||
EnvVar: "PHOTOPRISM_DAEMON_LOG_PATH",
|
||||
Value: "/srv/photoprism/photoprism.log",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "daemonize, d",
|
||||
Usage: "run Photoprism as Daemon",
|
||||
EnvVar: "PHOTOPRISM_DAEMON_MODE",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -56,6 +56,9 @@ type Params struct {
|
|||
DarktableBin string `yaml:"darktable-bin" flag:"darktable-bin"`
|
||||
ExifToolBin string `yaml:"exiftool-bin" flag:"exiftool-bin"`
|
||||
HeifConvertBin string `yaml:"heifconvert-bin" flag:"heifconvert-bin"`
|
||||
DaemonPIDPath string `yaml:"daemon-pid-path" flag:"daemon-pid-path"`
|
||||
DaemonLogPath string `yaml:"daemon-log-path" flag:"daemon-log-path"`
|
||||
DaemonMode bool `yaml:"daemon-mode" flag:"daemonize"`
|
||||
}
|
||||
|
||||
// NewParams() creates a new configuration entity by using two methods:
|
||||
|
|
|
@ -92,8 +92,8 @@ func (m *MediaFile) Location() (*models.Location, error) {
|
|||
}
|
||||
|
||||
if len(openstreetmapLocation.Name) > 1 {
|
||||
location.LocName = strings.ReplaceAll(openstreetmapLocation.Name, " - ", " / ")
|
||||
location.LocName = util.Title(strings.TrimSpace(strings.ReplaceAll(location.LocName, "_", " ")))
|
||||
location.LocName = strings.Replace(openstreetmapLocation.Name, " - ", " / ", -1)
|
||||
location.LocName = util.Title(strings.TrimSpace(strings.Replace(location.LocName, "_", " ", -1)))
|
||||
}
|
||||
|
||||
location.LocHouseNr = strings.TrimSpace(openstreetmapLocation.Address.HouseNumber)
|
||||
|
@ -106,11 +106,11 @@ func (m *MediaFile) Location() (*models.Location, error) {
|
|||
location.LocCountryCode = strings.TrimSpace(openstreetmapLocation.Address.CountryCode)
|
||||
location.LocDisplayName = strings.TrimSpace(openstreetmapLocation.DisplayName)
|
||||
|
||||
locationCategory := strings.TrimSpace(strings.ReplaceAll(openstreetmapLocation.Category, "_", " "))
|
||||
locationCategory := strings.TrimSpace(strings.Replace(openstreetmapLocation.Category, "_", " ", -1))
|
||||
location.LocCategory = locationCategory
|
||||
|
||||
if openstreetmapLocation.Type != "yes" && openstreetmapLocation.Type != "unclassified" {
|
||||
locationType := strings.TrimSpace(strings.ReplaceAll(openstreetmapLocation.Type, "_", " "))
|
||||
locationType := strings.TrimSpace(strings.Replace(openstreetmapLocation.Type, "_", " ", -1))
|
||||
location.LocType = locationType
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,17 @@ func Exists(filename string) bool {
|
|||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
// Overwrite overwrites the file with data. Creates file if not present.
|
||||
func Overwrite(fileName string, data []byte) bool {
|
||||
f, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = f.Write(data)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Returns full path; ~ replaced with actual home directory
|
||||
func ExpandedFilename(filename string) string {
|
||||
if filename == "" {
|
||||
|
|
Loading…
Add table
Reference in a new issue