Skip to content

Commit 7457ff1

Browse files
committed
feat: send X-OpenBoot-Version header, show upgrade hint from server
- All CLI→Server HTTP requests include X-OpenBoot-Version header - CLI reads X-OpenBoot-Upgrade response header and prints one-time hint - SetClientVersion() called at startup from root command
1 parent 0431eee commit 7457ff1

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

internal/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ shell configuration, and macOS preferences.`,
3939
# Capture your current environment
4040
openboot snapshot --json > my-setup.json`,
4141
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
42+
config.SetClientVersion(version)
4243
updater.AutoUpgrade(version)
4344
config.RefreshPackagesFromRemote()
4445

internal/config/config.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,49 @@ import (
1111
"os"
1212
"regexp"
1313
"strings"
14+
"sync"
1415
"time"
1516

1617
"github.com/openbootdotdev/openboot/internal/system"
1718
"gopkg.in/yaml.v3"
1819
)
1920

21+
var upgradeHintOnce sync.Once
22+
23+
// checkUpgradeHint reads the X-OpenBoot-Upgrade header from server responses.
24+
// If the server says the CLI is too old, print a one-time hint to stderr.
25+
func checkUpgradeHint(resp *http.Response) {
26+
if resp.Header.Get("X-OpenBoot-Upgrade") == "true" {
27+
upgradeHintOnce.Do(func() {
28+
minVer := resp.Header.Get("X-OpenBoot-Min-Version")
29+
if minVer == "" {
30+
minVer = "latest"
31+
}
32+
fmt.Fprintf(os.Stderr, "Notice: your CLI is older than the server expects (min %s). Run: brew upgrade openboot\n", minVer)
33+
})
34+
}
35+
}
36+
2037
// isAllowedAPIURL delegates to the shared implementation in system package.
2138
var isAllowedAPIURL = system.IsAllowedAPIURL
2239

40+
// clientVersion is set by the CLI at startup via SetClientVersion.
41+
var clientVersion = "dev"
42+
43+
// SetClientVersion sets the version string sent in X-OpenBoot-Version headers.
44+
func SetClientVersion(v string) { clientVersion = v }
45+
46+
// versionTransport wraps http.DefaultTransport to inject the version header.
47+
type versionTransport struct{ base http.RoundTripper }
48+
49+
func (t *versionTransport) RoundTrip(req *http.Request) (*http.Response, error) {
50+
req.Header.Set("X-OpenBoot-Version", clientVersion)
51+
return t.base.RoundTrip(req)
52+
}
53+
2354
var remoteHTTPClient = &http.Client{
24-
Timeout: 15 * time.Second,
55+
Timeout: 15 * time.Second,
56+
Transport: &versionTransport{base: http.DefaultTransport},
2557
}
2658

2759
//go:embed data/presets.yaml
@@ -425,6 +457,7 @@ func fetchConfigBySlug(apiBase, username, slug, token string) (*http.Response, e
425457

426458
func parseConfigResponse(resp *http.Response, username, slug, token string) (*RemoteConfig, error) {
427459
defer resp.Body.Close()
460+
checkUpgradeHint(resp)
428461

429462
if resp.StatusCode == 403 {
430463
if token == "" {

internal/config/packages_remote.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,17 @@ func loadRemotePackages() ([]remotePackage, error) {
6464

6565
func fetchRemotePackages() ([]remotePackage, error) {
6666
apiURL := getAPIBase() + "/api/packages"
67-
client := &http.Client{Timeout: 8 * time.Second}
6867

69-
resp, err := client.Get(apiURL)
68+
req, err := http.NewRequest("GET", apiURL, nil)
69+
if err != nil {
70+
return nil, fmt.Errorf("fetch packages: %w", err)
71+
}
72+
73+
client := &http.Client{
74+
Timeout: 8 * time.Second,
75+
Transport: &versionTransport{base: http.DefaultTransport},
76+
}
77+
resp, err := client.Do(req)
7078
if err != nil {
7179
return nil, fmt.Errorf("fetch packages: %w", err)
7280
}

0 commit comments

Comments
 (0)