Skip to content

Commit 28e58e6

Browse files
committed
Merge branch 'release/v11.2.1' of https://github.com/utmstack/UTMStack into release/v11.2.1
2 parents fb490b8 + a4faafa commit 28e58e6

File tree

4 files changed

+132
-15
lines changed

4 files changed

+132
-15
lines changed

installer/config/const.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ const (
1111
UpdateInstanceDetailsEndpoint = "/api/v1/instances/details"
1212
HeartbeatEndpoint = "/api/v1/instances/heartbeat"
1313
GetUpdatesInfoEndpoint = "/api/v1/updates"
14-
SetUpdateSentEndpoint = "/api/v1/updates/sent"
15-
GetLicenseEndpoint = "/api/v1/licenses"
16-
HealthEndpoint = "/api/v1/health"
17-
LogCollectorEndpoint = "/api/v1/logcollectors/upload"
14+
SetUpdateSentEndpoint = "/api/v1/updates/sent"
15+
GetLicenseEndpoint = "/api/v1/licenses"
16+
HealthEndpoint = "/api/v1/health"
17+
LogCollectorEndpoint = "/api/v1/logcollectors/upload"
1818

1919
GitHubReleasesURL = "https://github.com/utmstack/UTMStack/releases/download/%s/installer"
2020

21-
ImagesPath = "/utmstack/images"
21+
ImagesPath = "/utmstack/images"
22+
InstallerBinPath = "/usr/local/bin/utmstack_installer"
2223

2324
RequiredMinCPUCores = 2
2425
RequiredMinDiskSpace = 30
@@ -39,6 +40,7 @@ var (
3940
VersionFilePath = filepath.Join(GetConfig().UpdatesFolder, "version.json")
4041
LicenseFilePath = filepath.Join(GetConfig().UpdatesFolder, "LICENSE")
4142
PendingUpdatesPath = filepath.Join(GetConfig().UpdatesFolder, "pending-updates.json")
43+
LastAdminEmailPath = filepath.Join(GetConfig().UpdatesFolder, "last-admin-email.txt")
4244
EventProcessorLogsPath = filepath.Join(GetConfig().DataDir, "events-engine-workdir", "logs")
4345
CheckUpdatesEvery = 5 * time.Minute
4446
SyncSystemLogsEvery = 5 * time.Minute

installer/updater/client.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"context"
66
"fmt"
77
"io"
8+
"mime/multipart"
89
"net/http"
910
"os"
11+
"path/filepath"
1012
"strings"
1113
"sync"
1214
"time"
@@ -170,11 +172,6 @@ func (c *UpdaterClient) CheckUpdate() error {
170172
}
171173

172174
func (c *UpdaterClient) UpdateInstaller(version string) error {
173-
execPath, err := os.Executable()
174-
if err != nil {
175-
return fmt.Errorf("error getting executable path: %v", err)
176-
}
177-
178175
// Download new installer from GitHub
179176
url := fmt.Sprintf(config.GitHubReleasesURL, version)
180177
resp, err := http.Get(url)
@@ -208,8 +205,8 @@ func (c *UpdaterClient) UpdateInstaller(version string) error {
208205
return fmt.Errorf("error making installer executable: %v", err)
209206
}
210207

211-
// Replace current binary
212-
if err := os.Rename(tmpPath, execPath); err != nil {
208+
// Replace binary at standard location
209+
if err := os.Rename(tmpPath, config.InstallerBinPath); err != nil {
213210
os.Remove(tmpPath)
214211
return fmt.Errorf("error replacing installer binary: %v", err)
215212
}
@@ -236,23 +233,32 @@ func (c *UpdaterClient) UploadLogs(ctx context.Context, path string) error {
236233
url := fmt.Sprintf("%s%s", c.Config.Server, config.LogCollectorEndpoint)
237234

238235
buf := &bytes.Buffer{}
239-
writer := io.MultiWriter(buf)
236+
writer := multipart.NewWriter(buf)
240237

241238
zipFile, err := os.Open(path)
242239
if err != nil {
243240
return err
244241
}
245242
defer zipFile.Close()
246243

247-
if _, err = io.Copy(writer, zipFile); err != nil {
244+
part, err := writer.CreateFormFile("file", filepath.Base(path))
245+
if err != nil {
246+
return err
247+
}
248+
249+
if _, err = io.Copy(part, zipFile); err != nil {
250+
return err
251+
}
252+
253+
if err = writer.Close(); err != nil {
248254
return err
249255
}
250256

251257
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, buf)
252258
if err != nil {
253259
return err
254260
}
255-
req.Header.Set("Content-Type", "application/zip")
261+
req.Header.Set("Content-Type", writer.FormDataContentType())
256262
req.Header.Set("id", c.Config.InstanceID)
257263
req.Header.Set("key", c.Config.InstanceKey)
258264

installer/updater/register.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"os"
78
"path/filepath"
9+
"strings"
810
"time"
911

1012
"github.com/utmstack/UTMStack/installer/config"
@@ -120,6 +122,12 @@ func PollAndUpdateAdminEmail(instanceConf InstanceConfig) {
120122
continue
121123
}
122124

125+
// Check if this email was already sent
126+
lastEmail, _ := os.ReadFile(config.LastAdminEmailPath)
127+
if strings.TrimSpace(string(lastEmail)) == email {
128+
return
129+
}
130+
123131
// Email found, update instance details
124132
updateReq := InstanceDTOInput{
125133
Name: serverConfig.ServerName,
@@ -146,6 +154,9 @@ func PollAndUpdateAdminEmail(instanceConf InstanceConfig) {
146154
continue
147155
}
148156

157+
// Save the email to avoid re-sending
158+
_ = os.WriteFile(config.LastAdminEmailPath, []byte(email), 0644)
159+
149160
config.Logger().Info("Successfully updated instance with admin email: %s", email)
150161
return
151162
}

installer/updater/service.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package updater
22

33
import (
4+
"fmt"
5+
"io"
6+
"os"
47
"time"
58

69
"github.com/kardianos/service"
@@ -14,6 +17,7 @@ func GetConfigServ() *service.Config {
1417
Name: "UTMStackComponentsUpdater",
1518
DisplayName: "UTMStack Components Updater",
1619
Description: "UTMStack Components Updater",
20+
Executable: config.InstallerBinPath,
1721
Arguments: []string{"--run"},
1822
}
1923

@@ -32,6 +36,11 @@ func (p *program) Stop(s service.Service) error {
3236
}
3337

3438
func (p *program) run() {
39+
// Migrate service to standard path if needed
40+
if migrated := migrateServiceIfNeeded(); migrated {
41+
return // Exit, new service will start from standard path
42+
}
43+
3544
go MonitorConnection(config.GetCMServer(), 30*time.Second, 3, &config.ConnectedToInternet)
3645
time.Sleep(5 * time.Second)
3746

@@ -73,6 +82,11 @@ func (p *program) run() {
7382
}
7483

7584
func InstallService() {
85+
// Copy current binary to standard location
86+
if err := copyInstallerToStandardPath(); err != nil {
87+
config.Logger().Fatal("error copying installer to standard path: %v", err)
88+
}
89+
7690
svcConfig := GetConfigServ()
7791
prg := new(program)
7892
newService, err := service.New(prg, svcConfig)
@@ -90,6 +104,90 @@ func InstallService() {
90104
}
91105
}
92106

107+
func copyInstallerToStandardPath() error {
108+
currentExec, err := os.Executable()
109+
if err != nil {
110+
return fmt.Errorf("error getting current executable path: %v", err)
111+
}
112+
113+
// If already at standard path, skip copy
114+
if currentExec == config.InstallerBinPath {
115+
return nil
116+
}
117+
118+
srcFile, err := os.Open(currentExec)
119+
if err != nil {
120+
return fmt.Errorf("error opening source binary: %v", err)
121+
}
122+
defer srcFile.Close()
123+
124+
dstFile, err := os.Create(config.InstallerBinPath)
125+
if err != nil {
126+
return fmt.Errorf("error creating destination binary: %v", err)
127+
}
128+
defer dstFile.Close()
129+
130+
if _, err = io.Copy(dstFile, srcFile); err != nil {
131+
return fmt.Errorf("error copying binary: %v", err)
132+
}
133+
134+
if err = os.Chmod(config.InstallerBinPath, 0755); err != nil {
135+
return fmt.Errorf("error setting permissions on binary: %v", err)
136+
}
137+
138+
return nil
139+
}
140+
141+
func migrateServiceIfNeeded() bool {
142+
currentExec, err := os.Executable()
143+
if err != nil {
144+
config.Logger().ErrorF("error getting current executable path: %v", err)
145+
return false
146+
}
147+
148+
// Already running from standard path, no migration needed
149+
if currentExec == config.InstallerBinPath {
150+
return false
151+
}
152+
153+
config.Logger().Info("Migrating service to standard path: %s", config.InstallerBinPath)
154+
155+
// Copy current binary to standard location
156+
if err := copyInstallerToStandardPath(); err != nil {
157+
config.Logger().ErrorF("error copying installer during migration: %v", err)
158+
return false
159+
}
160+
161+
// Uninstall old service
162+
serviceName := GetConfigServ().Name
163+
if err := utils.UninstallService(serviceName); err != nil {
164+
config.Logger().ErrorF("error uninstalling old service during migration: %v", err)
165+
return false
166+
}
167+
168+
// Install new service pointing to standard path
169+
svcConfig := GetConfigServ()
170+
prg := new(program)
171+
newService, err := service.New(prg, svcConfig)
172+
if err != nil {
173+
config.Logger().ErrorF("error creating new service during migration: %v", err)
174+
return false
175+
}
176+
177+
if err := newService.Install(); err != nil {
178+
config.Logger().ErrorF("error installing new service during migration: %v", err)
179+
return false
180+
}
181+
182+
if err := newService.Start(); err != nil {
183+
config.Logger().ErrorF("error starting new service during migration: %v", err)
184+
return false
185+
}
186+
187+
config.Logger().Info("Service migrated successfully to %s", config.InstallerBinPath)
188+
return true
189+
}
190+
93191
func RunService() {
94192
svcConfig := GetConfigServ()
95193
prg := new(program)

0 commit comments

Comments
 (0)