Skip to content

Commit

Permalink
Add update logic
Browse files Browse the repository at this point in the history
  • Loading branch information
pappz committed Oct 19, 2023
1 parent 9ff428a commit 27d0d59
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 2 deletions.
41 changes: 39 additions & 2 deletions client/ui/client_ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ type serviceClient struct {
mDown *systray.MenuItem
mAdminPanel *systray.MenuItem
mSettings *systray.MenuItem
mVersion *systray.MenuItem
mUpdate *systray.MenuItem
mQuit *systray.MenuItem

// application with main windows.
Expand All @@ -119,6 +121,8 @@ type serviceClient struct {
managementURL string
preSharedKey string
adminURL string

update *version.Update
}

// newServiceClient instance constructor
Expand All @@ -131,6 +135,7 @@ func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient
app: a,

showSettings: showSettings,
update: version.NewUpdate(),
}

if runtime.GOOS == "windows" {
Expand Down Expand Up @@ -342,6 +347,8 @@ func (s *serviceClient) updateStatus() error {
s.mDown.Disable()
s.mUp.Enable()
}

s.update.SetDaemonVersion(status.DaemonVersion)
return nil
}, &backoff.ExponentialBackOff{
InitialInterval: time.Second,
Expand Down Expand Up @@ -377,11 +384,16 @@ func (s *serviceClient) onTrayReady() {
systray.AddSeparator()

versionString := normalizedVersion()
v := systray.AddMenuItem(versionString, "Client Version: "+versionString)
v.Disable()
v := systray.AddMenuItem("About", "About")
s.mVersion = v.AddSubMenuItem(versionString, "Client Version: "+versionString)
s.mVersion.Disable()
s.mUpdate = v.AddSubMenuItem("Download latest version", "Download latest version")
s.mUpdate.Hide()

systray.AddSeparator()
s.mQuit = systray.AddMenuItem("Quit", "Quit the client app")

s.update.SetOnUpdateListener(s.onUpdateAvailable)
go func() {
s.getSrvConfig()
for {
Expand Down Expand Up @@ -439,6 +451,11 @@ func (s *serviceClient) onTrayReady() {
case <-s.mQuit.ClickedCh:
systray.Quit()
return
case <-s.mUpdate.ClickedCh:
err := openURL(version.DownloadUrl())
if err != nil {
log.Errorf("%s", err)
}
}
if err != nil {
log.Errorf("process connection: %v", err)
Expand Down Expand Up @@ -515,6 +532,26 @@ func (s *serviceClient) getSrvConfig() {
}
}

// todo hide after success daemon update
func (s *serviceClient) onUpdateAvailable(version string) {
s.mUpdate.Show()
}

func openURL(url string) error {
var err error
switch runtime.GOOS {
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
case "linux":
err = exec.Command("xdg-open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
return err
}

// checkPIDFile exists and return error, or write new.
func checkPIDFile() error {
pidFile := path.Join(os.TempDir(), "wiretrustee-ui.pid")
Expand Down
163 changes: 163 additions & 0 deletions version/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package version

import (
"io"
"net/http"
"sync"
"time"

goversion "github.com/hashicorp/go-version"
log "github.com/sirupsen/logrus"
)

const (
versionURL = "https://pkgs.netbird.io/releases/latest/version"
)

type Update struct {
uiVersion *goversion.Version
daemonVersion *goversion.Version
lastAvailable *goversion.Version
versionsLock sync.Mutex

onUpdateListener func(version string)
listenerLock sync.Mutex
}

func NewUpdate() *Update {
currentVersion, err := goversion.NewVersion(version)
if err != nil {
currentVersion, _ = goversion.NewVersion("0.0.0")
}

lastAvailable, _ := goversion.NewVersion("0.0.0")

u := &Update{
lastAvailable: lastAvailable,
uiVersion: currentVersion,
}

go u.startFetcher()
return u
}

func (u *Update) SetDaemonVersion(newVersion string) {
daemonVersion, err := goversion.NewVersion(newVersion)
if err != nil {
daemonVersion, _ = goversion.NewVersion("0.0.0")
}

u.versionsLock.Lock()
if u.daemonVersion != nil && u.daemonVersion.Equal(daemonVersion) {
u.versionsLock.Unlock()
return
}

u.daemonVersion = daemonVersion
u.versionsLock.Unlock()
u.checkUpdate()
return

Check failure on line 59 in version/update.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

S1023: redundant `return` statement (gosimple)

Check failure on line 59 in version/update.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

S1023: redundant `return` statement (gosimple)
}

func (u *Update) SetOnUpdateListener(updateFn func(version string)) {
u.listenerLock.Lock()
defer u.listenerLock.Unlock()

u.onUpdateListener = updateFn
if u.isUpdateAvailable() {
u.onUpdateListener(version)
}
}

func (u *Update) startFetcher() {
changed := u.fetchVersion()
if changed {
u.checkUpdate()
}

uptimeTicker := time.NewTicker(30 * time.Minute)
for {

Check failure on line 79 in version/update.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

S1000: should use for range instead of for { select {} } (gosimple)

Check failure on line 79 in version/update.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

S1000: should use for range instead of for { select {} } (gosimple)
select {
case <-uptimeTicker.C:
changed := u.fetchVersion()
if changed {
u.checkUpdate()
}

}
}

}

func (u *Update) fetchVersion() bool {
resp, err := http.Get(versionURL)
if err != nil {
log.Error("failed to fetch version info: %s", err)

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / test (jsonfile)

github.com/sirupsen/logrus.Error call has possible formatting directive %s

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / lint (macos-latest)

printf: github.com/sirupsen/logrus.Error call has possible Printf formatting directive %s (govet)

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / test (sqlite)

github.com/sirupsen/logrus.Error call has possible formatting directive %s

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / test (386, jsonfile)

github.com/sirupsen/logrus.Error call has possible formatting directive %s

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

printf: github.com/sirupsen/logrus.Error call has possible Printf formatting directive %s (govet)

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / test (386, sqlite)

github.com/sirupsen/logrus.Error call has possible formatting directive %s

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / test (amd64, jsonfile)

github.com/sirupsen/logrus.Error call has possible formatting directive %s

Check failure on line 95 in version/update.go

View workflow job for this annotation

GitHub Actions / test (amd64, sqlite)

github.com/sirupsen/logrus.Error call has possible formatting directive %s
return false
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
log.Errorf("invalid status code: %d", resp.StatusCode)
return false
}

if resp.ContentLength > 100 {
log.Errorf("too large response: %d", resp.ContentLength)
return false
}

content, err := io.ReadAll(resp.Body)
if err != nil {
log.Errorf("failed to read content: %s", err)
return false
}

lastAvailable, err := goversion.NewVersion(string(content))
if err != nil {
log.Errorf("faield to parse the version string: %s", err)
return false
}

u.versionsLock.Lock()
defer u.versionsLock.Unlock()

if u.lastAvailable.Equal(lastAvailable) {
return false
}
u.lastAvailable = lastAvailable

return true
}

func (u *Update) checkUpdate() {
if !u.isUpdateAvailable() {
return
}

u.listenerLock.Lock()
defer u.listenerLock.Unlock()
if u.onUpdateListener == nil {
return
}

u.onUpdateListener(u.lastAvailable.String())
}

func (u *Update) isUpdateAvailable() bool {
u.versionsLock.Lock()
defer u.versionsLock.Unlock()

if u.lastAvailable.GreaterThan(u.uiVersion) {
return true
}

if u.daemonVersion == nil {
return false
}

if u.lastAvailable.GreaterThan(u.daemonVersion) {
return true
}
return false
}
46 changes: 46 additions & 0 deletions version/url.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package version

import (
"os/exec"
"runtime"
)

const (
downloadURL = "https://app.netbird.io/install"
macIntelURL = "https://pkgs.netbird.io/macos/amd64"
macM1M2URL = "https://pkgs.netbird.io/macos/arm64"
)

func DownloadUrl() string {
switch runtime.GOOS {
case "windows":
return downloadURL
case "darwin":
return darwinDownloadUrl()
case "linux":
return downloadURL
default:
return downloadURL
}
}

func darwinDownloadUrl() string {
cmd := exec.Command("brew", "list --formula | grep -i netbird")
if err := cmd.Start(); err != nil {
goto PKGINSTALL
}

if err := cmd.Wait(); err == nil {
return downloadURL
}

PKGINSTALL:
switch runtime.GOARCH {
case "amd64":
return macIntelURL
case "arm64":
return macM1M2URL
default:
return downloadURL
}
}

0 comments on commit 27d0d59

Please sign in to comment.