Skip to content

feat(update): optional GitHub token for firmware update checks - #152

Open
mrdarrengriffin wants to merge 1 commit into
OpenDisplay:mainfrom
mrdarrengriffin:feat/github-token
Open

mrdarrengriffin wants to merge 1 commit into
OpenDisplay:mainfrom
mrdarrengriffin:feat/github-token

Conversation

@mrdarrengriffin

Copy link
Copy Markdown

Motivation

The firmware update entity talks to the GitHub REST API anonymously (releases/latest every 6 h per device, releases/tags/<tag> on install). Anonymous calls are limited to 60 requests per hour per public IP, and that budget is shared by every OpenDisplay device in the house and every other tool on the same network that touches the GitHub API (HACS, other integrations, scripts, browsers on the same NAT).

With many displays this runs out. With 18 displays here, every device scans at startup and every 6 h, and update.install regularly fails with:

Could not fetch release metadata: 403, message='rate limit exceeded'

The error gives no hint about why or what to do, and nothing in the integration lets you use an authenticated request (5,000 per hour).

What changed

  • Options flow: a new optional GitHub token field (password selector, stored in entry.options, not data). It is never logged and is added to the diagnostics redaction list. The rate limit is per network rather than per device, so a token set on any OpenDisplay entry is used for all of them. You don't have to paste it into every device.
  • Authenticated requests: when a token is set, requests to api.github.com send Authorization: Bearer <token>. The asset download (browser_download_url, served from github.com/CDN rather than the API) is unchanged and sends no token. With no token set, behaviour is the same as before.
  • Shared release metadata: API responses are cached per URL for 1 h in hass.data, with a per-URL lock, and shared by all entries. N devices on the same firmware repo now cost one request per scan (and one at startup) instead of N. Errors are not cached.
  • Clear rate-limit error: a 403/429 with x-ratelimit-remaining: 0 raises a translated HomeAssistantError (github_rate_limited). It states the limit, the local reset time (from x-ratelimit-reset) and suggests adding a token. The periodic check logs the same hint as a warning. Other 403s keep the existing handling.
  • Translations: I edited strings.json and translations/en.json by hand (option label, description and the new exception). Other languages are left to the translate workflow, and scripts/verify_translations.py passes.
  • Tests (tests/test_update.py, tests/test_options_flow.py):
    • no token → no Authorization header
    • token in options → Bearer header and the latest version is still reported
    • a second scan within the cache window makes no new request
    • a rate-limited install raises github_rate_limited with the limit placeholder, never starts the flash and clears progress
    • the options flow stores the token in options (not data) and removes it when the field is emptied

How to test

  1. scripts/test (and scripts/test --min-ha) runs the new tests with the rest of the suite.
  2. By hand: open any OpenDisplay device → Configure → paste a fine-grained GitHub token. It needs no permissions: public repositories, read-only.
    • Reload, then check the response headers of a release request. x-ratelimit-limit should be 5000, or turn on debug logging and watch the update checks succeed.
    • Clear the field and save. Requests are anonymous again.
  3. Rate-limit message: without a token, use up the anonymous limit (for example for i in $(seq 61); do curl -s -o /dev/null https://api.github.com/repos/OpenDisplay/Firmware_Silabs/releases/latest; done). Then press Install on an update entity. The error should name the reset time and suggest a token.

Notes

  • Ruff 0.15.17 (the repo's pinned version): ruff check and ruff format --check are clean.
  • I could not run the pytest suite locally: it needs Python 3.14 plus the PHCC/HA environment from uv.lock. CI should cover it.

Screenshots

N/A (one extra field in the existing options form).

Firmware update checks call the GitHub REST API anonymously, which allows
60 requests per hour per public IP. That budget is shared by every
OpenDisplay device and every other tool on the same network, so a household
with many displays runs out and update.install fails with
"Could not fetch release metadata: 403, rate limit exceeded".

- Options flow: optional "GitHub token" (password field, stored in entry
  options, never logged, redacted from diagnostics). A token set on any
  OpenDisplay entry is used for all of them, since the limit is per network.
  When set, api.github.com requests send "Authorization: Bearer <token>"
  (5,000 requests per hour). Behaviour without a token is unchanged.
- Release metadata responses are cached per URL for an hour and shared by all
  entries, so N devices on the same firmware repo cost one request per scan
  instead of N (also at startup).
- A 403/429 with x-ratelimit-remaining: 0 now raises a translated
  HomeAssistantError that names the limit, the reset time (from
  x-ratelimit-reset) and suggests adding a token; the periodic check logs the
  same hint instead of a bare "rate limited".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant