Skip to content

Commit

Permalink
Refactor to cmd and app packages
Browse files Browse the repository at this point in the history
  • Loading branch information
billgraziano committed Aug 4, 2018
1 parent 2d577c3 commit 1417b55
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ _testmain.go
*.test
*.prof
.vs
/deploy
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The program compiles and runs on GO 1.8. The generated executable accepts a sin
* debug - runs the program from the command-line
* install - installs a windows service
* remove - removes the windows service
* start
* stop
* pause
* continue

## Installing and Updating a Service

Expand All @@ -27,24 +31,30 @@ The service can be removed from an Administrative command prompt by typing:

## Customizing

All service boilerplate code is in the four files with a "svc_" prefix. There should
The code exists in two packabages

* cmd/gosvc - Wrapper to control the service
* app - Your application

All service boilerplate code is in the four files in `cmd/gosvc` with a "svc_" prefix. There should
be no need to modify this code.

The only code you should need to change is in main.go.

* `svcName` - This constant is the name of the installed service. This is used for NET START and NET STOP commands.
* `svcNameLong` - This is the longer service name that appears in the Services control panel.
* `setup()` - This function is called to do any application setup. It can return an error. If the error is non-nil, the service will exit.
* `svcLauncher()` - This launches app.Run passing it a Windows Event Logger, the `svcName`, and the SHA1 hash from GIT.

You should also rename the `gosvc` directory to the name of your executable.

* `setup()` - This function is called to do any application setup. If returns a non-nil error, the service will exit.
* `yourApp()` - This is launched as a GO routine. This is the body of your application. Here you can launch web servers, listeners, or whatever your application does.

The two fucntions are controlled through a function named `svcLauncher()` that you can customize if you like.


## Advanced
### Logging
The service exposes an `eLog` variable that is a logger. This will write to the console when running interactively and to the Application Event Log when running as a service.

### Removing the Global Constants
If you don't want the service names as global constants (and you probably won't) you can use the values in `svc_main.go` at the top of `main()` function instead.
The `server` struct exposes a `winlog` variable that is a logger. This will write to the console when running interactively and to the Winodws Application Event Log when running as a service. I typically use this for any service errors, start and stop notification, and any issues reading configuration or setting up logging.

### Other
This uses the [GO error wrapper package]("github.com/pkg/errors"). You can easily
Expand Down
22 changes: 22 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package app

import "time"

// The wrapper of your app
func yourApp(s server) {

s.winlog.Info(1, "In app.yourApp")

// This is just some sample code to do something
time.Sleep(1 * time.Second)
s.winlog.Info(1, "Still running")

time.Sleep(2 * time.Second)
s.winlog.Info(1, "And running")

time.Sleep(3 * time.Second)
s.winlog.Info(1, "But the service will keep running")

// Notice that if this exits, the service continues to run
// You can launch web servers, etc.
}
20 changes: 20 additions & 0 deletions app/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package app

import (
"github.com/pkg/errors"
"golang.org/x/sys/windows/svc/debug"
)

// Run launches the service
func Run(wl *debug.Log, svcName, sha1ver string) error {

s, err := setup(wl, svcName, sha1ver)
if err != nil {
return errors.Wrap(err, "setup")
}

// Your service should be launched as a GO routine
go yourApp(s)

return nil
}
12 changes: 12 additions & 0 deletions app/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app

import (
"golang.org/x/sys/windows/svc/debug"
)

type server struct {
winlog debug.Log
// a local logger
// a database connection
// your app configuration
}
32 changes: 32 additions & 0 deletions app/setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package app

import (
"fmt"

"golang.org/x/sys/windows/svc/debug"
)

// if setup returns an error, the service doesn't start
func setup(wl *debug.Log, svcName, sha1ver string) (server, error) {
var s server

// did we get a full SHA1?
if len(sha1ver) == 40 {
sha1ver = sha1ver[0:7]
}

if sha1ver == "" {
sha1ver = "dev"
}

s.winlog = *wl

// Note: any logging here goes to Windows App Log
// I suggest you setup local logging
s.winlog.Info(1, fmt.Sprintf("%s: setup (%s)", svcName, sha1ver))

// read configuration
// configure more logging

return s, nil
}
31 changes: 31 additions & 0 deletions cmd/gosvc/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build windows

package main

import (
"github.com/billgraziano/go-windows-svc/app"
"github.com/pkg/errors"
)

// This is the name you will use for the NET START command
const svcName = "gosvc"

// This is the name that will appear in the Services control panel
const svcNameLong = "GO Service"

// This is assigned the full SHA1 hash from GIT
var sha1ver string

func svcLauncher() error {

err := app.Run(&elog, svcName, sha1ver)
if err != nil {
return errors.Wrap(err, "app.run")
}

return nil
}
5 changes: 4 additions & 1 deletion svc_install.go → cmd/gosvc/svc_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
)

func exePath() (string, error) {
var err error
prog := os.Args[0]
p, err := filepath.Abs(prog)
if err != nil {
Expand All @@ -29,8 +30,10 @@ func exePath() (string, error) {
err = fmt.Errorf("%s is directory", p)
}
if filepath.Ext(p) == "" {
var fi os.FileInfo

p += ".exe"
fi, err := os.Stat(p)
fi, err = os.Stat(p)
if err == nil {
if !fi.Mode().IsDir() {
return p, nil
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions svc_service.go → cmd/gosvc/svc_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func runService(name string, isDebug bool) {
}
defer elog.Close()

elog.Info(1, fmt.Sprintf("starting %s service", name))
elog.Info(1, fmt.Sprintf("%s: starting", name))
run := svc.Run
if isDebug {
run = debug.Run
Expand All @@ -87,5 +87,5 @@ func runService(name string, isDebug bool) {
elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
return
}
elog.Info(1, fmt.Sprintf("%s service stopped", name))
elog.Info(1, fmt.Sprintf("%s: stopped", name))
}
59 changes: 0 additions & 59 deletions main.go

This file was deleted.

18 changes: 18 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
TARGETDIR=.\deploy
proj=github.com\billgraziano\go-windows-svc
sha1ver := $(shell git rev-parse HEAD)
test := $(shell date /t)


all: vet test buildEXE

vet:
go vet -all -shadow .\cmd\gosvc
go vet -all -shadow .\app

test:
go.exe test -timeout 30s $(proj)\app

# The sha1 stuff isn't working as of now
buildEXE:
go build -o "$(TARGETDIR)\gosvc.exe" -a -ldflags "-X main.sha1ver=$(sha1ver)" .\cmd\gosvc

0 comments on commit 1417b55

Please sign in to comment.