Skip to content

Commit

Permalink
Added new methods SetUser and SetPassword to allow for running daemons
Browse files Browse the repository at this point in the history
as non-root user on all supported operating systems

Remove debug print statement
  • Loading branch information
bunjiboys committed Nov 2, 2020
1 parent e049c78 commit 760246b
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 31 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.DS_Store
.idea
vendor/
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 40 additions & 5 deletions daemon_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
Expand All @@ -237,6 +270,8 @@ var propertyList = `<?xml version="1.0" encoding="UTF-8"?>
{{range .Args}}<string>{{.}}</string>
{{end}}
</array>
<key>UserName</key>
<string>{{.Username}}</string>
<key>RunAtLoad</key>
<true/>
<key>WorkingDirectory</key>
Expand Down
28 changes: 22 additions & 6 deletions daemon_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type bsdRecord struct {
name string
description string
kind Kind
username string
dependencies []string
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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}}
Expand All @@ -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"
`
6 changes: 3 additions & 3 deletions daemon_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion daemon_linux_systemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type systemDRecord struct {
name string
description string
kind Kind
username string
dependencies []string
}

Expand Down Expand Up @@ -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
Expand All @@ -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, " "),
Expand Down Expand Up @@ -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}}
Expand All @@ -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
Expand Down
22 changes: 19 additions & 3 deletions daemon_linux_systemv.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type systemVRecord struct {
name string
description string
kind Kind
username string
dependencies []string
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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}}
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 760246b

Please sign in to comment.