├── .gitignore ├── README.md ├── cfg.example.json ├── control ├── cron ├── control.go ├── heartbeat.go ├── random.go ├── request.go ├── response.go ├── start.go └── stop.go ├── g ├── cfg.go ├── g.go └── var.go ├── http ├── common.go ├── http.go └── proc.go ├── main.go └── owl /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *.sw[op] 25 | /cfg.json 26 | /var 27 | /log 28 | /tmp 29 | /.idea 30 | *.iml 31 | *.log 32 | /ops-updater* 33 | /updater* 34 | 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # updater 2 | 3 | updater只有一个功能,就是升级其他业务系统的agent。 4 | 5 | ## 功能描述 6 | 7 | 每隔指定周期(配置文件中的interval,单位是秒)调用一下ops-meta的http接口,上报当前管理的各个agent的状态和版本号,比如: 8 | 9 | ``` 10 | [ 11 | { 12 | "name": "falcon-agent", 13 | "version": "1.0.0", 14 | "status": "started" 15 | }, 16 | { 17 | "name": "dinp-agent", 18 | "version": "2.0.0", 19 | "status": "stoped" 20 | } 21 | ] 22 | ``` 23 | 24 | response中顺便带回服务端配置的meta信息,也就是各个agent的版本号、tarball地址、要求的操作等等,比如 25 | 26 | ``` 27 | [ 28 | { 29 | "name": "falcon-agent", 30 | "version": "1.0.0", 31 | "tarball": "http://11.11.11.11:8888/falcon", 32 | "md5": "http://11.11.11.11:8888/falcon", 33 | "cmd": "start" 34 | }, 35 | { 36 | "name": "dinp-agent", 37 | "version": "2.0.0", 38 | "tarball": "http://11.11.11.11:8888/dinp", 39 | "md5": "", 40 | "cmd": "stop" 41 | } 42 | ] 43 | ``` 44 | 45 | updater将这个信息与自我内存中的信息做对比,该升级的升级,该启动的启动,该停止的停止。sorry,不能与内存中的信息做对比,应该直接去各个agent目录调用control脚本查看,因为agent有可能crash,或者做了一些手工操作,这将导致updater内存中的信息并不能实时反应线上情况 46 | 47 | 此处tarball和md5的配置并不是一个全路径(类似这样: http://11.11.11.11:8888/falcon/falcon-agent-1.0.0.tar.gz ),只是一部分路径,因为我们已经知道name和version了,就可以拼接出全路径了,算是一种规范化吧,拼接规范是:`{$tarball}/{$name}-{$version}.tar.gz` 48 | 49 | ## 目录结构 50 | 51 | 举个例子: 52 | 53 | ``` 54 | /home/work/ops 55 | |- ops-updater 56 | |- ops-updater.pid 57 | |- ops-updater.log 58 | |- falcon-agent 59 | |- 1.0.0 60 | |- falcon-agent 61 | |- control 62 | |- 1.0.1 63 | |- falcon-agent 64 | |- control 65 | |- .version 66 | |- dinp-agent 67 | |- 2.0.0 68 | |- 2.0.1 69 | |- .version 70 | ``` 71 | 72 | `.version`文件是当前这个agent应该使用的版本 73 | 74 | ## 启停 75 | 76 | ``` 77 | ./control start|stop|restart|tail 78 | ``` 79 | 80 | 配置文件是cfg.json,我们提供了cfg.example.json作为配置模板,`mv cfg.example.json cfg.json`,然后修改成合适的配置 81 | 82 | ## 设计注意点 83 | 84 | - updater访问ops-mata不能太集中,故而要加一个随机数,启动之后先随机sleep一下,然后再发送心跳请求 85 | - updater只升级agent就可以了,事情做得少才不容易出错 86 | - updater可能依赖一些Linux工具才能正常工作,比如tar、md5sum等,在启动之前要做验证,如果没有,退出 87 | 88 | ## agent约定 89 | 90 | - 必须提供control脚本 91 | - `./control start`可以启动agent 92 | - `./control stop`可以停止agent 93 | - `./control status`打印出状态信息,只能是started或者stoped 94 | - control文件已经有可执行权限,并且在tarball根目录下 95 | 96 | 97 | -------------------------------------------------------------------------------- /cfg.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "debug": true, 3 | "hostname": "", 4 | "desiredAgent": "", 5 | "server": "127.0.0.1:2000", 6 | "interval": 300, 7 | "http": { 8 | "enabled": true, 9 | "listen": "0.0.0.0:2001" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WORKSPACE=$(cd $(dirname $0)/; pwd) 4 | cd $WORKSPACE 5 | 6 | mkdir -p var 7 | 8 | module=updater-https 9 | app=ops-$module 10 | conf=cfg.json 11 | pidfile=var/app.pid 12 | logfile=var/app.log 13 | 14 | function check_pid() { 15 | if [ -f $pidfile ];then 16 | pid=`cat $pidfile` 17 | if [ -n $pid ]; then 18 | running=`ps -p $pid|grep -v "PID TTY" |wc -l` 19 | return $running 20 | fi 21 | fi 22 | return 0 23 | } 24 | 25 | function start() { 26 | check_pid 27 | running=$? 28 | if [ $running -gt 0 ];then 29 | echo -n "$app now is running already, pid=" 30 | cat $pidfile 31 | return 1 32 | fi 33 | 34 | nohup ./$app -c $conf &> $logfile & 35 | echo $! > $pidfile 36 | echo "$app started..., pid=$!" 37 | } 38 | 39 | function stop() { 40 | pid=`cat $pidfile` 41 | kill $pid 42 | echo "$app stoped..." 43 | } 44 | 45 | function restart() { 46 | stop 47 | sleep 1 48 | start 49 | } 50 | 51 | function status() { 52 | check_pid 53 | running=$? 54 | if [ $running -gt 0 ];then 55 | echo "started" 56 | else 57 | echo "stoped" 58 | fi 59 | } 60 | 61 | function tailf() { 62 | tail -f $logfile 63 | } 64 | 65 | function build() { 66 | go build -a 67 | if [ $? -ne 0 ]; then 68 | exit $? 69 | fi 70 | mv $module $app 71 | ./$app -v 72 | } 73 | 74 | function pack() { 75 | build 76 | version=`./$app -v` 77 | tar zcvf $app-$version.tar.gz control cfg.example.json $app 78 | } 79 | 80 | function packbin() { 81 | build 82 | version=`./$app -v` 83 | tar zcvf $app-bin-$version.tar.gz $app 84 | } 85 | 86 | function help() { 87 | echo "$0 pid|reload|build|pack|packbin|start|stop|restart|status|tail" 88 | } 89 | 90 | function pid() { 91 | cat $pidfile 92 | } 93 | 94 | function reload() { 95 | curl --insecure -s https://127.0.0.1:2001/config/reload | python -m json.tool 96 | } 97 | 98 | if [ "$1" == "" ]; then 99 | help 100 | elif [ "$1" == "stop" ];then 101 | stop 102 | elif [ "$1" == "start" ];then 103 | start 104 | elif [ "$1" == "restart" ];then 105 | restart 106 | elif [ "$1" == "status" ];then 107 | status 108 | elif [ "$1" == "tail" ];then 109 | tailf 110 | elif [ "$1" == "build" ];then 111 | build 112 | elif [ "$1" == "pack" ];then 113 | pack 114 | elif [ "$1" == "packbin" ];then 115 | packbin 116 | elif [ "$1" == "pid" ];then 117 | pid 118 | elif [ "$1" == "reload" ];then 119 | reload 120 | else 121 | help 122 | fi 123 | -------------------------------------------------------------------------------- /cron/control.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "log" 5 | "os/exec" 6 | ) 7 | 8 | func Control(workdir, arg string) (string, error) { 9 | cmd := exec.Command("./control", arg) 10 | cmd.Dir = workdir 11 | bs, err := cmd.CombinedOutput() 12 | if err != nil { 13 | log.Printf("cd %s; ./control %s fail %v. output: %s", workdir, arg, err, string(bs)) 14 | } 15 | return string(bs), err 16 | } 17 | 18 | func ControlStatus(workdir string) (string, error) { 19 | return Control(workdir, "status") 20 | } 21 | 22 | func ControlStart(workdir string) (string, error) { 23 | return Control(workdir, "start") 24 | } 25 | 26 | func ControlStop(workdir string) (string, error) { 27 | return Control(workdir, "stop") 28 | } 29 | -------------------------------------------------------------------------------- /cron/heartbeat.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/Cepave/ops-common/model" 8 | "github.com/Cepave/ops-common/utils" 9 | "github.com/Cepave/ops-updater/g" 10 | "github.com/toolkits/net/httplib" 11 | "log" 12 | "time" 13 | ) 14 | 15 | func Heartbeat() { 16 | SleepRandomDuration() 17 | for { 18 | heartbeat() 19 | d := time.Duration(g.Config().Interval) * time.Second 20 | time.Sleep(d) 21 | } 22 | } 23 | 24 | func heartbeat() { 25 | agentDirs, err := ListAgentDirs() 26 | if err != nil { 27 | return 28 | } 29 | 30 | hostname, err := utils.Hostname(g.Config().Hostname) 31 | if err != nil { 32 | return 33 | } 34 | 35 | heartbeatRequest := BuildHeartbeatRequest(hostname, agentDirs) 36 | if g.Config().Debug { 37 | log.Println("====>>>>") 38 | log.Println(heartbeatRequest) 39 | } 40 | 41 | bs, err := json.Marshal(heartbeatRequest) 42 | if err != nil { 43 | log.Println("encode heartbeat request fail", err) 44 | return 45 | } 46 | 47 | url := fmt.Sprintf("https://%s/heartbeat", g.Config().Server) 48 | 49 | httpRequest := httplib.Post(url).SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).SetTimeout(time.Second*10, time.Minute) 50 | httpRequest.Body(bs) 51 | httpResponse, err := httpRequest.Bytes() 52 | if err != nil { 53 | log.Printf("curl %s fail %v", url, err) 54 | return 55 | } 56 | 57 | var heartbeatResponse model.HeartbeatResponse 58 | err = json.Unmarshal(httpResponse, &heartbeatResponse) 59 | if err != nil { 60 | log.Println("decode heartbeat response fail", err) 61 | return 62 | } 63 | 64 | if g.Config().Debug { 65 | log.Println("<<<<====") 66 | log.Println(heartbeatResponse) 67 | } 68 | 69 | HandleHeartbeatResponse(&heartbeatResponse) 70 | 71 | } 72 | -------------------------------------------------------------------------------- /cron/random.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "github.com/Cepave/ops-updater/g" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func SleepRandomDuration() { 10 | ns := int64(g.Config().Interval) * 1000000000 11 | // 以当前时间为随机数种子,如果所有ops-updater在同一时间启动,系统时间是相同的,那么随机种子就是一样的 12 | // 问题不大,批量ssh去启动ops-updater的话也是一个顺次的过程 13 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 14 | d := time.Duration(r.Int63n(ns)) * time.Nanosecond 15 | time.Sleep(d) 16 | } 17 | -------------------------------------------------------------------------------- /cron/request.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Cepave/ops-common/model" 6 | "github.com/Cepave/ops-updater/g" 7 | f "github.com/toolkits/file" 8 | "log" 9 | "os/exec" 10 | "path" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func BuildHeartbeatRequest(hostname string, agentDirs []string) model.HeartbeatRequest { 16 | req := model.HeartbeatRequest{Hostname: hostname} 17 | 18 | realAgents := []*model.RealAgent{} 19 | now := time.Now().Unix() 20 | 21 | for _, agentDir := range agentDirs { 22 | // 如果目录下没有.version,我们认为这根本不是一个agent 23 | versionFile := path.Join(g.SelfDir, agentDir, ".version") 24 | if !f.IsExist(versionFile) { 25 | continue 26 | } 27 | 28 | version, err := f.ToTrimString(versionFile) 29 | if err != nil { 30 | log.Printf("read %s/.version fail: %v", agentDir, err) 31 | continue 32 | } 33 | 34 | controlFile := path.Join(g.SelfDir, agentDir, version, "control") 35 | if !f.IsExist(controlFile) { 36 | log.Printf("%s is nonexistent", controlFile) 37 | continue 38 | } 39 | 40 | cmd := exec.Command("./control", "status") 41 | cmd.Dir = path.Join(g.SelfDir, agentDir, version) 42 | bs, err := cmd.CombinedOutput() 43 | 44 | status := "" 45 | if err != nil { 46 | status = fmt.Sprintf("exec `./control status` fail: %s", err) 47 | } else { 48 | status = strings.TrimSpace(string(bs)) 49 | } 50 | 51 | realAgent := &model.RealAgent{ 52 | Name: agentDir, 53 | Version: version, 54 | Status: status, 55 | Timestamp: now, 56 | } 57 | 58 | realAgents = append(realAgents, realAgent) 59 | } 60 | 61 | req.RealAgents = realAgents 62 | return req 63 | } 64 | 65 | func ListAgentDirs() ([]string, error) { 66 | agentDirs, err := f.DirsUnder(g.SelfDir) 67 | if err != nil { 68 | log.Println("list dirs under", g.SelfDir, "fail", err) 69 | } 70 | return agentDirs, err 71 | } 72 | -------------------------------------------------------------------------------- /cron/response.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "github.com/Cepave/ops-common/model" 5 | "github.com/Cepave/ops-updater/g" 6 | "log" 7 | ) 8 | 9 | func HandleHeartbeatResponse(respone *model.HeartbeatResponse) { 10 | if respone.ErrorMessage != "" { 11 | log.Println("receive error message:", respone.ErrorMessage) 12 | return 13 | } 14 | 15 | das := respone.DesiredAgents 16 | if das == nil || len(das) == 0 { 17 | return 18 | } 19 | 20 | for _, da := range das { 21 | da.FillAttrs(g.SelfDir) 22 | 23 | if g.Config().DesiredAgent == "" || g.Config().DesiredAgent == da.Name { 24 | HandleDesiredAgent(da) 25 | } 26 | } 27 | } 28 | 29 | func HandleDesiredAgent(da *model.DesiredAgent) { 30 | if da.Cmd == "start" { 31 | StartDesiredAgent(da) 32 | } else if da.Cmd == "stop" { 33 | StopDesiredAgent(da) 34 | } else { 35 | log.Println("unknown cmd", da) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cron/start.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Cepave/ops-common/model" 6 | "github.com/Cepave/ops-common/utils" 7 | "github.com/toolkits/file" 8 | "io/ioutil" 9 | "log" 10 | "os/exec" 11 | "path" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | func StartDesiredAgent(da *model.DesiredAgent) { 17 | if err := InsureDesiredAgentDirExists(da); err != nil { 18 | return 19 | } 20 | 21 | if err := InsureNewVersionFiles(da); err != nil { 22 | return 23 | } 24 | 25 | if err := Untar(da); err != nil { 26 | return 27 | } 28 | 29 | if err := StopAgentOf(da.Name, da.Version); err != nil { 30 | return 31 | } 32 | 33 | if err := ControlStartIn(da.AgentVersionDir); err != nil { 34 | return 35 | } 36 | 37 | file.WriteString(path.Join(da.AgentDir, ".version"), da.Version) 38 | } 39 | 40 | func Untar(da *model.DesiredAgent) error { 41 | cmd := exec.Command("tar", "zxf", da.TarballFilename) 42 | cmd.Dir = da.AgentVersionDir 43 | err := cmd.Run() 44 | if err != nil { 45 | log.Println("tar zxf", da.TarballFilename, "fail", err) 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func ControlStartIn(workdir string) error { 53 | out, err := ControlStatus(workdir) 54 | if err == nil && strings.Contains(out, "started") { 55 | return nil 56 | } 57 | 58 | _, err = ControlStart(workdir) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | time.Sleep(time.Second * 3) 64 | 65 | out, err = ControlStatus(workdir) 66 | if err == nil && strings.Contains(out, "started") { 67 | return nil 68 | } 69 | 70 | return err 71 | } 72 | 73 | func InsureNewVersionFiles(da *model.DesiredAgent) error { 74 | if FilesReady(da) { 75 | return nil 76 | } 77 | content, err := ioutil.ReadFile("./password") 78 | password := strings.Trim(string(content), "\n") 79 | if err != nil { 80 | panic(err) 81 | } 82 | downloadTarballCmd := exec.Command("wget", "--no-check-certificate", "--auth-no-challenge", "--user=owl", "--password="+password, da.TarballUrl, "-O", da.TarballFilename) 83 | downloadTarballCmd.Dir = da.AgentVersionDir 84 | err = downloadTarballCmd.Run() 85 | if err != nil { 86 | log.Println("wget -q --no-check-certificate --auth-no-challenge --user=owl --password="+password, da.TarballUrl, "-O", da.TarballFilename, "fail", err) 87 | return err 88 | } 89 | 90 | downloadMd5Cmd := exec.Command("wget", "--no-check-certificate", "--auth-no-challenge", "--user=owl", "--password="+password, da.Md5Url, "-O", da.Md5Filename) 91 | downloadMd5Cmd.Dir = da.AgentVersionDir 92 | err = downloadMd5Cmd.Run() 93 | if err != nil { 94 | log.Println("wget -q --no-check-certificate --auth-no-challenge --user=owl --password="+password, da.Md5Url, "-O", da.Md5Filename, "fail", err) 95 | return err 96 | } 97 | 98 | if utils.Md5sumCheck(da.AgentVersionDir, da.Md5Filename) { 99 | return nil 100 | } else { 101 | return fmt.Errorf("md5sum -c fail") 102 | } 103 | } 104 | 105 | func FilesReady(da *model.DesiredAgent) bool { 106 | if !file.IsExist(da.Md5Filepath) { 107 | return false 108 | } 109 | 110 | if !file.IsExist(da.TarballFilepath) { 111 | return false 112 | } 113 | 114 | if !file.IsExist(da.ControlFilepath) { 115 | return false 116 | } 117 | 118 | return utils.Md5sumCheck(da.AgentVersionDir, da.Md5Filename) 119 | } 120 | 121 | func InsureDesiredAgentDirExists(da *model.DesiredAgent) error { 122 | err := file.InsureDir(da.AgentDir) 123 | if err != nil { 124 | log.Println("insure dir", da.AgentDir, "fail", err) 125 | return err 126 | } 127 | 128 | err = file.InsureDir(da.AgentVersionDir) 129 | if err != nil { 130 | log.Println("insure dir", da.AgentVersionDir, "fail", err) 131 | } 132 | return err 133 | } 134 | -------------------------------------------------------------------------------- /cron/stop.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "github.com/Cepave/ops-common/model" 5 | "github.com/Cepave/ops-updater/g" 6 | "github.com/toolkits/file" 7 | "log" 8 | "path" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func StopDesiredAgent(da *model.DesiredAgent) { 14 | if !file.IsExist(da.ControlFilepath) { 15 | return 16 | } 17 | 18 | ControlStopIn(da.AgentVersionDir) 19 | } 20 | 21 | func StopAgentOf(agentName, newVersion string) error { 22 | agentDir := path.Join(g.SelfDir, agentName) 23 | versionFile := path.Join(agentDir, ".version") 24 | 25 | if !file.IsExist(versionFile) { 26 | log.Printf("WARN: %s is nonexistent", versionFile) 27 | return nil 28 | } 29 | 30 | version, err := file.ToTrimString(versionFile) 31 | if err != nil { 32 | log.Printf("WARN: read %s fail %s", version, err) 33 | return nil 34 | } 35 | 36 | if version == newVersion { 37 | // do nothing 38 | return nil 39 | } 40 | 41 | versionDir := path.Join(agentDir, version) 42 | if !file.IsExist(versionDir) { 43 | log.Printf("WARN: %s nonexistent", versionDir) 44 | return nil 45 | } 46 | 47 | return ControlStopIn(versionDir) 48 | } 49 | 50 | func ControlStopIn(workdir string) error { 51 | if !file.IsExist(workdir) { 52 | return nil 53 | } 54 | 55 | out, err := ControlStatus(workdir) 56 | if err == nil && strings.Contains(out, "stoped") { 57 | return nil 58 | } 59 | 60 | _, err = ControlStop(workdir) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | time.Sleep(time.Second * 3) 66 | 67 | out, err = ControlStatus(workdir) 68 | if err == nil && strings.Contains(out, "stoped") { 69 | return nil 70 | } 71 | 72 | return err 73 | } 74 | -------------------------------------------------------------------------------- /g/cfg.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/toolkits/file" 7 | "log" 8 | "sync" 9 | ) 10 | 11 | type HttpConfig struct { 12 | Enabled bool `json:"enabled"` 13 | Listen string `json:"listen"` 14 | } 15 | 16 | type GlobalConfig struct { 17 | Debug bool `json:"debug"` 18 | Hostname string `json:"hostname"` 19 | DesiredAgent string `json:"desiredAgent"` 20 | Server string `json:"server"` 21 | Interval int `json:"interval"` 22 | Http *HttpConfig `json:"http"` 23 | } 24 | 25 | var ( 26 | ConfigFile string 27 | config *GlobalConfig 28 | configLock = new(sync.RWMutex) 29 | ) 30 | 31 | func Config() *GlobalConfig { 32 | configLock.RLock() 33 | defer configLock.RUnlock() 34 | return config 35 | } 36 | 37 | func ParseConfig(cfg string) error { 38 | if cfg == "" { 39 | return fmt.Errorf("use -c to specify configuration file") 40 | } 41 | 42 | if !file.IsExist(cfg) { 43 | return fmt.Errorf("config file %s is nonexistent", cfg) 44 | } 45 | 46 | ConfigFile = cfg 47 | 48 | configContent, err := file.ToTrimString(cfg) 49 | if err != nil { 50 | return fmt.Errorf("read config file %s fail %s", cfg, err) 51 | } 52 | 53 | var c GlobalConfig 54 | err = json.Unmarshal([]byte(configContent), &c) 55 | if err != nil { 56 | return fmt.Errorf("parse config file %s fail %s", cfg, err) 57 | } 58 | 59 | configLock.Lock() 60 | defer configLock.Unlock() 61 | 62 | config = &c 63 | 64 | log.Println("read config file:", cfg, "successfully") 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /g/g.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | const ( 9 | VERSION = "1.0.5" 10 | ) 11 | 12 | func init() { 13 | runtime.GOMAXPROCS(runtime.NumCPU()) 14 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) 15 | } 16 | -------------------------------------------------------------------------------- /g/var.go: -------------------------------------------------------------------------------- 1 | package g 2 | 3 | import ( 4 | "github.com/toolkits/file" 5 | ) 6 | 7 | var SelfDir string 8 | 9 | func InitGlobalVariables() { 10 | SelfDir = file.SelfDir() 11 | } 12 | -------------------------------------------------------------------------------- /http/common.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/Cepave/ops-updater/g" 5 | "github.com/toolkits/file" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func configCommonRoutes() { 11 | http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { 12 | w.Write([]byte("ok")) 13 | }) 14 | 15 | http.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) { 16 | w.Write([]byte(g.VERSION)) 17 | }) 18 | 19 | http.HandleFunc("/workdir", func(w http.ResponseWriter, r *http.Request) { 20 | RenderDataJson(w, file.SelfDir()) 21 | }) 22 | 23 | http.HandleFunc("/config/reload", func(w http.ResponseWriter, r *http.Request) { 24 | if strings.HasPrefix(r.RemoteAddr, "127.0.0.1") { 25 | err := g.ParseConfig(g.ConfigFile) 26 | AutoRender(w, g.Config(), err) 27 | } else { 28 | w.Write([]byte("no privilege")) 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/Cepave/ops-updater/g" 6 | "github.com/kardianos/osext" 7 | "log" 8 | "net/http" 9 | _ "net/http/pprof" 10 | "os" 11 | ) 12 | 13 | type Dto struct { 14 | Msg string `json:"msg"` 15 | Data interface{} `json:"data"` 16 | } 17 | 18 | func init() { 19 | configCommonRoutes() 20 | configProcRoutes() 21 | } 22 | 23 | func RenderJson(w http.ResponseWriter, v interface{}) { 24 | bs, err := json.Marshal(v) 25 | if err != nil { 26 | http.Error(w, err.Error(), http.StatusInternalServerError) 27 | return 28 | } 29 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 30 | w.Write(bs) 31 | } 32 | 33 | func RenderDataJson(w http.ResponseWriter, data interface{}) { 34 | RenderJson(w, Dto{Msg: "success", Data: data}) 35 | } 36 | 37 | func RenderMsgJson(w http.ResponseWriter, msg string) { 38 | RenderJson(w, map[string]string{"msg": msg}) 39 | } 40 | 41 | func AutoRender(w http.ResponseWriter, data interface{}, err error) { 42 | if err != nil { 43 | RenderMsgJson(w, err.Error()) 44 | return 45 | } 46 | RenderDataJson(w, data) 47 | } 48 | 49 | var ( 50 | _ = determineWorkingDirectory() 51 | certFilename = "cert.pem" 52 | keyFilename = "key.pem" 53 | ) 54 | 55 | func determineWorkingDirectory() string { 56 | executablePath, err := osext.ExecutableFolder() 57 | if err != nil { 58 | log.Fatal("Error: Couldn't determine working directory: " + err.Error()) 59 | } 60 | os.Chdir(executablePath) 61 | return "" 62 | } 63 | 64 | func Start() { 65 | if !g.Config().Http.Enabled { 66 | return 67 | } 68 | 69 | addr := g.Config().Http.Listen 70 | if addr == "" { 71 | return 72 | } 73 | //s := &http.Server{ 74 | // Addr: addr, 75 | // MaxHeaderBytes: 1 << 30, 76 | //} 77 | log.Println("http listening", addr) 78 | err := http.ListenAndServeTLS(addr, certFilename, keyFilename, nil) 79 | log.Fatalln(err) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /http/proc.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func configProcRoutes() { 8 | 9 | // 这个文件中主要放置一些调试接口,展示内存状态,/proc/echo/只是个占位的,其他方法可以拷贝这个模板 10 | http.HandleFunc("/proc/echo/", func(w http.ResponseWriter, r *http.Request) { 11 | arg := r.URL.Path[len("/proc/echo/"):] 12 | w.Write([]byte(arg)) 13 | }) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/Cepave/ops-updater/cron" 7 | "github.com/Cepave/ops-updater/g" 8 | "github.com/Cepave/ops-updater/http" 9 | "github.com/toolkits/sys" 10 | "log" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | cfg := flag.String("c", "cfg.json", "configuration file") 16 | version := flag.Bool("v", false, "show version") 17 | flag.Parse() 18 | 19 | if *version { 20 | fmt.Println(g.VERSION) 21 | os.Exit(0) 22 | } 23 | 24 | if err := g.ParseConfig(*cfg); err != nil { 25 | log.Fatalln(err) 26 | } 27 | 28 | g.InitGlobalVariables() 29 | 30 | CheckDependency() 31 | 32 | go http.Start() 33 | go cron.Heartbeat() 34 | 35 | select {} 36 | } 37 | 38 | func CheckDependency() { 39 | _, err := sys.CmdOut("wget", "--help") 40 | if err != nil { 41 | log.Fatalln("dependency wget not found") 42 | } 43 | 44 | _, err = sys.CmdOut("md5sum", "--help") 45 | if err != nil { 46 | log.Fatalln("dependency md5sum not found") 47 | } 48 | 49 | _, err = sys.CmdOut("tar", "--help") 50 | if err != nil { 51 | log.Fatalln("dependency tar not found") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /owl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /etc/init.d/functions 4 | 5 | WORKSPACE=$(cd $(dirname $0)/; pwd) 6 | cd $WORKSPACE 7 | 8 | mkdir -p var 9 | 10 | module=updater-https 11 | 12 | owl=/usr/local/owl/owl-agent-updater 13 | app=$owl/ops-$module 14 | conf=$owl/cfg.json 15 | pidfile=$owl/var/app.pid 16 | logfile=$owl/var/app.log 17 | 18 | function check_pid() { 19 | if [ -f $pidfile ];then 20 | pid=`cat $pidfile` 21 | if [ -n $pid ]; then 22 | running=`ps -p $pid|grep -v "PID TTY" |wc -l` 23 | return $running 24 | fi 25 | fi 26 | return 0 27 | } 28 | 29 | function start() { 30 | check_pid 31 | running=$? 32 | if [ $running -gt 0 ];then 33 | echo -n "$app now is running already, pid=" 34 | cat $pidfile 35 | return 0 36 | fi 37 | 38 | nohup $app -c $conf &> $logfile & 39 | echo $! > $pidfile 40 | echo "$app started..., pid=$!" 41 | } 42 | 43 | function stop() { 44 | pid=`cat $pidfile` 45 | kill $pid 46 | echo "$app stoped..." 47 | return 1; 48 | } 49 | 50 | function restart() { 51 | stop 52 | sleep 1 53 | start 54 | } 55 | 56 | function status() { 57 | check_pid 58 | running=$? 59 | if [ $running -gt 0 ];then 60 | echo "started" 61 | return 0 62 | else 63 | echo "stoped" 64 | return 1 65 | fi 66 | } 67 | 68 | function tailf() { 69 | tail -f $logfile 70 | } 71 | 72 | function build() { 73 | go build -a 74 | if [ $? -ne 0 ]; then 75 | exit $? 76 | fi 77 | mv $module $app 78 | ./$app -v 79 | } 80 | 81 | function pack() { 82 | build 83 | version=`./$app -v` 84 | tar zcvf $app-$version.tar.gz control cfg.example.json $app 85 | } 86 | 87 | function packbin() { 88 | build 89 | version=`./$app -v` 90 | tar zcvf $app-bin-$version.tar.gz $app 91 | } 92 | 93 | function help() { 94 | echo "$0 pid|reload|build|pack|packbin|start|stop|restart|status|tail" 95 | } 96 | 97 | function pid() { 98 | cat $pidfile 99 | } 100 | 101 | function reload() { 102 | curl --insecure -s https://127.0.0.1:2001/config/reload | python -m json.tool 103 | } 104 | 105 | if [ "$1" == "" ]; then 106 | help 107 | elif [ "$1" == "stop" ];then 108 | stop 109 | elif [ "$1" == "start" ];then 110 | start 111 | elif [ "$1" == "restart" ];then 112 | restart 113 | elif [ "$1" == "status" ];then 114 | status 115 | elif [ "$1" == "tail" ];then 116 | tailf 117 | elif [ "$1" == "build" ];then 118 | build 119 | elif [ "$1" == "pack" ];then 120 | pack 121 | elif [ "$1" == "packbin" ];then 122 | packbin 123 | elif [ "$1" == "pid" ];then 124 | pid 125 | elif [ "$1" == "reload" ];then 126 | reload 127 | else 128 | help 129 | fi 130 | --------------------------------------------------------------------------------