Commit cfa24f9d authored by wei.xuan's avatar wei.xuan

Merge branch '1106' into 'master'

fix:fix redial

See merge request !7
parents ecf22296 33aa7847
......@@ -6,4 +6,7 @@ majora-cli
bin
nohup.out
release
./majora
\ No newline at end of file
./majora
*.log
majora.log
std.log
\ No newline at end of file
......@@ -3,7 +3,7 @@ export PATH := $(GOPATH)/bin:$(PATH)
export GO111MODULE=on
LDFLAGS := -s -w
DATE=$(shell date +"%Y-%m-%d")
version=v0.0.3
version=latest
BUILDINFO := -X main.Version=$(version) -X main.Date=$(DATE)
os-archs=darwin:amd64 darwin:arm64 freebsd:386 freebsd:amd64 linux:386 linux:amd64 linux:arm linux:arm64 windows:386 windows:amd64 linux:mips64 linux:mips64le
......
......@@ -55,12 +55,12 @@ func init() {
func initial(cfg *model.Configure) {
if cfg.LogLevel > 0 {
_ = getty.SetLoggerCallerDisable()
_ = getty.SetLoggerLevel(getty.LoggerLevel(cfg.LogLevel - 1))
_ = getty.Online(cfg.LogPath)
}
addr := fmt.Sprintf("127.0.0.1:%d", cfg.PprofPort)
if cfg.PprofPort > 0 {
go func() {
addr := fmt.Sprintf("127.0.0.1:%d", cfg.PprofPort)
getty.GetLogger().Infof("enable pprof: %s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}()
......@@ -75,12 +75,6 @@ func initial(cfg *model.Configure) {
}
getty.GetLogger().Infof("use custom dns server %s", dnsServer)
}
//if !cfg.DisableUpdate {
// if _, err := infra.Update("majora", Version); err != nil {
// getty.GetLogger().Errorf("check update error %+s", err.Error())
// }
//}
}
func parseFromCmd(cfg *model.Configure) {
......@@ -102,11 +96,13 @@ func cli() {
parseFromCmd(cfg)
}
initial(cfg)
getty.GetLogger().Infof("current Version %s, build at %s", Version, Date)
getty.GetLogger().Infof("hostinfo os:%s, arch:%s", runtime.GOOS, runtime.GOARCH)
runtime.GOMAXPROCS(runtime.NumCPU() * 2)
debug.SetGCPercent(200)
getty.GetLogger().Warnf("cpu count %d proc %d", runtime.NumCPU(), runtime.NumCPU()*2)
getty.GetLogger().Warnf("current Version %s, build at %s", Version, Date)
getty.GetLogger().Warnf("hostinfo os:%s, arch:%s", runtime.GOOS, runtime.GOARCH)
cfgInfo, _ := json.Marshal(cfg)
getty.GetLogger().Infof("config info:%s", string(cfgInfo))
getty.GetLogger().Warnf("config info:%s", string(cfgInfo))
client.NewClientWithConf(cfg)
}
......@@ -116,10 +112,6 @@ func main() {
fmt.Println(Version)
os.Exit(0)
}
runtime.GOMAXPROCS(runtime.NumCPU() * 2)
debug.SetGCPercent(200)
getty.GetLogger().Warnf("cpu count %d,proc %d", runtime.NumCPU(), runtime.NumCPU()*2)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
......@@ -129,6 +121,6 @@ func main() {
select {
case <-sigs:
time.Sleep(time.Second * 3)
getty.GetLogger().Info("main process exit...")
getty.GetLogger().Warn("main process exit...")
}
}
......@@ -14,7 +14,6 @@ type MajoraEventListener struct {
}
func (m *MajoraEventListener) OnOpen(session getty.Session) error {
getty.GetLogger().Infof("connect to nathost %s success ...", m.client.config.TunnelAddr)
packet := protocol.TypeRegister.CreatePacket()
packet.Extra = m.client.config.ClientID
extraMap := make(map[string]string, 1)
......@@ -24,20 +23,17 @@ func (m *MajoraEventListener) OnOpen(session getty.Session) error {
getty.GetLogger().Errorf("register to server error %+v", err)
return err
}
getty.GetLogger().Infof("[OnOpen] registe to %s success", m.client.config.TunnelAddr)
getty.GetLogger().Warnf("[OnOpen] registe to %s success", m.client.config.TunnelAddr)
return nil
}
func (m *MajoraEventListener) OnClose(session getty.Session) {
getty.GetLogger().Infof("OnClose->%v,clean local session...", session.IsClosed())
getty.GetLogger().Errorf("OnClose->%v,clean local session...", session.IsClosed())
m.client.CloseAll(session)
}
func (m *MajoraEventListener) OnError(session getty.Session, err error) {
getty.GetLogger().Errorf("OnError %s", err.Error())
//if errors.Is(err, common.ErrInvalidMagic) {
// session.Close()
//}
m.client.CloseAll(session)
}
......
......@@ -91,5 +91,5 @@ func (client *Client) Redial(session getty.Session) {
client.CloseAll(session)
getty.GetLogger().Warnf("=================redial->wait %v ==============", client.config.Redial.WaitTime)
time.Sleep(client.config.Redial.WaitTime)
infra.Redial(client.config)
infra.Redial(client.config, session)
}
#!/bin/bash
CURDIR=$(cd $(dirname "$0");pwd)
CONF=$CURDIR/majora.ini
chmod +x majora
echo "$CURDIR"/majora -conf "$CONF"
exec "$CURDIR"/majora -conf "$CONF"
\ No newline at end of file
......@@ -10,6 +10,7 @@ disable_update = false
log_level = 1
reconn_interval = 5s
;client_id =
log_path = "./majora.log"
[extra]
account = superman
......
......@@ -9,6 +9,7 @@ disable_update = false
log_level = 2
reconn_interval = 5s
;client_id =
log_path = /root/majora.log
[extra]
account = superman
......
[Unit]
Description=majora client
After=network.target
[Service]
Type=simple
User=root
Restart=always
RestartSec=5s
ExecStart=/root/majora -conf /root/majora.ini
ExecReload=/root/majora -conf /root/majora.ini
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
\ No newline at end of file
#!/usr/bin/env bash
. /etc/profile
. /root/.bash_profile
rm -fr /usr/lib/systemd/system/majora.service
# create link
ln -s /root/majora.service /usr/lib/systemd/system/majora.service
echo 'shutdown majora-cli server'
now_dir=`pwd`
systemctl daemon-reload
cd `dirname $0`
script_dir=`pwd`
echo "restart..."
function getPid(){
echo `ps -ef | grep "majora" | grep ".ini" | grep -v "grep" | grep -v "startup.sh" | awk '{print $2}'`
}
remote_pid=`getPid`
systemctl restart majora.service
echo remote_pid:${remote_pid}
if [[ -n "${remote_pid}" ]] ;then
echo kill pid ${remote_pid}
kill -9 ${remote_pid}
fi
echo "start majora-cli server"
sleep 2
remote_pid=`getPid`
if [[ -n "${remote_pid}" ]] ;then
# 被supervisor自动守护
exit 0
fi
std_log=../log
if [[ ! -d ${std_log} ]] ;then
mkdir ${std_log}
fi
nohup bash exec.sh >> std.log 2>&1 &
sleep 2
remote_pid=`getPid`
echo "remote pid:${remote_pid}"
\ No newline at end of file
systemctl status majora.service
#!/usr/bin/env bash
wget https://oss.virjar.com/majora/bin/latest/majora-cli_latest_linux_amd64.tar.gz -O majora-cli.tar.gz
tar -zxvf majora-cli.tar.gz
rm -fr majora
rm -fr exec.sh
rm -fr start.sh
rm -fr majora.ini
rm -fr majora.service
rm -fr majora-dev.ini
mv -f majora-cli*/* .
rm -fr /usr/lib/systemd/system/majora.service
# create link
ln -s /root/majora.service /usr/lib/systemd/system/majora.service
systemctl daemon-reload
echo "restart..."
systemctl restart majora.service
systemctl status majora.service
......@@ -3,15 +3,13 @@ module virjar.com/majora-go
go 1.17
require (
github.com/adamweixuan/getty v1.8.2
github.com/adamweixuan/getty v1.8.4
github.com/adamweixuan/gostnops v1.11.20-0.20211029124314-3f0589fceea6
github.com/blang/semver/v4 v4.0.0
github.com/google/uuid v1.3.0
gopkg.in/ini.v1 v1.63.2
)
require (
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
......
github.com/adamweixuan/getty v1.8.2 h1:y/0KmxLPo0QiErZRr4xBlcsVHmXkuTXTs/EIPZyar1s=
github.com/adamweixuan/getty v1.8.2/go.mod h1:JFfNdX0dvMtvPiF7LEfU4X8OLJYao5BGTHS9cf4kng8=
github.com/adamweixuan/getty v1.8.4 h1:YhHVYwrSqky3cMrPV5/dfHUwrhc9XB7emobVXTe1wY4=
github.com/adamweixuan/getty v1.8.4/go.mod h1:+P0Afn1ky1/4q96pjJmR6GssmVYXN+2dZ/0BxkIDpGo=
github.com/adamweixuan/gostnops v1.11.20-0.20211029124314-3f0589fceea6 h1:UGMsACjkGfNRKCz5U8ijtkpKL8o/25rGHxTV1adWjak=
github.com/adamweixuan/gostnops v1.11.20-0.20211029124314-3f0589fceea6/go.mod h1:zZYjPhU2+ujyEOWWE0l1pEdf5H4CyfiMG//rSpNjwcQ=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
......
package infra
import (
"crypto/tls"
"net/http"
"time"
"github.com/adamweixuan/getty"
)
// 网络检测
var (
httpcli *http.Client
)
const (
defTimeout = time.Second * 10
baiduUrl = "https://www.baidu.com"
)
func init() {
httpcli = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Timeout: defTimeout,
}
}
func Ping(url string) bool {
resp, err := httpcli.Head(url)
if err != nil {
getty.GetLogger().Warnf("ping %s with error %+v", url, err)
return false
}
defer resp.Body.Close()
getty.GetLogger().Warnf("ping %s status code %d", url, resp.StatusCode)
return resp.StatusCode == http.StatusOK
}
func PingBaidu() bool {
return Ping(baiduUrl)
}
package infra
import (
"testing"
)
func TestPing(t *testing.T) {
type args struct {
url string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "baidu",
args: args{url: "https://www.baidu.com"},
want: true,
},
{
name: "google",
args: args{url: "https://www.taobao.com"},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Ping(tt.args.url); got != tt.want {
t.Errorf("Ping() = %v, want %v", got, tt.want)
}
})
}
}
......@@ -19,6 +19,7 @@ func Restart() {
if err := cmd.Run(); err != nil {
getty.GetLogger().Errorf("Restart error %+v", err)
}
}
func RestartBySignal(signal chan struct{}) {
......
......@@ -3,6 +3,7 @@ package infra
import (
"os/exec"
"runtime"
"time"
"github.com/adamweixuan/getty"
"virjar.com/majora-go/model"
......@@ -13,16 +14,34 @@ const (
cmdUnix = "-c"
)
func Redial(cfg *model.Configure) {
func Redial(cfg *model.Configure, session getty.Session) {
getty.GetLogger().Warnf("[redial] start, session status:%s", session.Stat())
status := command(cfg)
getty.GetLogger().Warnf("[redial] end:%v, session status: %s", status, session.Stat())
pingBaidu := PingBaidu()
getty.GetLogger().Warnf("[redial] net check: %v", pingBaidu)
if !pingBaidu {
getty.GetLogger().Warnf("[redial] net check fail,redial...")
Redial(cfg, session)
}
}
func command(cfg *model.Configure) bool {
defer func(start time.Time) {
getty.GetLogger().Warnf("[redial] cost %v", time.Since(start))
}(time.Now())
execPath := cfg.Redial.ExecPath
if len(execPath) == 0 {
getty.GetLogger().Warn("redial exec file is empty")
return
getty.GetLogger().Warn("[redial] exec file is empty")
return true
}
command := cfg.Redial.Command
if len(command) == 0 {
getty.GetLogger().Warn("redial command is empty")
return
getty.GetLogger().Warn("[redial] command is empty")
return true
}
args := cmdUnix
......@@ -34,7 +53,8 @@ func Redial(cfg *model.Configure) {
output, err := cmd.Output()
if err != nil {
getty.GetLogger().Errorf("[redial] Execute Shell:%s failed with error:%s", command, err.Error())
return
return false
}
getty.GetLogger().Warnf("[redial] redial success %+v resp:%s", cmd, string(output))
getty.GetLogger().Warnf("[redial] success %+v resp:%s", cmd, string(output))
return true
}
package infra
import (
"testing"
"virjar.com/majora-go/model"
)
func TestRedial(t *testing.T) {
conf := model.InitConf("/Users/weixuan/code/gcode/majora-go/conf/majora.ini")
redial(conf)
}
package infra
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"runtime"
"strings"
"github.com/adamweixuan/getty"
"github.com/blang/semver/v4"
"virjar.com/majora-go/common"
)
// getBinary 下载二进制
func getBinary(name, os, arch, version string) (io.ReadCloser, error) {
fileName := fmt.Sprintf(common.VersionTpl, name, version, os, arch)
if os == "windows" {
fileName += ".exe"
}
url := common.UpdateServer + common.UpdateBinaryPath + fileName
getty.GetLogger().Debugf("getBinary url %s, filename %s", url, fileName)
body, err := http.DefaultClient.Get(url)
if err != nil {
return nil, err
}
return body.Body, nil
}
func downloadFile(name, os, arch, version string, f *os.File) error {
binary, err := getBinary(name, os, arch, version)
if err != nil {
return err
}
defer func(binary io.ReadCloser) {
_ = binary.Close()
}(binary)
body, err := ioutil.ReadAll(binary)
if err != nil {
return err
}
cnt, err := f.Write(body)
if err != nil {
return err
}
if cnt != len(body) {
return errors.New("write to file is not enough")
}
return nil
}
func rename(src, dst string) error {
if err := os.Chmod(src, common.DefaultMode); err != nil {
return err
}
if err := os.Rename(src, dst); err != nil {
return err
}
getty.GetLogger().Debugf("rename %s to %s", src, dst)
return nil
}
func UpdateCore(name, latestVer, targetFile string) (bool, error) {
tf, err := ioutil.TempFile("", "")
if err != nil {
getty.GetLogger().Debugf("UpdateCore TempFile with error %s", err.Error())
return false, err
}
if err := downloadFile(name, runtime.GOOS, runtime.GOARCH, latestVer, tf); err != nil {
getty.GetLogger().Debugf("UpdateCore downloadFile with error %s", err.Error())
return false, err
}
if err := tf.Close(); err != nil {
getty.GetLogger().Debugf("UpdateCore tf close with error %s", err.Error())
return false, err
}
if err := rename(tf.Name(), targetFile); err != nil {
return false, err
}
Restart()
return true, nil
}
func getLatestVersion() (string, error) {
url := common.UpdateServer + common.UpdateBinaryPath + common.Latest
getty.GetLogger().Debugf("getLatestVersion from %s", url)
resp, err := http.DefaultClient.Get(url) //nolint:bodyclose
if err != nil || resp == nil {
return "", err
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
if resp.StatusCode != http.StatusOK {
getty.GetLogger().Errorf("get latest version with error %s->%d", url, resp.StatusCode)
return "", nil
}
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return strings.ReplaceAll(string(bytes), "\n", ""), nil
}
func needUpdate(curVer string) (bool, string) {
if len(curVer) == 0 {
return false, ""
}
latestVer, err := getLatestVersion()
if err != nil {
getty.GetLogger().Errorf("getLatestVersion with error %s", err.Error())
return false, ""
}
if len(latestVer) == 0 {
getty.GetLogger().Errorf("getLatestVersion with empty")
return false, ""
}
if curVer[0] == 'v' || curVer[0] == 'V' {
curVer = curVer[1:]
}
latest := latestVer
if latestVer[0] == 'v' || latestVer[0] == 'V' {
latest = latest[1:]
}
cur, _ := semver.Make(curVer)
next, _ := semver.Make(latest)
getty.GetLogger().Debugf("curVer %s->cur %s, latestVer %s->next %s", curVer, cur, latest, next)
return cur.Compare(next) < 0, latestVer
}
// Update update to the latest version
func Update(name, curVer string) (bool, error) {
defer func() {
if err := recover(); err != nil {
getty.GetLogger().Errorf("Update with error %+v", err)
}
}()
// get current exec path
executable, err := os.Executable()
if err != nil {
return false, err
}
getty.GetLogger().Debugf("current exec info %s", executable)
// check has new version
needUpdate, latestVer := needUpdate(curVer)
getty.GetLogger().Debugf("curVer %s, latestVer %s, need update %v", curVer, latestVer, needUpdate)
if !needUpdate {
getty.GetLogger().Debugf("no need update ...")
return false, nil
}
updateStatus, err := UpdateCore(name, latestVer, executable)
if err != nil {
getty.GetLogger().Errorf("update to latest version with error %s", err)
return false, err
}
getty.GetLogger().Infof("update from %s to %s with success %v", curVer, latestVer, updateStatus)
return updateStatus, nil
}
//// getBinary 下载二进制
//func getBinary(name, os, arch, version string) (io.ReadCloser, error) {
// fileName := fmt.Sprintf(common.VersionTpl, name, version, os, arch)
// if os == "windows" {
// fileName += ".exe"
// }
// url := common.UpdateServer + common.UpdateBinaryPath + fileName
// getty.GetLogger().Debugf("getBinary url %s, filename %s", url, fileName)
//
// body, err := http.DefaultClient.Get(url)
// if err != nil {
// return nil, err
// }
// return body.Body, nil
//}
//
//func downloadFile(name, os, arch, version string, f *os.File) error {
// binary, err := getBinary(name, os, arch, version)
// if err != nil {
// return err
// }
//
// defer func(binary io.ReadCloser) {
// _ = binary.Close()
// }(binary)
//
// body, err := ioutil.ReadAll(binary)
//
// if err != nil {
// return err
// }
//
// cnt, err := f.Write(body)
// if err != nil {
// return err
// }
//
// if cnt != len(body) {
// return errors.New("write to file is not enough")
// }
// return nil
//}
//
//func rename(src, dst string) error {
// if err := os.Chmod(src, common.DefaultMode); err != nil {
// return err
// }
//
// if err := os.Rename(src, dst); err != nil {
// return err
// }
// getty.GetLogger().Debugf("rename %s to %s", src, dst)
// return nil
//}
//
//func UpdateCore(name, latestVer, targetFile string) (bool, error) {
// tf, err := ioutil.TempFile("", "")
// if err != nil {
// getty.GetLogger().Debugf("UpdateCore TempFile with error %s", err.Error())
// return false, err
// }
//
// if err := downloadFile(name, runtime.GOOS, runtime.GOARCH, latestVer, tf); err != nil {
// getty.GetLogger().Debugf("UpdateCore downloadFile with error %s", err.Error())
// return false, err
// }
//
// if err := tf.Close(); err != nil {
// getty.GetLogger().Debugf("UpdateCore tf close with error %s", err.Error())
// return false, err
// }
//
// if err := rename(tf.Name(), targetFile); err != nil {
// return false, err
// }
// Restart()
// return true, nil
//}
//
//func getLatestVersion() (string, error) {
// url := common.UpdateServer + common.UpdateBinaryPath + common.Latest
// getty.GetLogger().Debugf("getLatestVersion from %s", url)
// resp, err := http.DefaultClient.Get(url) //nolint:bodyclose
// if err != nil || resp == nil {
// return "", err
// }
//
// defer func(Body io.ReadCloser) {
// _ = Body.Close()
// }(resp.Body)
//
// if resp.StatusCode != http.StatusOK {
// getty.GetLogger().Errorf("get latest version with error %s->%d", url, resp.StatusCode)
// return "", nil
// }
//
// bytes, err := ioutil.ReadAll(resp.Body)
// if err != nil {
// return "", err
// }
//
// return strings.ReplaceAll(string(bytes), "\n", ""), nil
//}
//
//func needUpdate(curVer string) (bool, string) {
// if len(curVer) == 0 {
// return false, ""
// }
// latestVer, err := getLatestVersion()
// if err != nil {
// getty.GetLogger().Errorf("getLatestVersion with error %s", err.Error())
// return false, ""
// }
//
// if len(latestVer) == 0 {
// getty.GetLogger().Errorf("getLatestVersion with empty")
// return false, ""
// }
//
// if curVer[0] == 'v' || curVer[0] == 'V' {
// curVer = curVer[1:]
// }
//
// latest := latestVer
// if latestVer[0] == 'v' || latestVer[0] == 'V' {
// latest = latest[1:]
// }
// cur, _ := semver.Make(curVer)
// next, _ := semver.Make(latest)
// getty.GetLogger().Debugf("curVer %s->cur %s, latestVer %s->next %s", curVer, cur, latest, next)
//
// return cur.Compare(next) < 0, latestVer
//}
//
//// Update update to the latest version
//func Update(name, curVer string) (bool, error) {
// defer func() {
// if err := recover(); err != nil {
// getty.GetLogger().Errorf("Update with error %+v", err)
// }
// }()
//
// // get current exec path
// executable, err := os.Executable()
// if err != nil {
// return false, err
// }
//
// getty.GetLogger().Debugf("current exec info %s", executable)
//
// // check has new version
// needUpdate, latestVer := needUpdate(curVer)
// getty.GetLogger().Debugf("curVer %s, latestVer %s, need update %v", curVer, latestVer, needUpdate)
// if !needUpdate {
// getty.GetLogger().Debugf("no need update ...")
// return false, nil
// }
// updateStatus, err := UpdateCore(name, latestVer, executable)
// if err != nil {
// getty.GetLogger().Errorf("update to latest version with error %s", err)
// return false, err
// }
// getty.GetLogger().Infof("update from %s to %s with success %v", curVer, latestVer, updateStatus)
// return updateStatus, nil
//}
......@@ -29,6 +29,7 @@ type Configure struct {
LocalAddr string `ini:"local_ip" json:"local_ip"`
ReconnInterval time.Duration `ini:"reconn_interval" json:"reconn_interval"`
ClientID string `ini:"client_id" json:"client_id"`
LogPath string `ini:"log_path" json:"log_path"`
Extra Extra `ini:"extra" json:"extra"`
Redial Redial `ini:"redial" json:"redial"`
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment