16
16
package updater
17
17
18
18
import (
19
- "bytes"
20
- "compress/gzip"
21
19
"crypto/sha256"
22
20
"encoding/json"
23
21
"errors"
24
22
"fmt"
25
23
"io"
26
24
"net/http"
27
- "os"
28
- "path/filepath"
29
25
"runtime"
30
- "strings"
31
26
32
- "github.com/kr/binarydist"
33
27
log "github.com/sirupsen/logrus"
34
- "gopkg.in/inconshreveable/go-update.v0"
35
28
)
36
29
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
+ }
62
42
63
43
const (
64
44
plat = runtime .GOOS + "-" + runtime .GOARCH
65
45
)
66
46
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
77
51
}
52
+ defer r .Close ()
78
53
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" )
114
60
}
61
+ return & res , nil
115
62
}
116
63
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
134
67
}
135
68
136
69
func fetch (url string ) (io.ReadCloser , error ) {
@@ -144,147 +77,3 @@ func fetch(url string) (io.ReadCloser, error) {
144
77
}
145
78
return resp .Body , nil
146
79
}
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