Bläddra i källkod

feat: support run as a service (#13)

Co-authored-by: jeessy2 <[email protected]>
jeessy2 3 år sedan
förälder
incheckning
bd9bc16a24
7 ändrade filer med 201 tillägg och 24 borttagningar
  1. 32 3
      .gitignore
  2. 7 10
      entity/config.go
  3. 1 1
      entity/config_backup.go
  4. 6 2
      go.mod
  5. 7 2
      go.sum
  6. 133 6
      main.go
  7. 15 0
      util/docker_util.go

+ 32 - 3
.gitignore

@@ -1,4 +1,33 @@
-*.exe*
-__debug_bin
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+/backup-x
+__*
 backup-x-files
-.DS_Store
+# Folders
+_obj
+_test
+.vagrant
+releases
+tmp
+/.idea/
+vendor/
+/dist
+
+# Architecture specific extensions/prefixes
+trace.out
+*.out
+.DS_Store
+_testmain.go
+
+*.exe
+*.test
+*.prof
+profile.cov
+coverage.html
+/go.sum
+
+# Emacs backup files
+*~
+.*~

+ 7 - 10
entity/config.go

@@ -9,15 +9,8 @@ import (
 	"gopkg.in/yaml.v2"
 )
 
-// ParentSavePath Parent Save Path
-const ParentSavePath = "backup-x-files"
-
-func init() {
-	_, err := os.Stat(ParentSavePath)
-	if err != nil {
-		os.Mkdir(ParentSavePath, 0750)
-	}
-}
+// parentSavePath Parent Save Path
+const parentSavePath = "backup-x-files"
 
 // Config yml格式的配置文件
 // go的实体需大写对应config.yml的key, key全部小写
@@ -101,5 +94,9 @@ func (conf *Config) SaveConfig() (err error) {
 
 // GetConfigFilePath 获得配置文件路径, 保存到备份目录下
 func getConfigFilePath() string {
-	return ParentSavePath + string(os.PathSeparator) + ".backup_x_config.yaml"
+	_, err := os.Stat(parentSavePath)
+	if err != nil {
+		os.Mkdir(parentSavePath, 0750)
+	}
+	return parentSavePath + string(os.PathSeparator) + ".backup_x_config.yaml"
 }

+ 1 - 1
entity/config_backup.go

@@ -11,7 +11,7 @@ type BackupConfig struct {
 
 // GetProjectPath 获得项目路径
 func (backupConfig *BackupConfig) GetProjectPath() string {
-	return ParentSavePath + "/" + backupConfig.ProjectName
+	return parentSavePath + "/" + backupConfig.ProjectName
 }
 
 // NotEmptyProject 是不是空的项目

+ 6 - 2
go.mod

@@ -3,8 +3,12 @@ module backup-x
 go 1.17
 
 require (
-	github.com/aws/aws-sdk-go v1.42.3
+	github.com/aws/aws-sdk-go v1.42.4
+	github.com/kardianos/service v1.2.0
 	gopkg.in/yaml.v2 v2.4.0
 )
 
-require github.com/jmespath/go-jmespath v0.4.0 // indirect
+require (
+	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 // indirect
+)

+ 7 - 2
go.sum

@@ -1,19 +1,24 @@
-github.com/aws/aws-sdk-go v1.42.3 h1:lBKr3tQ06m1uykiychMNKLK1bRfOzaIEQpsI/S3QiNc=
-github.com/aws/aws-sdk-go v1.42.3/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
+github.com/aws/aws-sdk-go v1.42.4 h1:L3gadqlmmdWCDE7aD52l3A5TKVG9jPBHZG1/65x9GVw=
+github.com/aws/aws-sdk-go v1.42.4/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
+github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
 golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 h1:7NCfEGl0sfUojmX78nK9pBJuUlSZWEJA/TwASvfiPLo=
+golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

+ 133 - 6
main.go

@@ -2,16 +2,31 @@ package main
 
 import (
 	"backup-x/client"
+	"backup-x/util"
 	"backup-x/web"
 	"embed"
+	"flag"
+	"net"
 	"os"
 
 	"log"
 	"net/http"
 	"time"
+
+	"github.com/kardianos/service"
 )
 
-var defaultPort = "9977"
+// 监听地址
+var listen = flag.String("l", ":9977", "监听地址")
+
+// 服务管理
+var serviceType = flag.String("s", "", "服务管理, 支持install, uninstall")
+
+// 默认备份路径当前运行目录
+var backupDirDefault, _ = os.Getwd()
+
+// 配置文件路径
+var backupDir = flag.String("d", backupDirDefault, "自定义备份目录地址")
 
 //go:embed static
 var staticEmbededFiles embed.FS
@@ -20,6 +35,40 @@ var staticEmbededFiles embed.FS
 var faviconEmbededFile embed.FS
 
 func main() {
+	flag.Parse()
+	if _, err := net.ResolveTCPAddr("tcp", *listen); err != nil {
+		log.Fatalf("解析监听地址异常,%s", err)
+	}
+
+	switch *serviceType {
+	case "install":
+		installService()
+	case "uninstall":
+		uninstallService()
+	default:
+		if util.IsRunInDocker() {
+			run(100 * time.Millisecond)
+		} else {
+			s := getService()
+			status, _ := s.Status()
+			if status != service.StatusUnknown {
+				// 以服务方式运行
+				s.Run()
+			} else {
+				// 非服务方式运行
+				switch s.Platform() {
+				case "windows-service":
+					log.Println("可使用 .\\backup-x.exe -s install 安装服务运行")
+				default:
+					log.Println("可使用 ./backup-x -s install 安装服务运行")
+				}
+				run(100 * time.Millisecond)
+			}
+		}
+	}
+}
+
+func run(firstDelay time.Duration) {
 	// 启动静态文件服务
 	http.Handle("/static/", http.FileServer(http.FS(staticEmbededFiles)))
 	http.Handle("/favicon.ico", http.FileServer(http.FS(faviconEmbededFile)))
@@ -30,19 +79,97 @@ func main() {
 	http.HandleFunc("/clearLog", web.BasicAuth(web.ClearLog))
 	http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest))
 
+	// 改变工作目录
+	os.Chdir(*backupDir)
+
 	// 运行
 	go client.DeleteOldBackup()
 	go client.RunLoop()
 
-	if os.Getenv("port") != "" {
-		defaultPort = os.Getenv("port")
-	}
-
-	err := http.ListenAndServe(":"+defaultPort, nil)
+	err := http.ListenAndServe(*listen, nil)
 
 	if err != nil {
 		log.Println("启动端口发生异常, 请检查端口是否被占用", err)
 		time.Sleep(time.Minute)
 	}
+}
 
+type program struct{}
+
+func (p *program) Start(s service.Service) error {
+	// Start should not block. Do the actual work async.
+	go p.run()
+	return nil
+}
+func (p *program) run() {
+	// 服务运行,延时10秒运行,等待网络
+	run(10 * time.Second)
+}
+func (p *program) Stop(s service.Service) error {
+	// Stop should not block. Return with a few seconds.
+	return nil
+}
+
+func getService() service.Service {
+	options := make(service.KeyValue)
+	options["UserService"] = true
+	svcConfig := &service.Config{
+		Name:        "backup-x",
+		DisplayName: "backup-x",
+		Description: "带Web界面的数据库/文件备份增强工具",
+		Arguments:   []string{"-l", *listen, "-d", *backupDir},
+		Option:      options,
+	}
+
+	prg := &program{}
+	s, err := service.New(prg, svcConfig)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	return s
+}
+
+// 卸载服务
+func uninstallService() {
+	s := getService()
+
+	status, _ := s.Status()
+	// 处理卸载
+	if status != service.StatusUnknown {
+		s.Stop()
+		if err := s.Uninstall(); err == nil {
+			log.Println("backup-x 服务卸载成功!")
+		} else {
+			log.Printf("backup-x 服务卸载失败, ERR: %s\n", err)
+		}
+	} else {
+		log.Printf("backup-x 服务未安装")
+	}
+}
+
+// 安装服务
+func installService() {
+	s := getService()
+
+	status, err := s.Status()
+	if err != nil && status == service.StatusUnknown {
+		// 服务未知,创建服务
+		if err = s.Install(); err == nil {
+			s.Start()
+			log.Println("安装 backup-x 服务成功! 程序会一直运行, 包括重启后。")
+			return
+		}
+
+		log.Printf("安装 backup-x 服务失败, ERR: %s\n", err)
+		switch s.Platform() {
+		case "windows-service":
+			log.Println("请确保使用如下命令: .\\backup-x.exe -s install")
+		default:
+			log.Println("请确保使用如下命令: ./backup-x -s install")
+		}
+	}
+
+	if status != service.StatusUnknown {
+		log.Println("backup-x 服务已安装, 无需在次安装")
+	}
 }

+ 15 - 0
util/docker_util.go

@@ -0,0 +1,15 @@
+package util
+
+import "os"
+
+// DockerEnvFile Docker容器中包含的文件
+const DockerEnvFile string = "/.dockerenv"
+
+// IsRunInDocker 是否在docker中运行
+func IsRunInDocker() bool {
+	_, err := os.Stat(DockerEnvFile)
+	if err != nil {
+		return false
+	}
+	return true
+}