Skip to content
This repository was archived by the owner on Mar 27, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
11960bc
Added status handler
cmaglie Dec 7, 2017
d324c03
Removed one level of indirection in mqtt callbacks
cmaglie Dec 13, 2017
9d70b7b
Added status.InfoCommandOutput to simplify sending commands output
cmaglie Dec 13, 2017
3585708
Added APT package manager handlers
cmaglie Dec 13, 2017
72c1a36
Added APT repository handlers
cmaglie Dec 13, 2017
eb29080
Add network control feature
facchinm Dec 13, 2017
5a28fa8
Allow the connector to be compiled with development info
matteosuppo Dec 22, 2017
7110ecb
Merge pull request #25 from arduino/dev
Dec 28, 2017
e6df8a1
Handle errors
matteosuppo Dec 28, 2017
30babe6
Don't replicate authurl and apiurl
matteosuppo Dec 28, 2017
b3a12e1
Added search parameter in apt package listing
cmaglie Dec 28, 2017
9ad37fe
Small improvement in apt-package listing
cmaglie Dec 28, 2017
941132e
Added pagination on package listing
cmaglie Dec 28, 2017
3c4bff6
Ensure all the endpoint have the same behaviour
matteosuppo Dec 22, 2017
717b95b
Docs
matteosuppo Dec 22, 2017
44c30ba
apt.list: return upgradable packages when search is not set
matteosuppo Jan 2, 2018
7b2c76d
Fixed typo in mqtt queue identifier
cmaglie Jan 4, 2018
86c107b
Small fix in readme
cmaglie Jan 4, 2018
47848f9
Fixed comment
cmaglie Jan 4, 2018
62227b4
Fixed linter warning on const name
cmaglie Jan 4, 2018
86ce704
add updater
AlbyIanna Jan 8, 2018
683f7f2
update readme
AlbyIanna Jan 8, 2018
536d977
Merge remote-tracking branch 'origin/status' into integration
AlbyIanna Jan 9, 2018
5a57826
sync with status refactor
AlbyIanna Jan 9, 2018
6b066a9
change download update feed repository
AlbyIanna Jan 9, 2018
7b3fedd
print version number
AlbyIanna Jan 9, 2018
c027a5a
version as var to allow rewrite via ldflag
AlbyIanna Jan 9, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add updater
  • Loading branch information
AlbyIanna committed Jan 8, 2018
commit 86ce7041059fee13bd68e07cb9acb603717b51cd
8 changes: 8 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (

const (
configFile = "./arduino-connector.cfg"
version = "1.0.0"
)

// Config holds the configuration needed by the application
Expand All @@ -50,6 +51,8 @@ type Config struct {
HTTPProxy string
HTTPSProxy string
ALLProxy string
updateUrl string
appName string
}

func (c Config) String() string {
Expand All @@ -69,6 +72,9 @@ func main() {
var doRegister = flag.Bool("register", false, "Registers on the cloud")
var listenFile = flag.String("listen", "", "Tail given file and report percentage")
var token = flag.String("token", "", "an authentication token")
flag.StringVar(&config.updateUrl, "updateUrl", "http://localhost/", "")
flag.StringVar(&config.appName, "appName", "arduino-connector", "")

flag.String(flag.DefaultConfigFlagname, "", "path to config file")
flag.StringVar(&config.ID, "id", "", "id of the thing in aws iot")
flag.StringVar(&config.URL, "url", "", "url of the thing in aws iot")
Expand Down Expand Up @@ -124,6 +130,8 @@ func (p program) run() {
// Note, all_proxy will not be used by any HTTP/HTTPS connections.
p.exportProxyEnvVars()

updateHandler(p.Config)

// Start nats-server on localhost:4222
opts := server.Options{}
opts.Port = 4222
Expand Down
74 changes: 74 additions & 0 deletions updater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

import (
"fmt"
"log"
"os/exec"
"strings"

"github.com/arduino/arduino-connector/updater"
"github.com/kardianos/osext"
)

func updateHandler(config Config) {

path, err := osext.Executable()

if err != nil {
//c.JSON(500, gin.H{"error": err.Error()})
return
}

var up = &updater.Updater{
CurrentVersion: version,
APIURL: config.updateUrl,
BinURL: config.updateUrl,
DiffURL: "",
Dir: "update/",
CmdName: config.appName,
}

err = up.BackgroundRun()

if err != nil {
return
}

//c.JSON(200, gin.H{"success": "Please wait a moment while the agent reboots itself"})
go restart(path)
}

func restart(path string) {
log.Println("called restart", path)
// relaunch ourself and exit
// the relaunch works because we pass a cmdline in
// that has serial-port-json-server only initialize 5 seconds later
// which gives us time to exit and unbind from serial ports and TCP/IP
// sockets like :8989
log.Println("Starting new spjs process")

// figure out current path of executable so we know how to restart
// this process using osext
exePath, err3 := osext.Executable()
if err3 != nil {
log.Printf("Error getting exe path using osext lib. err: %v\n", err3)
}

if path == "" {
log.Printf("exePath using osext: %v\n", exePath)
} else {
exePath = path
}

exePath = strings.Trim(exePath, "\n")

cmd := exec.Command(exePath)

fmt.Println(cmd)

err := cmd.Start()
if err != nil {
log.Printf("Got err restarting spjs: %v\n", err)
}
log.Fatal("Exited current spjs for restart")
}
259 changes: 259 additions & 0 deletions updater/updater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package updater

import (
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"time"

log "github.com/Sirupsen/logrus"
"github.com/blang/semver"
"github.com/kr/binarydist"
update "gopkg.in/inconshreveable/go-update.v0"

"github.com/kardianos/osext"
)

// Update protocol:
//
// GET hk.heroku.com/hk/linux-amd64.json
//
// 200 ok
// {
// "Version": "2",
// "Sha256": "..." // base64
// }
//
// then
//
// GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
//
// 200 ok
// [bsdiff data]
//
// or
//
// GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
//
// 200 ok
// [gzipped executable data]
//
//

const (
plat = runtime.GOOS + "-" + runtime.GOARCH
)

const devValidTime = 7 * 24 * time.Hour

var errHashMismatch = errors.New("new file hash mismatch after patch")
var up = update.New()

// Updater is the configuration and runtime data for doing an update.
//
// Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location.
//
// Example:
//
// updater := &selfupdate.Updater{
// CurrentVersion: version,
// ApiURL: "http://updates.yourdomain.com/",
// BinURL: "http://updates.yourdownmain.com/",
// DiffURL: "http://updates.yourdomain.com/",
// Dir: "update/",
// CmdName: "myapp", // app name
// }
// if updater != nil {
// go updater.BackgroundRun()
// }
type Updater struct {
CurrentVersion string // Currently running version.
APIURL string // Base URL for API requests (json files).
CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary.
BinURL string // Base URL for full binary downloads.
DiffURL string // Base URL for diff downloads.
Dir string // Directory to store selfupdate state.
Info struct {
Version string
Sha256 []byte
}
}

// BackgroundRun starts the update check and apply cycle.
func (u *Updater) BackgroundRun() error {
os.MkdirAll(u.getExecRelativeDir(u.Dir), 0777)
if err := up.CanUpdate(); err != nil {
log.Println(err)
return err
}
//self, err := osext.Executable()
//if err != nil {
// fail update, couldn't figure out path to self
//return
//}
// TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
if err := u.update(); err != nil {
return err
}
return nil
}

func fetch(url string) (io.ReadCloser, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
log.Errorf("bad http status from %s: %v", url, resp.Status)
return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
}
return resp.Body, nil
}

func verifySha(bin []byte, sha []byte) bool {
h := sha256.New()
h.Write(bin)
return bytes.Equal(h.Sum(nil), sha)
}

func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
r, err := fetch(u.DiffURL + u.CmdName + "/" + u.CurrentVersion + "/" + u.Info.Version + "/" + plat)
if err != nil {
return nil, err
}
defer r.Close()
var buf bytes.Buffer
err = binarydist.Patch(old, &buf, r)
return buf.Bytes(), err
}

func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
bin, err := u.fetchAndApplyPatch(old)
if err != nil {
return nil, err
}
if !verifySha(bin, u.Info.Sha256) {
return nil, errHashMismatch
}
return bin, nil
}

func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
bin, err := u.fetchBin()
if err != nil {
return nil, err
}
verified := verifySha(bin, u.Info.Sha256)
if !verified {
return nil, errHashMismatch
}
return bin, nil
}

func (u *Updater) fetchBin() ([]byte, error) {
r, err := fetch(u.BinURL + u.CmdName + "/" + u.Info.Version + "/" + plat + ".gz")
if err != nil {
return nil, err
}
defer r.Close()
buf := new(bytes.Buffer)
gz, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
if _, err = io.Copy(buf, gz); err != nil {
return nil, err
}

return buf.Bytes(), nil
}

func (u *Updater) fetchInfo() error {
r, err := fetch(u.APIURL + u.CmdName + "/" + plat + ".json")
if err != nil {
return err
}
defer r.Close()
err = json.NewDecoder(r).Decode(&u.Info)
if err != nil {
return err
}
if len(u.Info.Sha256) != sha256.Size {
return errors.New("bad cmd hash in info")
}
return nil
}

func (u *Updater) getExecRelativeDir(dir string) string {
filename, _ := osext.Executable()
path := filepath.Join(filepath.Dir(filename), dir)
return path
}

func (u *Updater) update() error {
path, err := osext.Executable()
if err != nil {
return err
}
old, err := os.Open(path)
if err != nil {
return err
}
defer old.Close()

err = u.fetchInfo()
if err != nil {
log.Println(err)
return err
}

v1, _ := semver.Make(u.Info.Version)
v2, _ := semver.Make(u.CurrentVersion)

if v1.Compare(v2) <= 0 {
return errors.New("already at latest version")
}
bin, err := u.fetchAndVerifyPatch(old)
if err != nil {
if err == errHashMismatch {
log.Println("update: hash mismatch from patched binary")
} else {
if u.DiffURL != "" {
log.Println("update: patching binary,", err)
}
}

bin, err = u.fetchAndVerifyFullBin()
if err != nil {
if err == errHashMismatch {
log.Println("update: hash mismatch from full binary")
} else {
log.Println("update: fetching full binary,", err)
}
return err
}
}

// close the old binary before installing because on windows
// it can't be renamed if a handle to the file is still open
old.Close()

err, errRecover := up.FromStream(bytes.NewBuffer(bin))
if errRecover != nil {
log.Errorf("update and recovery errors: %q %q", err, errRecover)
return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
}
if err != nil {
return err
}

return nil
}