Skip to content

Commit 9e9ea2a

Browse files
authored
Migrate to hostlink upgrade command for the upgrade
1 parent 7970f6f commit 9e9ea2a

36 files changed

+3948
-1530
lines changed

.goreleaser.yaml

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,6 @@ builds:
3030
ldflags:
3131
- -s -w -X hostlink/version.Version={{.Version}}
3232

33-
- id: hostlink-updater
34-
main: ./cmd/updater
35-
binary: hostlink-updater
36-
env:
37-
- CGO_ENABLED=0
38-
goos:
39-
- linux
40-
goarch:
41-
- amd64
42-
- arm64
43-
ldflags:
44-
- -s -w -X hostlink/version.Version={{.Version}}
45-
4633
archives:
4734
- id: hostlink-archive
4835
builds: [hostlink]
@@ -61,18 +48,6 @@ archives:
6148
dst: scripts
6249
strip_parent: true
6350

64-
- id: updater-archive
65-
builds: [hostlink-updater]
66-
formats: [tar.gz]
67-
name_template: >-
68-
hostlink-updater_
69-
{{- title .Os }}_
70-
{{- if eq .Arch "amd64" }}x86_64
71-
{{- else }}{{ .Arch }}{{ end }}
72-
{{- if .Arm }}v{{ .Arm }}{{ end }}
73-
files:
74-
- none*
75-
7651
changelog:
7752
sort: asc
7853
filters:

AGENTS.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
- ALWAYS USE PARALLEL TASKS SUBAGENTS FOR CODE EXPLORATION, DEEP DIVES, AND SO ON
2-
- I use jj instead of git
1+
- ALWAYS USE PARALLEL TASKS SUBAGENTS FOR CODE EXPLORATION, INVESTIGATION, DEEP DIVES
2+
- Use all tools available to keep current context window as small as possible
3+
- When reading files, DELEGATE to subagents, if possible
4+
- In plan mode, be bias to delegate to subagents
5+
- Use question tool more frequently
6+
- Use jj instead of git
37
- ALWAYS FOLLOW TDD, red phase to green phase
48
- Use ripgrep instead of grep, use fd instead of find
9+
10+
## Usage of question tool
11+
12+
Before any kind of implementation, interview me in detail using the question tool.
13+
14+
Ask about technical implementation, UI/UX, edge cases, concerns, and tradeoffs.
15+
Don't ask obvious questions, dig into the hard parts I might not have considered.
16+
17+
Keep interviewing until we've covered everything.
18+
19+
## Tests
20+
21+
- Test actual behavior, not the implementation
22+
- Only test implementation when there is a technical limit to simulating the behavior

app/jobs/selfupdatejob/selfupdatejob.go

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
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.
6061
type 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

Comments
 (0)