Update Server
An optional ASP.NET Core host that layers rollout, telemetry, gating, a publish API and an admin dashboard onto the same static feed — the client contract is unchanged.
When to run it
You don't need it to ship updates — a static host is enough. Run Beep.Installer.UpdateServer only for what a static host can't do:
| Capability | Needs a server? |
|---|---|
Ship updates, deltas, module updates, Required updates | No — static host |
| Staged / percentage rollout (stable per-client cohorts) | Yes |
| Telemetry & active-version stats | Yes |
| Token-gated (license) downloads | Yes |
| Publish API + admin dashboard | Yes |
Endpoints
| Method / Route | Auth | Purpose |
|---|---|---|
GET /{channel}/feed.json?cid=… | public | The rollout-tailored feed for a client (cohort from cid). |
GET /artifacts/{channel}/{version}/{**path} | public / ?token= | Setup.exe, _blobs/*, manifest — token-gated when the channel requires it. |
POST /api/telemetry | public | Client reports an update event. |
POST /api/publish | X-Api-Key | Publish a version (multipart: setup + optional payload + fields) — runs FeedPublisher. |
POST /api/rollout | X-Api-Key | {channel,version,percent?,enabled?}. |
POST /api/gate · /api/token | X-Api-Key | Toggle token-gated downloads; issue a download token. |
GET /api/channels · /api/stats | X-Api-Key | Admin reads (dashboard). |
Staged rollout uses a deterministic per-(client, version) cohort bucket, so a client consistently is (or isn't) in a rollout and the cohort reshuffles per release. Active-version stats count only completed applies, not checks.
Run & deploy
dotnet publish Beep.Installer.UpdateServer -c Release -o /opt/beep-updates
export UpdateServer__ApiKey="$(openssl rand -hex 24)"
export UpdateServer__TokenSecret="$(openssl rand -hex 32)"
export UpdateServer__StorageRoot="/var/lib/beep-updates"
export ASPNETCORE_URLS="http://127.0.0.1:5080"
/opt/beep-updates/Beep.Installer.UpdateServer
- Terminate HTTPS at nginx/Caddy/IIS and proxy to Kestrel; raise the proxy body limit for large Setup.exe uploads.
- The admin dashboard is at
/— and admin is locked until you change the default API key (the server rejectschange-me). - File-backed, no database:
channel.jsonper channel +telemetry.jsonl. Back upStorageRoot— it's the whole server. - Front
/artifacts/*with a CDN for large fleets (immutable, content-addressed).
Publish from CI
curl -X POST https://updates.example.com/api/publish \
-H "X-Api-Key: $KEY" \
-F channel=stable -F product=MyApp -F version=1.2.3 -F rolloutPercent=10 \
-F setup=@out/Setup-MyApp-1.2.3.exe \
-F payload=@out/payload.zip # optional; enables deltas
The server runs the same FeedPublisher the installer uses (immutable versions, solid→loose blob expansion), then stages the version at 10% rollout. Bump it in the dashboard when confident.