1
1
import axios from "axios"
2
2
import { execFile } from "child_process"
3
3
import { getBuildInfo } from "coder/site/src/api/api"
4
+ import * as crypto from "crypto"
4
5
import { createWriteStream } from "fs"
5
6
import { ensureDir } from "fs-extra"
6
7
import fs from "fs/promises"
@@ -81,31 +82,7 @@ export class Storage {
81
82
82
83
const buildInfo = await getBuildInfo ( )
83
84
const binPath = this . binaryPath ( )
84
- const exists = await fs
85
- . stat ( binPath )
86
- . then ( ( ) => true )
87
- . catch ( ( ) => false )
88
- if ( exists ) {
89
- // Even if the file exists, it could be corrupted.
90
- // We run `coder version` to ensure the binary can be executed.
91
- this . output . appendLine ( `Using cached binary:${ binPath } ` )
92
- const valid = await new Promise < boolean > ( ( resolve ) => {
93
- try {
94
- execFile ( binPath , [ "version" ] , ( err ) => {
95
- if ( err ) {
96
- this . output . appendLine ( "Check for binary corruption: " + err )
97
- }
98
- resolve ( err === null )
99
- } )
100
- } catch ( ex ) {
101
- this . output . appendLine ( "The cached binary cannot be executed: " + ex )
102
- resolve ( false )
103
- }
104
- } )
105
- if ( valid ) {
106
- return binPath
107
- }
108
- }
85
+ const exists = await this . checkBinaryExists ( binPath )
109
86
const os = goos ( )
110
87
const arch = goarch ( )
111
88
let binName = `coder-${ os } -${ arch } `
@@ -114,6 +91,23 @@ export class Storage {
114
91
binName += ".exe"
115
92
}
116
93
const controller = new AbortController ( )
94
+
95
+ if ( exists ) {
96
+ this . output . appendLine ( `Checking if binary outdated...` )
97
+ const outdated = await this . checkBinaryOutdated ( binName , baseURL , controller )
98
+ // If it's outdated, we fall through to the download logic.
99
+ if ( outdated ) {
100
+ this . output . appendLine ( `Found outdated version.` )
101
+ } else {
102
+ // Even if the file exists, it could be corrupted.
103
+ // We run `coder version` to ensure the binary can be executed.
104
+ this . output . appendLine ( `Using existing binary:${ binPath } ` )
105
+ const valid = await this . checkBinaryValid ( binPath )
106
+ if ( valid ) {
107
+ return binPath
108
+ }
109
+ }
110
+ }
117
111
const resp = await axios . get ( "/bin/" + binName , {
118
112
signal :controller . signal ,
119
113
baseURL :baseURL ,
@@ -236,6 +230,10 @@ export class Storage {
236
230
return path . join ( this . globalStorageUri . fsPath , "url" )
237
231
}
238
232
233
+ public getBinaryETag ( ) :string {
234
+ return crypto . createHash ( "sha1" ) . update ( this . binaryPath ( ) ) . digest ( "hex" )
235
+ }
236
+
239
237
private appDataDir ( ) :string {
240
238
switch ( process . platform ) {
241
239
case "darwin" :
@@ -270,6 +268,47 @@ export class Storage {
270
268
return binPath
271
269
}
272
270
271
+ private async checkBinaryExists ( binPath :string ) :Promise < boolean > {
272
+ return await fs
273
+ . stat ( binPath )
274
+ . then ( ( ) => true )
275
+ . catch ( ( ) => false )
276
+ }
277
+
278
+ private async checkBinaryValid ( binPath :string ) :Promise < boolean > {
279
+ return await new Promise < boolean > ( ( resolve ) => {
280
+ try {
281
+ execFile ( binPath , [ "version" ] , ( err ) => {
282
+ if ( err ) {
283
+ this . output . appendLine ( "Check for binary corruption: " + err )
284
+ }
285
+ resolve ( err === null )
286
+ } )
287
+ } catch ( ex ) {
288
+ this . output . appendLine ( "The cached binary cannot be executed: " + ex )
289
+ resolve ( false )
290
+ }
291
+ } )
292
+ }
293
+
294
+ private async checkBinaryOutdated ( binName :string , baseURL :string , controller :AbortController ) :Promise < boolean > {
295
+ const resp = await axios . get ( "/bin/" + binName , {
296
+ signal :controller . signal ,
297
+ baseURL :baseURL ,
298
+ headers :{
299
+ "If-None-Match" :this . getBinaryETag ( ) ,
300
+ } ,
301
+ } )
302
+
303
+ switch ( resp . status ) {
304
+ case 200 :
305
+ return true
306
+ case 304 :
307
+ default :
308
+ return false
309
+ }
310
+ }
311
+
273
312
private async updateSessionToken ( ) {
274
313
const token = await this . getSessionToken ( )
275
314
if ( token ) {