-
-
Notifications
You must be signed in to change notification settings - Fork 101
feat(version): check for a newer release once a day #778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c211c14
feat(version): check for a newer release once a day
SantiagoDePolonia 4a6252d
fix(version): answer /version from cache and address review findings
SantiagoDePolonia ebef699
test(version): stop the slow-manifest test deadlocking package cleanup
SantiagoDePolonia 757631e
fix(version): redact the manifest URL and compare prereleases without…
SantiagoDePolonia cc1e49a
fix(version): build mirror URLs on the path and log only the manifest…
SantiagoDePolonia 037877b
fix(version): accept only a canonical visit id and bound forwarded he…
SantiagoDePolonia 9ac5da0
fix(version): warn when a manifest mirror would send its query unencr…
SantiagoDePolonia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package config | ||
|
|
||
| import "github.com/enterpilot/gomodel/internal/versioncheck" | ||
|
|
||
| // VersionCheckConfig controls the daily update check against the GoModel | ||
| // release manifest. | ||
| // | ||
| // The check sends the running version, the distribution name, and an | ||
| // anonymous install identifier. It never sends API keys, provider | ||
| // credentials, model names, prompts, usage data, client addresses, or the | ||
| // hostname the gateway is served on. Set Enabled to false to stop all | ||
| // outbound traffic from this subsystem. | ||
| type VersionCheckConfig struct { | ||
| // Enabled turns the daily update check on. | ||
| // Default: true | ||
| Enabled bool `yaml:"enabled" env:"GOMODEL_VERSION_CHECK_ENABLED"` | ||
|
|
||
| // URL is the base URL of the version manifest. The channel file | ||
| // ("core.txt" or "pro.txt") is appended to it. | ||
| // Default: https://gomodel.enterpilot.io/version | ||
| URL string `yaml:"url" env:"GOMODEL_VERSION_CHECK_URL"` | ||
|
|
||
| // IntervalHours is how often the background check runs. Each run is | ||
| // jittered so gateways started together do not query in lockstep. | ||
| // Default: 24 | ||
| IntervalHours int `yaml:"interval_hours" env:"GOMODEL_VERSION_CHECK_INTERVAL_HOURS"` | ||
|
|
||
| // TimeoutSeconds bounds a single manifest request. | ||
| // Default: 5 | ||
| TimeoutSeconds int `yaml:"timeout_seconds" env:"GOMODEL_VERSION_CHECK_TIMEOUT_SECONDS"` | ||
|
|
||
| // MaxDailyChecks caps how many manifest requests this gateway makes per | ||
| // day in total, so a hostile client cycling cookies cannot turn /version | ||
| // into an outbound request amplifier. | ||
| // Default: 500 | ||
| MaxDailyChecks int `yaml:"max_daily_checks" env:"GOMODEL_VERSION_CHECK_MAX_DAILY"` | ||
| } | ||
|
|
||
| // DefaultVersionCheckURL is the public release manifest served by the GoModel | ||
| // website. "/core.txt" or "/pro.txt" is appended per distribution. | ||
| const DefaultVersionCheckURL = versioncheck.DefaultURL |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| --- | ||
| title: "Version Awareness" | ||
| description: "How GoModel checks for a newer release, exactly what the check sends, and how to turn it off." | ||
| icon: "arrow-up-circle" | ||
| keywords: ["version", "update", "upgrade", "telemetry", "privacy", "air-gapped"] | ||
| --- | ||
|
|
||
| GoModel checks once a day whether a newer release exists, so you find out about | ||
| an upgrade without watching the repository. The result appears at the top of the | ||
| dashboard's **Settings** page and is available as JSON from `GET /version`. | ||
|
|
||
| ## Why we ask for this | ||
|
|
||
| We use it to see how many deployments are running which version of GoModel. | ||
|
|
||
| That matters most when a vulnerability is found. It tells us how many people are | ||
| affected and how many are still on a release that needs patching, so we know how | ||
| urgently to push a fix and how loudly to warn. Without it we would be guessing. | ||
|
|
||
| **We recommend leaving it on.** It sends no API keys, no prompts, no model names | ||
| and no usage data — see [What the check sends](#what-the-check-sends) for the | ||
| exact list, and turn it off with one environment variable if you prefer. | ||
|
|
||
| If you have any concerns about this data, please tell us: open an issue on | ||
| [GitHub](https://github.com/ENTERPILOT/GoModel/issues) or find us on | ||
| [Discord](https://discord.gg/gaEB9BQSPH). For anything security-sensitive, use | ||
| the contact in [SECURITY.md](https://github.com/ENTERPILOT/GoModel/blob/main/SECURITY.md). | ||
|
|
||
| The check reads one plain-text file: | ||
|
|
||
| | Distribution | URL | Contents | | ||
| | ------------ | -------------------------------- | ----------- | | ||
| | GoModel | `(thiswebsite)/version/core.txt` | `X.Y.Z` | | ||
| | GoModel Pro | `(thiswebsite)/version/pro.txt` | `X.Y.Z-pro` | | ||
|
|
||
| ## When it runs | ||
|
|
||
| Two triggers, both throttled: | ||
|
|
||
| - **A daily timer**, so a headless gateway nobody opens a dashboard on still | ||
| notices releases. The first check waits a random few minutes after startup and | ||
| the interval carries ±10% jitter, so a fleet restarted together does not check | ||
| in lockstep. | ||
| - **The first dashboard visit of each day**, per browser. A cookie named | ||
| `gomodel_version_check` holds `YYYY-MM-DD-{id}` — the day this browser last | ||
| checked plus a random id minted on its first visit. On every page load after | ||
| the first one each day, the dashboard reads that date and makes no request at | ||
| all. | ||
|
|
||
| A failed or unreachable check is never an error. The gateway serves its cached | ||
| result and tries again later. | ||
|
|
||
| ## What the check sends | ||
|
|
||
| <Note> | ||
| Nothing about your traffic is ever sent: no model names, no provider names, no | ||
| prompts or responses, no usage or cost data, no user paths. | ||
| </Note> | ||
|
|
||
| Every check carries: | ||
|
|
||
| | Header | Example | Meaning | | ||
| | ------------------- | ----------- | ----------------------------------- | | ||
| | `X-GoModel-Version` | `0.1.81` | the release you are running | | ||
| | `X-GoModel-App` | `GoModel` | `GoModel` or `GoModel Pro` | | ||
| | `X-GoModel-Install` | a UUID | random per-deployment id, see below | | ||
| | `X-GoModel-Source` | `scheduled` | `scheduled` or `dashboard` | | ||
|
|
||
| A check triggered by a dashboard visit also forwards an **allowlisted** slice of | ||
| that visit: the browser's `User-Agent`, `Accept-Language`, `Sec-CH-UA*` client | ||
| hints, and the visit cookie value (`X-GoModel-Date`). | ||
|
|
||
| Because it is an allowlist, anything not on it is dropped — including `Cookie`, | ||
| `Authorization`, `X-API-Key`, and `Referer`. A dashboard session credential or | ||
| master key cannot leave your deployment through this path. | ||
|
|
||
| Two things are withheld on purpose: **the hostname your dashboard is served on**, | ||
| which would identify your organization, and **client IP addresses**, which are | ||
| personal data. Neither is ever sent. | ||
|
|
||
| ### The install identifier | ||
|
|
||
| A random UUID stored in `install-id` in the gateway's data directory, created on | ||
| first use. It encodes nothing about your host, your organization, or your | ||
| configuration; it exists only so repeated checks from one deployment count as | ||
| one deployment. Disabling the check means it is never created. | ||
|
|
||
| ## Turning it off if you don't want to get the updates | ||
|
|
||
| ```bash | ||
| GOMODEL_VERSION_CHECK_ENABLED=false | ||
| ``` | ||
|
|
||
| This stops everything: no timer, no outbound request, no install id on disk. | ||
| `GET /version` still reports your local build with `"enabled": false`, and the | ||
| dashboard simply shows no update notice. | ||
|
|
||
| ## Air-gapped and mirrored deployments | ||
|
|
||
| Point the check at your own host and serve `core.txt` (or `pro.txt`) from it: | ||
|
|
||
| ```bash | ||
| GOMODEL_VERSION_CHECK_URL=https://releases.internal.example.com/version | ||
| ``` | ||
|
|
||
| The gateway appends `/core.txt` or `/pro.txt` based on its distribution, and | ||
| expects a bare version string with no leading `v`. | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Setting | Environment variable | Default | | ||
| | --------------------------------- | ----------------------------------------- | --------------------------------------- | | ||
| | `version_check.enabled` | `GOMODEL_VERSION_CHECK_ENABLED` | `true` | | ||
| | `version_check.url` | `GOMODEL_VERSION_CHECK_URL` | `https://gomodel.enterpilot.io/version` | | ||
| | `version_check.interval_hours` | `GOMODEL_VERSION_CHECK_INTERVAL_HOURS` | `24` | | ||
| | `version_check.timeout_seconds` | `GOMODEL_VERSION_CHECK_TIMEOUT_SECONDS` | `5` | | ||
| | `version_check.max_daily_checks` | `GOMODEL_VERSION_CHECK_MAX_DAILY` | `500` | | ||
|
|
||
| `max_daily_checks` caps the gateway's total outbound checks per day, so | ||
| `/version` cannot be used as an outbound request amplifier. | ||
|
|
||
| ```yaml config.yaml | ||
| version_check: | ||
| enabled: true | ||
| interval_hours: 24 | ||
| ``` | ||
|
|
||
| ## The `/version` endpoint | ||
|
|
||
| Public and unauthenticated, alongside `/health`. It answers from the cache and | ||
| never blocks on the network — a due check runs in the background, so an | ||
| unreachable release host never slows the dashboard down: | ||
|
|
||
| ```bash | ||
| curl -s http://localhost:8080/version | ||
| ``` | ||
|
|
||
| ```json | ||
| { | ||
| "app": "GoModel", | ||
| "version": "0.1.81", | ||
| "latest": "0.1.82", | ||
| "update_available": true, | ||
| "checked_at": "2026-08-26T09:12:44Z", | ||
| "enabled": true | ||
| } | ||
| ``` | ||
|
|
||
| `update_available` is conservative: build metadata is ignored, prereleases are | ||
| compared the way semantic versioning specifies (`1.0.0-rc1` < `1.0.0-rc2` < | ||
| `1.0.0`), and a development build (`dev`, a bare commit) never reports an | ||
| update. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
38 changes: 0 additions & 38 deletions
38
internal/admin/dashboard/static/dist/assets/index-BoR5elPo.js
This file was deleted.
Oops, something went wrong.
38 changes: 38 additions & 0 deletions
38
internal/admin/dashboard/static/dist/assets/index-CM8dltc2.js
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package app | ||
|
|
||
| import ( | ||
| "log/slog" | ||
| "time" | ||
|
|
||
| "github.com/enterpilot/gomodel/config" | ||
| "github.com/enterpilot/gomodel/internal/version" | ||
| "github.com/enterpilot/gomodel/internal/versioncheck" | ||
| ) | ||
|
|
||
| // newVersionChecker builds the update checker from configuration. The install | ||
| // identifier is only materialized when checks are enabled, so a deployment | ||
| // that opted out never writes one. | ||
| func newVersionChecker(cfg config.VersionCheckConfig) *versioncheck.Checker { | ||
| checkerCfg := versioncheck.Config{ | ||
| Enabled: cfg.Enabled, | ||
| URL: cfg.URL, | ||
| App: version.App, | ||
| Version: version.Version, | ||
| Interval: time.Duration(cfg.IntervalHours) * time.Hour, | ||
| Timeout: time.Duration(cfg.TimeoutSeconds) * time.Second, | ||
| MaxDailyChecks: cfg.MaxDailyChecks, | ||
| } | ||
| if cfg.Enabled { | ||
| checkerCfg.InstallID = versioncheck.InstallID() | ||
| if versioncheck.LeaksQueryInCleartext(cfg.URL) { | ||
| slog.Warn("version check URL sends its query string unencrypted; use https if it carries a credential", | ||
| "host", versioncheck.SafeURL(cfg.URL)) | ||
| } | ||
| } | ||
| return versioncheck.New(checkerCfg) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use a real or explicitly relative manifest URL.
The URL column contains the literal
(thiswebsite), so readers cannot copy a valid Core or Pro manifest URL. Use the default host shown at Line 114, or label these entries as<version_check.url>/core.txtand<version_check.url>/pro.txt.🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@claude We don't want to make clawlers visiting the website