2019-06-10 09:33:19 +01:00
|
|
|
package home
|
2019-02-04 10:54:53 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
2019-02-05 11:09:05 +00:00
|
|
|
"runtime"
|
2019-07-02 10:56:23 +01:00
|
|
|
"syscall"
|
2019-02-04 10:54:53 +00:00
|
|
|
|
2019-02-25 13:44:22 +00:00
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2019-02-04 10:54:53 +00:00
|
|
|
"github.com/kardianos/service"
|
|
|
|
)
|
|
|
|
|
2019-02-05 11:09:05 +00:00
|
|
|
const (
|
2019-02-05 11:21:07 +00:00
|
|
|
launchdStdoutPath = "/var/log/AdGuardHome.stdout.log"
|
|
|
|
launchdStderrPath = "/var/log/AdGuardHome.stderr.log"
|
|
|
|
serviceName = "AdGuardHome"
|
|
|
|
serviceDisplayName = "AdGuard Home service"
|
|
|
|
serviceDescription = "AdGuard Home: Network-level blocker"
|
2019-02-05 11:09:05 +00:00
|
|
|
)
|
|
|
|
|
2019-02-04 10:54:53 +00:00
|
|
|
// Represents the program that will be launched by a service or daemon
|
|
|
|
type program struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start should quickly start the program
|
|
|
|
func (p *program) Start(s service.Service) error {
|
|
|
|
// Start should not block. Do the actual work async.
|
2019-02-05 11:09:05 +00:00
|
|
|
args := options{runningAsService: true}
|
2019-02-04 10:54:53 +00:00
|
|
|
go run(args)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops the program
|
|
|
|
func (p *program) Stop(s service.Service) error {
|
|
|
|
// Stop should not block. Return with a few seconds.
|
2019-07-02 10:56:23 +01:00
|
|
|
if config.appSignalChannel == nil {
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
config.appSignalChannel <- syscall.SIGINT
|
2019-02-04 10:54:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleServiceControlAction one of the possible control actions:
|
|
|
|
// install -- installs a service/daemon
|
|
|
|
// uninstall -- uninstalls it
|
|
|
|
// status -- prints the service status
|
|
|
|
// start -- starts the previously installed service
|
|
|
|
// stop -- stops the previously installed service
|
|
|
|
// restart - restarts the previously installed service
|
2019-02-05 11:09:05 +00:00
|
|
|
// run - this is a special command that is not supposed to be used directly
|
|
|
|
// it is specified when we register a service, and it indicates to the app
|
|
|
|
// that it is being run as a service/daemon.
|
2019-02-04 10:54:53 +00:00
|
|
|
func handleServiceControlAction(action string) {
|
|
|
|
log.Printf("Service control action: %s", action)
|
|
|
|
|
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Unable to find the path to the current directory")
|
|
|
|
}
|
|
|
|
svcConfig := &service.Config{
|
2019-02-05 11:21:07 +00:00
|
|
|
Name: serviceName,
|
|
|
|
DisplayName: serviceDisplayName,
|
|
|
|
Description: serviceDescription,
|
2019-02-04 10:54:53 +00:00
|
|
|
WorkingDirectory: pwd,
|
2019-02-05 11:09:05 +00:00
|
|
|
Arguments: []string{"-s", "run"},
|
2019-02-04 10:54:53 +00:00
|
|
|
}
|
2019-02-05 11:09:05 +00:00
|
|
|
configureService(svcConfig)
|
2019-02-04 10:54:53 +00:00
|
|
|
prg := &program{}
|
|
|
|
s, err := service.New(prg, svcConfig)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if action == "status" {
|
|
|
|
status, errSt := s.Status()
|
|
|
|
if errSt != nil {
|
|
|
|
log.Fatalf("failed to get service status: %s", errSt)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch status {
|
|
|
|
case service.StatusUnknown:
|
|
|
|
log.Printf("Service status is unknown")
|
|
|
|
case service.StatusStopped:
|
|
|
|
log.Printf("Service is stopped")
|
|
|
|
case service.StatusRunning:
|
|
|
|
log.Printf("Service is running")
|
|
|
|
}
|
2019-02-05 11:09:05 +00:00
|
|
|
} else if action == "run" {
|
|
|
|
err = s.Run()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to run service: %s", err)
|
|
|
|
}
|
2019-02-04 10:54:53 +00:00
|
|
|
} else {
|
2019-02-05 11:09:05 +00:00
|
|
|
if action == "uninstall" {
|
|
|
|
// In case of Windows and Linux when a running service is being uninstalled,
|
|
|
|
// it is just marked for deletion but not stopped
|
|
|
|
// So we explicitly stop it here
|
|
|
|
_ = s.Stop()
|
|
|
|
}
|
|
|
|
|
2019-02-04 10:54:53 +00:00
|
|
|
err = service.Control(s, action)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2019-02-05 11:09:05 +00:00
|
|
|
log.Printf("Action %s has been done successfully on %s", action, service.ChosenSystem().String())
|
|
|
|
|
|
|
|
if action == "install" {
|
|
|
|
// Start automatically after install
|
|
|
|
err = service.Control(s, "start")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to start the service: %s", err)
|
|
|
|
}
|
|
|
|
log.Printf("Service has been started")
|
2019-04-17 17:57:42 +01:00
|
|
|
|
|
|
|
if detectFirstRun() {
|
|
|
|
log.Printf(`Almost ready!
|
|
|
|
AdGuard Home is successfully installed and will automatically start on boot.
|
|
|
|
There are a few more things that must be configured before you can use it.
|
|
|
|
Click on the link below and follow the Installation Wizard steps to finish setup.`)
|
|
|
|
printHTTPAddresses("http")
|
|
|
|
}
|
|
|
|
|
2019-02-05 11:09:05 +00:00
|
|
|
} else if action == "uninstall" {
|
|
|
|
cleanupService()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// configureService defines additional settings of the service
|
|
|
|
func configureService(c *service.Config) {
|
|
|
|
c.Option = service.KeyValue{}
|
|
|
|
|
|
|
|
// OS X
|
|
|
|
// Redefines the launchd config file template
|
|
|
|
// The purpose is to enable stdout/stderr redirect by default
|
|
|
|
c.Option["LaunchdConfig"] = launchdConfig
|
|
|
|
// This key is used to start the job as soon as it has been loaded. For daemons this means execution at boot time, for agents execution at login.
|
|
|
|
c.Option["RunAtLoad"] = true
|
|
|
|
|
|
|
|
// POSIX
|
|
|
|
// Redirect StdErr & StdOut to files.
|
|
|
|
c.Option["LogOutput"] = true
|
2019-10-08 10:29:11 +01:00
|
|
|
|
|
|
|
// Add "After=" setting for systemd service file, because we must be started only after network is online
|
|
|
|
// Set "RestartSec" to 10
|
|
|
|
c.Option["SystemdScript"] = systemdScript
|
2019-02-05 11:09:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// cleanupService called on the service uninstall, cleans up additional files if needed
|
|
|
|
func cleanupService() {
|
|
|
|
if runtime.GOOS == "darwin" {
|
|
|
|
// Removing log files on cleanup and ignore errors
|
|
|
|
err := os.Remove(launchdStdoutPath)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
log.Printf("cannot remove %s", launchdStdoutPath)
|
|
|
|
}
|
|
|
|
err = os.Remove(launchdStderrPath)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
log.Printf("cannot remove %s", launchdStderrPath)
|
|
|
|
}
|
2019-02-04 10:54:53 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-05 11:09:05 +00:00
|
|
|
|
|
|
|
// Basically the same template as the one defined in github.com/kardianos/service
|
|
|
|
// but with two additional keys - StandardOutPath and StandardErrorPath
|
|
|
|
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
|
|
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
|
|
|
|
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
|
|
|
|
<plist version='1.0'>
|
|
|
|
<dict>
|
|
|
|
<key>Label</key><string>{{html .Name}}</string>
|
|
|
|
<key>ProgramArguments</key>
|
|
|
|
<array>
|
|
|
|
<string>{{html .Path}}</string>
|
|
|
|
{{range .Config.Arguments}}
|
|
|
|
<string>{{html .}}</string>
|
|
|
|
{{end}}
|
|
|
|
</array>
|
|
|
|
{{if .UserName}}<key>UserName</key><string>{{html .UserName}}</string>{{end}}
|
|
|
|
{{if .ChRoot}}<key>RootDirectory</key><string>{{html .ChRoot}}</string>{{end}}
|
|
|
|
{{if .WorkingDirectory}}<key>WorkingDirectory</key><string>{{html .WorkingDirectory}}</string>{{end}}
|
|
|
|
<key>SessionCreate</key><{{bool .SessionCreate}}/>
|
|
|
|
<key>KeepAlive</key><{{bool .KeepAlive}}/>
|
|
|
|
<key>RunAtLoad</key><{{bool .RunAtLoad}}/>
|
|
|
|
<key>Disabled</key><false/>
|
|
|
|
<key>StandardOutPath</key>
|
|
|
|
<string>` + launchdStdoutPath + `</string>
|
|
|
|
<key>StandardErrorPath</key>
|
|
|
|
<string>` + launchdStderrPath + `</string>
|
|
|
|
</dict>
|
|
|
|
</plist>
|
|
|
|
`
|
2019-10-08 10:29:11 +01:00
|
|
|
|
|
|
|
// Note: we should keep it in sync with the template from service_systemd_linux.go file
|
|
|
|
const systemdScript = `[Unit]
|
|
|
|
Description={{.Description}}
|
|
|
|
ConditionFileIsExecutable={{.Path|cmdEscape}}
|
|
|
|
After=syslog.target network-online.target
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
StartLimitInterval=5
|
|
|
|
StartLimitBurst=10
|
|
|
|
ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
|
|
|
|
{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
|
|
|
|
{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
|
|
|
|
{{if .UserName}}User={{.UserName}}{{end}}
|
|
|
|
{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
|
|
|
|
{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
|
|
|
|
{{if and .LogOutput .HasOutputFileSupport -}}
|
|
|
|
StandardOutput=file:/var/log/{{.Name}}.out
|
|
|
|
StandardError=file:/var/log/{{.Name}}.err
|
|
|
|
{{- end}}
|
|
|
|
Restart=always
|
|
|
|
RestartSec=10
|
|
|
|
EnvironmentFile=-/etc/sysconfig/{{.Name}}
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
`
|