Browse Source

Commit 2021-01-01 03:43

zry 3 years ago
parent
commit
dbbec80eb3

+ 2 - 0
.gitignore

@@ -24,3 +24,5 @@ _testmain.go
 *.test
 *.prof
 
+dist
+*_old

+ 113 - 0
config_sample/log.config.yaml

@@ -0,0 +1,113 @@
+# Enable logger
+enable: true
+# Backends
+backends:
+  # A sample of stdout backend.
+  sample_backend_console_stdout:
+    # Should be one of 'console', 'file', 'alisls'
+    type: "console"
+    # Minimum emit level, should be one of 'DEBUG', 'VERBOSE', 'INFO', 'WARN', 'ERROR', 'PANIC', 'FATAL'
+    level: "DEBUG"
+    # This field should be defined for type 'console'
+    console_config:
+      # Should be one of 'stdout', 'stderr'
+      to: "stdout"
+  # A sample of file without log-rotate
+  sample_backend_no_rotate_file:
+    # Should be one of 'console', 'file', 'alisls'
+    type: "file"
+    # Minimum emit level, should be one of 'DEBUG', 'VERBOSE', 'INFO', 'WARN', 'ERROR', 'PANIC', 'FATAL'
+    level: "INFO"
+    # This field should be defined for type 'file'
+    file_config:
+      # The path to log file
+      filename: "no-rotate-info.log"
+      # Use 'true' for using console backend formatter for file backend
+      fvconsole: false
+      # Use 'false' for disable rotate
+      rotate: false
+  # A sample of file without log-rotate
+  sample_backend_rotate_file:
+    # Should be one of 'console', 'file', 'alisls'
+    type: "file"
+    # Minimum emit level, should be one of 'DEBUG', 'VERBOSE', 'INFO', 'WARN', 'ERROR', 'PANIC', 'FATAL'
+    level: "VERBOSE"
+    # This field should exist for type 'file'
+    file_config:
+      # The path to log file
+      filename: "rotate-verbose.log"
+      # Use 'true' for using console backend formatter for file backend
+      fvconsole: false
+      # Use 'true' for enable rotate
+      rotate: true
+      # This field should exist for rotate log
+      rotate_config:
+        # The maximum size in megabytes of the log file before it gets rotated
+        maxsize: 10
+        # The maximum number of days to retain old log files based on the timestamp encoded in their filename.
+        # Note that a day is defined as 24 hours and may not exactly correspond to calendar days due to daylight
+        # savings, leap seconds, etc. '0' for not to remove old log files based on age
+        maxage: 0
+        # The maximum number of old log files to retain. '0' for retain all old log files
+        # (though MaxAge may still cause them to get deleted.)
+        maxbackups: 0
+        # This determines if the time used for formatting the timestamps in backup files is the computer's local time.
+        # 'false' to use UTC time, 'true' to use local time.
+        localtime: true
+        # This determines if the rotated log files should be compressed using gzip.
+        # 'true' for using gzip, 'false' for not using gzip
+        compress: true
+  sample_backend_alisls:
+    # Should be one of 'console', 'file', 'alisls'
+    type: "alisls"
+    # Minimum emit level, should be one of 'DEBUG', 'VERBOSE', 'INFO', 'WARN', 'ERROR', 'PANIC', 'FATAL'
+    level: "DEBUG"
+    # This field should exist for type 'file'
+    alisls_config:
+      # Use 'true' for enable AliSLS producer debug log.
+      # AliSLS producer will print log to stdout, and we have no way to disable it,
+      # but we can specify the output level. 'true' for level 'debug', 'false' for level 'error'
+      alisls_producer_debug: false
+      # Your Hieda Logger hostname
+      hostname: "<Your HiedaLogger Hostname>"
+      # Your Hieda Logger app name
+      appname: "<Your HiedaLogger App Name>"
+      # Your AliSLS appkey id
+      ak_id: "<Your AliSLS Appkey ID>"
+      # Your AliSLS appkey secret
+      ak_secret: "<Your AliSLS Appkey Secret>"
+      # Your AliSLS endpoint
+      endpoint: "<Your AliSLS Endpoint>"
+      # Your AliSLS project
+      project: "<Your AliSLS Project>"
+      # Your AliSLS logstore
+      logstore: "<Your AliSLS Logstore>"
+      # Your AliSLS topic
+      topic: "<Your AliSLS Topic>"
+      # The IP address for AliSLS emit parameter 'source'
+      emit_ip: "0.0.0.0"
+      # Use 'true' to override 'emit_ip' below with your real Internet IP address if possible
+      override_with_real_ip: false
+      # Do not specify this in most situation. Refer to Aliyun SLS documentation.
+#        extend_alisls_config:
+#          total_size_ln_bytes: 104857600
+#          max_io_worker_count: 50
+#          max_block_sec: 60
+#          max_batch_size: 524288
+#          max_batch_count: 4096
+#          linger_ms: 2000
+#          retries: 10
+#          max_reserved_attempts: 11
+#          base_retry_backoff_ms: 100
+#          max_retry_backoff_ms: 50000
+#          adjust_sharg_hash: true
+#          buckets: 64
+#          allow_log_level: ""
+#          log_file_name: ""
+#          is_json_type: false
+#          log_max_size: 0
+#          log_max_backups: 0
+#          log_compress: false
+#          no_retry_status_code_list:
+#            - 400
+#            - 404

+ 17 - 0
config_sample/poweroff.config.yaml

@@ -0,0 +1,17 @@
+# Socket Listening Config
+listen:
+  # Unix Socket File
+  sockfile:
+    enable: true
+    filepath: "/var/run/poweroffd.sock"
+  # TCP
+  tcp:
+    enable: true
+    listen: ":886"
+# Token Config
+token:
+  # Key For JWT
+  jwtkey: "<Your jwt key here>"
+# If you want to test this daemon but do not want to shutdown in reality, set to true.
+# Value 'true' for disable poweroff action, but other module will still work.
+poweroff_lock: false

+ 26 - 0
poweroff_daemon/cfg_def.go

@@ -0,0 +1,26 @@
+package main
+
+type YamlDef_PoweroffConfig struct {
+	Listen       YamlDef_Listen `yaml:"listen"`
+	Token        YamlDef_Token  `yaml:"token"`
+	PoweroffLock bool           `yaml:"poweroff_lock"`
+}
+
+type YamlDef_Listen struct {
+	SockFile YamlDef_ListenSockfile `yaml:"sockfile"`
+	TCP      YamlDef_ListenTCP      `yaml:"tcp"`
+}
+
+type YamlDef_ListenSockfile struct {
+	Enable   bool   `yaml:"enable"`
+	Filepath string `yaml:"filepath"`
+}
+
+type YamlDef_ListenTCP struct {
+	Enable bool   `yaml:"enable"`
+	Listen string `yaml:"listen"`
+}
+
+type YamlDef_Token struct {
+	JwtKey string `yaml:"jwtkey"`
+}

+ 70 - 0
poweroff_daemon/html.go

@@ -0,0 +1,70 @@
+package main
+
+const Html_Home = `
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>poweroffd</title>
+	</head>
+	<body>
+		<h1>poweroffd</h1>
+		<h3>GET /api/poweroff.satori?token=[Your JWT Token] to shutdown.</h3>
+		<h3>GET /jwt-tool/ to generate test token.</h3>
+	</body>
+</html>
+`
+
+const Html_jwtTool = `
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>poweroffd jwt Generate Tool</title>
+	</head>
+	<body>
+		<h1>poweroffd jwt Generate Tool</h1>
+		<form action="/api/jwt-tool.satori" target="_blank" method="post">
+			<label>jwt Key:</label>
+			<input name="jwtkey" type="text" />
+			<label>Caller:</label>
+			<input name="caller" type="text" />
+			<label>Message:</label>
+			<input name="msg" type="text" />
+			<input type="submit" />
+		</form>
+	</body>
+</html>
+`
+
+const Html_jwtToolFail = `
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>poweroffd jwt Generate Tool Result</title>
+	</head>
+	<body>
+		<h1>poweroffd jwt Generate Tool</h1>
+		<h3>Error</h3>
+		<p>%s</p>
+	</body>
+</html>
+`
+
+const Html_jwtToolResult = `
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>poweroffd jwt Generate Tool Result</title>
+	</head>
+	<body>
+		<h1>poweroffd jwt Generate Tool</h1>
+		<ul>
+			<li><b>Token:</b>{{.token}}</li>
+			<li>
+				<b>Directly Poweroff Link:</b>
+				<a href="/api/poweroff.satori?token={{.token}}"> Click Here</a>
+			</li>
+			<li><b>Expire:</b>{{.expire}}</li>
+		</ul>
+	</body>
+</html>
+`

+ 20 - 0
poweroff_daemon/main.go

@@ -0,0 +1,20 @@
+package main
+
+import "git.swzry.com/zry/zdaemon"
+
+func main() {
+	zdcfg := &zdaemon.ZDaemonConfig{
+		DaemonName:         "poweroffd",
+		Description:        "Poweroff Daemon",
+		InitRunnerFunction: initRunner,
+		StartOrder:         90,
+		StopOrder:          91,
+	}
+	zd := zdaemon.NewZDaemon(zdcfg)
+	zd.Run()
+}
+
+func initRunner() zdaemon.ProgramInterface {
+	runner := &Program{}
+	return runner
+}

+ 301 - 0
poweroff_daemon/program.go

@@ -0,0 +1,301 @@
+package main
+
+import (
+	"fmt"
+	"git.swzry.com/zry/GoHiedaLogger/hieda_ginutil"
+	"git.swzry.com/zry/GoHiedaLogger/hieda_yamlutil"
+	"git.swzry.com/zry/GoHiedaLogger/hiedalog"
+	"git.swzry.com/zry/pathutils"
+	"github.com/gin-gonic/gin"
+	"github.com/pascaldekloe/jwt"
+	"gopkg.in/yaml.v2"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"text/template"
+	"time"
+)
+
+const TimeFormatLayout = "2006-01-02 15:04:05"
+
+type Program struct {
+	localLogWriter     io.Writer
+	hyu                *hieda_yamlutil.HiedaLogYamlUtil
+	ge                 *gin.Engine
+	config             YamlDef_PoweroffConfig
+	executableFileName string
+	programPath        string
+}
+
+func (p *Program) SetLocalLogWriter(writer io.Writer) {
+	p.localLogWriter = writer
+}
+
+func (p *Program) Start() error {
+	var err error
+	p.executableFileName, err = filepath.Abs(os.Args[0])
+	if err != nil {
+		_, _ = fmt.Fprintln(p.localLogWriter, "Failed get executable path: ", err.Error())
+		return err
+	}
+	p.programPath = filepath.Dir(p.executableFileName)
+	ok := p.initLogger()
+	if !ok {
+		return fmt.Errorf("faild init logger")
+	}
+	ok = p.readConfig()
+	if !ok {
+		return fmt.Errorf("faild load poweroffd config")
+	}
+	p.ge = gin.Default()
+	if p.hyu != nil {
+		glucfg := hieda_ginutil.GinLoggerConfig{
+			Logger:       p.hyu.Logger,
+			ModuleName:   "webserver",
+			LevelMapFunc: hieda_ginutil.GetDefaultLevelMapFunc(),
+		}
+		gl := hieda_ginutil.GinLoggerWithComplexLogger(glucfg)
+		p.ge.Use(gl)
+	}
+	p.ge.GET("/", p.homepage)
+	p.ge.GET("/api/poweroff.satori", p.poweroff)
+	p.ge.GET("/jwt-tool/", p.jwtGenTool)
+	p.ge.POST("/api/jwt-tool.satori", p.jwtGenToolBackend)
+	if p.config.Listen.TCP.Enable {
+		p.printLog("webtcp", hiedalog.DLN_INFO, "Loading...")
+		go func() {
+			err := p.ge.Run(p.config.Listen.TCP.Listen)
+			if err != nil {
+				p.printLog("webtcp", hiedalog.DLN_ERROR, "Faild run TCP listener: %s", err.Error())
+			}
+			p.printLog("webtcp", hiedalog.DLN_INFO, "Listener end.")
+		}()
+	}
+	if p.config.Listen.SockFile.Enable {
+		p.printLog("webunix", hiedalog.DLN_INFO, "Loading...")
+		if e, _ := pathutils.PathExists(p.config.Listen.SockFile.Filepath); e {
+			err := os.Remove(p.config.Listen.SockFile.Filepath)
+			if err != nil {
+				p.printLog("webunix", hiedalog.DLN_ERROR, "Faild remove old unix sock file: %s", err.Error())
+			}
+		}
+		go func() {
+			err := p.ge.RunUnix(p.config.Listen.SockFile.Filepath)
+			if err != nil {
+				p.printLog("webunix", hiedalog.DLN_ERROR, "Faild run Unix sockfile listener: %s", err.Error())
+			}
+			p.printLog("webunix", hiedalog.DLN_INFO, "Listener end.")
+		}()
+	}
+	return nil
+}
+
+func (p *Program) Stop() error {
+	if p.hyu != nil {
+		p.hyu.SafeShutdown()
+	}
+	os.Exit(0)
+	return nil
+}
+
+func (p *Program) OtherOperation(op string, args []string) string {
+	return fmt.Sprintf("Invalid command '%s'.", op)
+}
+
+func (p *Program) GetStatus() (status string, hasExtra bool, extra map[string]string) {
+	return "running", false, map[string]string{}
+}
+
+func (p *Program) initLogger() bool {
+	ycfd, err := ioutil.ReadFile(path.Join(p.programPath, "log.config.yaml"))
+	if err != nil {
+		_, _ = fmt.Fprintln(p.localLogWriter, "Failed read file 'log.config.yaml':", err)
+		return false
+	}
+	var ycd hieda_yamlutil.CommonLogConfigYAML
+	err = yaml.Unmarshal(ycfd, &ycd)
+	if err != nil {
+		_, _ = fmt.Fprintln(p.localLogWriter, "Failed parse file 'log.config.yaml':", err)
+		return false
+	}
+	p.hyu, err = hieda_yamlutil.CreateHiedaLoggerFromYAMLData(ycd, true)
+	if err != nil {
+		_, _ = fmt.Fprintln(p.localLogWriter, "Failed Init Logger:", err)
+		return false
+	}
+	p.hyu.Logger.LogString("logger", hiedalog.DLN_INFO, "Logger initialized.")
+	return true
+}
+
+func (p *Program) readConfig() bool {
+	ycfd, err := ioutil.ReadFile(path.Join(p.programPath, "poweroff.config.yaml"))
+	if err != nil {
+		p.printLog("config", hiedalog.DLN_FATAL, "Failed read config file 'poweroff.config.yaml':%s", err)
+		return false
+	}
+	err = yaml.Unmarshal(ycfd, &(p.config))
+	if err != nil {
+		p.printLog("config", hiedalog.DLN_FATAL, "Failed parse config file 'poweroff.config.yaml':%s", err)
+		return false
+	}
+	return true
+}
+
+func (p *Program) homepage(ctx *gin.Context) {
+	ctx.Header("content-type", "text/html")
+	ctx.String(200, Html_Home)
+}
+
+func (p *Program) jwtGenTool(ctx *gin.Context) {
+	ctx.Header("content-type", "text/html")
+	ctx.String(200, Html_jwtTool)
+}
+
+func (p *Program) jwtGenToolBackend(ctx *gin.Context) {
+	jwtkey := ctx.Request.PostFormValue("jwtkey")
+	caller := ctx.Request.PostFormValue("caller")
+	msg := ctx.Request.PostFormValue("msg")
+	clm := jwt.Claims{}
+	clm.Issuer = caller
+	ntm := jwt.NewNumericTime(time.Now())
+	clm.Issued = ntm
+	clm.NotBefore = ntm
+	exptime := time.Now().Add(5 * time.Minute)
+	clm.Expires = jwt.NewNumericTime(exptime)
+	clm.Set = map[string]interface{}{
+		"msg": msg,
+	}
+	tok, err := clm.HMACSign(jwt.HS256, []byte(jwtkey))
+	if err != nil {
+		ctx.Header("content-type", "text/html")
+		ctx.String(200, Html_jwtToolFail, err.Error())
+		return
+	}
+	otpl := template.New("result")
+	tpl, err := otpl.Parse(Html_jwtToolResult)
+	if err != nil {
+		ctx.Header("content-type", "text/html")
+		ctx.String(200, Html_jwtToolFail, err.Error())
+		return
+	}
+	sb := strings.Builder{}
+	err = tpl.Execute(&sb, map[string]string{
+		"token":  string(tok),
+		"expire": exptime.Format(TimeFormatLayout),
+	})
+	if err != nil {
+		ctx.Header("content-type", "text/html")
+		ctx.String(200, Html_jwtToolFail, err.Error())
+		return
+	}
+	ctx.Header("content-type", "text/html")
+	ctx.String(200, sb.String())
+	return
+}
+
+func (p *Program) poweroff(ctx *gin.Context) {
+	jwtstr := ctx.Request.FormValue("token")
+	vclm, err := jwt.HMACCheck([]byte(jwtstr), []byte(p.config.Token.JwtKey))
+	if err != nil {
+		if p.hyu != nil {
+			ld := map[string]string{
+				"type":      "auth_failed",
+				"client_ip": ctx.ClientIP(),
+			}
+			p.hyu.Logger.LogComplex("auth", hiedalog.DLN_INFO, ld)
+		}
+		ctx.JSON(200, gin.H{
+			"suc": false,
+			"err": "auth failed",
+		})
+		return
+	}
+	isr := vclm.Issuer
+	var msg string
+	imsg, ok := vclm.Set["msg"]
+	if !ok {
+		msg = "<No Message>"
+	}
+	msg, ok = imsg.(string)
+	if !ok {
+		msg = "<No Message>"
+	}
+	if p.hyu != nil {
+		ld := map[string]string{
+			"caller":    isr,
+			"msg":       msg,
+			"client_ip": ctx.ClientIP(),
+			"time":      time.Now().Format(TimeFormatLayout),
+			"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
+		}
+		p.hyu.Logger.LogComplex("record", hiedalog.DLN_INFO, ld)
+	}
+	if p.config.PoweroffLock {
+		p.printLog("shutdown", hiedalog.DLN_WARN, "WARNING: 'poweroff_lock' option is enable. it will not shutdown.")
+		ctx.JSON(200, gin.H{
+			"suc":           true,
+			"caller":        isr,
+			"msg":           msg,
+			"client_ip":     ctx.ClientIP(),
+			"time":          time.Now().Format(TimeFormatLayout),
+			"timestamp":     strconv.FormatInt(time.Now().Unix(), 10),
+			"poweroff_lock": p.config.PoweroffLock,
+		})
+	} else {
+		ok := p.doShutdown()
+		if !ok {
+			ctx.JSON(200, gin.H{
+				"suc": false,
+				"err": "exec shutdown commands failed",
+			})
+		} else {
+			ctx.JSON(200, gin.H{
+				"suc":           true,
+				"caller":        isr,
+				"msg":           msg,
+				"client_ip":     ctx.ClientIP(),
+				"time":          time.Now().Format(TimeFormatLayout),
+				"timestamp":     strconv.FormatInt(time.Now().Unix(), 10),
+				"poweroff_lock": p.config.PoweroffLock,
+			})
+		}
+	}
+}
+
+func (p *Program) printLog(m string, lv string, f string, a ...interface{}) {
+	fs := fmt.Sprintf(f, a...)
+	if p.hyu != nil {
+		p.hyu.Logger.LogString(m, lv, fs)
+	} else {
+		_, _ = fmt.Fprintln(p.localLogWriter, fs)
+	}
+}
+
+func (p *Program) execCmd(cmd string) error {
+	c := exec.Command(cmd, "")
+	return c.Run()
+}
+
+func (p *Program) doShutdown() bool {
+	for i := 0; i < 3; i++ {
+		err := p.execCmd("/bin/sync")
+		if err != nil {
+			p.printLog("shutdown", hiedalog.DLN_WARN, "exec sync error: %s", err.Error())
+			return false
+		}
+	}
+	err := p.execCmd("/sbin/poweroff")
+	if err != nil {
+		p.printLog("shutdown", hiedalog.DLN_WARN, "exec poweroff error: %s", err.Error())
+		return false
+	}
+	if p.hyu != nil {
+		p.hyu.SafeShutdown()
+	}
+	return true
+}