Skip to content

Commit

Permalink
Merge pull request #4 from ZeljkoBenovic/feature/add-service-cmd
Browse files Browse the repository at this point in the history
* [Feature] add system service cli option
  • Loading branch information
ZeljkoBenovic authored Sep 8, 2024
2 parents cf3242a + a2d7414 commit 74ef5b8
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 8 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,23 @@ It is usually done via RADIUS server or similar solution.
Use the config file with `gombak -c config.yaml`

## System service
Gombak can be set to run as a system service using the provided CLI commands.
Once the `gombak` binary and its configuration YAML file is set in place, system service can be interacted with:
* `gombak install -c <absolute_path_to_the_config_file>` - install and run the `gombak` system service
* `gombak uninstall` - uninstall `gombak` system service

## Flags
Check which flags are available with `gombak -h`
```
-b, --backup-dir string mikrotik backup export directory (default "mt-backup")
-r, --backup-retention-days days of backup file retention (default 30)
--backup-frequency-days backup frequency in days (default 5)
-c, --config string configuration yaml file
--log.file string write logs to the specified file
--log.json output logs in json format
--log.level string define log level (default "info")
-m, --mode string mode of operation (default "single")
-r, --retention-days int days of retention (default 5)
--single.host string the ip address of the router
--single.pass string the password for the username
--single.ssh-port string the ssh port of the router (default "22")
Expand All @@ -94,5 +101,4 @@ Check which flags are available with `gombak -h`
## TODO
* Email report
* CLI command to set up a system service
* More discovery modes
23 changes: 20 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,38 @@ import (
"github.com/ZeljkoBenovic/gombak/internal/app"
"github.com/ZeljkoBenovic/gombak/pkg/config"
"github.com/ZeljkoBenovic/gombak/pkg/logger"
"github.com/ZeljkoBenovic/gombak/pkg/service"
)

func main() {
conf := config.NewConfig()

log, err := logger.New(conf)
if err != nil {
fmt.Printf("Could not create new logger: %s", err.Error())
fmt.Printf("could not create new logger: %s", err.Error())
os.Exit(1)
}

run := app.NewApp(conf, log).AppModeFactory()

if err = run(); err != nil {
log.Error(err.Error())
srv, err := service.New(conf, []string{"run", "-c", conf.ConfigFilePath}, log)
if err != nil {
log.Info("could not init new service", "err", err)
os.Exit(1)
}

err, isService := srv.HandleServiceCLICommands(run)
if err != nil {
log.Error("service error", "err", err)

os.Exit(1)
}

if !isService {
if err = run(); err != nil {
log.Error("run error", "err", err)

os.Exit(1)
}
}
}
12 changes: 9 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ var AvailableModes = map[string]Mode{
type Config struct {
Mode Mode `koanf:"mode"`
BackupFolder string `koanf:"backup-dir"`
BackupRetentionDays int `koanf:"retention-days"`
BackupRetentionDays int `koanf:"backup-retention-days"`
BackupFrequencyDays int `konaf:"backup-frequency-days"`
Single RouterInfo `koanf:"single"`
Discovery Discovery `koanf:"discovery"`
Multi []RouterInfo `koanf:"multi-router"`

Logger Log `koanf:"log"`

ConfigFilePath string
}

type RouterInfo struct {
Expand Down Expand Up @@ -91,7 +94,8 @@ func NewConfig() Config {
f.StringVarP(&confFile, "config", "c", "", "configuration yaml file")
f.StringVarP(&c.BackupFolder, "backup-dir", "b", "mt-backup", "mikrotik backup export directory")
f.StringVarP(&mode, "mode", "m", "single", "mode of operation")
f.IntVarP(&c.BackupRetentionDays, "retention-days", "r", 30, "days of retention")
f.IntVarP(&c.BackupRetentionDays, "backup-retention-days", "r", 30, "days of retention")
f.IntVarP(&c.BackupFrequencyDays, "backup-frequency-days", "", 5, "backup frequency in days")

f.StringVarP(&c.Single.Host, "single.host", "", "", "the ip address of the router")
f.StringVarP(&c.Single.Port, "single.ssh-port", "", "22", "the ssh port of the router")
Expand Down Expand Up @@ -139,7 +143,9 @@ func NewConfig() Config {

return Config{
BackupFolder: k.String("backup-dir"),
BackupRetentionDays: k.Int("retention-days"),
BackupRetentionDays: k.Int("backup-retention-days"),
BackupFrequencyDays: k.Int("backup-frequency-days"),
ConfigFilePath: k.String("config"),
Mode: AvailableModes[k.String("mode")],
Single: RouterInfo{
Host: k.String("single.host"),
Expand Down
123 changes: 123 additions & 0 deletions pkg/service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package service

import (
"fmt"
"log"
"os"
"time"

"github.com/ZeljkoBenovic/gombak/pkg/config"
"github.com/ZeljkoBenovic/gombak/pkg/logger"
srv "github.com/kardianos/service"
)

type Service struct {
svc srv.Service
log *logger.Logger

runner *serviceRunner
}

type serviceRunner struct {
runFn func() error
log srv.Logger
stopCh chan struct{}
conf config.Config
}

func New(conf config.Config, args []string, log *logger.Logger) (*Service, error) {
s := &Service{
log: log,
runner: &serviceRunner{
stopCh: make(chan struct{}),
conf: conf,
},
}

srvc, err := srv.New(s.runner, &srv.Config{
Name: "GoMBak",
DisplayName: "GoMBak",
Description: "Provides a Mikrotik router backup service. More info: https://github.com/zeljkobenovic/gombak",
Arguments: args,
})
if err != nil {
return nil, fmt.Errorf("could not init service: %w", err)
}

lgr, err := srvc.Logger(nil)
if err != nil {
return nil, fmt.Errorf("could not create service logger: %w", err)
}

s.svc = srvc
s.runner.log = lgr

return s, nil
}

// HandleServiceCLICommands will handle "install", "uninstall" and "run" cli commands which handle gombak as a system service.
// If these cli arguments are not set, this method returns false signaling that it should be run as a console program.
func (s *Service) HandleServiceCLICommands(runFn func() error) (err error, isService bool) {
isService = true
err = nil

switch os.Args[1] {
case "install":
if err := srv.Control(s.svc, "install"); err != nil {
return fmt.Errorf("could not install gombak service: %w", err), isService
}

if err := srv.Control(s.svc, "start"); err != nil {
return fmt.Errorf("could not start gombak service: %w", err), isService
}

return
case "uninstall":
if err := srv.Control(s.svc, "uninstall"); err != nil {
return fmt.Errorf("could not uninstall gombak service: %w", err), isService
}

return
case "run":
s.runner.runFn = runFn
err = s.svc.Run()

return
}

return nil, false
}

func (s *serviceRunner) Start(_ srv.Service) error {
if s.runFn == nil {
return fmt.Errorf("runFn function not initialized")
}

go func() {
ticker := time.NewTicker(time.Hour * 24 * time.Duration(s.conf.BackupFrequencyDays))
defer ticker.Stop()

for {
select {
case <-ticker.C:
_ = s.log.Info("running mikrotik backup per schedule")
if err := s.runFn(); err != nil {
if err := s.log.Error(err); err != nil {
log.Println(err)
}
}
case <-s.stopCh:
_ = s.log.Info("stopping gombak service")

return
}
}
}()

return nil
}

func (s *serviceRunner) Stop(_ srv.Service) error {
s.stopCh <- struct{}{}
return nil
}

0 comments on commit 74ef5b8

Please sign in to comment.