1616package updater
1717
1818import (
19- "bytes"
20- "compress/gzip"
2119 "crypto/sha256"
2220 "encoding/json"
2321 "errors"
2422 "fmt"
2523 "io"
2624 "net/http"
27- "os"
28- "path/filepath"
2925 "runtime"
30- "strings"
3126
32- "github.com/kr/binarydist"
3327 log "github.com/sirupsen/logrus"
34- "gopkg.in/inconshreveable/go-update.v0"
3528)
3629
37- // Update protocol:
38- //
39- // GET hk.heroku.com/hk/linux-amd64.json
40- //
41- // 200 ok
42- // {
43- // "Version": "2",
44- // "Sha256": "..." // base64
45- // }
46- //
47- // then
48- //
49- // GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
50- //
51- // 200 ok
52- // [bsdiff data]
53- //
54- // or
55- //
56- // GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
57- //
58- // 200 ok
59- // [gzipped executable data]
60- //
61- //
30+ // Start checks if an update has been downloaded and if so returns the path to the
31+ // binary to be executed to perform the update. If no update has been downloaded
32+ // it returns an empty string.
33+ func Start (src string ) string {
34+ return start (src )
35+ }
36+
37+ // CheckForUpdates checks if there is a new version of the binary available and
38+ // if so downloads it.
39+ func CheckForUpdates (currentVersion string , updateAPIURL , updateBinURL string , cmdName string ) (string , error ) {
40+ return checkForUpdates (currentVersion , updateAPIURL , updateBinURL , cmdName )
41+ }
6242
6343const (
6444 plat = runtime .GOOS + "-" + runtime .GOARCH
6545)
6646
67- var errHashMismatch = errors .New ("new file hash mismatch after patch" )
68- var errDiffURLUndefined = errors .New ("DiffURL is not defined, I cannot fetch and apply patch, reverting to full bin" )
69- var up = update .New ()
70-
71- // AddTempSuffixToPath adds the "-temp" suffix to the path to an executable file (a ".exe" extension is replaced with "-temp.exe")
72- func AddTempSuffixToPath (path string ) string {
73- if filepath .Ext (path ) == "exe" {
74- path = strings .Replace (path , ".exe" , "-temp.exe" , - 1 )
75- } else {
76- path = path + "-temp"
47+ func fetchInfo (updateAPIURL string , cmdName string ) (* availableUpdateInfo , error ) {
48+ r , err := fetch (updateAPIURL + cmdName + "/" + plat + ".json" )
49+ if err != nil {
50+ return nil , err
7751 }
52+ defer r .Close ()
7853
79- return path
80- }
81-
82- // RemoveTempSuffixFromPath removes "-temp" suffix from the path to an executable file (a "-temp.exe" extension is replaced with ".exe")
83- func RemoveTempSuffixFromPath (path string ) string {
84- return strings .Replace (path , "-temp" , "" , - 1 )
85- }
86-
87- // Updater is the configuration and runtime data for doing an update.
88- //
89- // Note that ApiURL, BinURL and DiffURL should have the same value if all files are available at the same location.
90- //
91- // Example:
92- //
93- // updater := &selfupdate.Updater{
94- // CurrentVersion: version,
95- // ApiURL: "http://updates.yourdomain.com/",
96- // BinURL: "http://updates.yourdownmain.com/",
97- // DiffURL: "http://updates.yourdomain.com/",
98- // Dir: "update/",
99- // CmdName: "myapp", // app name
100- // }
101- // if updater != nil {
102- // go updater.BackgroundRun()
103- // }
104- type Updater struct {
105- CurrentVersion string // Currently running version.
106- APIURL string // Base URL for API requests (json files).
107- CmdName string // Command name is appended to the ApiURL like http://apiurl/CmdName/. This represents one binary.
108- BinURL string // Base URL for full binary downloads.
109- DiffURL string // Base URL for diff downloads.
110- Dir string // Directory to store selfupdate state.
111- Info struct {
112- Version string
113- Sha256 []byte
54+ var res availableUpdateInfo
55+ if err := json .NewDecoder (r ).Decode (& res ); err != nil {
56+ return nil , err
57+ }
58+ if len (res .Sha256 ) != sha256 .Size {
59+ return nil , errors .New ("bad cmd hash in info" )
11460 }
61+ return & res , nil
11562}
11663
117- // BackgroundRun starts the update check and apply cycle.
118- func (u * Updater ) BackgroundRun () error {
119- os .MkdirAll (u .getExecRelativeDir (u .Dir ), 0777 )
120- if err := up .CanUpdate (); err != nil {
121- log .Println (err )
122- return err
123- }
124- //self, err := os.Executable()
125- //if err != nil {
126- // fail update, couldn't figure out path to self
127- //return
128- //}
129- // TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
130- if err := u .update (); err != nil {
131- return err
132- }
133- return nil
64+ type availableUpdateInfo struct {
65+ Version string
66+ Sha256 []byte
13467}
13568
13669func fetch (url string ) (io.ReadCloser , error ) {
@@ -144,147 +77,3 @@ func fetch(url string) (io.ReadCloser, error) {
14477 }
14578 return resp .Body , nil
14679}
147-
148- func verifySha (bin []byte , sha []byte ) bool {
149- h := sha256 .New ()
150- h .Write (bin )
151- return bytes .Equal (h .Sum (nil ), sha )
152- }
153-
154- func (u * Updater ) fetchAndApplyPatch (old io.Reader ) ([]byte , error ) {
155- if u .DiffURL == "" {
156- return nil , errDiffURLUndefined
157- }
158- r , err := fetch (u .DiffURL + u .CmdName + "/" + u .CurrentVersion + "/" + u .Info .Version + "/" + plat )
159- if err != nil {
160- return nil , err
161- }
162- defer r .Close ()
163- var buf bytes.Buffer
164- err = binarydist .Patch (old , & buf , r )
165- return buf .Bytes (), err
166- }
167-
168- func (u * Updater ) fetchAndVerifyPatch (old io.Reader ) ([]byte , error ) {
169- bin , err := u .fetchAndApplyPatch (old )
170- if err != nil {
171- return nil , err
172- }
173- if ! verifySha (bin , u .Info .Sha256 ) {
174- return nil , errHashMismatch
175- }
176- return bin , nil
177- }
178-
179- func (u * Updater ) fetchAndVerifyFullBin () ([]byte , error ) {
180- bin , err := u .fetchBin ()
181- if err != nil {
182- return nil , err
183- }
184- verified := verifySha (bin , u .Info .Sha256 )
185- if ! verified {
186- return nil , errHashMismatch
187- }
188- return bin , nil
189- }
190-
191- func (u * Updater ) fetchBin () ([]byte , error ) {
192- r , err := fetch (u .BinURL + u .CmdName + "/" + u .Info .Version + "/" + plat + ".gz" )
193- if err != nil {
194- return nil , err
195- }
196- defer r .Close ()
197- buf := new (bytes.Buffer )
198- gz , err := gzip .NewReader (r )
199- if err != nil {
200- return nil , err
201- }
202- if _ , err = io .Copy (buf , gz ); err != nil {
203- return nil , err
204- }
205-
206- return buf .Bytes (), nil
207- }
208-
209- func (u * Updater ) fetchInfo () error {
210- r , err := fetch (u .APIURL + u .CmdName + "/" + plat + ".json" )
211- if err != nil {
212- return err
213- }
214- defer r .Close ()
215- err = json .NewDecoder (r ).Decode (& u .Info )
216- if err != nil {
217- return err
218- }
219- if len (u .Info .Sha256 ) != sha256 .Size {
220- return errors .New ("bad cmd hash in info" )
221- }
222- return nil
223- }
224-
225- func (u * Updater ) getExecRelativeDir (dir string ) string {
226- filename , _ := os .Executable ()
227- path := filepath .Join (filepath .Dir (filename ), dir )
228- return path
229- }
230-
231- func (u * Updater ) update () error {
232- path , err := os .Executable ()
233- if err != nil {
234- return err
235- }
236-
237- path = AddTempSuffixToPath (path )
238-
239- old , err := os .Open (path )
240- if err != nil {
241- return err
242- }
243- defer old .Close ()
244-
245- err = u .fetchInfo ()
246- if err != nil {
247- log .Println (err )
248- return err
249- }
250- if u .Info .Version == u .CurrentVersion {
251- return nil
252- }
253- bin , err := u .fetchAndVerifyPatch (old )
254- if err != nil {
255- switch err {
256- case errHashMismatch :
257- log .Println ("update: hash mismatch from patched binary" )
258- case errDiffURLUndefined :
259- log .Println ("update: " , err )
260- default :
261- log .Println ("update: patching binary, " , err )
262- }
263-
264- bin , err = u .fetchAndVerifyFullBin ()
265- if err != nil {
266- if err == errHashMismatch {
267- log .Println ("update: hash mismatch from full binary" )
268- } else {
269- log .Println ("update: fetching full binary," , err )
270- }
271- return err
272- }
273- }
274-
275- // close the old binary before installing because on windows
276- // it can't be renamed if a handle to the file is still open
277- old .Close ()
278-
279- up .TargetPath = path
280- err , errRecover := up .FromStream (bytes .NewBuffer (bin ))
281- if errRecover != nil {
282- log .Errorf ("update and recovery errors: %q %q" , err , errRecover )
283- return fmt .Errorf ("update and recovery errors: %q %q" , err , errRecover )
284- }
285- if err != nil {
286- return err
287- }
288-
289- return nil
290- }
0 commit comments