Skip to content

Commit ef50716

Browse files
committed
Merge remote-tracking branch 'origin/v11' into release/v11.1.4
2 parents 8b23188 + 5c77208 commit ef50716

File tree

5 files changed

+133
-25
lines changed

5 files changed

+133
-25
lines changed

installer/config/const.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const (
1515
LogCollectorEndpoint = "/api/v1/logcollectors/upload"
1616
GetLatestVersionEndpoint = "/api/v1/versions/latest"
1717

18+
GitHubReleasesURL = "https://github.com/utmstack/UTMStack/releases/download/%s/installer"
19+
1820
ImagesPath = "/utmstack/images"
1921

2022
RequiredMinCPUCores = 2

installer/install.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ func Install(specificVersion string) error {
3030

3131
if isInstalledAlready {
3232
fmt.Println("UTMStack is already installed. If you want to re-install it, please remove the service UTMStackComponentsUpdater first.")
33+
if err := utils.RestartService("UTMStackComponentsUpdater"); err != nil {
34+
return fmt.Errorf("error restarting service: %v", err)
35+
}
3336
return nil
3437
}
3538

installer/updater/client.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ func (c *UpdaterClient) UpdateToNewVersion(version, edition, changelog string) e
140140
config.Logger().Info("Updating UTMStack to version %s-%s...", version, edition)
141141
config.Updating = true
142142

143+
// Update installer binary first (only in prod branch)
144+
cnf := config.GetConfig()
145+
if cnf.Branch == "prod" || cnf.Branch == "" {
146+
if err := c.UpdateInstaller(version); err != nil {
147+
config.Logger().ErrorF("error updating installer: %v", err)
148+
}
149+
}
150+
143151
err := docker.StackUP(version + "-" + edition)
144152
if err != nil {
145153
return fmt.Errorf("error updating UTMStack: %v", err)
@@ -153,9 +161,67 @@ func (c *UpdaterClient) UpdateToNewVersion(version, edition, changelog string) e
153161
config.Logger().Info("UTMStack updated to version %s-%s", version, edition)
154162
config.Updating = false
155163

156-
err = utils.RunCmd("docker", "system", "prune", "-f")
164+
time.Sleep(3 * time.Minute)
165+
166+
err = utils.RunCmd("docker", "image", "prune", "-a", "-f")
167+
if err != nil {
168+
config.Logger().ErrorF("error cleaning up old Docker images after update: %v", err)
169+
}
170+
171+
// Restart service to load new installer binary
172+
if cnf.Branch == "prod" || cnf.Branch == "" {
173+
go func() {
174+
time.Sleep(5 * time.Second)
175+
utils.RestartService("UTMStackComponentsUpdater")
176+
}()
177+
}
178+
179+
return nil
180+
}
181+
182+
func (c *UpdaterClient) UpdateInstaller(version string) error {
183+
execPath, err := os.Executable()
184+
if err != nil {
185+
return fmt.Errorf("error getting executable path: %v", err)
186+
}
187+
188+
// Download new installer from GitHub
189+
url := fmt.Sprintf(config.GitHubReleasesURL, version)
190+
resp, err := http.Get(url)
191+
if err != nil {
192+
return fmt.Errorf("error downloading installer from %s: %v", url, err)
193+
}
194+
defer resp.Body.Close()
195+
196+
if resp.StatusCode != http.StatusOK {
197+
return fmt.Errorf("error downloading installer: status %d", resp.StatusCode)
198+
}
199+
200+
// Create temp file
201+
tmpFile, err := os.CreateTemp("", "installer-*")
157202
if err != nil {
158-
config.Logger().ErrorF("error cleaning up Docker system after update: %v", err)
203+
return fmt.Errorf("error creating temp file: %v", err)
204+
}
205+
tmpPath := tmpFile.Name()
206+
207+
// Download to temp file
208+
_, err = io.Copy(tmpFile, resp.Body)
209+
tmpFile.Close()
210+
if err != nil {
211+
os.Remove(tmpPath)
212+
return fmt.Errorf("error writing installer to temp file: %v", err)
213+
}
214+
215+
// Make executable
216+
if err := os.Chmod(tmpPath, 0755); err != nil {
217+
os.Remove(tmpPath)
218+
return fmt.Errorf("error making installer executable: %v", err)
219+
}
220+
221+
// Replace current binary
222+
if err := os.Rename(tmpPath, execPath); err != nil {
223+
os.Remove(tmpPath)
224+
return fmt.Errorf("error replacing installer binary: %v", err)
159225
}
160226

161227
return nil

installer/updater/window.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,91 @@
11
package updater
22

33
import (
4+
"encoding/json"
45
"time"
56

6-
"github.com/robfig/cron/v3"
77
"github.com/utmstack/UTMStack/installer/config"
88
)
99

10-
var windowConfig string
10+
type MaintenanceWindow struct {
11+
Days []int `json:"days"` // 0=Sunday, 1=Monday, ..., 6=Saturday
12+
StartTime string `json:"startTime"` // Format: "HH:MM"
13+
EndTime string `json:"endTime"` // Format: "HH:MM"
14+
}
15+
16+
var windowConfig *MaintenanceWindow
1117

1218
func UpdateWindowConfig() {
1319
for {
14-
window, err := getWindowMaintaince()
15-
if err != nil {
16-
// Only log error if it's not a maintenance error
17-
if !IsBackendMaintenanceError(err) {
18-
config.Logger().ErrorF("Error getting maintenance window config: %v", err)
19-
}
20-
// If backend is in maintenance, just skip this iteration silently
21-
}
22-
23-
if window != "" {
20+
window, _ := getWindowMaintaince()
21+
if window != nil {
2422
windowConfig = window
25-
config.Logger().Info("Updated maintenance window config: %s", windowConfig)
2623
}
2724

2825
time.Sleep(config.CheckUpdatesEvery)
2926
}
3027
}
3128

3229
func IsInMaintenanceWindow() bool {
33-
if windowConfig == "" {
30+
if windowConfig == nil {
31+
return true
32+
}
33+
34+
if len(windowConfig.Days) == 0 {
3435
return true
3536
}
3637

37-
parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
38+
if windowConfig.StartTime == "" || windowConfig.EndTime == "" {
39+
return true
40+
}
41+
42+
startTime, err := time.Parse("15:04", windowConfig.StartTime)
43+
if err != nil {
44+
config.Logger().ErrorF("Error parsing start time %s: %v", windowConfig.StartTime, err)
45+
return true
46+
}
3847

39-
schedule, err := parser.Parse(windowConfig)
48+
endTime, err := time.Parse("15:04", windowConfig.EndTime)
4049
if err != nil {
41-
config.Logger().ErrorF("Error parsing cron expression %s: %v", windowConfig, err)
50+
config.Logger().ErrorF("Error parsing end time %s: %v", windowConfig.EndTime, err)
51+
return true
52+
}
53+
54+
now := time.Now()
55+
56+
currentDay := int(now.Weekday())
57+
dayAllowed := false
58+
for _, day := range windowConfig.Days {
59+
if day == currentDay {
60+
dayAllowed = true
61+
break
62+
}
63+
}
64+
65+
if !dayAllowed {
4266
return false
4367
}
4468

45-
now := time.Now().Truncate(time.Minute)
46-
prev := schedule.Next(now.Add(-1 * time.Minute))
69+
currentTime, _ := time.Parse("15:04", now.Format("15:04"))
70+
71+
if startTime.Before(endTime) || startTime.Equal(endTime) {
72+
return !currentTime.Before(startTime) && !currentTime.After(endTime)
73+
}
4774

48-
return prev.Equal(now)
75+
return !currentTime.Before(startTime) || !currentTime.After(endTime)
4976
}
5077

51-
func getWindowMaintaince() (string, error) {
78+
func getWindowMaintaince() (*MaintenanceWindow, error) {
5279
backConf, err := getConfigFromBackend(8)
5380
if err != nil {
54-
return "", err
81+
return nil, err
82+
}
83+
84+
var window MaintenanceWindow
85+
err = json.Unmarshal([]byte(backConf[0].ConfParamValue), &window)
86+
if err != nil {
87+
return nil, err
5588
}
5689

57-
return backConf[0].ConfParamValue, nil
90+
return &window, nil
5891
}

installer/utils/services.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ func StopService(name string) error {
88
return RunCmd("systemctl", "stop", name)
99
}
1010

11+
func RestartService(name string) error {
12+
return RunCmd("systemctl", "restart", name)
13+
}
14+
1115
func UninstallService(name string) error {
1216
err := RunCmd("systemctl", "disable", name)
1317
if err != nil {

0 commit comments

Comments
 (0)