diff --git a/go/src/themaru/Gopkg.lock b/go/src/themaru/Gopkg.lock new file mode 100644 index 0000000..d408aab --- /dev/null +++ b/go/src/themaru/Gopkg.lock @@ -0,0 +1,113 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:9d957ea60a89df67d3f51eee85812b90e1acb47227977d48cd4c3dce204f3cae" + name = "github.com/gin-contrib/multitemplate" + packages = ["."] + pruneopts = "UT" + revision = "f9896279eead500934dc33f97657bf039781c9d3" + +[[projects]] + branch = "master" + digest = "1:a4b5a6b32f33323c7850fae4ec085a0fbac3be93c057b6b8cef5bcabe24645ac" + name = "github.com/gin-contrib/sse" + packages = ["."] + pruneopts = "UT" + revision = "5545eab6dad3bbbd6c5ae9186383c2a9d23c0dae" + +[[projects]] + digest = "1:d8bd2a337f6ff2188e08f72c614f2f3f0fd48e6a7b37a071b197e427d77d3a47" + name = "github.com/gin-gonic/gin" + packages = [ + ".", + "binding", + "internal/json", + "render", + ] + pruneopts = "UT" + revision = "b75d67cd51eb53c3c3a2fc406524c940021ffbda" + version = "v1.4.0" + +[[projects]] + digest = "1:318f1c959a8a740366fce4b1e1eb2fd914036b4af58fbd0a003349b305f118ad" + name = "github.com/golang/protobuf" + packages = ["proto"] + pruneopts = "UT" + revision = "c823c79ea1570fb5ff454033735a8e68575d1d0f" + version = "v1.3.0" + +[[projects]] + digest = "1:f5a2051c55d05548d2d4fd23d244027b59fbd943217df8aa3b5e170ac2fd6e1b" + name = "github.com/json-iterator/go" + packages = ["."] + pruneopts = "UT" + revision = "0ff49de124c6f76f8494e194af75bde0f1a49a29" + version = "v1.1.6" + +[[projects]] + digest = "1:e150b5fafbd7607e2d638e4e5cf43aa4100124e5593385147b0a74e2733d8b0d" + name = "github.com/mattn/go-isatty" + packages = ["."] + pruneopts = "UT" + revision = "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7" + version = "v0.0.7" + +[[projects]] + digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563" + name = "github.com/modern-go/concurrent" + packages = ["."] + pruneopts = "UT" + revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" + version = "1.0.3" + +[[projects]] + digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" + name = "github.com/modern-go/reflect2" + packages = ["."] + pruneopts = "UT" + revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" + version = "1.0.1" + +[[projects]] + digest = "1:d0072748c62defde1ad99dde77f6ffce492a0e5aea9204077e497c7edfb86653" + name = "github.com/ugorji/go" + packages = ["codec"] + pruneopts = "UT" + revision = "2adff0894ba3bc2eeb9f9aea45fefd49802e1a13" + version = "v1.1.4" + +[[projects]] + branch = "master" + digest = "1:9ca267f99487ef450fff0c2a49eaf0788d9ff3562bbaeff19f564d380f84bc6d" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "UT" + revision = "791d8a0f4d093bd8fe272dea2cd28991154796fb" + +[[projects]] + digest = "1:cbc72c4c4886a918d6ab4b95e347ffe259846260f99ebdd8a198c2331cf2b2e9" + name = "gopkg.in/go-playground/validator.v8" + packages = ["."] + pruneopts = "UT" + revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" + version = "v8.18.2" + +[[projects]] + digest = "1:4d2e5a73dc1500038e504a8d78b986630e3626dc027bc030ba5c75da257cdb96" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "51d6538a90f86fe93ac480b35f37b2be17fef232" + version = "v2.2.2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/gin-contrib/multitemplate", + "github.com/gin-gonic/gin", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/go/src/themaru/Gopkg.toml b/go/src/themaru/Gopkg.toml new file mode 100644 index 0000000..f8ab90c --- /dev/null +++ b/go/src/themaru/Gopkg.toml @@ -0,0 +1,38 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "github.com/gin-contrib/multitemplate" + +[[constraint]] + name = "github.com/gin-gonic/gin" + version = "1.4.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/go/src/themaru/build.sh b/go/src/themaru/build.sh new file mode 100755 index 0000000..d171cce --- /dev/null +++ b/go/src/themaru/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +GOARCH=arm go build -o webserv -ldflags="-s -w" . && ./upx --ultra-brute webserv diff --git a/go/src/themaru/env.go b/go/src/themaru/env.go new file mode 100644 index 0000000..60f0c78 --- /dev/null +++ b/go/src/themaru/env.go @@ -0,0 +1,74 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "regexp" +) + +func getEnv(blockn int) (map[string]string, error) { + m := make(map[string]string) + + f, err := os.Open("/dev/mmcblk3boot1") + if err != nil { + return m, err + } + defer f.Close() + + b := make([]byte, 512) + f.ReadAt(b, int64(blockn)*512) + + n := bytes.IndexByte(b, 0) + s := string(b[:n]) + + r := regexp.MustCompile(`(.+?)=(.+)\n`) + ss := r.FindAllStringSubmatch(s, -1) + for _, v := range ss { + if len(v) != 3 { + continue + } + + m[v[1]] = v[2] + } + + return m, nil +} + +func setEnv(blockn int, env map[string]string) error { + err := disableReadOnly() + if err != nil { + return err + } + + f, err := os.OpenFile("/dev/mmcblk3boot1", os.O_RDWR, 0666) + if err != nil { + return err + } + defer f.Close() + + var s string + for k, v := range env { + s += fmt.Sprintf("%s=%s\n", k, v) + } + s += "\x00" + + _, err = f.WriteAt([]byte(s), int64(blockn)*512) + if err != nil { + return err + } + + return nil +} + +func disableReadOnly() error { + f, err := os.OpenFile("/sys/block/mmcblk3boot1/force_ro", os.O_RDWR, 0666) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString("0\n") + + return err +} diff --git a/go/src/themaru/http.go b/go/src/themaru/http.go new file mode 100644 index 0000000..8d08871 --- /dev/null +++ b/go/src/themaru/http.go @@ -0,0 +1,183 @@ +package main + +import ( + "html/template" + "net/http" + "os" + "sync" + + "github.com/gin-contrib/multitemplate" + "github.com/gin-gonic/gin" +) + +var mu sync.Mutex + +var indexPage = template.Must(template.New("index.html").Parse(` + +
+System Version: {{.Version}}
+{{.Message}}
+ + + +`)) + +func runHttp() { + gin.SetMode(gin.ReleaseMode) + + g := gin.Default() + + r := multitemplate.NewRenderer() + r.Add("index.html", indexPage) + r.Add("reboot.html", rebootPage) + + g.HTMLRender = r + + g.GET("/", renderIndex) + + g.POST("/address", handleAddress) + g.POST("/system", handleSystem) + g.POST("/application", handleApplication) + + g.GET("/reboot", handleReboot) + g.GET("/restore", handleRestore) + + g.Run(":8988") +} + +func handleAddress(c *gin.Context) { + mu.Lock() + defer mu.Unlock() + + addr := c.PostForm("address") + addr2 := c.PostForm("address2") + + if err := setIpAddress(addr, addr2); err != nil { + c.String(http.StatusBadRequest, "address: setIpAddress: %s", err.Error()) + return + } + + c.HTML(http.StatusOK, "reboot.html", gin.H{"Message": "IP address is updated."}) +} + +func handleSystem(c *gin.Context) { + mu.Lock() + defer mu.Unlock() + + if err := saveFormFile(c, "image", "/tmp/system.zip"); err != nil { + c.String(http.StatusBadRequest, "system: saveFormFile: %s", err.Error()) + return + } + + if err := updateSystem(); err != nil { + c.String(http.StatusBadRequest, "system: updateSystem: %s", err.Error()) + return + } + + c.HTML(http.StatusOK, "reboot.html", gin.H{"Message": "System is updated."}) +} + +func handleApplication(c *gin.Context) { + mu.Lock() + defer mu.Unlock() + + if err := saveFormFile(c, "image", "/tmp/application.zip"); err != nil { + c.String(http.StatusBadRequest, "application: saveFormFile: %s", err.Error()) + return + } + + if err := updateApp(); err != nil { + c.String(http.StatusBadRequest, "application: updateApplication: %s", err.Error()) + return + } + + c.HTML(http.StatusOK, "reboot.html", gin.H{"Message": "Application is updated."}) +} + +func handleReboot(c *gin.Context) { + reboot() + + c.String(http.StatusOK, "OK") +} + +func handleRestore(c *gin.Context) { + mu.Lock() + defer mu.Unlock() + + restore() + + c.HTML(http.StatusOK, "reboot.html", gin.H{"Message": "Restoration is done."}) +} + +func renderIndex(c *gin.Context) { + device := "System" + if len(os.Args) > 1 { + device = os.Args[1] + } + + c.HTML(http.StatusOK, "index.html", gin.H{ + "Device": device, + "IP": getIpAddress(), + "IP2": getIpAddress2(), + "Version": getVersion(), + }) +} + +func saveFormFile(c *gin.Context, name string, dst string) error { + file, err := c.FormFile(name) + if err != nil { + return err + } + + return c.SaveUploadedFile(file, dst) +} diff --git a/go/src/themaru/ip.go b/go/src/themaru/ip.go new file mode 100644 index 0000000..a3f09bd --- /dev/null +++ b/go/src/themaru/ip.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + "regexp" + "strconv" +) + +func getIpAddress() string { + env, err := getEnv(0) + if err != nil { + return "" + } + + return env["ipaddr"] +} + +func getIpAddress2() string { + env, err := getEnv(0) + if err != nil { + return "" + } + + return env["ipaddr2"] +} + +func setIpAddress(address, address2 string) error { + address = extractIpAddress(address) + if address == "" { + return errors.New("invalid IP address") + } + + address2 = extractIpAddress(address2) + if address2 == "" { + return errors.New("invalid IP address2") + } + + env, err := getEnv(0) + if err != nil { + return err + } + + env["ipaddr"] = address + env["ipaddr2"] = address2 + + err = setEnv(0, env) + if err != nil { + return err + } + + return nil +} + +func extractIpAddress(address string) string { + r := regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)`) + s := r.FindStringSubmatch(address) + if len(s) != 5 { + return "" + } + + for i, v := range s { + if i == 0 { + continue + } + + u, err := strconv.ParseUint(v, 10, 8) + if err != nil { + return "" + } + + if u > 255 { + return "" + } + } + + return s[0] +} diff --git a/go/src/themaru/main.go b/go/src/themaru/main.go new file mode 100644 index 0000000..241ff1a --- /dev/null +++ b/go/src/themaru/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + runHttp() +} diff --git a/go/src/themaru/system.go b/go/src/themaru/system.go new file mode 100644 index 0000000..abdc75c --- /dev/null +++ b/go/src/themaru/system.go @@ -0,0 +1,114 @@ +package main + +import ( + "io/ioutil" + "os/exec" +) + +func getVersion() string { + env, err := getEnv(1) + if err != nil { + return "" + } + + version, exist := env["falinux_version"] + if !exist { + return "" + } + + return version +} + +func updateSystem() error { + env, err := getEnv(1) + if err != nil { + return err + } + + bootpart := env["bootpart"] + + var dir string + var partname string + switch bootpart { + case "1": + dir = "/boot1" + partname = "/dev/mmcblk3p2" + env["bootpart"] = "2" + case "2": + dir = "/boot0" + partname = "/dev/mmcblk3p1" + env["bootpart"] = "1" + default: + dir = "/boot0" + partname = "/dev/mmcblk3p1" + env["bootpart"] = "1" + } + + if !partitionIsExists() { + makePartition() + } else { + formatPartition(partname) + } + + if err := exec.Command("mount", "-o", "rw", dir).Run(); err != nil { + return err + } + defer exec.Command("umount", dir).Run() + + if err := exec.Command("unzip", "-o", "/tmp/system.zip", "-d", dir).Run(); err != nil { + return err + } + + exec.Command("sync").Run() + + if ver, err := ioutil.ReadFile(dir + "/version"); err != nil { + env["falinux_version"] = "" + } else { + env["falinux_version"] = string(ver) + } + + return setEnv(1, env) +} + +func updateApp() error { + const cmdline = `\ + rm -rf /app/bin.old /app/bin.new; \ + mkdir /app/bin.new && \ + unzip -o /tmp/application.zip -d /app/bin.new && \ + sync && \ + mv /app/bin /app/bin.old; \ + mv /app/bin.new /app/bin; \ + sync` + + return exec.Command("/bin/sh", "-c", cmdline).Run() +} + +func partitionIsExists() bool { + err := exec.Command("test", "-e", "/dev/mmcblk3p1", "-a", "-e", "/dev/mmcblk3p2").Run() + return err == nil +} + +func makePartition() { + exec.Command("umount", "/app").Run() + exec.Command("/bin/sh", "-c", "/root/.falinux/mk-mmc-part.sh").Run() +} + +func formatPartition(partname string) { + exec.Command("mkfs.fat", partname).Run() +} + +func reboot() { + exec.Command("killall", "-19", "watchdog").Run() + exec.Command("reboot").Run() +} + +func restore() { + m := make(map[string]string) + + setEnv(0, m) + setEnv(1, m) + setEnv(2, m) + setEnv(3, m) + + exec.Command("/bin/sh", "-c", "/root/.falinux/mk-mmc-part.sh").Run() +} diff --git a/go/src/themaru/upx b/go/src/themaru/upx new file mode 100755 index 0000000000000000000000000000000000000000..1c53e841ff1ca756edab1220104ecf98c65682b3 GIT binary patch literal 422160 zcmZ^~Q>-vRvn{x7+qP}nwr$(C@on3-ZQHhOYyN{fx$`icR93HARjGdJPAXmgqB3GY zfPeu1X8{550sI$3&=vb{m%#rK{x1{&-~a#y_#frJVnP2e4d8$M{|EK#!TwwQKL`Lo z@ZbEu^8QEpZwdh5@IUqbU;JM;LmkKe>=6D36i-}C{y%yD8~6VQ|C3N_AcYL=VE`H#qpHBaF i2Nw=T2-
zkX`kuYzbO}N9(ce!U9#5!JgsaXD052=1O_$ee?r6(<9;LmDei5t8*j*Fh&vzF~v8r
zBME>{%cy@oa+(L^4~C*3CPm%enu>4Aoh;dL$zfup{uCsF{88$!r)|o|R>Me=L#~51
zm&sc}7dZ)B_ql2gKnx~!qU`OS3fTZ*z^`VVaIfhLSbIqWQsDUfF4)>44R+g%nexZy
z9`WfgA|YLR-@R{PPI}`-Nx2$YUn{efjxJG5ke{+{A|OOy5gt(Z&GqetFmZ$u!F-x2
z@4RVOoq=~jw08j+)=in_4>zym7}>Ic>sFnIRexC4!Nl-<%6aC?g_iq9O!8K}SYAi6
zP-(8Y5b)gaZsg|loK~fWG%@0_OO{}SU$1TTGQU?jU#vVE7|-4KsfsaWin-cQe9)09
zTc{7-0;H0KIl`*14e}l!jnh5UwAyUWe~Yi`Wxr6Tvkm&VKRvf;{0)f>v&|9u2If;T
zuGne5n9E-Ug4tdW#3oY6Sgy1OF=8*!gN^m~*m*?k~7I
ziMaMTAZGTA%gJ?!@?;
BEss}pokY(|~W-!D1eO9yhVqD*>XFy;>RamG7yEs_R2J2QRTKKWK+ob?zPTjh`R
zE|SbQ!{1B&WL-ojZ_)jrOy%yi2bFec8EY@o03eQULpOi(%6JCT-1LxkJ?x)w#d)6P
z!u&F>B+XBYvUU4OkJm^~tIw=MznU&btUT7@tR)#^-Fm1~oZz@j*+*@7wrm0}NX9j=
zt6ZN7L=~7%oM6XAnGU>uh-gCamJf?O%vRp~XB
zZ9S0%bFlJKSN)521K#gj-wu9e7co!ba)jd}wrux5SirfBWl*2AUz-C7g@C2!V95kL}O!UsCvYers%Yumu%m_
z*0o4WfXy}FWDy1?uo1UT+#_J!g?h609wl?{&BIG_!>C_48QtV@3lF5FGit8eP{c^U
zi*S)bIBy*88tM~$>&Pr@1rLO&3@HSz8oqQU#YYz;u9lI2q
!A2#Z8jaVn88q>geDahNeE3H|w>;sq@%BRQM+Gn!g9Hp9
z503GhESFKOK)i!IHdlsq+QW`wyrRU?cDR!E6hcRl`V^x0<}e>;nkQf9Y+)_U(MXj&
zE)EES?bW^QECyys0wX^|E4eAsd`$7ke}%N}JxPU4SdVTTK$(#lJ9o+y1Xcf+78IGa
zk-OEkyYwN}_UuL5PVV7&oh