|
Download docs/autoupdate.md from graphene-lab/AgentBridge: direct link, hf CLI and curl.
- Browser
- Download file 7.25 kB
-
https://huggingface.co/graphene-lab/AgentBridge/resolve/main/docs/autoupdate.md
- Command line
-
hf download hf://graphene-lab/AgentBridge/docs/autoupdate.md
-
curl -L -o autoupdate.md https://huggingface.co/graphene-lab/AgentBridge/resolve/main/docs/autoupdate.md
7.25 kB
| # AgentBridge β automatic updates (architecture) | |
| How AgentBridge updates itself from the GitHub Releases page, and why it is designed | |
| this way. Implemented in `AutoUpdate.cs` (a self-contained static class) with two hooks | |
| in `Program.cs` and one menu item in `Tui.cs`. | |
| ## What it does | |
| At **startup** the app asks GitHub for the latest release. When the released version is | |
| **newer** than the running one β and auto-update is enabled β it: | |
| 0. **refreshes the tool plugins first** (`PluginUpdater`): every loaded plugin is | |
| checked against its repo's GitHub release and, when a newer self-contained zip | |
| exists, installed into `Tools/<Plugin>/` before the app archive is applied (the | |
| archive also carries the plugins β this covers plugin releases that landed between | |
| two app releases). When agents are executing the refresh is refused and the whole | |
| update is retried after 30 minutes (`ScheduleRetryIn`); | |
| 1. downloads the platform archive (`agentbridge-<rid>.tar.gz`) to `%TEMP%`; | |
| 2. extracts it to a temp folder; | |
| 3. spawns the **new** executable from the temp extract as an updater process | |
| (`--apply-update <target> <extract> <oldPid>`) and exits; | |
| 4. the updater waits for the old process to terminate, copies the **changed** files | |
| (the executable **last**, via a `.old` rename for rollback), restarts the app with | |
| the original command line and cleans up. | |
| Any failure along the way leaves the current version running untouched β the update is | |
| best-effort by design. | |
| ## Why the two-process swap | |
| On Windows a running executable cannot be overwritten, and at startup the app is | |
| already the process mapping its own exe β so the executable can **never replace | |
| itself**, neither during execution nor at the next start. The swap must be done by a | |
| **different process**: the new exe extracted to `%TEMP%` is a different file, so it can | |
| replace the installed one once the old process is gone. | |
| This was verified both against Microsoft's documentation (`CreateFile` sharing modes: | |
| the image is opened read-only for the lifetime of the process) and empirically on | |
| Windows: a child process does **not** keep the parent's exe locked after the parent | |
| exits β the lock lasts only as long as the owning process. Hence the pattern "spawn the | |
| updater and exit immediately" (never wait for the updater from the app). | |
| ## The file storage tiers (what an update may touch) | |
| The update mechanism respects the three tiers defined in | |
| [RELEASING.md](../docs-dev/RELEASING.md#what-an-update-must-never-touch--the-file-storage-tiers): | |
| | Tier | Rule | | |
| |---|---| | |
| | `PersistentData\` (user-editable config) | never touched β not present in the archive, never written | | |
| | OS app-data folder `<AppData>\agent\` (SMTP/IMAP credentials, `setup.json`, `autoupdate.json`; LLM API keys are NOT here β they live per-provider in `providers.json`) | never touched β outside the app folder by construction | | |
| | Distribution content (everything else) | replaced when changed, with **two exceptions** | | |
| The exceptions: **`appsettings.json`** (server config) and **`providers.json`** (LLM | |
| provider definitions) are stripped from the extracted copy before the swap, so the | |
| user's configuration is never overwritten. Everything else β including the other | |
| `.json` files in the archive (`.playwright/package/*.json`, | |
| `agent.staticwebassets.endpoints.json`) β is distribution content and is replaced. | |
| Protection is by **whitelist**, never by file extension. | |
| Files are copied only when they changed (length + SHA-256 comparison); the executable | |
| is always replaced (never compared β a fresh build is the point of the update). | |
| ## Version check (no GitHub API) | |
| - Current version: `Assembly.GetExecutingAssembly().GetName().Version` β the numeric | |
| `1.yy.MM.dd` baked into the binary by the csproj (the `-prerelease` suffix lives only | |
| in the informational version, so a prerelease build compares by its numeric date). | |
| - Latest version: an HTTP `HEAD`/`GET` to | |
| `https://github.com/Graphene-Lab/AgentBridge/releases/latest` with | |
| `AllowAutoRedirect = false`; the redirect's `Location` header carries the tag | |
| (`v1.26.8.10`). No API call, so the unauthenticated rate limit (60 req/h/IP) is never | |
| an issue. A download URL is then constructed deterministically: | |
| `https://github.com/Graphene-Lab/AgentBridge/releases/download/<tag>/agentbridge-<rid>.tar.gz`. | |
| - GitHub imposes no bandwidth limit on release downloads (the Acceptable Use Policies | |
| "Excessive Bandwidth Use" clause is a relative anti-abuse rule, not a quota); the | |
| client still uses bounded timeouts so a slow/offline GitHub never blocks startup. | |
| ## Platform detection | |
| The archive name maps from the running OS + architecture (the RIDs built by | |
| `release.yml`): | |
| | Platform | Archive | | |
| |---|---| | |
| | Windows x64 | `agentbridge-win-x64.tar.gz` | | |
| | Linux x64 / arm64 | `agentbridge-linux-x64.tar.gz` / `agentbridge-linux-arm64.tar.gz` | | |
| | macOS x64 / arm64 | `agentbridge-osx-x64.tar.gz` / `agentbridge-osx-arm64.tar.gz` | | |
| | anything else (e.g. Windows arm64) | no archive β the check is skipped | | |
| The update is also skipped when the app runs from `dotnet run`/`dotnet <dll>` (dev | |
| mode): the entry assembly is a `.dll`, so the swap would target `dotnet` itself. | |
| ## Enabling / disabling | |
| - **Default: enabled** (`true`). | |
| - **TUI**: menu **File β Auto-Update** toggles the check on/off and persists the choice | |
| to `<AppData>\agent\autoupdate.json` (the OS app-data folder, same tier as | |
| `setup.json` β updates never touch it). | |
| - **`appsettings.json`**: `"AutoUpdate": { "Enabled": true }` is the shipped default. | |
| - **Command line**: `--no-update` disables the check for that launch (for services/CI | |
| that manage the binary themselves β e.g. systemd units should pass it). | |
| Precedence: `--no-update` > persisted toggle > `appsettings.json` > built-in `true`. | |
| ## Restart and rollback | |
| - The updater restarts the app with the **original command line** (minus `--no-update`), | |
| so a TUI session stays a TUI and a `--headless` service stays headless. | |
| - The old executable is kept as `agent(.exe).old` until the **next successful start**, | |
| which deletes it (and any stale `%TEMP%\agentbridge-update` area). A broken new | |
| executable can be recovered by renaming the `.old` back manually. | |
| - If the old process does not exit within two minutes, the updater aborts and the old | |
| version keeps running. | |
| ## Security notes | |
| - Downloads travel over HTTPS from GitHub's CDN; archives are not checksum-verified | |
| (GitHub does not publish per-asset hashes). A `sha256.txt` asset could be added to | |
| `release.yml` later if verification is wanted. | |
| - The binaries are unsigned; the trust model is "GitHub + HTTPS". Fine for personal | |
| distribution; sign the binaries before wider roll-out. | |
| ## Testing | |
| - Unit-testable pieces: version comparison, RID mapping, "file changed" comparison | |
| (length + SHA-256). | |
| - End-to-end (Windows): run a published build from a folder, set | |
| `AutoUpdate.Enabled` to force a fake/lower current version (or run an old build), | |
| and observe the swap: temp extract β updater β `.old` β restart with the new exe. | |
| - The updater wait loop can be observed by keeping the old process busy past its exit | |
| deadline β the update must abort cleanly. | |