diff --git a/agent/install/service_darwin.go b/agent/install/service_darwin.go new file mode 100644 index 00000000..6dd7cfa3 --- /dev/null +++ b/agent/install/service_darwin.go @@ -0,0 +1,24 @@ +package install + +import ( + "github.com/kardianos/service" +) + +const ( + ServiceName = "categraf" +) + +var ( + serviceConfig = &service.Config{ + // 服务显示名称 + Name: ServiceName, + // 服务名称 + DisplayName: "categraf", + // 服务描述 + Description: "Opensource telemetry collector", + } +) + +func ServiceConfig() *service.Config { + return serviceConfig +} diff --git a/agent/install/service_freebsd.go b/agent/install/service_freebsd.go new file mode 100644 index 00000000..75608d79 --- /dev/null +++ b/agent/install/service_freebsd.go @@ -0,0 +1,47 @@ +package install + +import ( + "github.com/kardianos/service" +) + +const ( + // freebsd的服务名中间不能有"-" + ServiceName = "categraf" + + SysvScript = `#!/bin/sh +# +# PROVIDE: {{.Name}} +# REQUIRE: networking syslog +# KEYWORD: +# Add the following lines to /etc/rc.conf to enable the {{.Name}}: +# +# {{.Name}}_enable="YES" +# +. /etc/rc.subr +name="{{.Name}}" +rcvar="{{.Name}}_enable" +command="{{.Path}}" +pidfile="/var/run/$name.pid" +start_cmd="/opt/categraf/categraf -configs /opt/categraf/conf" +load_rc_config $name +run_rc_command "$1" +` +) + +var ( + serviceConfig = &service.Config{ + // 服务显示名称 + Name: ServiceName, + // 服务名称 + DisplayName: "categraf", + // 服务描述 + Description: "Opensource telemetry collector", + Option: service.KeyValue{ + "SysvScript": SysvScript, + }, + } +) + +func ServiceConfig() *service.Config { + return serviceConfig +} diff --git a/agent/install/service_linux.go b/agent/install/service_linux.go new file mode 100644 index 00000000..ace41bf1 --- /dev/null +++ b/agent/install/service_linux.go @@ -0,0 +1,219 @@ +package install + +import ( + "bytes" + "fmt" + "log" + "os/exec" + "strings" + "time" + + "github.com/kardianos/service" + + "flashcat.cloud/categraf/pkg/cmdx" +) + +const ( + systemdScript = `[Unit] +Description={{.Description}} +ConditionFileIsExecutable={{.Path|cmdEscape}} +{{range $i, $dep := .Dependencies}} +{{$dep}} {{end}} + +[Service] +StandardOutput=journal+console +StandardError=journal+console +StartLimitInterval=3600 +StartLimitBurst=10 +ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}} +{{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}} +{{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}} +{{if .UserName}}User={{.UserName}}{{end}} +{{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}} +{{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}} +{{if and .LogOutput .HasOutputFileSupport -}} +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier={{.Name}} +{{- end}} +{{if gt .LimitNOFILE -1 }}LimitNOFILE={{.LimitNOFILE}}{{end}} +{{if .Restart}}Restart={{.Restart}}{{end}} +{{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}} +RestartSec=120 +EnvironmentFile=-/etc/sysconfig/{{.Name}} +KillMode=process +[Install] +WantedBy=multi-user.target +` + + // NOTE: sysvinit script below is copied from https://github.com/kardianos/service/master/service_sysv_linux.go + // with modification: + // * In chkconfig configuration [default_runlevels start_priority stop_priority], + // runlevels to be started by default is modified which should be consistent + // with LSB init configuration below. And two priorities is also modified + // to be consistent with installing code in library. + // * "Required-Start" part of LSB init configuration is modified according to + // https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/facilname.html + // for service dependency ordering, like what systemd provides. + // See https://linux.die.net/man/8/chkconfig for more details + sysvScript = `#!/bin/sh +# For RedHat and cousins: +# chkconfig: 2345 50 02 +# description: {{.Description}} +# processname: {{.Path}} + +### BEGIN INIT INFO +# Provides: {{.Path}} +# Required-Start: $local_fs $network $named $remote_fs +# Required-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: {{.DisplayName}} +# Description: {{.Description}} +### END INIT INFO + +cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}" + +name=$(basename $(readlink -f $0)) +pid_file="/var/run/$name.pid" +stdout_log="/var/log/$name.log" +stderr_log="/var/log/$name.err" + +[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name + +get_pid() { + cat "$pid_file" +} + +is_running() { + [ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1 +} + +case "$1" in + start) + if is_running; then + echo "Already started" + else + echo "Starting $name" + {{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}} + $cmd >> "$stdout_log" 2>> "$stderr_log" & + echo $! > "$pid_file" + if ! is_running; then + echo "Unable to start, see $stdout_log and $stderr_log" + exit 1 + fi + fi + ;; + stop) + if is_running; then + echo -n "Stopping $name.." + kill $(get_pid) + for i in $(seq 1 10) + do + if ! is_running; then + break + fi + echo -n "." + sleep 1 + done + echo + if is_running; then + echo "Not stopped; may still be shutting down or shutdown may have failed" + exit 1 + else + echo "Stopped" + if [ -f "$pid_file" ]; then + rm "$pid_file" + fi + fi + else + echo "Not running" + fi + ;; + restart) + $0 stop + if is_running; then + echo "Unable to stop, will not attempt to start" + exit 1 + fi + $0 start + ;; + status) + if is_running; then + echo "Running" + else + echo "Stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac +exit 0 +` +) + +func isSystemd() bool { + var stdout bytes.Buffer + var stderr bytes.Buffer + + cmd := exec.Command("ls", "-l", "/sbin/init") //nolint:gosec + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err, timeout := cmdx.RunTimeout(cmd, time.Second*2) + if timeout { + log.Printf("E! run command: %s timeout", cmd) + return false + } + + if err != nil { + fmt.Errorf("failed to run command: %s | error: %v | stdout: %s | stderr: %s", + cmd, err, stdout.String(), stderr.String()) + return false + } + + if strings.Contains(stdout.String(), "systemd") { + return true + } + return false +} + +func ServiceConfig() *service.Config { + ServiceName := "categraf" + depends := []string{} + option := make(service.KeyValue) + if isSystemd() { + ServiceName = "categraf" + // Official doc https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/ + // suggests both After= and Wants= configuration to delay a service after + // network is up. Need validation on ALL distros and releases. + depends = append(depends, "After=network-online.target") + depends = append(depends, "Wants=network-online.target") + option["SystemdScript"] = systemdScript + option["Restart"] = "on-failure" + + // REMEMBER: Explicit disable LogOutput option of kardianos/service, and + // use StandardOutput/StandardError settings manually written above. + option["LogOutput"] = false + } else { + ServiceName = "categraf" + option["SysvScript"] = sysvScript + option["LogOutput"] = true + } + + return &service.Config{ + // 服务显示名称 + Name: ServiceName, + // 服务名称 + DisplayName: ServiceName, + // 服务描述 + Description: "Opensource telemetry collector", + // TODO: Use symbolic link for shorter path needs more adaption + // Executable: "/opt/categraf/categraf", + Dependencies: depends, // + Option: option, + } +} diff --git a/agent/install/service_windows.go b/agent/install/service_windows.go new file mode 100644 index 00000000..6dd7cfa3 --- /dev/null +++ b/agent/install/service_windows.go @@ -0,0 +1,24 @@ +package install + +import ( + "github.com/kardianos/service" +) + +const ( + ServiceName = "categraf" +) + +var ( + serviceConfig = &service.Config{ + // 服务显示名称 + Name: ServiceName, + // 服务名称 + DisplayName: "categraf", + // 服务描述 + Description: "Opensource telemetry collector", + } +) + +func ServiceConfig() *service.Config { + return serviceConfig +} diff --git a/go.mod b/go.mod index 0e78c770..fdd59069 100644 --- a/go.mod +++ b/go.mod @@ -130,6 +130,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 github.com/bmatcuk/doublestar/v3 v3.0.0 github.com/coreos/go-systemd/v22 v22.3.2 + github.com/kardianos/service v1.2.2 github.com/karrick/godirwalk v1.10.3 github.com/likexian/whois v1.15.0 github.com/likexian/whois-parser v1.24.8 diff --git a/go.sum b/go.sum index 23ca19ce..53da9bfb 100644 --- a/go.sum +++ b/go.sum @@ -854,6 +854,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= +github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3 h1:lOpSw2vJP0y5eLBW906QwKsUK/fe/QDyoqM5rnnuPDY= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= @@ -1683,6 +1685,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/main.go b/main.go index 5488745d..60fae8b4 100644 --- a/main.go +++ b/main.go @@ -10,17 +10,19 @@ import ( "path/filepath" "syscall" + "github.com/chai2010/winsvc" + "github.com/kardianos/service" "github.com/toolkits/pkg/net/tcpx" + "github.com/toolkits/pkg/runner" "gopkg.in/natefinch/lumberjack.v2" "flashcat.cloud/categraf/agent" + agentInstall "flashcat.cloud/categraf/agent/install" "flashcat.cloud/categraf/api" "flashcat.cloud/categraf/config" "flashcat.cloud/categraf/heartbeat" "flashcat.cloud/categraf/pkg/osx" "flashcat.cloud/categraf/writer" - "github.com/chai2010/winsvc" - "github.com/toolkits/pkg/runner" ) var ( @@ -31,6 +33,11 @@ var ( interval = flag.Int64("interval", 0, "Global interval(unit:Second)") showVersion = flag.Bool("version", false, "Show version.") inputFilters = flag.String("inputs", "", "e.g. cpu:mem:system") + install = flag.Bool("install", false, "Install categraf service") + remove = flag.Bool("remove", false, "Remove categraf service") + start = flag.Bool("start", false, "Start categraf service") + stop = flag.Bool("stop", false, "Stop categraf service") + status = flag.Bool("status", false, "Show categraf service status") ) func init() { @@ -71,6 +78,13 @@ func main() { fmt.Println(config.Version) os.Exit(0) } + if *install || *remove || *start || *stop || *status { + err := serviceProcess() + if err != nil { + log.Println("E!", err) + } + return + } // init configs if err := config.InitConfig(*configDir, *debugMode, *testMode, *interval, *inputFilters); err != nil { @@ -132,3 +146,129 @@ func printEnv() { log.Println("I! runner.fd_limits:", runner.FdLimits()) log.Println("I! runner.vm_limits:", runner.VMLimits()) } + +type program struct{} + +func (p *program) Start(s service.Service) error { + return nil +} + +func (p *program) Stop(s service.Service) error { + return nil +} + +func serviceProcess() error { + svcConfig := agentInstall.ServiceConfig() + prg := &program{} + s, err := service.New(prg, svcConfig) + if err != nil { + fmt.Println("generate categraf service error " + err.Error()) + return nil + } + + if *stop { + if sts, err := s.Status(); err != nil { + log.Println("W! show categraf service status failed:", err) + } else { + switch sts { + case service.StatusRunning: + log.Println("I! categraf service status: running") + case service.StatusStopped: + log.Println("I! categraf service status: stopped") + default: + log.Println("I! categraf service status: unknown") + } + } + if err := s.Stop(); err != nil { + log.Println("E! stop categraf service failed:", err) + } else { + log.Println("I! stop categraf service ok") + } + return nil + } + + if *remove { + if sts, err := s.Status(); err != nil { + log.Println("W! show categraf service status failed:", err) + } else { + switch sts { + case service.StatusRunning: + log.Println("I! categraf service status: running") + case service.StatusStopped: + log.Println("I! categraf service status: stopped") + default: + log.Println("I! categraf service status: unknown") + } + } + if err := s.Stop(); err != nil { + log.Println("W! stop categraf service failed:", err) + } else { + log.Println("I! stop categraf service ok") + } + if err := s.Uninstall(); err != nil { + log.Println("E! remove categraf service failed:", err) + } else { + log.Println("I! remove categraf service ok") + } + return nil + } + + if *install { + if sts, err := s.Status(); err != nil { + log.Println("W! show categraf service status failed:", err) + } else { + switch sts { + case service.StatusRunning: + log.Println("I! categraf service status: running") + case service.StatusStopped: + log.Println("I! categraf service status: stopped") + default: + log.Println("I! categraf service status: unknown") + } + } + if err := s.Install(); err != nil { + log.Println("E! install categraf service failed:", err) + } else { + log.Println("I! install categraf service ok") + } + return nil + } + + if *start { + if sts, err := s.Status(); err != nil { + log.Println("W! show categraf service status failed:", err) + } else { + switch sts { + case service.StatusRunning: + log.Println("I! categraf service status: running") + case service.StatusStopped: + log.Println("I! categraf service status: stopped") + default: + log.Println("I! categraf service status: unknown") + } + } + if err := s.Start(); err != nil { + log.Println("E! start categraf service failed:", err) + } else { + log.Println("I! start categraf service ok") + } + return nil + } + if *status { + if sts, err := s.Status(); err != nil { + log.Println("E! show categraf service status failed:", err) + } else { + switch sts { + case service.StatusRunning: + log.Println("I! show categraf service status: running") + case service.StatusStopped: + log.Println("I! show categraf service status: stopped") + default: + log.Println("I! show categraf service status: unknown") + } + } + + return nil + } + return nil +}