Commit Graph
572 Commits
Author SHA1 Message Date
DatCaptainHorseandClaude Opus 5.5 bfa7f2054f fix(neshub): send a new client live media, not what queued before it connected
A client's session is created with its first connection, and its media
channels with it, but each channel's writer only starts when that
carrier's own connection attaches. Every packet broadcast in between
piled up and went out in one burst once the connection was ready.
Client session records showed it as about 160 ms of audio arriving at
once at session start, and over half a second of video.

A session now takes a stream's media only once that stream's writer
exists. Before then there is nobody to give it to, and media is live or
nothing. No threshold: the rule is the attach event.

A video attach also asks for a keyframe and holds deltas until one
goes out. A fresh writer has sent no reference frame, so nothing else
is decodable, and the client no longer has to fail a decode and ask, a
round trip later.

New e2e test: audio broadcast before the audio carrier attaches never
reaches the client (fails without the fix: first position 0), and a
video attach asks for a keyframe. The pipeline-delay tests now build v4
payloads; they had been passing on v3 byte offsets by accident.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:20:23 +03:00
DatCaptainHorseandClaude Opus 5.5 e014c6f1f7 feat(protocol): put time on every media unit (v4)
Step 1 of nescore's 2026-10-16 media timing spec. Media carried no
send time and audio no time at all, so every layer had to infer lateness
from gaps between arrivals.

- clock: one sender clock, CLOCK_MONOTONIC in u64 microseconds, shared
  by every process that stamps media. Off unix the client uses its own
  process clock, which is all a receiver needs.
- Datagram headers (and PyroWave's) carry sent_us, stamped by neshub at
  hand-off to QUIC. Keyframe streams open with it too.
- Video carries captured_us (from nescapture's present time) in place of
  milliseconds since the encoder started. Audio carries its first
  sample's position on the PipeWire graph clock, which neswire already
  read to fill missed cycles. The IPC stamp is a u64 carrying either.
- delay: microseconds. The baseline no longer expires on a 30 s timer:
  clock skew is measured as the least-squares slope of each second's
  minimum one-way delay, and only a path change resets the baseline.
  observe() returns each unit's queueing delay.
- ReceiverReport carries video and audio queueing delay summaries for
  the rate control that will consume them.
- Shared encode/decode helpers for video and audio payloads, so the hub
  and receivers stop hand-building byte offsets.

No compatibility kept. STREAM_VERSION is 4. nescore needs a pin and the
client side (step 2) before it can talk to this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:23:02 +03:00
DatCaptainHorseandClaude Opus 5.5 3aeae631f6 fix(nesinit): grant the audio graph a nice level too, or the server stays non-RT
With RLIMIT_RTPRIO alone, neswire's client loop went SCHED_FIFO 83 and
the PipeWire server's graph loop stayed SCHED_OTHER. The server's
module-rt asks for nice -11 first, and when that is refused (Max nice
priority 0) it hands off to rtkit, which grants nothing in a box. The
client config asks for no nice level, which is why only neswire got it.

RLIMIT_NICE 39 (nice down to -19) next to rtprio 95, as PipeWire's own
25-pw-rlimits.conf grants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:38:51 +03:00
DatCaptainHorseandClaude Opus 5.5 36f13bfcfc fix(nesinit): give the audio graph realtime priority by rlimit, not rtkit
Inside a box, every PipeWire, wireplumber and neswire thread was running
SCHED_OTHER. The rtkit -> polkit -> system bus chain started but granted
nothing. With a game keeping the vCPUs busy, the graph's 5 ms cycles ran
late. pw-top counted no xruns, but neswire sent packets in bursts, and
the client saw 15-25 ms gaps in audio alone (video arriving on time in
the same seconds), which emptied its 10 ms cushion every 30 s or so.

pipewire and neswire now exec with RLIMIT_RTPRIO 95, set before the uid
drop. module-rt checks that first and sets SCHED_FIFO itself, which is
how PipeWire is meant to run outside a desktop session. The workload
doesn't get the grant, so a game can't take the CPU away from the graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:28:47 +03:00
DatCaptainHorseandClaude Opus 5.5 87092d4693 fix(nesinit,neshub): bind the media port the host forwarded
neslet now puts nestri.port= on the boot line and installs its forward
and pinned masquerade for that port before the box boots. nesinit hands
it to neshub as NESTRI_BIND_PORT, and neshub binds 0.0.0.0:<port> in
place of iroh's random default. Every flow, the relay's included, then
leaves on one public port, and the address the relay reports is the one
peers can punch through to. No nestri.port= keeps the old behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:41:18 +03:00
Wanjohi 6bbce800c7 feat(install): bash <(curl …) -u updates a host; the script asks sudo itself (#411)
```
bash <(curl -fsSL https://api.nestri.io/install.sh) -u            # update this host
bash <(curl -fsSL https://api.nestri.io/install.sh) <install-token> # make this machine a host
bash <(curl -fsSL https://api.nestri.io/install.sh) -h            # help
```
Piping still works: `curl -fsSL … | sh -s -- -u`.

**`-u` / `--update`.** No token and no questions. On a registered
machine it downloads and checksum-verifies the current host agent, swaps
it in and restarts the service. The agent then pulls whatever its
release changed (a raised image layout re-pulls the guest, jail and
Proton). It refuses:
- on a machine that was never installed, before downloading anything;
- while a box is running, since restarting the agent would end that
session;
- on an unknown option.

On a machine that already has the target version, it says so and exits
0.

**Root, asked once.** The host agent runs as root, so when the script is
started as anyone else it asks sudo once, before it changes anything.
Neither a pipe nor `bash <(…)` leaves a file sudo could run again, so it
fetches the same script to a private temp file, runs that as root with
the same mode and token, and removes the copy as soon as root has it
open. Only the settings somebody actually set (`NESTRI_HOST_VERSION`,
`NESTRI_BIN_DIR`, `NESTRI_BOX_STORE`) are carried across.

**Tested.**
- **On an NVIDIA host:** `--update` took it 0.2.17 → 0.2.18, verified
and restarted. A second run said "already 0.2.18. Nothing to do".
- **On the fleet host, run as an ordinary user** with `bash <(curl …)
-u`:
- it asked for sudo, the root copy ran and said "Nothing to do", and no
temp copy was left behind;
  - `-h` prints usage, and an unknown flag is refused.
- **In a clean container:** `-u` refuses with "not a Nestri host yet",
and a run with no token asks for one.
2026-10-16 00:31:51 +03:00
Wanjohi b50dce2cef feat(session): a finished session says why it ended (#412)
Until now every finished run said only `ended` or `failed`. A user stop,
a trial window running out, an idle timeout, a host restart and a game
quitting were indistinguishable afterwards.

**Schema.** Migration `0026_session_end_reason` adds two nullable
columns to `session`:
- `end_reason`: one of `user`, `window`, `hours`, `idle`, `left_queue`,
`game_exited`, `launch_failed`, `host_stopped`, `host_restart`.
- `time_claimed`: when a host took the run, set on `requested →
starting`.

**API.**
- `POST /session/:id/state` takes an optional `endReason`. With `ended`
or `failed` it is stored. With `starting` or `live` it is a 400 and the
run does not move.
- Leaving it out of a terminal report stores null, so host agents that
predate this keep working.
- `endReason` and `timeClaimed` are returned on every session response.
- The API writes the reasons it decides itself: `user` when a run nobody
has claimed is stopped, and `left_queue` when a waiting run is abandoned
(the `Left the queue` message is kept).

**New transition, `starting → ended`.** A run stopped while its game is
still downloading has failed at nothing, and never reached `live`, which
is where billing starts. Until now the only way out of `starting` was
`failed`.

**Release order: this goes first.** The host agent that sends
`endReason` is a separate change. This route's body schema is strict, so
without this deployed the current API answers that agent with a 400.

**Tests.**
- API: 185 pass, 5 of them new.
- Core: 41 session tests pass, 3 of them new.
- The new tests fail when the implementation is broken on purpose.
- No new type errors.
2026-10-16 00:20:49 +03:00
Wanjohi cf0606a81a chore(install): host agent 0.2.18 (#410)
Brings dev's install.sh to the 0.2.18 prod serves, so the next dev→prod
release cannot send installs backwards.
2026-10-16 00:16:02 +03:00
Wanjohi d565e6d445 fix(billing): every webhook says what it did, with the provider's ids (#406)
**Problem.** A billing webhook that changes nothing answers `200`
exactly like one that worked.
- A subscription made before checkouts carried the team id (`db43068`)
never matches a team.
- When one was cancelled, the API logged `a paid subscription matched no
team` with only the event type. Nothing said whose subscription it was,
so nobody could act on it.
- Deliveries that did apply were not logged at all.

**Changes.**
- `Polar.receive` returns the provider's `subscriptionId` and
`customerId`, in both camelCase and snake_case, as the rest of the
payload is read.
- For a `subscription.*` event, the event's own id is the
subscription's.
- For anything else, such as `order.refunded`, it is the
`subscription_id` field.
- The webhook route logs one line per delivery: `billing webhook {type,
team, standing, applied, subscription, customer}`.
- The unmatched-paid warning carries those ids and says what to do: find
the customer in the provider dashboard and set the team's plan by hand.
- A delivery naming a team that no longer exists is logged as an error,
not silently applied to nothing.

No behaviour change: the same deliveries apply to the same teams. This
is logging only.

**Tests.**
- `bun test src/billing/polar.test.ts`: 17 pass. One new test covers the
ids in both spellings, on a subscription event and on a non-subscription
one.
- `tsc` has no errors in the changed files. The existing errors are in
`utils/hook.ts` and `utils/validator.ts`, from Zod 4.
2026-10-15 19:31:36 +03:00
Wanjohi ebf31b5fe8 fix(build): the guest carries the libraries Proton's media plugins need (#405)
**Problem.** Proton's GStreamer plugins link `libvpx`, `speex`,
`libtheora`, `libvdpau` and `jsoncpp`, and neither Proton nor the guest
image carries them. Boxes log:

```
Failed to load plugin '.../gstreamer-1.0/libgstlibav.so': libvpx.so.12: cannot open shared object file
Failed to load plugin '.../gstreamer-1.0/libgstspeex.so': libspeex.so.1: cannot open shared object file
```

Wine's media stack then has no decoders, so a game whose videos go
through it plays them as nothing.

**Scope.**
- Not new. The stock `11.0-20261005` Proton and the patched `-n1` build
need exactly the same set.
- The fleet logged it on 2026-10-10 and again on 2026-10-15.

**Fix.** These five packages are added to the guest's runtime package
list.
- They were found by listing every `NEEDED` library of each published
Proton against this image; they were the whole gap.
- They track the sonames Proton was built against, as the new comment
says.

**Verification.** In progress: the image is building, and the library
check is rerun against it for both Protons.
2026-10-15 19:28:31 +03:00
Wanjohi 2d74cf73fd chore(install): dev serves the versions prod does (host agent 0.2.17, desktop 0.1.2) (#403)
Dev still said host agent 0.2.13 and desktop 0.1.0, while prod serves
0.2.16 and 0.1.2. The next dev→prod merge would have sent new installs
back three host releases. This brings dev to prod's numbers. No other
change: the rest of this branch is already on dev.
2026-10-15 11:53:14 +03:00
Wanjohi 424b13e277 fix: OpenGL games draw, Chromium games present, Proton makes non-ASCII file names (#401)
Games that showed a black screen with sound now render. Found by testing
on an AMD host: none of these were GPU-vendor specific.

**OpenGL games (Dome Keeper, Godot): fixed and confirmed.**
- `c017462`: the guest's Mesa had no EGL/GLX frontends, so a GL game had
nothing to load and never created a device. The image now ships them,
with glvnd's vendor file, so GL reaches zink and the capture layer sees
the frames.
- `5f2ccdf`: `vm.overcommit_memory=1`. The kernel default refused Dome
Keeper's 9 GiB reservation on an 8 GiB box before a byte was used.

**Chromium / NW.js games (Sheepy) stalled after one frame: fixed.**
- `a73c0c4`: nescope answers every commit with frame callbacks and
feedback, not only commits from a surface it maps to a window.
Chromium's GPU process draws for a window another process owns. Wine's
Wayland driver gives that surface no role, so its FIFO present waited
forever in the driver.
- `23ba9b5`: a window is matched by its surface, not its xdg toplevel,
so Xwayland windows are paced too.

**Proton could not create non-ASCII file names: patch added, build
pending.**
- `6770684`: Steam starts games with `LC_ALL=C`, and Wine reads that as
ASCII for every Unix file name. NW.js stops unpacking its app at
`media/• drum loop….webm` and shows an empty window.
- The patch reads an ASCII locale as UTF-8. Proton now takes patches
from `build/patches/proton/<submodule>/`, and a patched image is tagged
`-nN` (`11.0-20261005-n1`).
- Nothing points at that tag yet; hosts move to it in a separate change,
after it is built and tested.

Not covered: Metro 2033 Redux's report looks like the frame-callback
stall, but it was not retested.
2026-10-15 08:19:23 +00:00
Wanjohi 6770684e5c fix(build): let Proton's Wine create non-ASCII file names under Steam
Steam starts games with LC_ALL=C, and Wine reads the C locale as ASCII
for every Unix file name it makes, so a name like '• drum loop.webm'
could not be created at all. Sheepy, an NW.js game, unpacks its app from
a zip at start, stopped at that file, and showed NW.js's empty window.

Proton is now patched before it builds (patches/proton/<submodule>/), and
the first patch reads an ASCII locale as UTF-8. The image name gains -nN
for our patch level, so a patched build never takes the plain tag's name.
2026-10-15 10:56:29 +03:00
Wanjohi a73c0c4cfc fix(nescope): answer every commit, not only a mapped window's
A Vulkan client presenting FIFO waits in the driver for its frame
callback. Chromium's GPU process draws for a window another process
owns, which Wine's Wayland driver gives a surface with no role, so no
window here matched it and the game presented one frame and waited
forever: Sheepy's black screen. Nothing here paces against a display,
so every commit now gets its frame callbacks and feedback.
2026-10-15 10:28:44 +03:00
Wanjohi 23ba9b5cae fix(nescope): send frame callbacks to Xwayland windows
Since frame callbacks moved onto the commit path, the commit handler
found a window by its xdg toplevel. An Xwayland window has none, so no
Proton game got a frame callback or presentation feedback. Xwayland then
completes a flip only on its one-second fallback timer, and a game
presenting FIFO stalls in the driver after its first frame.
2026-10-15 10:03:44 +03:00
Wanjohi 5f2ccdfbf2 fix(nesinit): let a game reserve more address space than the box has RAM
The kernel's default overcommit policy refuses any single allocation
larger than RAM plus swap. A box has no swap, so the ceiling is the
box's own size, and an engine or driver that reserves address space it
never touches is refused before it uses any of it. Dome Keeper asked
for 9 GiB on an 8 GiB box and never started.

Set vm.overcommit_memory=1 alongside vm.max_map_count, so a request is
granted and the OOM killer acts on what is actually used. In a box that
is the game: init has already exempted itself.
2026-10-15 00:41:30 +03:00
Wanjohi c0174628cd fix(build): give the guest's OpenGL driver its EGL and GLX frontends
Mesa was built with zink as its only Gallium driver and every frontend disabled, so a game rendering with OpenGL found no GL and showed a black screen. EGL, GLX, GBM and GLES2 are enabled, with the x11 platform for Xwayland clients.
2026-10-14 11:23:35 +03:00
Wanjohi 1301ff0806 fix(billing): a returning customer's subscription reaches their team
The provider reuses a customer it already has under the same email and
leaves our team id off it, so the subscription arrived with no team,
was acknowledged, and changed nothing: a paid plan never applied. The
checkout now carries the team in its metadata, which the subscription
keeps, and the webhook reads it there when the customer has none. A
paid delivery that still matches no team is logged instead of dropped
in silence.
2026-10-13 21:37:22 +03:00
DatCaptainHorse 4d7e5d3b4e feat: update pixelforge 2026-10-12 13:49:50 +03:00
Wanjohi 24ad9f6b78 fix(api): a Steam sign-in on any host links the account
A sign-in on a person's own host recorded the enrolment but never
linked the Steam account, so their first launch asked them to link it
again with nothing in the app to do it from. The QR sign-in is the same
proof on their host as on ours.
2026-10-11 17:33:18 +03:00
Wanjohi 84bd6b899a feat(install): the desktop app downloads from a link that never changes
/download/windows and /download/linux redirect to a one-minute signed URL
for the current desktop app, from the same private bucket as the host
agent, so a download page can link to them once and every download still
passes through the API. Which version they hand out is a constant bumped
by a commit after a release is published, as the host installer's is.

/desktop.sh installs it on Linux for the calling user: one binary in
~/.local/bin and a launcher entry, no root.
2026-10-11 14:13:44 +03:00
Wanjohi 122fc12f86 chore(install): host agent 0.2.13
(cherry picked from commit 3d381673f8)
2026-10-11 14:07:40 +03:00
Wanjohi 6a9f675b9f feat(session): runs wait in line for a free slot on their host
A host holds only so many runs at once, and until now every waiting run
was handed to it regardless; the ones it could not hold failed there.

The host now says on its heartbeat how many runs it holds, and is handed
only as many waiting runs as it has free. The rest wait in line: paid
runs before free-weekend runs, then first come first served. Reading a
waiting run returns its place in line, and says when a paid run that
asked later has gone ahead, so a free player is never reordered quietly.

A waiting run nobody has asked after for a minute was abandoned, and
leaves the line rather than holding up everyone behind it. A host that
does not report its slots is handed every run, as before.
2026-10-11 13:44:18 +03:00
Wanjohi 4627343e3c feat(trial): the free days are Saturday, Sunday and Monday
Monday in place of Friday: the window is Saturday 00:00 to Tuesday 00:00,
Helsinki time, and the refusal outside it says Saturday to Monday.
2026-10-11 13:22:22 +03:00
Wanjohi aa824a1905 feat(box): a listed box names its machine's host and whether it is the fleet
The desktop app gives a new cloud user a fleet box and then opens that
machine's Steam sign-in on its hostname; GET /box now says which hostname,
and which box is the fleet one.
2026-10-11 12:08:07 +03:00
Wanjohi 4fc93ca4aa chore(install): host agent 0.2.12 2026-10-10 20:50:54 +03:00
Wanjohi 9269ee5d51 fix(neshub): an encoder that missed the codec choice is told again
The codec the clients agree on goes to the encoder over a socket that does
not exist until the game draws, and a client can connect first. The command
was dropped, the encoder started on AV1, and a client without AV1 sat on a
splash with sound and no picture. Commands now wait for the socket, and a
frame in a codec no client decodes sends the agreed set again, which also
covers a launcher handing over to a game with an encoder of its own.
2026-10-10 15:34:23 +03:00
Wanjohi ac713e62ae feat(billing): sizes move up one; 1080p is the reference and the default
xs is a box with no stream, sm 720p, md 1080p, lg 1440p and xl 4K. Each
resolution keeps its price, so md is now 1x. New boxes default to md, and
existing boxes move up one size so none of them drops a resolution.
2026-10-10 15:18:23 +03:00
Wanjohi 0f38898fd9 fix(session): commit the migration that adds a run's size 2026-10-10 15:13:54 +03:00
Wanjohi 8608506682 feat(session): a run picks its own size
POST /session takes an optional tier. The host starts the run at that
size, the allowance is checked against it, and the run is billed at it.
A run asked for without one runs at its box's size, as before.

The size belongs to the run rather than the box: a box is a game's
install on a machine, and the size is chosen each time it is played, so
playing bigger never means a second box and a second install.
2026-10-10 14:03:13 +03:00
Wanjohi 05a3fee87a feat(session): list your runs that have not stopped
Asking for a run on a busy box is refused, but the refusal does not say
what holds the box. A client that just lost its connection needs to know
whether the run in the way is the game it asked for, which it can rejoin,
or another one it should offer to stop. GET /session answers that.
2026-10-10 11:19:20 +03:00
Wanjohi 152337296d fix(library): downloads only from your boxes' hosts, named by their GPU
GET /library picked each game's most recent download row from any host,
so one person's library could show another person's machine id and
progress. It now reads only hosts the caller's boxes are on.

Boxes and library downloads gain `gpu`, the host's first reported card
model, so a client names a place by hardware a person recognises rather
than by a machine id. Null until a host reports one.
2026-10-10 10:03:49 +03:00
Wanjohi 77595ecb65 chore(install): host agent 0.2.11 2026-10-09 13:29:58 +03:00
Wanjohi 8054e62bd9 feat(machine): let the owner read what their machine has downloaded
GET /machine/:id/downloads lists every game the machine is downloading,
has ready or failed to download, with progress and each game's name.
The library already carries a download per game, but it is the most
recent one across every host, which is not what a person watching their
own machine set up wants to see.

Owner-scoped in the query: a machine that is not yours answers 404, the
same as one that does not exist.
2026-10-09 12:50:44 +03:00
Wanjohi 7670988ece feat(lifecycle): tell the host how many clients are watching
A host cannot tell a run somebody is playing from one nobody has looked
at in an hour, so it cannot end the second without guessing.

The hub now answers the address socket with a second line,
`viewers <n>`, counting clients that are streaming. Init reads it
alongside the address and sends a new `viewers` message to the host
when the count changes. A reader that takes only the first line gets
the address exactly as before, a server that never writes the second
line is still read, and a host that does not know the message skips it,
so either end can ship first.
2026-10-09 12:33:43 +03:00
Wanjohi f0bfe82d58 feat(session): let the owner end a run
POST /session/:id/stop. A run no host has claimed ends at once; one that
is starting or live is marked with time_stop_requested and its host is
handed a session.stop with reason `user` on every poll until it reports
the run ended. The host already ends a run the same way whatever the
reason, so no host change is needed.

Asking twice keeps the first time asked, and asking of a finished run
changes nothing. Somebody else's run answers 404, and a host cannot
stop a run as its owner.
2026-10-09 12:27:09 +03:00
Wanjohi 2ca3229d57 chore(install): host agent 0.2.10 2026-10-09 11:56:43 +03:00
Wanjohi 3fbcf459f2 machine: a host reports its GPUs, and the machine keeps them
The host report gains gpus: each card's model, VRAM in MiB when the
driver says, and the PCI id it was named by. Kept on the machine (a jsonb
column, written only when it changes) and returned by /machine. What a
person calls the machine, and the first measured part of the capacity a
scheduler will place by.
2026-10-09 11:37:40 +03:00
Wanjohi 3975de5027 install: host agent 0.2.9 2026-10-09 10:33:19 +03:00
DatCaptainHorseandClaude Opus 5.5 4054bb20d9 build: mesa patch keeping the GE rings off the discardable path
An evicted discardable BO has no GTT fallback, so under VRAM pressure a
box could lose its GE rings and fail every graphics submission until
VRAM freed up. Seen live: Control failed 127k submissions after a
neighbour inside its share evicted it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 15:35:17 +03:00
Wanjohi ba9191825c library: a sync drops the games the account no longer owns
A sync is the whole of what the account owns, but it only ever upserted,
so a revoked licence or a library from a previous Steam account stayed
listed forever. A sync that fully lands now soft-deletes the rest; an
empty list prunes nothing, and a game owned again comes back.
2026-10-08 12:19:05 +03:00
Wanjohi 4445f4836e api: accept the host's own tier label in a report
The report schema pinned each box's tier to our size enum, but hosts name
a box's shape by resolution (1080p). Every report naming a box was a 400,
so stop reasons never reached the run. Placement already stores the size;
the reported label is free text and is not stored.
2026-10-08 12:14:46 +03:00
Wanjohi 56e64d4ba8 install: host agent 0.2.8 2026-10-08 11:10:59 +03:00
Wanjohi a939defd5e install: host agent 0.2.7 2026-10-08 10:29:09 +03:00
Wanjohi 2fb59f4dd8 install: host agent 0.2.6 2026-10-08 09:58:43 +03:00
Wanjohi d89d73c263 install: host agent 0.2.5 (#368)
Pins the installer to host agent 0.2.5.
2026-10-08 06:37:58 +00:00
Wanjohi 514212bbbe Host agent runs as root; NVIDIA host programs ship in the base image (#366)
- **Installer:** piped into `sudo`, installs to `/usr/local/bin`, and
hands onboarding `/var/lib/nestri`. Root lets the host agent jail each
box under its own uid and give guests a network. A host that ran the
agent as the sudo user has that state copied over (never moved), so it
keeps its machine identity and its downloads. Pins host agent 0.2.4.
- **Base image:** `vhost-user-nvgpu` and `nvgpu-userspace`, static
(musl), from the same `NVGPU_REF` as the guest driver and Vulkan layer,
under `/boot/host`. The host agent takes them out at unpack, as it does
the kernel, and forwards the GPU when it finds the NVIDIA driver loaded.

Merge after the host agent 0.2.4 release: the installer pins it.
2026-10-07 19:59:29 +00:00
Wanjohi 7481150e97 fix(install): stop a running host agent before upgrading it 2026-10-07 19:09:01 +03:00
Wanjohi b0a42b4c37 fix(install): fetch the glibc host build, which is what releases publish from 0.2.3 2026-10-07 19:05:23 +03:00
Wanjohi 8520ddf6c5 install.sh: install host agent 0.2.3 2026-10-07 18:48:46 +03:00