eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "net/http"
5 "sync"
6 "time"
7)
8
9// Instance-side update awareness. The instance never applies updates itself —
10// it runs sandboxed and can't restart anything; the box updater (`lchat update
11// run`, root) does the staging and swapping. The instance's two jobs here:
12//
13// - Tell the box updater what the admin chose: GET /api/update-prefs (read
14// over the instance's unix socket, like the deploy health checks). Un-authed
15// by design — it reveals less than the already-public /api/version build id,
16// and an old binary 404ing means "defaults" (auto-update on, stable).
17// - Know whether its channel has something newer, so the admin console can
18// say "a new version is available" — a periodic fetch+verify of the signed
19// release manifest (release.go), surfaced through GET /api/admin/settings.
20// A fetch failure just leaves the last result stale; nothing acts on it.
21
22// updateState is the last verified manifest fetch, guarded by mu — written by
23// the checker goroutine, read by getSettings.
24type updateState struct {
25 mu sync.Mutex
26 manifest *releaseManifest
27 checkedAt time.Time
28}
29
30// getUpdatePrefs reports the admin's update settings to the box updater.
31func (s *Server) getUpdatePrefs(w http.ResponseWriter, r *http.Request) {
32 writeJSON(w, http.StatusOK, map[string]any{
33 "auto_update": s.db.updateAuto(),
34 "auto_download": s.db.updateDownload(),
35 "channel": s.db.updateChannel(),
36 "pin": s.db.updatePin(),
37 "major": s.db.updateMajor(),
38 })
39}
40
41// runUpdateChecker refreshes the availability info: once shortly after boot
42// (so a fresh admin console isn't blank for hours), then every 6h. The box
43// updater runs its own checks; this one is display-only, so failures are
44// logged by the caller of checkForUpdate and otherwise ignored.
45func (s *Server) runUpdateChecker() {
46 time.Sleep(time.Minute)
47 for {
48 _ = s.checkForUpdate()
49 time.Sleep(6 * time.Hour)
50 }
51}
52
53// checkForUpdate fetches and verifies the release manifest (the same fetch
54// the box updater runs — fetchReleaseManifest, updater.go), keeping the whole
55// channel map — so an admin switching channels sees that channel's version
56// immediately, without waiting for the next fetch.
57func (s *Server) checkForUpdate() error {
58 m, err := fetchReleaseManifest(releaseManifestURL)
59 if err != nil {
60 return err
61 }
62 s.upd.mu.Lock()
63 s.upd.manifest, s.upd.checkedAt = m, time.Now()
64 s.upd.mu.Unlock()
65 // A changed channel head alerts the space's admins (releasealert.go) —
66 // the detection half of the supply-chain bake window.
67 s.noteReleaseSightings(m)
68 return nil
69}
70
71// updateInfo is the availability block getSettings serves the admin consoles:
72// what we run, what the tracked channel offers, and how fresh that knowledge is.
73func (s *Server) updateInfo() map[string]any {
74 info := map[string]any{"build": buildVersion}
75 s.upd.mu.Lock()
76 m, checkedAt := s.upd.manifest, s.upd.checkedAt
77 s.upd.mu.Unlock()
78 if m == nil {
79 return info // no successful check yet — the console shows just the build
80 }
81 info["checked_at"] = checkedAt.Unix()
82 if e, ok := m.Channels[s.db.updateChannel()]; ok {
83 info["available_version"] = e.Version
84 info["available_notes"] = e.Notes
85 if e.Semver != "" {
86 info["available_semver"] = e.Semver
87 }
88 // A major pin holding this head back is worth explaining in the console
89 // — same reason bake_hours below is served.
90 if hold := majorHold(s.db.updateMajor(), e); hold != "" {
91 info["major_held"] = true
92 }
93 // The channel's supply-chain bake window (bake.go), so the console can
94 // explain why a fresh release hasn't auto-installed yet. The instance
95 // can't see the box's bake clock — only the policy — so this is an
96 // "up to N hours" statement, never a countdown.
97 if h := bakeHours(s.db.updateChannel()); h > 0 {
98 info["bake_hours"] = h
99 }
100 }
101 return info
102}
103