diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..cdc6d4622 --- /dev/null +++ b/circle.yml @@ -0,0 +1,9 @@ +machine: + timezone: + Asia/Tokyo + +test: + override: + - make lint + - make cover + - test `gofmt -l . | wc -l` = 0 diff --git a/init_windows.go b/init_windows.go new file mode 100644 index 000000000..2c1c2f9c6 --- /dev/null +++ b/init_windows.go @@ -0,0 +1,31 @@ +// +build windows + +package main + +import ( + "os" + "path/filepath" + "syscall" + "unsafe" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32") + procGetModuleFileName = kernel32.NewProc("GetModuleFileNameW") +) + +func getModuleFileName() (string, error) { + var p [syscall.MAX_PATH]uint16 + result, _, err := procGetModuleFileName.Call(0, uintptr(unsafe.Pointer(&p[0])), uintptr(len(p))) + if result == 0 { + return os.Args[0], err + } + return syscall.UTF16ToString(p[:]), nil +} + +func init() { + if p, err := getModuleFileName(); err == nil { + os.Setenv("PATH", filepath.Dir(p)+ + string(filepath.ListSeparator)+os.Getenv("PATH")) + } +} diff --git a/spec/linux/interface_test.go b/spec/linux/interface_test.go index 65bf70725..fc702cfe8 100644 --- a/spec/linux/interface_test.go +++ b/spec/linux/interface_test.go @@ -22,6 +22,10 @@ func TestInterfaceGenerate(t *testing.T) { t.Errorf("should not raise error: %v", err) } + if os.Getenv("CIRCLECI") != "" { + t.Skip("Skip in CircleCI for now") + } + if len(value) == 0 { t.Error("should have at least 1 interface") return @@ -53,6 +57,10 @@ func TestGenerateByIpCommand(t *testing.T) { t.Errorf("should not raise error: %v", err) } + if os.Getenv("CIRCLECI") != "" { + t.Skip("Skip in CircleCI for now") + } + name := "eth0" if _, ok := interfaces[name]; !ok { t.Error("should have interfaces") diff --git a/wix/build.bat b/wix/build.bat index feff10987..c864e7342 100755 --- a/wix/build.bat +++ b/wix/build.bat @@ -25,7 +25,8 @@ go get github.com/mackerelio/mackerel-agent/wix/wrapper go get github.com/mackerelio/mackerel-agent/wix/replace go get github.com/mackerelio/mackerel-agent/wix/generate_wxs -go build -o ..\build\wrapper.exe wrapper\wrapper_windows.go +go build -o ..\build\wrapper.exe wrapper\wrapper_windows.go wrapper\install.go + go build -o ..\build\replace.exe replace\replace_windows.go go build -o ..\build\generate_wxs.exe generate_wxs\generate_wxs.go diff --git a/wix/wrapper/install.go b/wix/wrapper/install.go new file mode 100644 index 000000000..df04dddd8 --- /dev/null +++ b/wix/wrapper/install.go @@ -0,0 +1,90 @@ +// This is based on https://github.com/golang/sys/tree/master/windows/svc + +// +build windows + +package main + +import ( + "fmt" + "os" + "path/filepath" + + "golang.org/x/sys/windows/svc/eventlog" + "golang.org/x/sys/windows/svc/mgr" +) + +func exePath() (string, error) { + prog := os.Args[0] + p, err := filepath.Abs(prog) + if err != nil { + return "", err + } + fi, err := os.Stat(p) + if err == nil { + if !fi.Mode().IsDir() { + return p, nil + } + err = fmt.Errorf("%s is directory", p) + } + if filepath.Ext(p) == "" { + p += ".exe" + fi, err := os.Stat(p) + if err == nil { + if !fi.Mode().IsDir() { + return p, nil + } + err = fmt.Errorf("%s is directory", p) + } + } + return "", err +} + +func installService(name, desc string) error { + exepath, err := exePath() + if err != nil { + return err + } + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err == nil { + s.Close() + return fmt.Errorf("service %s already exists", name) + } + s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: desc}, "is", "auto-started") + if err != nil { + return err + } + defer s.Close() + err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) + if err != nil { + s.Delete() + return fmt.Errorf("SetupEventLogSource() failed: %s", err) + } + return nil +} + +func removeService(name string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + s, err := m.OpenService(name) + if err != nil { + return fmt.Errorf("service %s is not installed", name) + } + defer s.Close() + err = s.Delete() + if err != nil { + return err + } + err = eventlog.Remove(name) + if err != nil { + return fmt.Errorf("RemoveEventLogSource() failed: %s", err) + } + return nil +} diff --git a/wix/wrapper/wrapper_windows.go b/wix/wrapper/wrapper_windows.go index 26c2a95bf..3928ef051 100644 --- a/wix/wrapper/wrapper_windows.go +++ b/wix/wrapper/wrapper_windows.go @@ -4,10 +4,12 @@ import ( "bufio" "io" "log" + "os" "os/exec" "path/filepath" "regexp" "syscall" + "time" "unsafe" "golang.org/x/sys/windows/svc" @@ -21,7 +23,28 @@ const startEid = 2 const stopEid = 3 const loggerEid = 4 +var ( + kernel32 = syscall.NewLazyDLL("kernel32") + procAllocConsole = kernel32.NewProc("AllocConsole") + procGenerateConsoleCtrlEvent = kernel32.NewProc("GenerateConsoleCtrlEvent") + procGetModuleFileName = kernel32.NewProc("GetModuleFileNameW") +) + func main() { + if len(os.Args) == 2 { + var err error + switch os.Args[1] { + case "install": + err = installService("mackerel-agent", "mackerel agent") + case "remove": + err = removeService("mackerel-agent") + } + if err != nil { + log.Fatal(err) + } + return + } + elog, err := eventlog.Open(name) if err != nil { log.Fatal(err.Error()) @@ -42,7 +65,14 @@ type handler struct { } func (h *handler) start() error { - cmd := exec.Command(filepath.Join(filepath.Dir(execdir()), "mackerel-agent.exe")) + procAllocConsole.Call() + dir := execdir() + cmd := exec.Command(filepath.Join(dir, "mackerel-agent.exe")) + cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, + } + cmd.Dir = dir + h.cmd = cmd r, w := io.Pipe() cmd.Stderr = w @@ -78,8 +108,26 @@ func (h *handler) start() error { return cmd.Start() } +func interrupt(p *os.Process) error { + r1, _, err := procGenerateConsoleCtrlEvent.Call(syscall.CTRL_BREAK_EVENT, uintptr(p.Pid)) + if r1 == 0 { + return err + } + return nil +} + func (h *handler) stop() error { if h.cmd != nil && h.cmd.Process != nil { + err := interrupt(h.cmd.Process) + if err == nil { + end := time.Now().Add(10 * time.Second) + for time.Now().Before(end) { + if h.cmd.ProcessState != nil && h.cmd.ProcessState.Exited() { + return nil + } + time.Sleep(1 * time.Second) + } + } return h.cmd.Process.Kill() } return nil @@ -133,14 +181,10 @@ L: } func execdir() string { - var ( - kernel32 = syscall.NewLazyDLL("kernel32") - procGetModuleFileName = kernel32.NewProc("GetModuleFileNameW") - ) var wpath [syscall.MAX_PATH]uint16 r1, _, err := procGetModuleFileName.Call(0, uintptr(unsafe.Pointer(&wpath[0])), uintptr(len(wpath))) if r1 == 0 { log.Fatal(err) } - return syscall.UTF16ToString(wpath[:]) + return filepath.Dir(syscall.UTF16ToString(wpath[:])) }