77 "sync"
88 "time"
99
10+ "github.com/google/uuid"
1011 log "github.com/sirupsen/logrus"
1112
1213 "hostlink/app/services/updatecheck"
@@ -50,11 +51,11 @@ type StateWriterInterface interface {
5051 Write (data update.StateData ) error
5152}
5253
53- // SpawnFunc is a function that spawns the updater binary .
54- type SpawnFunc func (updaterPath string , args []string ) error
54+ // SpawnFunc is a function that spawns a binary with the given args .
55+ type SpawnFunc func (binaryPath string , args []string ) error
5556
56- // InstallUpdaterFunc is a function that extracts and installs the updater binary from a tarball.
57- type InstallUpdaterFunc func (tarPath , destPath string ) error
57+ // InstallBinaryFunc extracts a binary from a tarball to a destination path .
58+ type InstallBinaryFunc func (tarPath , destPath string ) error
5859
5960// SelfUpdateJobConfig holds the configuration for the SelfUpdateJob.
6061type SelfUpdateJobConfig struct {
@@ -65,11 +66,10 @@ type SelfUpdateJobConfig struct {
6566 LockManager LockManagerInterface
6667 StateWriter StateWriterInterface
6768 Spawn SpawnFunc
68- InstallUpdater InstallUpdaterFunc
69+ InstallBinary InstallBinaryFunc
6970 CurrentVersion string
70- UpdaterPath string // Where to install the extracted updater binary
71- StagingDir string // Where to download tarballs
72- BaseDir string // Base update directory (for -base-dir flag to updater)
71+ InstallPath string // Target install path (e.g., /usr/bin/hostlink)
72+ StagingDir string // Where to download tarballs and extract binary
7373}
7474
7575// SelfUpdateJob periodically checks for and applies updates.
@@ -133,10 +133,11 @@ func (j *SelfUpdateJob) runUpdate(ctx context.Context) error {
133133 return nil
134134 }
135135
136- log .Infof ("update available: %s -> %s" , j .config .CurrentVersion , info .TargetVersion )
136+ updateID := uuid .NewString ()
137+ log .Infof ("update available: %s -> %s (update_id=%s)" , j .config .CurrentVersion , info .TargetVersion , updateID )
137138
138139 // Step 2: Pre-flight checks
139- requiredSpace := info .AgentSize + info . UpdaterSize
140+ requiredSpace := info .AgentSize
140141 if requiredSpace == 0 {
141142 requiredSpace = defaultRequiredSpace
142143 }
@@ -161,64 +162,78 @@ func (j *SelfUpdateJob) runUpdate(ctx context.Context) error {
161162 // Step 4: Write initialized state
162163 if err := j .config .StateWriter .Write (update.StateData {
163164 State : update .StateInitialized ,
165+ UpdateID : updateID ,
164166 SourceVersion : j .config .CurrentVersion ,
165167 TargetVersion : info .TargetVersion ,
166168 }); err != nil {
167169 return fmt .Errorf ("failed to write initialized state: %w" , err )
168170 }
169171
172+ // Helper to write error state (best-effort, errors ignored)
173+ writeErrorState := func (errMsg string ) {
174+ j .config .StateWriter .Write (update.StateData {
175+ State : update .StateInitialized ,
176+ UpdateID : updateID ,
177+ SourceVersion : j .config .CurrentVersion ,
178+ TargetVersion : info .TargetVersion ,
179+ Error : & errMsg ,
180+ })
181+ }
182+
170183 if err := ctx .Err (); err != nil {
171184 return err
172185 }
173186
174187 // Step 5: Download agent tarball
175188 agentDest := filepath .Join (j .config .StagingDir , updatedownload .AgentTarballName )
176189 if _ , err := j .config .Downloader .DownloadAndVerify (ctx , info .AgentURL , agentDest , info .AgentSHA256 ); err != nil {
190+ writeErrorState (fmt .Sprintf ("failed to download agent: %s" , err ))
177191 return fmt .Errorf ("failed to download agent: %w" , err )
178192 }
179193
180194 if err := ctx .Err (); err != nil {
195+ writeErrorState (err .Error ())
181196 return err
182197 }
183198
184- // Step 6: Download updater tarball
185- updaterDest := filepath .Join (j .config .StagingDir , updatedownload .UpdaterTarballName )
186- if _ , err := j .config .Downloader .DownloadAndVerify (ctx , info .UpdaterURL , updaterDest , info .UpdaterSHA256 ); err != nil {
187- return fmt .Errorf ("failed to download updater: %w" , err )
199+ // Step 6: Extract hostlink binary from tarball to staging dir
200+ stagedBinary := filepath .Join (j .config .StagingDir , "hostlink" )
201+ if err := j .config .InstallBinary (agentDest , stagedBinary ); err != nil {
202+ writeErrorState (fmt .Sprintf ("failed to extract binary from tarball: %s" , err ))
203+ return fmt .Errorf ("failed to extract binary from tarball: %w" , err )
188204 }
189205
190206 if err := ctx .Err (); err != nil {
207+ writeErrorState (err .Error ())
191208 return err
192209 }
193210
194211 // Step 7: Write staged state
195212 if err := j .config .StateWriter .Write (update.StateData {
196213 State : update .StateStaged ,
214+ UpdateID : updateID ,
197215 SourceVersion : j .config .CurrentVersion ,
198216 TargetVersion : info .TargetVersion ,
199217 }); err != nil {
200218 return fmt .Errorf ("failed to write staged state: %w" , err )
201219 }
202220
203221 if err := ctx .Err (); err != nil {
222+ writeErrorState (err .Error ())
204223 return err
205224 }
206225
207- // Step 8: Extract updater binary from tarball
208- if err := j .config .InstallUpdater (updaterDest , j .config .UpdaterPath ); err != nil {
209- return fmt .Errorf ("failed to install updater binary: %w" , err )
210- }
211-
212- // Step 9: Release lock before spawning updater
226+ // Step 8: Release lock before spawning upgrade
213227 j .config .LockManager .Unlock ()
214228 locked = false
215229
216- // Step 10: Spawn updater in its own process group
217- args := []string {"-version" , info .TargetVersion , "-base-dir" , j .config .BaseDir }
218- if err := j .config .Spawn (j .config .UpdaterPath , args ); err != nil {
219- return fmt .Errorf ("failed to spawn updater: %w" , err )
230+ // Step 9: Spawn staged binary with upgrade subcommand
231+ args := []string {"upgrade" , "--install-path" , j .config .InstallPath , "--update-id" , updateID , "--source-version" , j .config .CurrentVersion }
232+ if err := j .config .Spawn (stagedBinary , args ); err != nil {
233+ writeErrorState (err .Error ())
234+ return fmt .Errorf ("failed to spawn upgrade: %w" , err )
220235 }
221236
222- log .Infof ("updater spawned for version %s" , info .TargetVersion )
237+ log .Infof ("upgrade spawned for version %s" , info .TargetVersion )
223238 return nil
224239}
0 commit comments