Skip to content

Commit 52f61f9

Browse files
committed
Move installer to ui code
1 parent 6d1ab80 commit 52f61f9

File tree

4 files changed

+208
-7
lines changed

4 files changed

+208
-7
lines changed

client/internal/updatemanager/installer/installer.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"io"
66
"os"
77
"path/filepath"
8+
9+
log "github.com/sirupsen/logrus"
810
)
911

1012
const (
@@ -22,15 +24,11 @@ func (u *Installer) copyUpdater() (string, error) {
2224

2325
dstPath := filepath.Join(u.tempDir, updaterBinary)
2426

25-
execPath, err := os.Executable()
26-
if err != nil {
27-
return "", fmt.Errorf("failed to get executable path: %w", err)
28-
}
29-
30-
updaterSrcPath := filepath.Join(filepath.Dir(execPath), uiName)
31-
27+
//updaterSrcPath := "/Applications/NetBird.app/Contents/MacOS/netbird-ui"
28+
updaterSrcPath := "/Users/pzoli/go/src/github.com/netbirdio/netbird/client/ui/ui"
3229
srcFile, err := os.Open(updaterSrcPath)
3330
if err != nil {
31+
log.Debugf("Failed to open updater binary: %v", err)
3432
return "", fmt.Errorf("failed to open source file: %w", err)
3533
}
3634
defer srcFile.Close()

client/ui/client_ui.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
"github.com/netbirdio/netbird/client/ui/desktop"
4444
"github.com/netbirdio/netbird/client/ui/event"
4545
"github.com/netbirdio/netbird/client/ui/process"
46+
"github.com/netbirdio/netbird/client/ui/update"
4647

4748
"github.com/netbirdio/netbird/util"
4849

@@ -59,6 +60,10 @@ const (
5960
)
6061

6162
func main() {
63+
if update.IsUpdateBinary() {
64+
update.Execute()
65+
return
66+
}
6267
flags := parseFlags()
6368

6469
// Initialize file logging if needed.

client/ui/update/main.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package update
2+
3+
import (
4+
"context"
5+
"flag"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"time"
10+
11+
log "github.com/sirupsen/logrus"
12+
13+
"github.com/netbirdio/netbird/client/internal/updatemanager/installer"
14+
"github.com/netbirdio/netbird/util"
15+
)
16+
17+
var (
18+
tempDirFlag string
19+
installerFile string
20+
serviceDirFlag string
21+
dryRunFlag bool
22+
)
23+
24+
func init() {
25+
flag.StringVar(&tempDirFlag, "temp-dir", "", "temporary dir")
26+
flag.StringVar(&installerFile, "installer-file", "", "installer file")
27+
flag.StringVar(&serviceDirFlag, "service-dir", "", "service directory")
28+
flag.BoolVar(&dryRunFlag, "dry-run", false, "dry run the update process without making any changes")
29+
}
30+
31+
// IsUpdateBinary checks if the current executable is named "update" or "update.exe"
32+
func IsUpdateBinary() bool {
33+
// Remove extension for cross-platform compatibility
34+
execPath, err := os.Executable()
35+
if err != nil {
36+
return false
37+
}
38+
baseName := filepath.Base(execPath)
39+
name := strings.TrimSuffix(baseName, filepath.Ext(baseName))
40+
41+
return name == installer.UpdaterBinaryNameWithoutExtension()
42+
}
43+
44+
func Execute() {
45+
ui := NewUI()
46+
47+
// Create a context with timeout for the entire update process
48+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
49+
defer cancel()
50+
51+
ui.ShowUpdateProgress(ctx)
52+
// Run the update function in a goroutine
53+
go func() {
54+
defer cancel()
55+
56+
if err := updateFunc(); err != nil {
57+
log.Errorf("update failed: %v", err)
58+
// The UI will handle the error display through context cancellation
59+
return
60+
}
61+
62+
// Success - the UI will automatically close the window
63+
log.Infof("update completed successfully")
64+
}()
65+
66+
// Start the Fyne app event loop (blocks until window is closed or context is done)
67+
ui.Run()
68+
}
69+
70+
func updateFunc() error {
71+
if err := setupLogToFile(tempDirFlag); err != nil {
72+
return err
73+
}
74+
75+
log.Infof("updater started: %s", serviceDirFlag)
76+
updater := installer.NewWithDir(tempDirFlag)
77+
if err := updater.Setup(context.Background(), dryRunFlag, installerFile, serviceDirFlag); err != nil {
78+
log.Errorf("failed to update application: %v", err)
79+
return err
80+
}
81+
return nil
82+
}
83+
84+
func setupLogToFile(dir string) error {
85+
logFile := filepath.Join(dir, installer.LogFile)
86+
87+
if _, err := os.Stat(logFile); err == nil {
88+
if err := os.Remove(logFile); err != nil {
89+
log.Errorf("failed to remove existing log file: %v\n", err)
90+
}
91+
}
92+
93+
return util.InitLog("debug", util.LogConsole, logFile)
94+
}

client/ui/update/progresswindow.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package update
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
"time"
9+
10+
"fyne.io/fyne/v2"
11+
"fyne.io/fyne/v2/app"
12+
"fyne.io/fyne/v2/container"
13+
"fyne.io/fyne/v2/widget"
14+
log "github.com/sirupsen/logrus"
15+
)
16+
17+
type UI struct {
18+
app fyne.App
19+
wUpdateProgress fyne.Window
20+
}
21+
22+
func NewUI() *UI {
23+
// Create the Fyne application.
24+
a := app.NewWithID("NetBird-update")
25+
26+
return &UI{
27+
app: a,
28+
}
29+
}
30+
31+
func (u *UI) Run() {
32+
u.app.Run()
33+
}
34+
35+
func (u *UI) ShowUpdateProgress(ctx context.Context) {
36+
log.Infof("show installer progress window")
37+
u.wUpdateProgress = u.app.NewWindow("Automatically updating client")
38+
39+
statusLabel := widget.NewLabel("Updating...")
40+
infoLabel := widget.NewLabel("Your client version is older than the auto-update version set in Management.\nUpdating client now.")
41+
content := container.NewVBox(infoLabel, statusLabel)
42+
u.wUpdateProgress.SetContent(content)
43+
u.wUpdateProgress.CenterOnScreen()
44+
u.wUpdateProgress.SetFixedSize(true)
45+
u.wUpdateProgress.SetCloseIntercept(func() {}) // lock window until result known
46+
u.wUpdateProgress.RequestFocus()
47+
u.wUpdateProgress.Show()
48+
49+
// Initialize dot updater
50+
updateText := dotUpdater()
51+
52+
// Channel to receive the result from RPC call
53+
resultErrCh := make(chan error, 1)
54+
resultOkCh := make(chan struct{}, 1)
55+
56+
// Update UI with dots and wait for result
57+
go func() {
58+
ticker := time.NewTicker(time.Second)
59+
defer ticker.Stop()
60+
61+
for {
62+
select {
63+
case <-ctx.Done():
64+
u.showInstallerResult(statusLabel, ctx.Err())
65+
return
66+
case err := <-resultErrCh:
67+
u.showInstallerResult(statusLabel, err)
68+
return
69+
case <-resultOkCh:
70+
u.wUpdateProgress.SetCloseIntercept(nil)
71+
u.wUpdateProgress.Close()
72+
return
73+
case <-ticker.C:
74+
statusLabel.SetText(updateText())
75+
}
76+
}
77+
}()
78+
}
79+
80+
func (u *UI) showInstallerResult(statusLabel *widget.Label, err error) {
81+
u.wUpdateProgress.SetCloseIntercept(nil)
82+
switch {
83+
case errors.Is(err, context.DeadlineExceeded):
84+
log.Warn("update watcher timed out")
85+
statusLabel.SetText("Update timed out. Please try again.")
86+
case errors.Is(err, context.Canceled):
87+
log.Info("update watcher canceled")
88+
statusLabel.SetText("Update canceled.")
89+
case err != nil:
90+
log.Errorf("update failed: %v", err)
91+
statusLabel.SetText("Update failed: " + err.Error())
92+
default:
93+
u.wUpdateProgress.Close()
94+
}
95+
}
96+
97+
// dotUpdater returns a closure that cycles through dots for a loading animation.
98+
func dotUpdater() func() string {
99+
dotCount := 0
100+
return func() string {
101+
dotCount = (dotCount + 1) % 4
102+
return fmt.Sprintf("%s%s", "Updating", strings.Repeat(".", dotCount))
103+
}
104+
}

0 commit comments

Comments
 (0)