The counters this fills are the ones the windows already knew how to read. What was missing was anything that put a number in them. Burn is recorded as segments: one stretch of one run at one unchanging rate, opened when the rate becomes true and closed when it stops being. Not a row per session, because a session's rate does not survive its own lifetime — a second run changes what the account spends per second while the first is still going, and a rate that applied from that moment must not be backdated over the time before it. Not a row per event either, because burn accrues against an envelope that is held rather than per thing consumed. Closing a segment is what moves burn into the counters, so a long run lands incrementally instead of all at the end. Burn that only arrives when a session stops is burn that cannot refuse the next one, and a bar that does not move while something is running is a bar nobody believes. The counters are written with the staleness rule as a single statement: add to the total if its stamp is still inside the window, otherwise start again from this amount. Reading and then deciding would be two statements with a gap, and the gap is where a concurrent tick doubles or vanishes. The first tick for a team and the thousandth are the same call, for the same reason. The gate sits at the one moment it is allowed to speak — before a run starts, never again. A limit refuses the next run and never interrupts one already going; someone losing a session mid-game to a meter does not come back. Every window is checked rather than the shortest, because they protect different things over different spans. The answer comes back with the created run rather than being thrown away: the response carries where each window stands, what the account spends per second now, and what one more run would cost. Every surface that can start a run has to show that before the click, and a second call for it is a call nobody makes. Asking twice would also let the number shown and the number billed disagree. Accrual is wired to the run's own state transition, in the same transaction that moves it. A session that went live without its meter starting is free hardware; one that ended without its meter stopping bills forever. Both are silent, so neither may be a second write that might not happen.
Run your games on a GPU you don't own — or one you do. Nestri puts an interactive workload in a hardware-accelerated virtual machine and streams it to you over QUIC, at a latency that lets you play rather than watch.
Note
This repository is mid-rewrite, and the documentation is behind the code. The guest-side components arrived recently and their docs are thin. Nothing here is stable yet: expect directories to move and interfaces to change. Proper documentation is on the way — issues and questions are welcome in the meantime, and are genuinely useful for deciding what to write first.
Try it now — nesdoctor
One thing here is finished and runs on its own machine, today:
# Linux and macOS
curl -fsSL https://doctor.nestri.io/install.sh | sh
# Windows
powershell -c "irm https://doctor.nestri.io/install.ps1 | iex"
It tells you whether your machine could host games for other people, and
measures the number that actually decides whether streaming a game feels
right — not your download speed, but how much latency your connection adds
when it is busy. A 500 Mbps uplink that queues for 300 ms under load cannot
carry a game; a 25 Mbps one with fq_codel can. Almost nobody has seen their
own figure.
upstream 35 Mbps
latency, idle floor 56 ms
latency, loaded 185 ms
added under load +129 ms grade F
presentation path x11 · bspwm
eDP-1 1920x1200 @ 60 Hz, 8-bit
Vulkan decode h264, h265
It also reads your display out of its EDID — resolution, refresh, colour depth, HDR transfer functions, BT.2020, chroma — and what your hardware can decode. Those decide what is worth sending over the wire, and we would otherwise be guessing from one panel in one room.
It does not stream a game. It is the piece that has to exist before
anything else can, and most machines will come back CLIENT — which is a real
answer, not a failure.
Downloads one binary, verifies its checksum, runs it, deletes it. Installs
nothing, needs no administrator rights, touches no system directory. Nothing is
uploaded: it prints a link, lists exactly what the link contains, and opens it
only if you press Enter. The scripts those URLs serve are
apps/nesdoctor/install/ in this repository, so you
can read them before you run them.
Source and the full story: apps/nesdoctor.
What is here
Two halves that meet over the network and share very little else, plus one thing that runs on your own machine.
The control plane — TypeScript
apps/api |
The public REST API. Identity, teams, machines, games, pairing. |
apps/auth |
A self-hosted OpenAuth issuer — Steam and SSH-key login. |
packages/core |
The domain: every table, every operation, no HTTP. |
packages/auth |
Shared auth types and subjects. |
Postgres for state. Both run on Cloudflare Workers today and as ordinary
containers wherever you like — one handler each, no infrastructure-as-code, and
a Dockerfile in each app. See docs/deploy.md and
docs/dns.md.
The guest — Rust, inside the box
These run inside a virtual machine, beside the game. None of them talk to the control plane.
apps/nescope |
A headless Wayland compositor for one fullscreen client. A lighter answer to the same problem gamescope solves. |
apps/nescapture |
A Vulkan implicit layer. It captures frames from inside the workload's own process and encodes them on the GPU that drew them — no copy out to the CPU and back. |
apps/neswire |
Audio capture and transport. |
apps/neshub |
One connection out of the box. Muxes video, audio, cursor and input into a single QUIC stream to the client. |
crates/nesprotocol |
The wire types they all share, so no two ends can drift apart silently. |
On your own machine — Rust
apps/nesdoctor |
Whether a machine can host a box, and what its connection and display can really do. The first executable form of our host requirements — until it existed, a host was qualified by a human reading a table. Four dependencies; everything that could be done with the standard library is. |
The hypervisor the guest components run under is nesbox,
a separate repository: a micro-VM with a real GPU in it, using virtio-gpu native
context rather than passthrough, so one card can host several boxes at once.
Why a virtual machine
A container shares the host kernel, which makes strong isolation hard and a GPU harder. A micro-VM boots in about as long, isolates properly, and — with native context — gets close to bare-metal graphics. That choice is what makes "many sandboxes, one GPU" possible instead of one tenant per card.
Getting started
bun install
cp .env.example .env # compose reads every credential from here
docker compose up postgres # the database
bun run db:migrate # schema
bun dev # control plane, local Cloudflare runtime
docker compose up --build # or: the whole control plane as containers
cargo build --workspace # guest components
cargo test --workspace
The guest components expect a Linux host with a Wayland-capable GPU stack, and are not much use on their own yet — they are pieces of a box, and the thing that assembles a box is not open yet.
nesdoctor is the exception and needs none of that:
cargo run --release -p nesdoctor
Status
Working: nesdoctor — released, and the only part a stranger can operate
today. The API, auth, the domain model, and the guest components listed above.
Not here yet: the box lifecycle, storage, the edge, and the client. Some of that will open as it is written; some is deliberately closed. What decides which is whether it handles your data — that half is open on principle — or decides our capacity, which is the part we sell.
Contributing
Early, and the ground moves. The two most useful things you can do right now
cost a minute each: run nesdoctor and send the result, because we have
almost no idea what the machines on the other end of this look like; and tell
us where the documentation failed you. Conventional commits; explain why in
the body.