diff --git a/.gitignore b/.gitignore index e43b0f9..2fdca05 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .DS_Store +.idea +vendor/ diff --git a/README.md b/README.md index 27dd650..b67092b 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,24 @@ WantedBy=multi-user.target See `examples/cron/cron_job.go` +### SetUser and SetPassword +If you want the system daemon to run under a normal user and not root, you can call `SetUser(username string)` to +change the username the daemon will run as. + +If username isn't set specifically the default username will be set to `root` or `Administrator` depending on the OS. + +#### Windows specific instructions +If you are installing a service on Windows you will also need to set the user's password as well, using the +`SetPassword(password string)` function. On Windows, you will also need to grant the user permissions to +"Log on as a Service" permissions. This is done by opening the `Local Security Policy` tool, then navigating to +`Local Policies` > `User Rights Assignment` > `Log on as a service` and add the username to this list. + +If the above permissions aren't granted, the service will install correctly, but will fail to start, as the user is +unable to launch the daemon. + +For `SetUser` the username needs to be in the format of `DOMAIN\username`. If you are using a local system account, +this can be written in shorthand notation, for example: `.\username` + ## Contributors (unsorted) - [Sheile](https://github.com/Sheile) diff --git a/daemon.go b/daemon.go index e5b0fa2..00e949e 100644 --- a/daemon.go +++ b/daemon.go @@ -193,6 +193,12 @@ type Daemon interface { // Run - run executable service Run(e Executable) (string, error) + + // SetUser - Sets the user the service will run as + SetUser(username string) error + + // SetPassword - Sets the password for the user that will run the service. Only used for Windows services + SetPassword(password string) error } // Executable interface defines controlling methods of executable service diff --git a/daemon_darwin.go b/daemon_darwin.go index 0c99343..39caf76 100644 --- a/daemon_darwin.go +++ b/daemon_darwin.go @@ -19,12 +19,13 @@ type darwinRecord struct { name string description string kind Kind + username string dependencies []string } func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) { - return &darwinRecord{name, description, kind, dependencies}, nil + return &darwinRecord{name, description, kind, "", dependencies}, nil } // Standard service path for system daemons @@ -102,6 +103,23 @@ func (darwin *darwinRecord) Install(args ...string) (string, error) { return installAction + failed, err } + if darwin.username == "" { + if darwin.kind == UserAgent { + usr, err := user.Current() + if err != nil { + return installAction + failed, err + } + darwin.username = usr.Username + } else if darwin.kind == GlobalAgent { + usr := os.Getenv("SUDO_USER") + if usr != "" { + darwin.username = usr + } + } else { + darwin.username = "root" + } + } + templ, err := template.New("propertyList").Parse(propertyList) if err != nil { return installAction + failed, err @@ -110,9 +128,9 @@ func (darwin *darwinRecord) Install(args ...string) (string, error) { if err := templ.Execute( file, &struct { - Name, Path string + Name, Path, Username string Args []string - }{darwin.name, execPatch, args}, + }{darwin.name, execPatch, darwin.username, args}, ); err != nil { return installAction + failed, err } @@ -213,16 +231,31 @@ func (darwin *darwinRecord) Run(e Executable) (string, error) { } // GetTemplate - gets service config template -func (linux *darwinRecord) GetTemplate() string { +func (darwin *darwinRecord) GetTemplate() string { return propertyList } // SetTemplate - sets service config template -func (linux *darwinRecord) SetTemplate(tplStr string) error { +func (darwin *darwinRecord) SetTemplate(tplStr string) error { propertyList = tplStr return nil } +// SetUser - Sets the user the service will run as +func (darwin *darwinRecord) SetUser(username string) error { + if darwin.kind == UserAgent || darwin.kind == GlobalAgent { + return ErrUserNameNotSupported + } + + darwin.username = username + return nil +} + +// SetPassword - Sets the password for the user that will run the service. Only used for macOS +func (darwin *darwinRecord) SetPassword(_ string) error { + return ErrUnsupportedSystem +} + var propertyList = ` @@ -237,6 +270,8 @@ var propertyList = ` {{range .Args}}{{.}} {{end}} + UserName + {{.Username}} RunAtLoad WorkingDirectory diff --git a/daemon_freebsd.go b/daemon_freebsd.go index 2d84d5f..a5c7073 100644 --- a/daemon_freebsd.go +++ b/daemon_freebsd.go @@ -20,6 +20,7 @@ type bsdRecord struct { name string description string kind Kind + username string dependencies []string } @@ -72,7 +73,7 @@ func (bsd *bsdRecord) getCmd(cmd string) string { // Get the daemon properly func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) { - return &bsdRecord{name, description, kind, dependencies}, nil + return &bsdRecord{name, description, kind, "", dependencies}, nil } func execPath() (name string, err error) { @@ -130,6 +131,10 @@ func (bsd *bsdRecord) Install(args ...string) (string, error) { return installAction + failed, err } + if bsd.username == "" { + bsd.username = "root" + } + templ, err := template.New("bsdConfig").Parse(bsdConfig) if err != nil { return installAction + failed, err @@ -138,8 +143,8 @@ func (bsd *bsdRecord) Install(args ...string) (string, error) { if err := templ.Execute( file, &struct { - Name, Description, Path, Args string - }{bsd.name, bsd.description, execPatch, strings.Join(args, " ")}, + Name, Description, Username, Path, Args string + }{bsd.name, bsd.description, bsd.username, execPatch, strings.Join(args, " ")}, ); err != nil { return installAction + failed, err } @@ -240,16 +245,27 @@ func (bsd *bsdRecord) Run(e Executable) (string, error) { } // GetTemplate - gets service config template -func (linux *bsdRecord) GetTemplate() string { +func (bsd *bsdRecord) GetTemplate() string { return bsdConfig } // SetTemplate - sets service config template -func (linux *bsdRecord) SetTemplate(tplStr string) error { +func (bsd *bsdRecord) SetTemplate(tplStr string) error { bsdConfig = tplStr return nil } +// SetUser - Sets the user the service will run as +func (bsd *bsdRecord) SetUser(username string) error { + bsd.username = username + return nil +} + +// SetPassword - Sets the password for the user that will run the service +func (bsd *bsdRecord) SetPassword(password string) error { + return ErrUnsupportedSystem +} + var bsdConfig = `#!/bin/sh # # PROVIDE: {{.Name}} @@ -269,7 +285,7 @@ rcvar="{{.Name}}_enable" command="{{.Path}}" pidfile="/var/run/$name.pid" -start_cmd="/usr/sbin/daemon -p $pidfile -f $command {{.Args}}" +start_cmd="/usr/sbin/daemon -p $pidfile -u {{.Username}} -f $command {{.Args}}" load_rc_config $name run_rc_command "$1" ` diff --git a/daemon_linux.go b/daemon_linux.go index 524c7d8..6da77e7 100644 --- a/daemon_linux.go +++ b/daemon_linux.go @@ -13,12 +13,12 @@ import ( func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) { // newer subsystem must be checked first if _, err := os.Stat("/run/systemd/system"); err == nil { - return &systemDRecord{name, description, kind, dependencies}, nil + return &systemDRecord{name, description, kind, "", dependencies}, nil } if _, err := os.Stat("/sbin/initctl"); err == nil { - return &upstartRecord{name, description, kind, dependencies}, nil + return &upstartRecord{name, description, kind, "", dependencies}, nil } - return &systemVRecord{name, description, kind, dependencies}, nil + return &systemVRecord{name, description, kind, "", dependencies}, nil } // Get executable path diff --git a/daemon_linux_systemd.go b/daemon_linux_systemd.go index d6e6cca..e24c6fb 100644 --- a/daemon_linux_systemd.go +++ b/daemon_linux_systemd.go @@ -17,6 +17,7 @@ type systemDRecord struct { name string description string kind Kind + username string dependencies []string } @@ -77,6 +78,10 @@ func (linux *systemDRecord) Install(args ...string) (string, error) { return installAction + failed, err } + if linux.username == "" { + linux.username = "root" + } + templ, err := template.New("systemDConfig").Parse(systemDConfig) if err != nil { return installAction + failed, err @@ -85,10 +90,11 @@ func (linux *systemDRecord) Install(args ...string) (string, error) { if err := templ.Execute( file, &struct { - Name, Description, Dependencies, Path, Args string + Name, Description, Username, Dependencies, Path, Args string }{ linux.name, linux.description, + linux.username, strings.Join(linux.dependencies, " "), execPatch, strings.Join(args, " "), @@ -211,6 +217,17 @@ func (linux *systemDRecord) SetTemplate(tplStr string) error { return nil } +// SetUser - Sets the user the service will run as +func (linux *systemDRecord) SetUser(username string) error { + linux.username = username + return nil +} + +// SetPassword - Sets the password for the user that will run the service. Only used for macOS +func (linux *systemDRecord) SetPassword(_ string) error { + return ErrUnsupportedSystem +} + var systemDConfig = `[Unit] Description={{.Description}} Requires={{.Dependencies}} @@ -221,6 +238,7 @@ PIDFile=/var/run/{{.Name}}.pid ExecStartPre=/bin/rm -f /var/run/{{.Name}}.pid ExecStart={{.Path}} {{.Args}} Restart=on-failure +User={{.Username}} [Install] WantedBy=multi-user.target diff --git a/daemon_linux_systemv.go b/daemon_linux_systemv.go index 588f6db..c50b6ee 100644 --- a/daemon_linux_systemv.go +++ b/daemon_linux_systemv.go @@ -17,6 +17,7 @@ type systemVRecord struct { name string description string kind Kind + username string dependencies []string } @@ -82,11 +83,15 @@ func (linux *systemVRecord) Install(args ...string) (string, error) { return installAction + failed, err } + if linux.username == "" { + linux.username = "root" + } + if err := templ.Execute( file, &struct { - Name, Description, Path, Args string - }{linux.name, linux.description, execPatch, strings.Join(args, " ")}, + Name, Description, Username, Path, Args string + }{linux.name, linux.description, linux.username, execPatch, strings.Join(args, " ")}, ); err != nil { return installAction + failed, err } @@ -219,6 +224,17 @@ func (linux *systemVRecord) SetTemplate(tplStr string) error { return nil } +// SetUser - Sets the user the service will run as +func (linux *systemVRecord) SetUser(username string) error { + linux.username = username + return nil +} + +// SetPassword - Sets the password for the user that will run the service. Only used for macOS +func (linux *systemVRecord) SetPassword(_ string) error { + return ErrUnsupportedSystem +} + var systemVConfig = `#! /bin/sh # # /etc/rc.d/init.d/{{.Name}} @@ -273,7 +289,7 @@ start() { if ! [ -f $pidfile ]; then printf "Starting $servname:\t" echo "$(date)" >> $stdoutlog - $exec {{.Args}} >> $stdoutlog 2>> $stderrlog & + su -l {{.Username}} -c "$exec {{.Args}} >> $stdoutlog 2>> $stderrlog &" echo $! > $pidfile touch $lockfile success diff --git a/daemon_linux_upstart.go b/daemon_linux_upstart.go index 16216c7..fc5cda1 100644 --- a/daemon_linux_upstart.go +++ b/daemon_linux_upstart.go @@ -17,6 +17,7 @@ type upstartRecord struct { name string description string kind Kind + username string dependencies []string } @@ -77,16 +78,20 @@ func (linux *upstartRecord) Install(args ...string) (string, error) { return installAction + failed, err } - templ, err := template.New("upstatConfig").Parse(upstatConfig) + templ, err := template.New("upstartConfig").Parse(upstartConfig) if err != nil { return installAction + failed, err } + if linux.username == "" { + linux.username = "root" + } + if err := templ.Execute( file, &struct { - Name, Description, Path, Args string - }{linux.name, linux.description, execPatch, strings.Join(args, " ")}, + Name, Description, Username, Path, Args string + }{linux.name, linux.description, linux.username, execPatch, strings.Join(args, " ")}, ); err != nil { return installAction + failed, err } @@ -188,16 +193,27 @@ func (linux *upstartRecord) Run(e Executable) (string, error) { // GetTemplate - gets service config template func (linux *upstartRecord) GetTemplate() string { - return upstatConfig + return upstartConfig } // SetTemplate - sets service config template func (linux *upstartRecord) SetTemplate(tplStr string) error { - upstatConfig = tplStr + upstartConfig = tplStr return nil } -var upstatConfig = `# {{.Name}} {{.Description}} +// SetUser - Sets the user the service will run as +func (linux *upstartRecord) SetUser(username string) error { + linux.username = username + return nil +} + +// SetPassword - Sets the password for the user that will run the service. Only used for macOS +func (linux *upstartRecord) SetPassword(_ string) error { + return ErrUnsupportedSystem +} + +var upstartConfig = `# {{.Name}} {{.Description}} description "{{.Description}}" author "Pichu Chen " @@ -208,5 +224,5 @@ stop on runlevel [016] respawn #kill timeout 5 -exec {{.Path}} {{.Args}} >> /var/log/{{.Name}}.log 2>> /var/log/{{.Name}}.err +exec su -l {{.Username}} -c "{{.Path}} {{.Args}} >> /var/log/{{.Name}}.log 2>> /var/log/{{.Name}}.err" ` diff --git a/daemon_windows.go b/daemon_windows.go index 047b635..63e714c 100644 --- a/daemon_windows.go +++ b/daemon_windows.go @@ -25,12 +25,14 @@ type windowsRecord struct { name string description string kind Kind + username string + password string dependencies []string } func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) { - return &windowsRecord{name, description, kind, dependencies}, nil + return &windowsRecord{name, description, kind, "", "",dependencies}, nil } // Install the service @@ -55,12 +57,22 @@ func (windows *windowsRecord) Install(args ...string) (string, error) { return installAction + failed, ErrAlreadyRunning } - s, err = m.CreateService(windows.name, execp, mgr.Config{ + svcOpts := mgr.Config{ DisplayName: windows.name, Description: windows.description, StartType: mgr.StartAutomatic, Dependencies: windows.dependencies, - }, args...) + } + + if windows.username != "" { + if windows.password == "" { + return installAction + failed, ErrUserPasswordNotProvided + } + svcOpts.ServiceStartName = windows.username + svcOpts.Password = windows.password + } + + s, err = m.CreateService(windows.name, execp, svcOpts, args...) if err != nil { return installAction + failed, err } @@ -345,11 +357,26 @@ func (windows *windowsRecord) Run(e Executable) (string, error) { } // GetTemplate - gets service config template -func (linux *windowsRecord) GetTemplate() string { +func (windows *windowsRecord) GetTemplate() string { return "" } // SetTemplate - sets service config template -func (linux *windowsRecord) SetTemplate(tplStr string) error { +func (windows *windowsRecord) SetTemplate(tplStr string) error { return errors.New(fmt.Sprintf("templating is not supported for windows")) } + +// SetUser - Sets the user the service will run as +func (windows *windowsRecord) SetUser(username string) error { + windows.username = username + return nil +} + +// SetPassword - Sets the password for the user that will run the service +func (windows *windowsRecord) SetPassword(password string) error { + if password == "" { + return ErrUserPasswordNotProvided + } + windows.password = password + return nil +} diff --git a/helper.go b/helper.go index e4bd4c1..74df3dd 100644 --- a/helper.go +++ b/helper.go @@ -38,6 +38,12 @@ var ( // ErrAlreadyStopped appears if try to stop already stopped service ErrAlreadyStopped = errors.New("Service has already been stopped") + + // ErrUserNameNotSupported appears if you try to set the service username on an unsupported service + ErrUserNameNotSupported = errors.New("Service kind does not support changing user") + + // ErrUserPasswordNotProvided appears if you try and install a service on windows with a non-system account user + ErrUserPasswordNotProvided = errors.New("Password must be provided if using alternate user on Windows") ) // ExecPath tries to get executable path