2018-10-08 19:20:28 +00:00
package updater
2018-05-01 23:45:06 +00:00
import (
"os"
"runtime"
"time"
"golang.org/x/crypto/ssh/terminal"
"gopkg.in/urfave/cli.v2"
2018-10-08 19:20:28 +00:00
"github.com/cloudflare/cloudflared/log"
2018-05-01 23:45:06 +00:00
"github.com/equinox-io/equinox"
"github.com/facebookgo/grace/gracenet"
)
const (
appID = "app_idCzgxYerVD"
noUpdateInShellMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/argo-tunnel/reference/service/"
noUpdateOnWindowsMessage = "cloudflared will not automatically update on Windows systems."
)
2018-10-08 19:20:28 +00:00
var (
publicKey = [ ] byte ( `
2018-05-01 23:45:06 +00:00
-- -- - BEGIN ECDSA PUBLIC KEY -- -- -
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4OWZocTVZ8Do / L6ScLdkV + 9 A0IYMHoOf
dsCmJ / QZ6aw0w9qkkwEpne1Lmo6 + 0 pGexZzFZOH6w5amShn + RXt7qkSid9iWlzGq
EKx0BZogHSor9Wy5VztdFaAaVbsJiCbO
-- -- - END ECDSA PUBLIC KEY -- -- -
` )
2018-10-08 19:20:28 +00:00
logger = log . CreateLogger ( )
)
2018-05-01 23:45:06 +00:00
2019-02-14 22:26:33 +00:00
type UpdateOutcome struct {
2018-05-01 23:45:06 +00:00
Updated bool
Version string
Error error
}
2019-02-14 22:26:33 +00:00
func ( uo * UpdateOutcome ) noUpdate ( ) bool {
return uo . Error != nil && uo . Updated == false
}
func checkForUpdateAndApply ( ) UpdateOutcome {
2018-05-01 23:45:06 +00:00
var opts equinox . Options
if err := opts . SetPublicKeyPEM ( publicKey ) ; err != nil {
2019-02-14 22:26:33 +00:00
return UpdateOutcome { Error : err }
2018-05-01 23:45:06 +00:00
}
resp , err := equinox . Check ( appID , opts )
switch {
case err == equinox . NotAvailableErr :
2019-02-14 22:26:33 +00:00
return UpdateOutcome { }
2018-05-01 23:45:06 +00:00
case err != nil :
2019-02-14 22:26:33 +00:00
return UpdateOutcome { Error : err }
2018-05-01 23:45:06 +00:00
}
err = resp . Apply ( )
if err != nil {
2019-02-14 22:26:33 +00:00
return UpdateOutcome { Error : err }
2018-05-01 23:45:06 +00:00
}
2019-02-14 22:26:33 +00:00
return UpdateOutcome { Updated : true , Version : resp . ReleaseVersion }
2018-05-01 23:45:06 +00:00
}
2018-10-08 19:20:28 +00:00
func Update ( _ * cli . Context ) error {
2019-02-14 22:26:33 +00:00
updateOutcome := loggedUpdate ( )
if updateOutcome . Error != nil {
os . Exit ( 10 )
}
if updateOutcome . noUpdate ( ) {
logger . Infof ( "cloudflared is up to date (%s)" , updateOutcome . Version )
2018-05-01 23:45:06 +00:00
}
2019-02-14 22:26:33 +00:00
return updateOutcome . Error
2018-05-01 23:45:06 +00:00
}
2018-10-08 19:20:28 +00:00
func Autoupdate ( freq time . Duration , listeners * gracenet . Net , shutdownC chan struct { } ) error {
2018-05-01 23:45:06 +00:00
tickC := time . Tick ( freq )
for {
2019-02-14 22:26:33 +00:00
updateOutcome := loggedUpdate ( )
if updateOutcome . Updated {
2018-05-01 23:45:06 +00:00
os . Args = append ( os . Args , "--is-autoupdated=true" )
pid , err := listeners . StartProcess ( )
if err != nil {
logger . WithError ( err ) . Error ( "Unable to restart server automatically" )
return err
}
// stop old process after autoupdate. Otherwise we create a new process
// after each update
logger . Infof ( "PID of the new process is %d" , pid )
return nil
}
select {
case <- tickC :
case <- shutdownC :
return nil
}
}
}
2019-02-14 22:26:33 +00:00
// Checks for an update and applies it if one is available
func loggedUpdate ( ) UpdateOutcome {
updateOutcome := checkForUpdateAndApply ( )
if updateOutcome . Updated {
logger . Infof ( "cloudflared has been updated to version %s" , updateOutcome . Version )
2018-05-01 23:45:06 +00:00
}
2019-02-14 22:26:33 +00:00
if updateOutcome . Error != nil {
logger . WithError ( updateOutcome . Error ) . Error ( "update check failed" )
2018-05-01 23:45:06 +00:00
}
2019-02-14 22:26:33 +00:00
return updateOutcome
2018-05-01 23:45:06 +00:00
}
2018-10-08 19:20:28 +00:00
func IsAutoupdateEnabled ( c * cli . Context ) bool {
2018-05-01 23:45:06 +00:00
if runtime . GOOS == "windows" {
logger . Info ( noUpdateOnWindowsMessage )
return false
}
if isRunningFromTerminal ( ) {
logger . Info ( noUpdateInShellMessage )
return false
}
return ! c . Bool ( "no-autoupdate" ) && c . Duration ( "autoupdate-freq" ) != 0
}
func isRunningFromTerminal ( ) bool {
return terminal . IsTerminal ( int ( os . Stdout . Fd ( ) ) )
}