Commit Graph
520 Commits
Author SHA1 Message Date
Wanjohi 191b08847b Release: rented GPUs, the free weekend, and everything on dev (#357)
Ships everything on dev to prod, including:

- POST /box: create a box on your own host or on the fleet
- Fleet entitlement during your own run; fleet runs billed to the box
owner's personal team
- The free weekend trial: Fri–Sun, 2 hours, one per team, email and
Steam account, off once five teams pay; session.stop jobs for overdue
trial runs
- Install tokens that register a host to an organisation's fleet
- Migrations 0018 and 0019, both additive

Plus the sign-in screen work, the guest image changes and the team/box
reads already on dev.

Fleet placement stays off until FLEET_ORGANISATION_ID is set on the API
host.
prod-191b088
2026-10-07 06:45:34 +00:00
Wanjohi 6fee7f86e6 Merge branch 'feat/fleet-trial' into dev 2026-10-07 09:43:44 +03:00
Wanjohi d92bc13ed9 feat(machine): an install command that adds a host to the fleet
An install token can now name an organisation instead of a team, and the
host it registers belongs to that organisation's fleet. Only members of
the organisation may mint one, since fleet hardware is rented to other
people. A token for both is refused, by the route and by a check
constraint, as it already is for a machine.
2026-10-07 09:42:51 +03:00
Wanjohi 47f8ff6f61 feat(trial): a free weekend on rented GPUs, for teams without a plan
A team with no plan may run on the fleet from Friday 00:00 to Monday
00:00, Helsinki time, for two hours of live play per weekend. One trial
per team, per email address and per Steam account; the claim outlives
the account, so closing it and signing up again is not a second trial.
Once five teams pay, the trial closes for everyone.

A trial run is marked on the session. Its time is counted from the run's
own live interval and kept out of the plan's allowance. Unlike an
allowance, the trial ends a run: the host's work poll now carries a
session.stop job for any trial run past its hours or its window, which
an agent that does not know the kind already skips.
2026-10-07 09:30:28 +03:00
Wanjohi 2da95deb36 feat(billing): bill a run on rented hardware to the box owner's team
A fleet machine belongs to an organisation, which is not something that
pays, so fleet runs were neither checked nor metered. The payer is now
the box owner's personal team, and the meter that starts and stops with
a run asks the same function the start check does, so the team checked
and the team billed cannot differ.
2026-10-07 09:25:32 +03:00
Wanjohi 08bbb22a1c feat(machine): rented hardware is open during a run you asked for
Fleet machines refused everyone, because nothing could yet say who had
paid for them. A run on one can only exist once it passed the start
check, so a starting or live run of your own box on that machine is now
what grants access, and it ends with the run. Holding a box is not
enough: a box costs nothing to hold.
2026-10-07 09:24:18 +03:00
Wanjohi f82666254e feat(box): create a box, on your own host or on rented GPUs
POST /box places a box once, at creation. On your own host it keeps the
existing rule (exactly one host, or refuse). On the fleet it picks an
online machine owned by the fleet organisation, the one holding the
fewest boxes. Each person holds at most one fleet box. Which organisation
is the fleet is deploy configuration, so no deployment rents hardware it
was not told to.

No machine online is a 429, since the same request succeeds once one is
back.
2026-10-07 09:23:12 +03:00
Wanjohi 9d7239a280 Merge branch 'feat/team-and-box-reads' into dev 2026-10-07 08:49:07 +03:00
DatCaptainHorse 459f787360 feat: proton-cachyos returns 2026-10-05 16:25:33 +03:00
DatCaptainHorseandClaude Opus 5.5 10fdeddc70 feat(neshub): iroh 1.3, and send each frame as one datagram batch
send_many_datagrams queues a frame's fragments under one lock hold and
one driver wake instead of one per fragment. It also rejects the whole
frame on TooLarge, so a path that shrinks mid-frame no longer leaves half
a frame on the wire for the client to time out. Eviction behaviour is
unchanged. A refused PyroWave frame now keeps its sequence number, since
nothing went out under it. pyro_bench mirrors both ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:57:53 +03:00
DatCaptainHorse 030dcc5a8c feat: RT for audio 2026-10-05 14:19:11 +03:00
Wanjohi 8c61cb5cbb feat(build): put virtio-nvgpu's Vulkan layer into the guest rootfs (#356)
On an NVIDIA host, NVIDIA's Vulkan driver in the guest lists ray
tracing, optical flow, binary import and CUDA kernel launch, then fails
`vkCreateDevice` with `VK_ERROR_INITIALIZATION_FAILED` for each of them.
An engine that enables what it is offered does not start. virtio-nvgpu's
guest layer (`vklayer/`, nestrilabs/virtio-nvgpu#5) hides those
extensions. It acts only on an NVIDIA device in a guest that has the
virtio-nvgpu driver, so on AMD and Intel boxes it loads and does
nothing.

- A new `nvgpu-layer-build` stage builds the layer from virtio-nvgpu at
`NVGPU_REF`, the same setting the kernel build uses for the guest
driver. The commit is recorded in `/usr/share/nestri/virtio-nvgpu.rev`.
- The layer and its manifest go in `/usr/lib` and
`/usr/share/vulkan/implicit_layer.d`. The missing-library check covers
the layer.

**Merge after nestrilabs/virtio-nvgpu#5.** The default `NVGPU_REF=dev`
has no `vklayer/` until that PR lands, so `make build` fails at this
stage before then.

Built on nestripc-1 with `NVGPU_REF=feat/v0.2-compute` (virtio-nvgpu
112eac4) and pushed:
- `ghcr.io/nestrilabs/nestri/base:latest` sha256:152a4485…
- `ghcr.io/nestrilabs/nesbox/jail:latest` sha256:df813051…, rebuilt on
that base from nesbox dev 6d37597, which includes the vhost-user
forwarding

Measured on box2 (RTX 3060, 595.104.02) with virtio-nvgpu's vkrt probe:
with the layer on, a plain device is created and none of the seven
extensions are offered. With it off, all seven are offered and fail with
-3.
2026-10-04 02:30:10 +00:00
DatCaptainHorse ef32d5f394 fix: Remove proton leftovers 2026-10-03 20:47:21 +03:00
Wanjohi 01d6f2302d build: put virtio-nvgpu's Vulkan layer into the guest rootfs
On an NVIDIA host the guest's Vulkan driver offers ray tracing, optical
flow and a few others it cannot create a device with, and an engine that
enables them does not start. virtio-nvgpu's layer hides them; it acts
only on a forwarded NVIDIA device, so AMD and Intel boxes are unaffected.

Built from virtio-nvgpu at NVGPU_REF, the ref the kernel build already
takes the guest driver from, and recorded in the image.
2026-10-02 22:43:03 +03:00
DatCaptainHorse 1aa20cffbb smaller rootfs 2026-10-02 17:09:15 +03:00
DatCaptainHorseandClaude Sonnet 5.5 86249001db feat: let a frame reader hand back what it read past a frame
The end that reads the first frame of an attach stream to learn whom the
connection is for then forwards the rest untouched, and what arrived behind that
frame in the same read belongs to the stream.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 17:09:15 +03:00
DatCaptainHorseandClaude Sonnet 5.5 d0ec87a139 feat: attach a shell to a running box
The host can now ask a box for a shell on a pseudo-terminal, the way a container
runtime attaches to a container. `HostToGuest::Attach` starts the process, and
the guest then dials the host on a vsock port of its own (7001) and carries the
terminal's bytes over a small framed stream: data both ways, a resize, a hangup,
and the exit status last. A screenful of output on the JSON-lines control channel
would hold every other message in the box behind it, which is why it is not
there. A failure to start is the exception, and is said on the control channel
because nothing has connected to the data port yet.

A shell is not a launch: it never ends the box, and the box ending ends it. It
is started through the same reaper as everything else, so it cannot become the
zombie that exists to be prevented. With a terminal on the other end the PTY is
ordinary; without one it is put in raw mode so a command run for its output gets
its bytes back unrewritten. Closing the stream hangs the shell up, process group
and all.

The frame codec is a pure bytes-in, frames-out module in nesprotocol with a
payload cap and no skipping of unknown tags, since a length read from a guest is
read across a trust boundary. No version bump: adding a message does not need
one, and a guest that predates it simply never dials.

The runtime stage gains strace, lsof and less, so a root shell in a box is not
one that cannot trace a process.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 17:09:15 +03:00
DatCaptainHorseandClaude Sonnet 5.5 e5447a91e4 fix: raise the map-count and open-file limits a Windows game needs
There is no service manager in a box, so nothing applies the distribution's
sysctl configuration, and the guest ran on the kernel's own defaults. Two of
them are too low for a modern engine under Proton:

- vm.max_map_count defaults to 65530. A large Unreal Engine 5 title maps far
  more regions than that, and the failure is a null allocation deep inside the
  engine or Wine rather than a message about mappings. It is set to 2147483642,
  the value SteamOS and the distributions that package Proton use. It is read
  back after the write, because an accepted write that did not take reports
  success while the box is still unconfigured.
- RLIMIT_NOFILE is raised on init, which every process in the box inherits. A
  child can lower a limit but cannot raise its hard one, so the unprivileged
  workload could never get above what it started with. 524288 is the hard limit
  Proton's documentation asks for. It is only ever raised.

Both are best effort and each failure says what it costs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 17:09:15 +03:00
DatCaptainHorseandClaude Sonnet 5.5 71801261a4 fix: get Rust from rustup in the builder stage
The distribution's rust package is built against the distribution's LLVM, and
when the two repositories this image draws from disagree about llvm-libs, rustc
itself fails to start with a symbol-lookup error, before any of our code is
involved and in every stage that builds Rust. rustup's toolchain links nothing
of the distribution's, so `rustup default stable` replaces the package.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 17:09:15 +03:00
DatCaptainHorseandClaude Sonnet 5.5 85e6f19977 feat: build umu-launcher into the guest image
`umu-run` is how a title that is not Steam's is launched, so the game fixes
Proton carries apply outside Steam. It is built from a pinned tag in its own
stage rather than installed from the distribution, whose package declares a
long list of lib32 libraries and desktop packages for the Steam Linux Runtime
container this image never runs.

It is built against the distribution's urllib3 and pyzstd instead of the copies
it can vendor: those are built from source with pinned build tooling, and a
rolling distribution moved past a pin (setuptools-scm 10 against a <10
ceiling) and failed the build. The image adds python-xlib, python-urllib3 and
python-pyzstd, none of which pull in anything 32-bit.

Two checks keep it honest. umu-run has to start, because a missing Python module
otherwise shows up as a game that never starts and reads like a script failure;
and no lib32 package may be installed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 17:09:15 +03:00
Wanjohi 55aa08b673 fix(auth): sign-in screen polish, and a local preview of every state (#355)
Polishes the sign-in screens and adds a way to see all of them locally.

**Screens**
- Code fields are drawn as one box per character (device code shows a
dash between halves). One real input sits under the boxes, so typing,
paste and autofill work normally, and with script off it falls back to
the plain field.
- A wrong answer or a refused character shakes the field and rings it
red.
- Focus is white instead of the brand colour; the button stays dim until
the form can be sent.
- One alert at a time: an error replaces "Code sent to …" instead of
joining it.
- The device confirm screen says "A device" instead of the client's
self-chosen name, with the Deny warning moved under the buttons.
- A device code typed with its dash (`BCDF-3467`) is no longer cut to
`BCDF-346`.

**Preview**
`bun run preview` in `apps/auth` serves all 18 screen states on :1338,
at desktop and phone sizes, reloading on save. No database or mail
needed.

To make that possible the issuer's screens moved out of the route
handlers into `packages/auth/src/ui/issuer.ts`, unchanged.

Tests: `bun test` in `packages/auth` (67 pass).
2026-10-02 03:13:34 +00:00
Wanjohi 1f82954037 style(auth): ring a refused or wrong answer red while it shakes
A code field silently dropped a character it does not accept, so typing
a letter into the emailed code, which is digits only, looked exactly
like a field that does not work. It now shakes the boxes and rings them
red, the same answer a wrong code gets. Dashes and spaces are still
taken quietly, since they are how a code is written.

The shake an error page plays on load now rings the field red too,
fading back to its usual border once it settles, on every field
including the email address. Reduced motion keeps the red and drops
the movement, since the red is the message.
2026-10-02 06:10:05 +03:00
Wanjohi 890ada8ead fix(auth): type code boxes through one real field
The boxes were one input each, with script moving focus between them.
That worked in a clean browser and failed in an ordinary one: on the
emailed code, whose first box is marked as a one-time-code field, typing
and pasting did nothing, while the device code, which is not marked,
worked. A password manager or autofill prompt hooking that field and a
script moving focus between six fields were working against each other.
It also left gaps: pasting into the third box with the second one empty
produced a code with a character missing.

Now the real input is stretched invisibly over the boxes and is the only
thing typed into, pasted into or autofilled; the boxes only display what
it holds, with the next one ringed and showing a caret. With nothing
moving focus there is nothing to fight, and a code cannot have a gap.
The browser's own validation message works again too, since the field
it points at is where the boxes are.
2026-10-02 06:03:48 +03:00
Wanjohi 39e4bebf64 feat(auth): draw code fields as one box per character
The emailed code and the device code are now typed into a row of boxes,
with a dash drawn between the device code's two halves. Typing moves to
the next box, backspace on an empty box goes back, the arrow keys move,
and a pasted or autofilled code, dashes and all, is spread across them.

It is an enhancement of the field and not a replacement for it. The
server still sends the single plain input, which works on its own with
script off; a short inline script draws the boxes over it and keeps it
in step. The real input is still the one that submits and the one whose
validity dims the button, so nothing else had to learn about boxes.
Pressing the button early moves to the first empty box, since the
browser's own message would point at a field nobody can see.
2026-10-02 06:00:08 +03:00
Wanjohi 6d654f666f fix(auth): accept a device code typed with its dash
A device code is shown as BCDF-3467 and will be typed or pasted that
way. The field allowed exactly 8 characters, so a correct code was cut
to BCDF-346 before the server, which ignores separators, ever saw it.
A grouped code's field now has room for its separators and a pattern
that allows a dash or a space between groups, and the device code is
declared as two groups of four.
2026-10-02 05:55:44 +03:00
Wanjohi e9b716fa63 style(auth): shake the field when its answer was wrong
Every error arrives as a new page, so an animation that runs on load
runs exactly when one does, with no script to trigger it. It plays once,
only on a form showing an error alert, and not at all for anyone who
has asked for reduced motion.
2026-10-02 05:55:44 +03:00
Wanjohi 11f62c69aa fix(auth): say "a device" on the confirm screen, and move the warning under it
The confirm screen named the client asking to sign in. That name is
chosen by whoever started the request, so it let a stranger's request
introduce itself as anything at all; and to the person reading, it is
just a device. It now says "A device is asking to sign in", and a test
checks that a client's self-chosen name never appears.

The warning about choosing Deny moves under the two buttons, as small
print in the same style as the terms line on the email screen, so the
question and its two answers read first.
2026-10-02 05:50:55 +03:00
Wanjohi 5353b9a1db fix(auth): reload the preview on every save, not just the first
bun --watch watches each imported file, and many editors save by
writing a new file and renaming it over the old one. The watch does not
follow the rename, so the first such save reloaded the preview and every
later one was silently ignored.

The preview now runs under a small supervisor that watches the source
directories and restarts the server, which sees a save however it was
made. It also stops the server when it is stopped itself, so the port
is not left held.
2026-10-02 05:48:10 +03:00
Wanjohi 86394dd769 fix(auth): show one alert at a time on the code screen
A wrong code showed "Invalid code" above "Code sent to ...", two answers
to one question in two colours. An error now replaces the confirmation
instead of joining it.
2026-10-02 05:48:10 +03:00
Wanjohi 97459421f8 style(auth): white focus, and a dim button until the form can be sent
A focused field was ringed in the brand orange, which reads as an error
and competes with the wordmark. Focus is now grey while the value is
incomplete and white once it would pass, so the field says it is done.

The submit button is dimmed while any field in its form is invalid. It
uses the browser's own validity, so there is still no script, and the
code field gains a pattern so a partly typed code counts as invalid.
The button stays clickable, so pressing it early still gets the
browser's message saying what is missing.
2026-10-02 05:48:09 +03:00
Wanjohi 36de16af7a fix(auth): say what went wrong under the input, and drop the default headline
Alerts were a bordered, tinted box with an icon, above the fields. They
are now a line of small text under the fields: red when something went
wrong, green when something just happened, no box and no icon.

The sign-in screens no longer carry the "One place for all the ways you
play" heading. Screens that pass their own heading (device approve,
deny and the rest) keep it.
2026-10-02 05:42:11 +03:00
Wanjohi bd4029ba6e feat(auth): preview every sign-in screen state locally
bun run preview (in apps/auth) serves a gallery of all 18 states the
sign-in flow can show, at desktop and phone sizes, each one also
viewable full size. It needs no database or mail: each state is the
real screen value, drawn by the real renderer with the real theme.

bun --watch restarts the server on any change to the renderer, the
stylesheet or the copy, and every page polls for the restart and
reloads itself, so styling is edit, save, look.
2026-10-02 05:27:28 +03:00
Wanjohi 8326d88894 refactor(auth): make every issuer screen a value of its own
The issuer's screens were written inline in its route handlers, so the
only way to look at one was to reach the condition that produces it: an
expired cookie, a refused grant, ten wrong guesses. They now live in
ui/issuer.ts and the handlers call them, with no change to copy, status
or markup.

The app's theme moves to its own module for the same reason: it can be
used without importing the issuer and its database stores.
2026-10-02 05:27:27 +03:00
Wanjohi 61eeecce43 fix: Change wording 2026-10-02 05:18:35 +03:00
DatCaptainHorseandClaude Sonnet 5.5 3149a9d4b1 feat: mount a compatibility layer image, and take Proton out of the rootfs
The boot descriptor gains `images`: read-only EROFS images mounted on their
own, with no writable layer over them. nesinit mounts them first, before
overlays and shares, with the same nosuid/nodev guards every layer has and
without noexec because what is in one is run. CONTROL_VERSION moves to 5: the
descriptor refuses fields it does not know, and a guest image without Proton is
useless to a host that does not send one, so the mismatch is named at the
handshake.

The guest rootfs no longer contains Proton. The checks that used to guard the
in-image copy (every library its programs and Wine's core link, an interpreter
behind the entry point) move to a compat_check stage that verifies the Proton
named by PROTON_IMAGE and leaves a marker in the image saying which one. The
leaf stages copy that marker out of it so the stage cannot be skipped as
unused. With nothing at a Proton path, a box started without one fails saying
where it looked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-02 02:20:41 +03:00
cf46bf4d12 feat: pyrowave support (#354)
At this point, why not..

Calling it `nespyro` since it's slightly modified, uses slang shader
language instead, along other things..

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:53:19 +03:00
DatCaptainHorseandClaude Sonnet 5.5 ca7115dba8 feat(kernel): refresh the config seed, build with thin-LTO
The seed was a release behind, so it now matches the tree it is migrated
onto. The scheduler, block-layer and TCP additions the tree ships are pinned
in the fragment, where the build checks them, rather than left to the seed's
defaults. Transparent huge pages move to always, since games map big heaps
without asking for them.

Clang builds the kernel now: whether thin-LTO is offered at all is decided
when Kconfig runs, so LLVM=1 applies to the config steps as well as the
compile, and missing tools fail before anything starts. Allocation and call
path hardening (init-on-alloc, freelist hardening, stack protector) and
debug info are pinned off alongside the speculation mitigations, and the
slab pin the minimal seed needs is restored.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 17:30:32 +03:00
DatCaptainHorse a493157d41 fix: nescope presentation handling, add mold to rootfs builds, update
mesa rev
2026-09-30 14:07:03 +03:00
DatCaptainHorse c283a3ea60 fix: nescapture overholding locks, nescope pacing and mistakes 2026-09-29 17:00:00 +03:00
Wanjohi 1c1900e509 readme: host requirements, and Unraid as an untested VM route 2026-09-29 10:22:36 +03:00
Wanjohi bc59f7842b feat(api): create a team; slugs the website serves as pages are reserved
POST /team makes another team with the caller as owner. The website now
serves a team at nestri.io/<slug>, so Team.Slug refuses its top-level
pages (home, new, pricing, ...) and a personal team whose generated slug
would be one gets a suffix instead.
2026-09-28 23:56:13 +03:00
Wanjohi df657e84ab feat(api): read and rename your teams, list their members, list your boxes
GET /team, PATCH /team/:id (owner or admin; a taken slug is a 409 on
slug), GET /team/:id/members, GET /box. A team you are not on is a 404
everywhere, so ids cannot be probed. Team.Name and Team.Slug are exported
so a client checks the same rule the API does.
2026-09-28 23:09:27 +03:00
Wanjohi 38b8a43a1b Release: host install, billing fixes, nvgpu guest, media bitrate (#352)
Promotes `dev` to `prod`: install tokens with `/install.sh` and signed
downloads (#351), controller support (#350), the nvgpu guest (#349),
media bitrate control and HDR (#346), and three billing fixes. Includes
migration `0017_install_token`.
prod-38b8a43
2026-09-28 08:33:44 +00:00
Wanjohi b281cb102e feat: Install a host with one pasted command (#351)
Lets a person put a machine on their team by pasting one command from
the dashboard, instead of copying a user session onto the machine.

## What changes

- **Install tokens** (`packages/core/src/machine/install-token.*`,
migration `0017`). Issued for one team, spent by their first use, expire
after an hour, stored only as a SHA-256 digest. Redeeming is one
conditional `UPDATE` inside the registration transaction: concurrent
redemptions of one token register exactly one machine, and a failed
registration leaves the token unspent.
- **`POST /machine/install-token`** (session): mints a token for a team
the caller belongs to, with the same team resolution and membership rule
as `/register`.
- **`POST /machine/install`** (public): spends a token and returns the
same id, slug and one-time secret as `/register`. Unknown, expired and
used tokens are refused identically.
- **`GET /install.sh`**: a POSIX script embedded at build time. It
checks the platform, asks where box images should live, downloads the
host agent at a pinned version, verifies `SHA256SUMS`, installs to
`~/.local/bin` for the calling user (never root), and hands over with
the token in the environment rather than argv.
- **`GET /install/:component/:version/:asset`**: redirects to a
one-minute signed URL on a private S3-compatible bucket, so every
download passes through a route that can be logged or turned off. The
SigV4 signer uses Web Crypto and is tested against AWS's published
example.

## Configuration

New optional settings: `RELEASES_BUCKET`, `RELEASES_ENDPOINT`,
`RELEASES_REGION`, `RELEASES_ACCESS_KEY_ID`,
`RELEASES_SECRET_ACCESS_KEY`. Without them, downloads return `503` and
everything else is unaffected. Scope the key to reads on that one
bucket.

## Tested

- Core: single use, a five-way race registering exactly one machine,
expired and unknown tokens refused alike.
- Signer: matches the AWS example signature.
- End to end on a real Linux host against a local stack: install script,
signed download from an S3 store that verifies SigV4 (a tampered
signature is refused), registration, and the agent coming online.
2026-09-28 08:07:04 +00:00
Wanjohi 065af689b3 feat(api): serve the host installer and its downloads
GET /install.sh serves a POSIX script, embedded in the API at build time
so the script and the routes that redeem its token ship together. It
checks the platform, asks where box images should live, downloads the
host agent at a pinned version, verifies it against SHA256SUMS, installs
it for the calling user and hands over with the token in the environment
rather than argv.

GET /install/:component/:version/:asset redirects to a one-minute signed
URL on a private S3-compatible bucket, so every download passes through a
route that can be logged or switched off. The SigV4 signer is written
against Web Crypto and checked against AWS's published example.
2026-09-28 09:12:23 +03:00
Wanjohi 54dbe8628a feat(api): issue and redeem install tokens
POST /machine/install-token mints a token for a team the caller belongs
to, with the same team resolution and membership rule as /register.
POST /machine/install needs no session: it spends the token and returns
the same id, slug and one-time secret as registering directly, refusing
unknown, expired and used tokens identically.
2026-09-28 08:36:12 +03:00
Wanjohi f691b56c0c feat(core): one-time install tokens that register a host to a team
Installing on a host should be a command a person pastes, not a user
session copied onto a machine. A token is issued for a team, spent by its
first use, expires after an hour and is stored only as a digest, because
it travels in that command and so lands in shell history.

Redeeming is one conditional update inside the registration transaction:
concurrent redemptions of one token register exactly one machine, and a
registration that fails leaves the token unspent.
2026-09-28 08:36:09 +03:00
451b495de6 feat: controller/gamepad support (#350)
Adds `nesgamepad`, basically "diet-coke vimputti" for Nestri's microVM
needs here.

Comes with direct mapping for:
- Sony
  - DualShock 4 (v2), DualSense
- Microsoft
  - Xbox 360 pad, Xbox One S pad
- Nintendo
  - Pro Controller
- Generic pad for unknown controllers

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 17:50:02 +03:00
291fabd145 feat: nvgpu guest (#349)
Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:17:00 +03:00
0811f57f1a feat: media bitrate control, HDR (#346)
Fixes: #335 

Still a work-in-progress.

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Wanjohi <elviswanjohi47@gmail.com>
2026-09-25 12:13:34 +03:00