146 lines
3.4 KiB
Go
146 lines
3.4 KiB
Go
// +build windows
|
|
|
|
package main
|
|
|
|
// Copypasta from the example files:
|
|
// https://github.com/golang/sys/blob/master/windows/svc/example
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
cli "gopkg.in/urfave/cli.v2"
|
|
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/eventlog"
|
|
"golang.org/x/sys/windows/svc/mgr"
|
|
)
|
|
|
|
const (
|
|
windowsServiceName = "CloudflareWarp"
|
|
windowsServiceDescription = "Cloudflare Warp agent"
|
|
)
|
|
|
|
func runApp(app *cli.App) {
|
|
app.Commands = append(app.Commands, &cli.Command{
|
|
Name: "service",
|
|
Usage: "Manages the Cloudflare Warp Windows service",
|
|
Subcommands: []*cli.Command{
|
|
&cli.Command{
|
|
Name: "install",
|
|
Usage: "Install Cloudflare Warp as a Windows service",
|
|
Action: installWindowsService,
|
|
},
|
|
&cli.Command{
|
|
Name: "uninstall",
|
|
Usage: "Uninstall the Cloudflare Warp service",
|
|
Action: uninstallWindowsService,
|
|
},
|
|
},
|
|
})
|
|
|
|
isIntSess, err := svc.IsAnInteractiveSession()
|
|
if err != nil {
|
|
log.Fatalf("failed to determine if we are running in an interactive session: %v", err)
|
|
}
|
|
|
|
if isIntSess {
|
|
app.Run(os.Args)
|
|
return
|
|
}
|
|
|
|
elog, err := eventlog.Open(windowsServiceName)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer elog.Close()
|
|
|
|
elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName))
|
|
err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog})
|
|
if err != nil {
|
|
elog.Error(1, fmt.Sprintf("%s service failed: %v", windowsServiceName, err))
|
|
return
|
|
}
|
|
elog.Info(1, fmt.Sprintf("%s service stopped", windowsServiceName))
|
|
}
|
|
|
|
type windowsService struct {
|
|
app *cli.App
|
|
elog *eventlog.Log
|
|
}
|
|
|
|
func (s *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
|
changes <- svc.Status{State: svc.StartPending}
|
|
go s.app.Run(args)
|
|
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
|
loop:
|
|
for {
|
|
select {
|
|
case c := <-r:
|
|
switch c.Cmd {
|
|
case svc.Interrogate:
|
|
changes <- c.CurrentStatus
|
|
case svc.Stop, svc.Shutdown:
|
|
break loop
|
|
default:
|
|
s.elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
|
|
}
|
|
}
|
|
}
|
|
close(shutdownC)
|
|
changes <- svc.Status{State: svc.StopPending}
|
|
return
|
|
}
|
|
|
|
func installWindowsService(c *cli.Context) error {
|
|
exepath, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer m.Disconnect()
|
|
s, err := m.OpenService(windowsServiceName)
|
|
if err == nil {
|
|
s.Close()
|
|
return fmt.Errorf("service %s already exists", windowsServiceName)
|
|
}
|
|
s, err = m.CreateService(windowsServiceName, exepath, mgr.Config{DisplayName: windowsServiceDescription}, "is", "auto-started")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.Close()
|
|
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
|
|
if err != nil {
|
|
s.Delete()
|
|
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func uninstallWindowsService(c *cli.Context) error {
|
|
m, err := mgr.Connect()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer m.Disconnect()
|
|
s, err := m.OpenService(windowsServiceName)
|
|
if err != nil {
|
|
return fmt.Errorf("service %s is not installed", windowsServiceName)
|
|
}
|
|
defer s.Close()
|
|
err = s.Delete()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = eventlog.Remove(windowsServiceName)
|
|
if err != nil {
|
|
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
|
|
}
|
|
return nil
|
|
}
|