golang实现注册系统服务(Windows、Darwin)
2023-12-20 00:06:46
golang实现注册系统服务(Windows、Darwin)
仓库地址:https://github.com/ziyifast/yiSystemService
使用第三方包:go get “github.com/kardianos/service”
日志库:go get “github.com/sirupsen/logrus”
- log “github.com/sirupsen/logrus”
1 初始化日志
util/log.go:
package util
import (
log "github.com/sirupsen/logrus"
"io"
"os"
)
func InitLog(logPath string) {
//设置输出样式,自带的只有两种样式logrus.JSONFormatter{}和logrus.TextFormatter{}
log.SetFormatter(&log.TextFormatter{})
log.SetOutput(os.Stdout)
//设置output,默认为stderr,可以为任何io.Writer,比如文件*os.File
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
writers := []io.Writer{
file,
os.Stdout}
//同时写文件和屏幕
fileAndStdoutWriter := io.MultiWriter(writers...)
if err == nil {
log.SetOutput(fileAndStdoutWriter)
} else {
log.Info("failed to log to file.")
}
//设置最低loglevel
log.SetLevel(log.InfoLevel)
}
2 导入三方Service库+编写serviceConfig(描述、可执行文件路径等)
2.1 consts
①consts/consts.go:
package consts
import "path"
const (
ServiceVersion = "v1.0"
ServiceName = "yiService"
ServicePort = 9999
ServiceDisplayName = "yi.service"
ServiceDescription = "my test service"
)
var LogPath string
func init() {
LogPath = path.Join(WorkDir, "yiService.log")
}
②consts/consts_darwin.go:
//go:build darwin
// +build darwin
package consts
import "path/filepath"
var (
StartupBash = filepath.Join(WorkDir, "startup.sh")
ShutdownBash = filepath.Join(WorkDir, "shutdown.sh")
)
const (
WorkDir = "/usr/local/yiService"
MainExe = "yiService"
)
③consts/consts_windows.go:
package consts
import "path/filepath"
var (
StartupBash = filepath.Join(WorkDir, "startup.bat")
ShutdownBash = filepath.Join(WorkDir, "shutdown.bat")
)
const (
WorkDir = "c:/yiService"
MainExe = "yiService.exe"
)
2.2 system_service
system_service/system_service.go:
package system_service
import (
"awesomeProject1/consts"
"fmt"
"github.com/kardianos/service"
log "github.com/sirupsen/logrus"
"os"
"runtime"
)
var (
logger service.Logger
svc service.Service
)
func init() {
svcConfig := &service.Config{
Name: consts.ServiceName,
WorkingDirectory: consts.WorkDir,
DisplayName: consts.ServiceDisplayName,
Description: consts.ServiceDescription,
Arguments: []string{"service", "run"}, //服务注册成功之后,由服务去执行yiService.exe service run【运行服务】
Executable: fmt.Sprintf("%s/%s", consts.WorkDir, consts.ServiceName),
Option: service.KeyValue{},
}
if runtime.GOOS == "windows" {
svcConfig.Executable = fmt.Sprintf("%s\\%s.exe", consts.WorkDir, consts.ServiceName)
}
var err error
program := &Program{}
svc, err = service.New(program, svcConfig)
if err != nil {
log.Errorf("create service fail %v\n", err)
return
}
errChan := make(chan error, 5)
logger, err = svc.Logger(errChan)
if err != nil {
log.Errorf("%v\n", err)
return
}
if err != nil {
log.Errorf("%v\n", err)
return
}
go func() {
log.Info("watching err chan....")
for {
err := <-errChan
if err != nil {
log.Fatalf("service err %v", err)
}
}
}()
}
func StartSVC() {
log.Infof("StartSVC...")
serviceControl("install")
serviceControl("start")
}
func StopSVC() {
log.Infof("try to stop service, if already exists.")
serviceControl("stop")
}
func RunSVC() {
fmt.Sprintf("%s service running \n", runtime.GOOS)
if err := svc.Run(); err != nil {
fmt.Sprintf("%s service running fail %v \n", runtime.GOOS, err)
os.Exit(1)
}
}
func serviceControl(action string) {
log.Infof("%s service %s \n", runtime.GOOS, action)
if err := service.Control(svc, action); err != nil {
log.Infof("%s service: %v \n", action, err)
}
}
3 编写服务脚本(startup、shutdown脚本)
3.1 startup脚本
- assets/startup.bat.tmpl
@echo off
@REM It is recommended to start here.
@REM Modify bash args below.
@REM "name" and "tags" must be alphanumeric sequence: [a-zA-Z-_.]
@REM Chinese chars are not supported here in bash file.
@REM
@REM Example:
@REM
@REM yiService.exe service start ^
@REM
@REM Startup from here
@REM
c:/yiService/yiService.exe service start
echo "yiService started!"
echo ""
pause
- assets/startup.sh.tmpl
#!/bin/bash
# It is recommended to start here.
# Modify bash args below.
# "name" and "tags" must be alphanumeric sequence: [a-zA-Z-_.]
# Chinese chars are not supported here in bash file.
#
# Example:
#
# yiService.exe service start \
#
# Startup from here
#
# launchctl start yiService
./yiService service start
echo yiService started!
ps aux |grep yiService
echo ""
3.2 shutdown脚本
- assets/shutdown.bat.tmpl
@echo off
@REM This command bash will stop the yiService windows service
@REM And then uninstall this service from operation system
@REM Configurations will be remained in directory c:/yiService/yiService on the disk.
@REM You can restart from those configurations in the near future.
@REM
c:/yiService/yiService.exe service stop
set "$process=yiService.exe"
for %%a in (%$process%) do tasklist /fi "imagename eq %%a" | find "%%a" && taskkill /f /im %%a
echo shutdown yiService successfully!
pause
- assets/shutdown.sh.tmpl
#!/bin/bash
# This command bash will stop the yiService windows service
# And then uninstall this service from operation system
# Configurations will be remained in directory c:/yiService on the disk.
# You can restart from those configurations in the near future.
#
./yiService service stop
PID=$(ps -eaf | grep '/usr/local/yiService' | grep -v grep | awk '{print $2}')
if [[ "" != "$PID" ]]; then
echo "killing $PID"
kill -9 "$PID"
fi
echo shutdown yiService successfully!
4 编写extractFiles(执行exe文件之后,拷贝exe到服务工作目录)
system_service/extract_files.go:
package system_service
import (
"awesomeProject1/assets"
"awesomeProject1/consts"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"os"
"runtime"
)
func ExtractFiles() {
CopyMainExe()
//copy startup\shutdown
if err := os.WriteFile(consts.StartupBash, assets.StartupBatTmpl, os.ModePerm); err != nil {
log.Errorf("create startup bash failed %v", err)
}
if err := os.WriteFile(consts.ShutdownBash, assets.ShutdownBatTmpl, os.ModePerm); err != nil {
log.Errorf("create shutdown bash failed %v", err)
}
}
func CopyMainExe() {
executable, err := os.Executable()
log.Infof("install %s to %s", executable, consts.MainExe)
if err != nil {
log.Errorf("%v", err)
}
sFile, err := os.Open(executable)
if err != nil {
log.Errorf("%v", err)
}
defer sFile.Close()
exePath := fmt.Sprintf("%s/%s", consts.WorkDir, consts.MainExe)
if runtime.GOOS == "windows" {
exePath = fmt.Sprintf("%s\\%s", consts.WorkDir, consts.MainExe)
}
_, err = os.Stat(exePath)
if err == nil {
//overwrite
if err := os.RemoveAll(exePath); err != nil {
log.Errorf("%v", err)
}
}
eFile, err := os.Create(exePath)
if err != nil {
log.Errorf("%v", err)
}
defer eFile.Close()
if _, err = io.Copy(eFile, sFile); err != nil {
log.Errorf("%v", err)
}
if err = eFile.Sync(); err != nil {
log.Errorf("%v", err)
}
if err = os.Chdir(consts.WorkDir); err != nil {
log.Errorf("%v\n", err)
}
if err = os.Chmod(consts.MainExe, os.FileMode(0777)); err != nil {
log.Errorf("%v", err)
}
}
5 编写firewall部分+main函数部分(开放端口+由系统服务拉起进程)
firewall部分:以管理员身份运行,开放进程服务端口
cmd/main.go部分:初次运行尝试卸载服务,带os.Args参数运行启动服务
5.1 firewall部分
- darwin
system_service/firewall/firewall_darwin.go:
//go:build darwin
// +build darwin
package firewall
import (
"awesomeProject1/consts"
"bytes"
"fmt"
log "github.com/sirupsen/logrus"
"os/exec"
"strconv"
"strings"
)
func OpenPort() {
log.Infof("darwin firewall checking\n")
cmd0 := exec.Command("/usr/bin/nc", "-z", "127.0.0.1", fmt.Sprintf("%d", consts.ServicePort))
log.Warnf("cmd0=%s\n", cmd0)
stdout, err := cmd0.CombinedOutput()
result := string(stdout)
if err != nil {
log.Infof("err=%v \n", err)
return
}
if strings.Contains(result, "command not found") {
fmt.Println("[warn]:", result)
return
}
if strings.Contains(result, "not running") {
fmt.Println("[warn]:", result)
return
}
if strings.Contains(result, strconv.Itoa(consts.ServicePort)) {
log.Warnf("%d already opened\n", consts.ServicePort)
return
}
cmd := exec.Command("bash", "-c", fmt.Sprintf("firewall-cmd --zone=public --add-port=%d/tcp --permanent && firewall-cmd --reload", consts.ServicePort))
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.Warnf("%s", stderr.String())
log.Warnf("%v", err)
}
log.Warnf(out.String())
}
- Windows
system_service/firewall/firewall_windows.go
//go:build windows
// +build windows
package firewall
import (
"bytes"
"fmt"
log "github.com/sirupsen/logrus"
"os/exec"
"runtime"
)
func OpenPort() {
log.Infot("windows firewall checking")
cmd := exec.Command("cmd", "/c", "netsh advfirewall firewall delete rule name=\"yiService\"")
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if runtime.GOOS == "windows" {
}
if err := cmd.Run(); err != nil {
log.Errorf("%s", stderr.String())
log.Errorf("%v", err)
}
cmd2 := exec.Command("cmd", "/c",
fmt.Sprintf("netsh advfirewall firewall add rule name=\"yiService\" dir=in action=allow protocol=TCP localport=%d",
consts.ServicePort,
))
var out2 bytes.Buffer
var stderr2 bytes.Buffer
cmd2.Stdout = &out2
cmd2.Stderr = &stderr2
if runtime.GOOS == "windows" {
}
if err := cmd2.Run(); err != nil {
log.Errorf("%s", stderr2.String())
log.Errorf("%v", err)
}
}
5.2 main函数
cmd/main.go
package main
import (
"awesomeProject1/consts"
"awesomeProject1/system_service"
"awesomeProject1/system_service/firewall"
"awesomeProject1/util"
log "github.com/sirupsen/logrus"
"os"
)
func init() {
//os.Setenv("dev", "true")
util.InitLog(consts.LogPath)
}
func main() {
if len(os.Getenv("dev")) != 0 {
system_service.StopSVC()
firewall.OpenPort()
system_service.StartSVC()
system_service.RunSVC()
}
log.Errorf("os.Args=%v len=%v \n", os.Args, len(os.Args))
if len(os.Args) == 1 {
//stop svc if exist
system_service.StopSVC()
log.Errorf("install %v \n", consts.ServiceName)
if err := os.MkdirAll(consts.WorkDir, os.ModePerm); err != nil {
log.Errorf("%v\n", err)
}
firewall.OpenPort()
system_service.ExtractFiles()
pwd, err := os.Getwd()
if err != nil {
log.Errorf("%v\n", err)
}
log.Infof("install svc, working directory %s", pwd)
system_service.StartSVC()
log.Infof("yiService installed!")
return
}
os.Chdir(consts.WorkDir)
log.Errorf("service %s \n", os.Args[2])
switch os.Args[2] {
case "start":
system_service.StartSVC()
return
case "stop":
system_service.StopSVC()
return
default:
system_service.RunSVC()
log.Info("running yiService")
}
}
文章来源:https://blog.csdn.net/weixin_45565886/article/details/135094723
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!