Report update progress and log to Hammerhead while updating

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-26 17:58:34 -06:00
1 parent 049b6bad97
commit 2ad529fa19
4 files changed
+50 -4

No files matched your search

+42 -2
View File
@@ -43,7 +43,7 @@ type Config struct {
// Hammerhead, by calling UpdateNow (which normally restarts the process into the new binary). // Hammerhead, by calling UpdateNow (which normally restarts the process into the new binary).
Version string Version string
AutoUpdate bool AutoUpdate bool
UpdateNow func(ctx context.Context) (newVersion string, err error) UpdateNow func(ctx context.Context, progress func(stage string, pct int, line string)) (newVersion string, err error)
// Traefik, when its Path is set, serves http/https resources instead of the built-in proxy. // Traefik, when its Path is set, serves http/https resources instead of the built-in proxy.
Traefik TraefikConfig Traefik TraefikConfig
@@ -116,6 +116,10 @@ type Blacktip struct {
updating atomic.Bool updating atomic.Bool
updateMsg string // outcome of the update Hammerhead asked for, reported back updateMsg string // outcome of the update Hammerhead asked for, reported back
updateID string // the request that updateMsg answers updateID string // the request that updateMsg answers
updStage string // progress of the running update, reported to Hammerhead
updPct int
updLog []string
updSent time.Time
dockerMu sync.Mutex dockerMu sync.Mutex
dockerAt time.Time dockerAt time.Time
dockerList []model.Container dockerList []model.Container
@@ -534,6 +538,7 @@ func (b *Blacktip) report() {
} }
b.mu.Lock() b.mu.Lock()
rep.UpdateMsg, rep.UpdateID = b.updateMsg, b.updateID rep.UpdateMsg, rep.UpdateID = b.updateMsg, b.updateID
rep.UpdateStage, rep.UpdatePct, rep.UpdateLog = b.updStage, b.updPct, append([]string(nil), b.updLog...)
for id, chk := range b.mailChecks { for id, chk := range b.mailChecks {
rep.MailChecks[id] = chk rep.MailChecks[id] = chk
} }
@@ -760,19 +765,25 @@ func (b *Blacktip) requestUpdate(id string) {
return // already handled, or in progress return // already handled, or in progress
} }
b.updateID, b.updateMsg = id, "" b.updateID, b.updateMsg = id, ""
b.updStage, b.updPct, b.updLog = "starting", 0, nil
b.updNote("Update requested from the dashboard")
if !b.cfg.AutoUpdate || b.cfg.UpdateNow == nil { if !b.cfg.AutoUpdate || b.cfg.UpdateNow == nil {
b.updateMsg = "auto-update is off on this machine (set BLACKTIP_AUTO_UPDATE=1 and restart it)" b.updateMsg = "auto-update is off on this machine (set BLACKTIP_AUTO_UPDATE=1 and restart it)"
b.updStage = "failed"
b.updNote("Refused: " + b.updateMsg)
go b.report() go b.report()
return return
} }
if !b.updating.CompareAndSwap(false, true) { if !b.updating.CompareAndSwap(false, true) {
b.updateMsg = "another update is already running" b.updateMsg = "another update is already running"
b.updStage = "failed"
b.updNote("Refused: " + b.updateMsg)
go b.report() go b.report()
return return
} }
go func() { go func() {
defer b.updating.Store(false) defer b.updating.Store(false)
tag, err := b.cfg.UpdateNow(b.ctx) // on success this restarts the process and does not return tag, err := b.cfg.UpdateNow(b.ctx, b.updProgress) // on success this restarts the process and does not return
b.mu.Lock() b.mu.Lock()
switch { switch {
case err != nil: case err != nil:
@@ -786,3 +797,32 @@ func (b *Blacktip) requestUpdate(id string) {
b.report() b.report()
}() }()
} }
// updNote adds a timestamped line to the update log. Caller holds b.mu.
func (b *Blacktip) updNote(line string) {
b.updLog = append(b.updLog, time.Now().Format("15:04:05")+" "+line)
if len(b.updLog) > 200 {
b.updLog = b.updLog[len(b.updLog)-200:]
}
}
// updProgress is called by the updater as it works; Hammerhead gets a fresh report at most once a
// second (and straight away for every new log line), so its progress bar follows along.
func (b *Blacktip) updProgress(stage string, pct int, line string) {
b.mu.Lock()
b.updStage = stage
if pct >= 0 {
b.updPct = pct
}
if line != "" {
b.updNote(line)
}
send := line != "" || time.Since(b.updSent) > time.Second
if send {
b.updSent = time.Now()
}
b.mu.Unlock()
if send {
go b.report()
}
}
+5 -1
View File
@@ -55,7 +55,11 @@ func main() {
Hammerhead: strings.TrimRight(*hh, "/"), Token: *token, TunnelAddr: *tun, DataDir: *data, Docker: *dockerOn, Hammerhead: strings.TrimRight(*hh, "/"), Token: *token, TunnelAddr: *tun, DataDir: *data, Docker: *dockerOn,
Traefik: blacktip.TraefikConfig{Path: *traefikPath, ACMEDNS: *acmeDNS, NoACME: *noACME}, Traefik: blacktip.TraefikConfig{Path: *traefikPath, ACMEDNS: *acmeDNS, NoACME: *noACME},
Version: version, AutoUpdate: *autoUpdate, Version: version, AutoUpdate: *autoUpdate,
UpdateNow: func(ctx context.Context) (string, error) { return selfupdate.CheckOnce(ctx, upd) }, UpdateNow: func(ctx context.Context, progress func(string, int, string)) (string, error) {
c := upd
c.Progress = func(e selfupdate.Event) { progress(e.Stage, e.Pct, e.Line) }
return selfupdate.CheckOnce(ctx, c)
},
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
+1 -1
View File
@@ -3,7 +3,7 @@ module git.veillax.com/Veillax/blacktip
go 1.27.3 go 1.27.3
require ( require (
git.veillax.com/Veillax/reef v0.1.6 git.veillax.com/Veillax/reef v0.1.7
github.com/emersion/go-msgauth v0.7.0 github.com/emersion/go-msgauth v0.7.0
golang.org/x/crypto v0.58.0 golang.org/x/crypto v0.58.0
) )
+2
View File
@@ -6,6 +6,8 @@ git.veillax.com/Veillax/reef v0.1.5 h1:BydjLJsfGgU4zEIqUsePJRjPi9NgbFdmvq0M8dvJC
git.veillax.com/Veillax/reef v0.1.5/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso= git.veillax.com/Veillax/reef v0.1.5/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
git.veillax.com/Veillax/reef v0.1.6 h1:NgZ4u2Z3hJ+BYD/zX9lov8fox4s2i8txtYNOpIsikVo= git.veillax.com/Veillax/reef v0.1.6 h1:NgZ4u2Z3hJ+BYD/zX9lov8fox4s2i8txtYNOpIsikVo=
git.veillax.com/Veillax/reef v0.1.6/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso= git.veillax.com/Veillax/reef v0.1.6/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
git.veillax.com/Veillax/reef v0.1.7 h1:5C2udonlJac2xvDOE5e+zDxej+AbZ9ttrNSE4owmwuU=
git.veillax.com/Veillax/reef v0.1.7/go.mod h1:ZBUd/BARO5kkpUNTP81TTY1aQXLHFR6bKDhCxckjHso=
github.com/Microsoft/go-winio v0.6.4 h1:kJF2pamtYCWXf0xAT8sAkwKC0pMl2HYBrRPHAqyUc+g= github.com/Microsoft/go-winio v0.6.4 h1:kJF2pamtYCWXf0xAT8sAkwKC0pMl2HYBrRPHAqyUc+g=
github.com/Microsoft/go-winio v0.6.4/go.mod h1:1xl5JXG4ze/m74GH6Dc4Mwxfc3VL5siRVQjIAlHH07U= github.com/Microsoft/go-winio v0.6.4/go.mod h1:1xl5JXG4ze/m74GH6Dc4Mwxfc3VL5siRVQjIAlHH07U=
github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc= github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc=