2 Commits
Author SHA1 Message Date
VeillaxandClaude Sonnet 5.5 cb1243e4ed model: versions and update requests for edges and sites
CI / test (push) Successful in 1m22s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 16:56:34 -06:00
VeillaxandClaude Sonnet 5.5 122fade954 selfupdate: add Check to see the newest release without installing
CI / test (push) Successful in 1m41s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 15:59:26 -06:00
3 changed files with 82 additions and 14 deletions

No files matched your search

+22
View File
@@ -39,6 +39,13 @@ type Edge struct {
OrgIDs []string `json:"orgIds"` // orgs allowed to use this edge; empty = all
IPs []string `json:"ips"` // addresses the edge reported it can bind
LastSeen time.Time `json:"lastSeen"`
// Version is the release the program reports (e.g. v0.1.5). AutoUpdate says whether it may update
// itself; only then will it act on UpdateRequested. UpdateMsg is the outcome of the last requested update.
Version string `json:"version,omitempty"`
AutoUpdate bool `json:"autoUpdate,omitempty"`
UpdateRequested bool `json:"updateRequested,omitempty"`
UpdateMsg string `json:"updateMsg,omitempty"`
}
// Site is a bamboo agent: a private network that holds the real services.
@@ -56,6 +63,13 @@ type Site struct {
TunnelIP string `json:"tunnelIp,omitempty"` // the site's address inside the tunnel network
PublicKey string `json:"publicKey,omitempty"` // WireGuard public key, reported by the agent
// Version is the release the program reports (e.g. v0.1.5). AutoUpdate says whether it may update
// itself; only then will it act on UpdateRequested. UpdateMsg is the outcome of the last requested update.
Version string `json:"version,omitempty"`
AutoUpdate bool `json:"autoUpdate,omitempty"`
UpdateRequested bool `json:"updateRequested,omitempty"`
UpdateMsg string `json:"updateMsg,omitempty"`
}
// Domain is a base domain an org owns. Hostnames of web/tls mappings must sit under one.
@@ -216,6 +230,7 @@ type EdgeConfig struct {
Local []string `json:"local"` // site IDs the edge dials directly on its own machine
AcmeEmail string `json:"acmeEmail"`
AcmeStages bool `json:"acmeStaging"`
Update bool `json:"update,omitempty"` // an administrator asked this edge to update itself now
MailEgress []MailPolicy `json:"mailEgress"`
}
@@ -289,6 +304,9 @@ type EdgeReport struct {
Stats map[string]Stat `json:"stats"` // deltas since last report
Containers []Container `json:"containers"`
MailChecks map[string]MailCheck `json:"mailChecks"` // by mail-egress ID
Version string `json:"version,omitempty"`
AutoUpdate bool `json:"autoUpdate,omitempty"`
UpdateMsg string `json:"updateMsg,omitempty"` // outcome of the last requested update
}
// EdgeRef tells a site where to dial.
@@ -306,6 +324,7 @@ type SiteConfig struct {
SiteID string `json:"siteId"`
TunnelIP string `json:"tunnelIp"` // this site's WireGuard address
Edges []EdgeRef `json:"edges"`
Update bool `json:"update,omitempty"` // an administrator asked this agent to update itself now
MailEgress []MailSpec `json:"mailEgress"`
}
@@ -316,6 +335,9 @@ type SiteReport struct {
MailEgress map[string]string `json:"mailEgress"` // egress ID -> "" (listening) or an error
Connected []string `json:"connected"`
Containers []Container `json:"containers"`
Version string `json:"version,omitempty"`
AutoUpdate bool `json:"autoUpdate,omitempty"`
UpdateMsg string `json:"updateMsg,omitempty"`
}
// HostMatches reports whether host matches an exact name or a "*.example.com" pattern.
+43 -14
View File
@@ -111,21 +111,9 @@ func CheckOnce(ctx context.Context, cfg Config) (string, error) {
if !ok {
return "", fmt.Errorf("%q is not a release version", cfg.Current)
}
var rel struct {
Tag string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
Draft bool `json:"draft"`
Assets []struct {
Name string `json:"name"`
URL string `json:"browser_download_url"`
} `json:"assets"`
}
body, err := get(ctx, cfg.HTTP, cfg.Server+"/api/v1/repos/"+cfg.Repo+"/releases/latest", 1<<20)
rel, err := latestRelease(ctx, cfg)
if err != nil {
return "", fmt.Errorf("checking for a new release: %w", err)
}
if err := json.Unmarshal(body, &rel); err != nil {
return "", fmt.Errorf("reading the latest release: %w", err)
return "", err
}
if rel.Prerelease || rel.Draft {
return "", nil
@@ -208,6 +196,47 @@ func CheckOnce(ctx context.Context, cfg Config) (string, error) {
return rel.Tag, nil
}
type release struct {
Tag string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
Draft bool `json:"draft"`
Assets []struct {
Name string `json:"name"`
URL string `json:"browser_download_url"`
} `json:"assets"`
}
func latestRelease(ctx context.Context, cfg Config) (release, error) {
var rel release
body, err := get(ctx, cfg.HTTP, cfg.Server+"/api/v1/repos/"+cfg.Repo+"/releases/latest", 1<<20)
if err != nil {
return rel, fmt.Errorf("checking for a new release: %w", err)
}
if err := json.Unmarshal(body, &rel); err != nil {
return rel, fmt.Errorf("reading the latest release: %w", err)
}
return rel, nil
}
// Check reports the newest full release and whether it is newer than cfg.Current, without
// installing anything. A development build (a Current that isn't vX.Y.Z) is never "newer".
func Check(ctx context.Context, cfg Config) (latest string, newer bool, err error) {
cfg.defaults()
rel, err := latestRelease(ctx, cfg)
if err != nil {
return "", false, err
}
if rel.Prerelease || rel.Draft {
return "", false, nil
}
l, ok := parseVersion(rel.Tag)
if !ok {
return rel.Tag, false, nil
}
cur, ok := parseVersion(cfg.Current)
return rel.Tag, ok && l.newerThan(cur), nil
}
func get(ctx context.Context, c *http.Client, url string, max int64) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
+17
View File
@@ -135,3 +135,20 @@ func TestVersionOrdering(t *testing.T) {
}
}
}
func TestCheckReportsWithoutInstalling(t *testing.T) {
srv := fakeGitea(t, "v0.2.0", false, false)
for _, c := range []struct {
current string
newer bool
}{{"v0.1.0", true}, {"v0.2.0", false}, {"v0.3.0", false}, {"dev", false}, {"v0.1.0-local", false}} {
cfg, exe, restarted := setup(t, srv, c.current)
tag, newer, err := Check(context.Background(), cfg)
if err != nil || tag != "v0.2.0" || newer != c.newer {
t.Errorf("%s: tag=%q newer=%v err=%v", c.current, tag, newer, err)
}
if b, _ := os.ReadFile(exe); string(b) != "OLD-BINARY" || *restarted != "" {
t.Errorf("%s: Check changed things", c.current)
}
}
}