mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-24 11:38:19 +03:00
b296918ab4e71b6d5a5019f21800cc838efb8bea
178
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b296918ab4 |
feat(api): record what a host says it is running
A host agent already sends a full inventory snapshot on a cadence, and nothing served the endpoint it sends it to — so every one of those calls answered 404. It fails quietly by design, because a dropped snapshot is meant to be corrected by the next one, which is exactly why nobody noticed: the only symptom is a line in the agent's own log. Kept separate from the heartbeat because the two have different loss tolerance. A dropped beat moves a host towards offline and unplaces it; a dropped snapshot costs nothing until the next one arrives. Folding them together would let a malformed inventory field make a healthy host look dead. Three rules decide what a snapshot may do, and the last two are why this is one core function rather than a loop in the route: - a box we know, that the snapshot names, takes the reported state - a box we know that was running, and that the snapshot omits, is stopped and says so — absence inside a snapshot is information - a box the snapshot names that is not placed on the calling host is never created, only reported back as a divergence The scope is in the `where` clause and not in the agent asking politely about its own boxes: a machine credential is a long-lived secret sitting on hardware in somebody's living room. `pid` and `uptimeS` are accepted and deliberately dropped. A pid is a number in another machine's namespace, and uptime is derivable from a run's start time, which is already stored and already trustworthy. |
||
|
|
9258c8dfef |
fix(deploy): make bun dev actually start, and sign-in actually work
Six defects found by running the thing rather than reading it. The previous change was verified by bundling, by tests, and by the container images — none of which start a Worker, so every one of these was invisible. **`bun dev` did not start.** It ran one multi-config process, which does not connect a service binding between the workers it loads; the API reported `AUTH [not connected]` and could not verify a token. It is two processes now, which is what the dev registry connects, and the second is backgrounded with the first killed on exit so stopping the pair stops both. **Neither server could bind.** Wrangler resolves `localhost` and takes `::1` first; a host with no IPv6 address on its loopback dies with a bind error from inside the runtime that names neither the app nor the port. `dev.ip` is pinned to `127.0.0.1`, and `inspector_port` is now distinct per app — it is not derived from the port above, so the second server to start died on an address already in use. **The API worker failed to evaluate.** A specifier ending in `.sql` is claimed by the bundler as a module of its own, so the schema file was emitted verbatim beside the bundle and the runtime threw on an export it could not find. The route was reaching past the domain module into the schema to spell a status; it now asks the domain module, which is the rule everywhere else here and happens to also avoid the hazard. **Signing in failed on the second request that touched the database.** A pool is cached per connection string, and on a Worker an I/O object created while handling one request may not be touched while handling another. The first request always succeeded, which is why it went unnoticed — a sign-in is several. The cache is now kept only where a process outlives its requests, which is the case it was added for. **The images named a base that podman will not resolve.** A short name needs a registry; the database service alongside them already spelled one. **Compose pinned container names.** The name is not scoped to the project, so a second checkout got the same three, and `down` in one stopped the other's containers. This is not hypothetical — it stopped a running development database while this was being tested. Verified by signing in end to end against both dev servers: a code requested over HTTP, read from the issuer's log, redeemed, exchanged for tokens, and presented to the API, which resolved it to the account the sign-in had just created. |
||
|
|
f30a1432f8 |
fix(deploy): require every credential, and give sandbox its own domain
Three things review caught, and one shape correction. **No credential has a default any more.** The compose file shipped `ADMIN_SHARED_SECRET` falling back to a value written in this repository — and that header bypasses token verification entirely, so anyone reading the file could act as an operator against any deployment that had not overridden it. A default is worth less than it looks here: the deployment that never set the variable is exactly the one where the default is public. Every credential now comes from `.env`, and compose refuses to start naming the variable it wanted. That also takes the last literal password out of a tracked file. **The origin ports are on loopback.** Both services speak plain HTTP and mark no cookie `Secure`, because both expect to sit behind something that terminates TLS. Published on every interface they were a way to reach the issuer around that proxy, with sign-in codes and tokens in clear text. **Mail settings are passed through rather than fixed.** The issuer was pinned to printing sign-in codes to its log, and the three delivery settings never reached it — so the documented way to configure mail could not work, and every code and recipient went to the container log instead. Printing codes is now asked for in `.env` like everything else, and with nothing configured the issuer refuses to send rather than logging. **Sandbox becomes a domain rather than a prefix.** `api.sandbox.nestri.io` and `auth.sandbox.nestri.io`, because sandbox holds whatever is not production and that set grows. One certificate for `*.sandbox.nestri.io` then covers all of it, including unpredictable per-pull-request names, and cannot be presented for production's own domain — which the zone-wide wildcard the previous shape leaned on could. Also drops `STEAM_API_KEY`. It was declared in two type definitions and read by nothing: linking an account makes no outbound call that needs it. |
||
|
|
51ababc900 |
feat(deploy): drop the IaC layer, and make both apps runnable as containers
Moving the issuer's state into Postgres removed the last thing that tied either app to one hosting provider. What was left was a deployment tool describing resources that no longer existed — so this replaces it with `wrangler`, which is what actually deploys a Worker, and adds a second way to run each app that involves no provider at all. Each app now has a `wrangler.jsonc` with an environment per stage, and a `Dockerfile` beside it. The handler is the same one in both cases; what differs is only where its settings come from. Two of them gained a second spelling so that nothing has to branch on the runtime: Postgres arrives as a pooled binding or as `DATABASE_URL`, and the route to the issuer is a service binding or `AUTH_INTERNAL_URL`. That last one is new, and it is a split the binding was already making without saying so. `AUTH_ISSUER_URL` has to be the issuer's public name, because it is compared literally against every token's `iss` claim — but the public name is often not routable from inside a deployment. So the name and the route are two settings now rather than one that cannot be both. DNS moves out of code and into `docs/dns.md`, which lists every hostname and what it is for. Six records that change roughly never did not need a tool, and the table outlives whatever is answering the names — which is the point, since some of them will stop being Workers. The sandbox hostnames are hyphenated rather than nested for the same reason: a certificate covering `*.nestri.io` covers one label and not two, so `api-sandbox.nestri.io` can become an ordinary origin later without a certificate having to be ordered for it first. Also drops `EMAIL_DEV_LOG` from committed configuration into `.dev.vars`, which `wrangler deploy` cannot upload. Printing a live sign-in code to a log should not be one forgotten override away from production. |
||
|
|
f64f037574 |
fix(auth): keep one live key per kind, and report a key's own algorithm
Two problems found in review, both in the key store. Nothing stopped a kind from having two live keys, and the bootstrap path walks straight into it: two workers starting against an empty table both find no key and both insert one. From then on each signs and encrypts with its own. That is not the harmless split the comment here claimed — the issuer reaches for a single key rather than the published set when it decrypts a session cookie and when it verifies an access token, so a cookie written by one worker is unreadable to the other and a token minted by one is rejected by the other. It stays silent until someone cannot sign in. A partial unique index over the kind, where the key has not been retired, makes the second insert a dropped write instead. Both workers then read the table again and use the key that won, which is all that matters. The conflict clause stops naming a target: both indexes on the table mean the same thing at this call site, that the row already exists in some form. Creating a key is now attempted once rather than retried, because a store declining the write is an expected answer and spinning on it would hang the request instead of failing it. Separately, a key pair reported the algorithm the issuer currently uses rather than the one stored on the key it was built from, so a retained key would advertise the wrong algorithm in a token header and in the JWKS after a rotation — which defeats keeping it. The material was already being imported with the stored value; only what was handed back disagreed. Retiring a key and creating its replacement now have to happen together, so that a kind never has two live keys and never has none. |
||
|
|
f25c9af545 |
feat(auth): keep issuer state in Postgres
The issuer kept everything behind one get/set/remove/scan interface, which is what a library that must run on any provider's cache can offer. Three of the things kept there could not actually be served by it. An authorization code must be redeemable once and a refresh token spendable once, and through get and set the check and the write are separate steps — so two requests arriving together both read an unspent record, and both mint a session. In the refresh case that also means the reuse which reveals a stolen token is never recorded, because recording it is the write that the second caller overwrites. Each now has a table and an interface of its own: redeeming is one `delete ... returning`, spending is one `update ... where time_used is null returning *`, so exactly one caller is ever told it went first. This is the same argument the device grant already made, applied to the two records that had it too. Signing keys move for a different reason. Nothing races for them; they are the one record whose loss ends every session at once, and a cache is a place things may be evicted from. They are retired by setting a column rather than deleted, so the tokens they signed stay verifiable until they expire. Both credential tables store a hash and never the credential, as the device grant does. An authorization code travels in a query string and so passes through history, referrer headers and any log along the redirect; a refresh token resumes a session outright. What is left in the generic store is the rate-limit counters — written far more often than read, meaningless within the hour, and allowed to be approximate, since a lost increment costs one guess out of ten. Those move to Postgres too, so the only key-value binding this deploys with is gone and the control plane's state is one database. That was the point: nothing here now depends on a primitive a self-hoster cannot run. The generic scan also gained the separator on its prefix, so scanning `a` cannot return what is under `ab` — subjects and email addresses are both prefixes of longer subjects and email addresses. Deploying this signs everyone out. The signing keys and refresh tokens are in a store that is being left behind, so the issuer starts with a fresh key set and every existing token stops verifying. |
||
|
|
647e5c5264 |
test(core): claim two attempts at once, not one after the other
The mutual exclusion was asserted only through sequential calls, where the winner had already committed before the rival began. That never reaches the case the design is for: both attempts reading the run as unclaimed before either writes. Two tests, because the first can pass for the wrong reason. The concurrent transitions depend on how the transactions interleave; the paired updates skip the read entirely, so nothing but the predicate in the where clause can refuse the second. Both fail with two winners if the check is moved out of the write and left in the read above it. |
||
|
|
54d5c81edb |
feat(api): hold a run to the attempt that claimed it
The agent side sends a claim token on every write; this side rejected the field outright, so every state report and every ticket publish answered 400. Both bodies now take it. Underneath that, nothing compared a holder. A run was reachable by any caller on the right machine, and a box names exactly one machine — so two attempts polling the same job presented identical credentials and were told apart only by which one's select landed first. That is timing, not a rule, and no caller could be told which case it was in. The row now remembers which attempt holds it. Taking a claim requires there to be no holder; every write after it requires the caller to be the holder. The same state reported by a different attempt is a lost race and not a retry, and is refused whatever the state is - which is the only thing that separates the two 200s from the 409s. The ticket is held to the claim too, for a worse reason than a double start: the client re-reads the address rather than keeping the first, so a ticket written by a losing attempt produces a client that connects, successfully, to a machine running nothing. The holder is never cleared, including on a terminal state, so a settled claim cannot be replayed and a finished run still records which attempt ran it. It is not in what goes out - holding one permits writing to a run, and the owner reading their own session is not the holder. |
||
|
|
304bb1f2ef |
fix(auth): count sign-in codes against the mailbox, not the browser
The cap on how many codes a sign-in could ask for was held per attempt, keyed by a value in the caller's own cookie. That bounds nothing. The caller decides how many attempts to start, and starting a fresh one costs them a discarded cookie — so either replaying an older cookie or simply beginning again walked straight around it, and the only thing left spacing the mail out was the interval between sends. The count now sits against the claim, over a window. That is the thing being protected: the mailbox belongs to somebody who did not ask to hear from us, and whoever is pointing at it is not the party to trust with the tally. A resend also left the previous code live, with a budget of guesses of its own. Several resends therefore meant several working codes and several times the chances at them, which made asking for a new code the cheapest way to buy more tries at the old one. A new code now retires the one before it. Reported against the replay path. The replay was real and the same hole was wider than that: starting a new attempt needed no replay at all. |
||
|
|
fc825f5219 |
fix(auth): stop a caller working through the user code space
A user code is eight characters from a twenty-five character alphabet, which is a large space but a fixed one, and the endpoint that checked them had no opinion about how often you asked. That is the guessing attack RFC 8628 section 5.2 asks implementations to limit, and nothing here did. Wrong codes are now counted per caller address over a rolling window, and the endpoint stops answering once the budget is gone. Getting a code right is not charged for, so somebody who mistypes once and then succeeds is not walking towards a lockout. A caller whose address cannot be established shares one bucket with every other such caller, which makes stripping the headers that say where you are buy a smaller budget rather than an unlimited one. The counter lives in the general-purpose store and is approximate. The number that decides this is whether somebody is working through the code space, and a handful either way does not change that answer. |
||
|
|
355d1492d9 |
fix(auth): give a sign-in code a budget of guesses and a short life
A six-digit code has a million values, and nothing was counting how many of them a caller tried. The code travelled in an encrypted cookie the caller held, verification compared against that cookie, and a wrong answer simply re-rendered the form. Nobody has to be the person the code was mailed to: type somebody else's address into the first screen and the code goes to their mailbox while the cookie stays with you. At that point the only thing between a stranger and an account is a million requests, and the constant-time comparison protecting the code was guarding a door you could just keep knocking on. Guesses are now counted on the server, under a name that changes with every code. That placement is the point: a counter kept beside the code, in the cookie, is a counter the guesser can wind back by replaying an older copy. Starting over is still allowed and still costs a fresh code sent to the mailbox being aimed at, which is where somebody notices. A correct code spends its record too, so its remaining guesses do not carry into the next one. The cookie also lived for twenty-four hours, which made the pin a password with a million possible values and a day to try them. Ten minutes now, and the code stops being accepted when the clock says so rather than when the cookie happens to go away. Resend had no limit either, so the button was a way to mail a stranger as fast as requests go out. Codes to one address are spaced, and one attempt at signing in can only ask for so many. Both refusals say the same thing on purpose. Which of the two it was is a fact about somebody else's mailbox. |
||
|
|
36179150a1 |
fix(auth): make a device sign-in an answer somebody gave
Anybody could ask for a device code and be handed a link with the user code already in it. Following that link started a sign-in, and finishing the sign-in approved the grant. So sending somebody the link was enough: they saw an ordinary sign-in prompt, completed it, and whoever kept the device code polled and collected their access and refresh tokens. The victim never saw a question, because there was not one. There is now. Signing in says who the browser belongs to; it does not say the person meant to hand an account to a program somewhere else. Those are two questions and only the second authorizes anything, so the flow ends at a page that names the program, shows the code back so it can be compared with what the device is displaying, and offers Approve and Deny. Approving is a POST carrying a value from the cookie, so another site cannot submit it on somebody's behalf. Denial moved onto the same page: it used to be a GET anyone could fire, which meant a link scanner could cancel a real sign-in and a stranger with a user code could grief one. Three more things that were wrong underneath. The grant was read, modified and written back as a whole record. A poll that read a pending grant and then wrote its bookkeeping erased an approval that landed in between, and the client polled a dead grant until it expired. Grants moved to a table, where approving is one conditional update and redeeming is one delete that returns what it deleted, so neither party can undo the other and two polls cannot both be served. Tokens were minted when the person clicked and left sitting in storage until collected. They are minted at redemption now, so the lifetime the client is told about starts when it receives them, and a grant nobody collects leaves no usable refresh token behind. The client identifier was never checked, at either end. It is validated when the grant is created and has to match when the code is redeemed — without that, a leaked code is redeemable by anyone, and the identifier the token carries is whatever the last caller claimed. The device code is also stored as a hash now, since it is the credential the tokens are handed to. The store is an interface because the issuer cannot reach the database, and because the guarantees are the point: every method is one operation, and no caller reads a grant, decides, and writes it back. |
||
|
|
15f8d3eb34 |
fix(core): hold the account rules when two requests arrive together
Three rules here are enforced across a lookup and then a write, and each was only as good as whatever stopped the two from interleaving. Nothing did. The connection cap counted with `select ... for update` over the connections a user already had. That locks the rows it finds, and when it finds none it locks nothing — there are no gap locks under read committed — so several first-time links all counted zero and all inserted. Six concurrent links against a cap of four produced six. The count now happens under a lock on the account's own row, which is the one thing every caller for that account is guaranteed to contend on. Creating an account from a verified address looked the address up and then inserted. Two tabs finishing the same sign-in both found nothing, and the loser got the driver's constraint violation instead of the account the winner had just made. The unique index is the thing that actually arbitrates, so the loser now reads back what the winner wrote. Claiming an address on an older account had the same shape and now gives the same sentence a screen would have shown a moment earlier. The tests run each call several times at once against a real database, because run one at a time all three pass whether or not any of this exists. |
||
|
|
bd163392ca |
fix(core): declare the claim column the migration adds
The migration adds session.claim_token, but neither the schema nor the
snapshot knew about it. Nothing breaks today because the two agree with
each other; it breaks the moment someone declares the field, because
generate then diffs against a snapshot without it and emits
ALTER TABLE "session" ADD COLUMN "claim_token" text;
which fails on every database the migration has already run against.
Declared with no writer yet, so the schema, the snapshot and the
database say the same thing.
|
||
|
|
1b61d2251e |
fix(core): hold the connection cap on the path a settings screen uses
Connecting a Steam account wrote the row itself, so the limit on how many one person may connect was enforced on the sign-in path and nowhere else — and this is the path the settings screen calls, which makes it the one that would have gone over. It now resolves who is asking and hands over to the single place the rule lives. Two things fall out of that. A Steam account already connected to somebody else is a conflict rather than a silent success returning the other person's row id, and a Steam id of the wrong shape is refused before a lookup. |
||
|
|
2a1b7abe9a |
feat(auth): serve the device authorization grant
A program with no browser — the desktop app — had a client for RFC 8628 and nothing to point it at. This serves the other half: a device authorization request that hands back a code, a page a person enters that code on, and a token endpoint that answers the poll. Both of the paths the client already implements are now reachable. Polling faster than the advertised interval gets slow_down, and each warning widens the interval so ignoring one costs more than the last; refusing gets access_denied, so a request nobody started stops instead of being polled until it ages out. The interval is capped, because it only ever grows and a code has to stay pollable for the whole of its life. The codes live in the same storage as the other short-lived grants rather than in a table, since that is what they are. User codes are drawn from an alphabet with no vowels and no look-alike pairs, and are accepted back in whatever case and spacing a person retyped them in. |
||
|
|
1e81a8f92d |
feat(core): make one address one account, on rows that never had one
Runs against a database where every user was created by a gaming sign-in, so most rows have no email at all and nothing has ever stopped two rows from sharing one. The address is normalized first, duplicates are separated before the unique index exists — the older row keeps the address, the newer one is asked for a new one and loses nothing else — and the index is partial so that accounts with no address do not collide with each other. Verified against a database built to contain the awkward rows rather than against an empty schema, by the script alongside it: an account with no address, one with both, one with two connections, a duplicated address in two different cases, an account already over the connection cap, and a deleted row holding an address a live row also holds. Removing the de-duplication makes the index creation fail, which is how we know the fixtures are load-bearing. Also adds a nullable column recording which attempt holds a session run. It is not part of the change above and carries no reason of its own; the endpoint that reads and writes it arrives separately, and it is here because a schema change has one owner at a time. |
||
|
|
da65cca4f2 |
feat(core): an account is an email address, and Steam is a connection
Signing in with Steam used to create the account. That made a second Steam account a second person, and it made losing a Steam account lose everything attached to it — the boxes, the team, the billing history. Invert it. A user comes into existence by verifying an email address and nothing else; a Steam account hangs off a user that already exists, capped at four. Signing in with Steam resolves an account and refuses when there is none, so the accounts made before this keep working — they already have the connection this looks for — while nothing new is created behind a persona. The cap lives here rather than in the schema because a unique index cannot count the rows sharing a foreign key. The email column gains a partial unique index instead, which is the constraint that can be expressed, and the address is trimmed and lower-cased at the edge so two spellings are not two accounts. |
||
|
|
4eff67a11a | fix(core): point the placement marker at the decision that covers placement | ||
|
|
51dabddbd8 |
fix(api): a run drives the box under it, and needs a game and a claim
Four things the session endpoints did not do, or did wrongly. The box had three states and nothing wrote them. A box read `created` while a run on it was `live`, so every screen showing a person what their hardware is doing was reading a column no code had ever moved. A run reaching `live` now makes its box `running`, and a terminal run stops it: `ended` cleanly, `failed` not, carrying the reason the agent gave. Not every run state maps — a box has no `starting` on purpose, because that transition is synchronous from the agent's side and a state nobody sets is a state that lies. Both writes are one transaction, since "this run is live" and "the box under it is running" are one fact in two tables, and a box stuck `running` with nothing on it has nothing to correct it. `POST /session` accepted any game in the catalog. A run launches as a Steam account that has to own the game, so one outside the caller's library is a box that starts, tries to launch and fails minutes later with nothing to point at; it is now refused up front. Told apart from a game that does not exist rather than hidden, because the catalog is public and "you do not own this" is a sentence a person can act on. The library is a synced copy, so this refuses a game bought since the last sync — that is a staleness bug in the sync, not a reason to start runs that cannot work. Publishing a ticket only refused terminal runs, so a host could publish an address for a run it had never claimed. A ticket is the address of something being brought up, so only `starting` and `live` accept one, and the state is in the write rather than only in the check above it. The two refusals stay separate answers because they are different mistakes: one agent skipped a step, the other has nothing left to reach. The migration that adds the one-active-run index stopped older duplicate runs without clearing the ticket they had published, which is the invariant that same migration exists to establish. It clears it now, verified against a box carrying two unstopped runs. Nine tests, each checked against the unfixed code first. |
||
|
|
0d8630379b |
fix(api): a box gets one run, and a stopped run keeps no address
Two invariants the session endpoint stated but did not hold. A box runs one thing at a time. `POST /session` read `activeForBox` and refused when something was already running, but the read and the insert are two statements with nothing between them: two requests that both saw "nothing is running" each got a row, and the job poll then handed the host the same box to start twice. Demonstrated at 2 rows and 2 jobs from one box. That is the failure the state claim exists to prevent, one step earlier, and it takes the same answer — a partial unique index on the predicate the read asks about, so the database refuses the second insert. `Session.request` turns that refusal into the same 409 in the same words, so a caller cannot tell which of the two caught it. The migration resolves any existing duplicates before creating the index, keeping each box's newest unstopped run because that is the one a person is waiting on, and stopping the rest rather than deleting them. Separately, a run that reached `ended` or `failed` kept the last ticket it published. Publishing a new one is already refused, so the stale address was both the only ticket a client could read for a dead run and the one nothing was allowed to replace — and a client that polls would dial it. Terminal transitions now clear it, in `setState` as well as in the compare-and-set, so the invariant does not depend on which writer stopped the run. Seven tests, each checked against the unfixed code first. The published descriptions for the ticket field and the read endpoint now say that a stopped run has no address. |
||
|
|
bbe729e5c7 |
feat(api): the session endpoint, and a claim that only one caller can win
A run of a box had core support and no HTTP surface. This adds both halves of it: a person asks for a run and reads it back, and the host agent the box is placed on is handed the work and reports what happened. The access rule is the point. An agent may only see or touch a run whose box is placed on its own hardware, and that is a `where` clause on every one of the three agent endpoints rather than a check next to them — host credentials are long-lived secrets sitting on hardware in somebody's home, so what one leaking can reach has to be decided by the query. "No such run" and "not your run" are the same refusal, so ids cannot be discovered by reporting states at them. `Session.setState` updated on the id alone, which means two agents polling the same work both succeed and both start the same box. There is one host today, which is exactly why that would have been built wrong and stayed wrong. The state a run is moving out of is now part of the `where` clause, so the database picks the winner; the loser gets a conflict rather than a silent no-op. Three cases that look alike are kept apart: re-reporting a state you already reported changes nothing and is not an error, a transition that does not exist is refused with the run left where it was, and another host reporting anything is forbidden. Asking for a run makes no decision about where it happens — a box already names its hardware, so the run inherits it by join. Placement therefore gets an interface at box creation, where the decision actually is, with the single-host case as its implementation and a deliberate refusal when there is more than one candidate and no policy to choose with. Tests cover the wire shape from both sides, the query scoping, the claim, and the timestamp idempotence a run's billing rests on. |
||
|
|
cf56aaf04c |
docs: this repo is public, so say what things are, not who decided them
Comments and served API descriptions here had grown references that only make sense to someone with our internal notes: relative paths that escape this tree, filenames and titles of documents nobody outside can open, quoted prose from them, and the name of a component that has no public surface — once in an OpenAPI description, which is published output rather than source. None of it was load-bearing. Every case restates as what the code actually requires, and every rewrite came out shorter: "in the words the host agent reports" for a component name, "republished as addresses are discovered" for a quoted phrase, "a size tier sets vCPU, RAM and the output geometry" for a sentence that had been carrying a path. Internal reasoning is now cited exactly one way, ref(d-NNNN) in a source comment, with the rule that the sentence must still stand if the marker is deleted. CLAUDE.md leads with it, because the previous version of this mistake was made by people who knew the repo was public and it still took ten occurrences to notice, so "be careful" is not a mechanism. Commit messages get the stricter rule and carry no references at all: a comment can be fixed by the next commit and a published message cannot be fixed at all. Git hooks now enforce both halves. The check caught a real one while being written: the CLAUDE.md table spelled out the paths it was prohibiting, which discloses them to exactly the reader it protects against. 138 tests, 0 fail. |
||
|
|
4315510de8 |
feat(api): a host can say it is alive, and is told how often to
Second half of G1's "neslet registers against api.nestri.io and heartbeats".
Registration already worked; there was no heartbeat endpoint at all — grep for
it across apps/api and packages/core returned nothing, and neslet's own
main.rs says the same from its side.
POST /machine/heartbeat, machine credentials only. Two decisions worth stating
because neither is obvious from the diff:
**It returns the interval.** The auth middleware already touches lastSeen on
every authenticated machine request, so an endpoint that only did that would
add an endpoint and no capability. What a host cannot know on its own is how
often the control plane wants to hear from it, so the response carries the
cadence. A fleet whose interval can only change by shipping a new agent is a
fleet whose interval never changes.
**It takes no body.** neslet has a HostSummary ready to send, and week 2 owns
box state reporting. Accepting fields nothing acts on yet would mean a wire
shape we would have to keep, chosen before the thing that consumes it exists.
Online-ness is derived from lastSeen rather than stored: a host that stops
beating goes offline through the passage of time, which is the one mechanism
that cannot itself fail. Three missed beats, not one — a single missed beat is
a lost packet, and treating that as offline would make placement flap.
Also: the machine actor's teamID stops being optional. It was `...(teamId ? {}
: {})` in the middleware, a branch for a state that cannot exist now that
machine.team_id is notNull.
134 tests, 0 fail.
|
||
|
|
6c1d407985 |
feat(core): a box is a row, a session is the billing unit
Migration 1 of 0048, and the first of the seven weeks — nothing about a live
feed works without these two tables, so it is not a cleanup during them.
box a VM someone owns: an id that is also its DNS label, an editable
label, an owning user, the machine it sits on, a tier and a state.
Owned by a person and placed on a team's hardware, which are two
different relationships, hence both userId and machineId.
session one run of one box by one linked Steam account, and what costs
money. Separate from box because the ticket changes after bind as
addresses are discovered — the vsock contract calls it "a stream,
not one value" — so it is a column a client polls, not a value it
is handed once.
Box states are neslet's own three and no more. `starting` and `stopping` are
the obvious additions and both are omitted because nothing would ever write
them; a failed box is `stopped` with stopClean false, which is how neslet
models it too.
The generated migration would have failed on live rows in three ways, so it
is hand-written and tested against a database seeded at the old schema:
- machine.team_id becomes notNull, and *every existing row is null* because
the old registration path passed null. Personal teams are backfilled for
machine owners first, reusing a team they already own rather than minting
a second, with the owner membership row repaired where missing.
- game_download.host_id becomes a foreign key. It held free-form strings,
so unattributable rows are deleted before the cast — the only destructive
statement here, and a considered loss: it is a progress report neslet
re-derives from disk.
- Team.createPersonal was written and documented in packages/core/CLAUDE.md
as part of the login flow and never actually called, so no user has a
team. ensurePersonal is idempotent and now runs on every login, which is
what backfills accounts the migration does not reach.
Verified on a seeded legacy database: three null-team machines backfilled, an
existing team reused rather than duplicated, a blank display name handled, and
both unattributable download rows dropped while the attributable one survived.
Also fixes two things this work ran into rather than caused:
- Database.client() built a new postgres pool on every call, and use()
called it twice per invocation — pools of ten connections held for a 30s
idle timeout. Invisible in a Worker where requests are short; the suite
crossed 100 connections and Postgres said "sorry, too many clients
already" in whichever file ran last, which reads as a flaky test rather
than a leak. Now one pool per connection string.
- download.test.ts asserted against `hst_…` host ids, which is exactly the
unattributable row the new foreign key exists to refuse.
There is no "no team" any more: PATCH /machine/:id took teamId null to mean
"mine alone" and now requires a team, because the personal team is the one to
name. Its test is updated to the new contract rather than deleted.
113 → 128 tests, 0 fail.
|
||
|
|
1c24a6e684 |
fix(ci): the web job has been failing on two separate bugs (#311)
`bun run db:push` has been failing on **every pull request** with
`error: Script not found "db:push"`. A red check has meant nothing for
as long as that's been true.
There are **two independent causes**, and fixing only the reported one
leaves the job red.
## 1. The script isn't at the root
`db:push` lives in `packages/core/package.json`; CI runs from the root.
Added root passthroughs for `db:migrate` and `db:push`, so the command
CI runs is also the one a human can run.
## 2. `drizzle.config.ts` enabled TLS for any `DATABASE_URL`
```ts
ssl: !!process.env.DATABASE_URL ? { rejectUnauthorized: false } : false
```
That's true for *any* URL — so it failed against every plain Postgres,
**including CI's own `postgres:18-alpine` service container**. And
`drizzle-kit` reports that failure as a spinner and a non-zero exit with
no message attached, which is why it would have been maddening to find
from a log.
Measured against a local container:
| | result |
|---|---|
| `DATABASE_URL` set (TLS on) | migrations fail, no error text |
| `DATABASE_URL` unset, same database | all seven apply |
TLS is now decided by the connection string: an explicit `sslmode` wins,
otherwise a local host gets none (it doesn't speak TLS at all) and any
other host gets TLS without chain verification, which is what a hosted
Postgres usually needs. The URL is parsed once rather than eight times.
## CI now applies migrations instead of `push`
`drizzle-kit push` diffs the schema against whatever is already in the
database and is a development tool — CI wants exactly what's committed
in `packages/core/migrations`. And `push` under `strict: true` asks for
confirmation, which on a runner is a **hang**, not a failure.
## Verified
Locally against `postgres:18-alpine` from an empty database, running
exactly what the workflow runs:
```
bun install --frozen-lockfile ✓
bun run db:migrate ✓ 7 migrations applied
bun test ✓ 113 pass, 0 fail, 297 expect() calls
```
Worth landing ahead of #310 so that a red check starts meaning something
again.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR repairs the database-backed web CI job by exposing core database
commands at the workspace root, applying committed migrations instead of
schema push, and selecting PostgreSQL TLS behavior from the connection
URL.
- Adds root passthrough scripts for database migration and schema push
commands.
- Adds the core `drizzle-kit migrate` command and runs it in CI.
- Disables TLS for local PostgreSQL while honoring explicit `sslmode`
settings.
- Keeps migration and test steps pointed at the same temporary CI
database.
<h3>Confidence Score: 5/5</h3>
The PR appears safe to merge, with the migration command, working
directory, connection settings, and test database remaining aligned.
The changed CI path reaches the committed migration history through the
intended core package configuration, uses plaintext for the local
PostgreSQL service, and then tests against the same migrated database;
no changed-code defect remains.
<h3>Important Files Changed</h3>
| Filename | Overview |
|----------|----------|
| .github/workflows/ci.yml | Replaces schema push with committed
migration execution while preserving the shared CI database URL. |
| package.json | Adds root-level passthroughs to the core package's
database commands. |
| packages/core/drizzle.config.ts | Parses the database URL once and
selects TLS based on explicit mode or local-versus-remote host
inference. |
| packages/core/package.json | Adds the `drizzle-kit migrate` script
consumed by the root command and CI workflow. |
<h3>Flowchart</h3>
```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart LR
PR[Pull request or main push] --> CI[Web CI job]
CI --> PG[PostgreSQL 18 service]
CI --> Install[Bun frozen install]
Install --> Root[Root db:migrate script]
Root --> Core[packages/core db:migrate]
Core --> Config[drizzle.config.ts]
Config --> Migrations[Committed migrations]
Migrations --> PG
PG --> Tests[Bun tests]
```
<sub>Reviews (1): Last reviewed commit: ["fix(ci): the web job has been
failing
on..."](https://github.com/nestrilabs/nestri/commit/203e882fbd7ab1578922f63e2c8986dce0b78c6f)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=59430932)</sub>
<!-- /greptile_comment -->
|
||
|
|
77d4782c86 |
docs: split CLAUDE.md by scope, and say what this repo is
CLAUDE.md was 1,324 lines and all of it was about the TypeScript half, written before there was another half. Every line of it loaded on every turn regardless of what was being worked on, which is a real cost paid constantly for context that is usually irrelevant. Split by where it applies, so each guide loads when you are in the directory it describes: packages/core/CLAUDE.md 694 domain modules, fn(), actor, errors, auth apps/api/CLAUDE.md 284 routes, registration, error flow docs/alchemy.md 345 stages, bindings, secrets, the CLI CLAUDE.md 72 the repo, both toolchains, two hard rules Nothing was rewritten or dropped — the three files are the original text, verified identical after the split. What the root file now carries is only what is true repo-wide: the layout, the commands, where the detail lives, and the two rules that are not style preferences. One of those is that nothing closed may enter this repo, which is here because it has already been caught once. The README described a streaming platform in four bullets and did not mention that half the repository is Rust that runs inside a virtual machine. It now says what each component does, why a micro-VM rather than a container, what is deliberately absent, and what decides whether a thing is open — data is, capacity is not. It also says plainly that this is mid-rewrite and the docs are behind. Someone arriving at a repo whose documentation does not match its tree should be told that by the README rather than discover it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0143849129 |
feat: bring the control plane up to date
Squashes the current state of the internal working tree onto this history. The two trees had grown apart with no common ancestor, so this is a content sync rather than a merge, and the published history is preserved rather than rewritten — a force-push here would break every existing fork and clone to no benefit. What lands: - Waitlist: API route, core module, and migration 0006 alongside game aliases. - User verification. - CI, oxfmt config, editor settings. - Assorted fixes across the API routes and core modules. The repository's own README, the wordmark and the per-package READMEs are kept from this side; the internal tree had dropped them and they are what a stranger arriving here reads first. The marketing site in the internal tree is deliberately not here. It is a separate product with its own repo and its own licence, and this repo is the open one — a closed component does not belong in it regardless of how convenient the directory looked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a8b9a11de0 | chore: Update Readme | ||
|
|
3faac3008f | feat: Sync to OSS repo | ||
|
|
46d2a56180 | feat: Sync to OSS repo | ||
|
|
b743dab332 |
fix: Fix good chunk of packet loss and frame drop issues
- Also added new latency control parameter, not super visible differences, but it's cool :) |
||
|
|
549a98bc48 |
feat(relay): Deprecate websocket in favor of quic-v1
- Websocket stinky with non-HTTPs proxied relay we moved to |
||
|
|
e0751b368a |
feat: runner and playsite updates, mouse input stutter fix
- Also fixed mangohud config |
||
|
|
0f71b02271 | exp(relay): Restore playout delay to 0 | ||
|
|
f9b4269ce3 | exp(relay): Try setting high playout delay | ||
|
|
0475dd5766 | fix(relay): Specify IP as Host rather than srflx | ||
|
|
a318876eb3 | fix(relay): Use built-in method for checking private IP | ||
|
|
f20f01e6d6 | fix(relay): Ignore potential private/internal IPs | ||
|
|
49cc5e1ab9 |
feat: Runner image variants, CI improvements (#306)
## Description Next step would be having full DE environment variant I guess? I'll see later if it's doable in this PR or if I'll do separate one for keeping things small and manageable for once 😅 - Added easily doable variants for runners, with simple CI build matrix. - Added playsite in CI builds finally. - Some CI formatting and naming fixes. - Removed PR full runner builds as they kept failing due to lack of disk space on GH runner. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * New dedicated runner images for Steam, Heroic, and Minecraft plus a common runtime and builder images. * **Chores** * CI/workflow reorganization to build and publish more runner variants and base images. * Installer and package tweaks (package manager flags, CUDA enablement) and updated build tooling. * Unified startup to use a constructed launch command; removed two default environment exports. * Added container ignore patterns. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> |
||
|
|
c9a0e6ee29 | fix: Specify MangoHud config for gamescope | ||
|
|
d87a0b35dd |
feat: Fully use protobuf, fix controller issues and cleanup (#305)
## Description ### First commit Restructured protobuf schemas to make them easier to use across languages, switched to using them in-place of JSON for signaling as well, so there's no 2 different message formats flying about. Few new message types to deal with clients and nestri-servers better (not final format, may see changes still). General cleanup of dead/unused code along some bug squashing and package updates. TODO for future commits: - [x] Fix additional controllers not doing inputs (possibly needs vimputti changes) - [x] ~~Restructure relay protocols code a bit, to reduce bloatiness of the currently single file for them, more code re-use.~~ - Gonna keep this PR somewhat manageable without poking more at relay.. - [x] ~~Try to fix issue where with multiple clients, static stream content causes video to freeze until there's some movement.~~ - Was caused by server tuned profile being `throughput-performance`, causing CPU latency to be too high. - [x] Ponder the orb ### Second + third commit Redid the controller polling handling and fixed multi-controller handling in vimputti and nestri code sides. Remove some dead relay code as well to clean up the protocol source file, we'll revisit the meshing functionality later. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added software rendering option and MangoHud runtime config; controller sessions now support reconnection and batched state updates with persistent session IDs. * **Bug Fixes** * Restored previously-filtered NES-like gamepads so they connect correctly. * **Chores** * Modernized dependencies and protobuf tooling, migrated to protobuf-based messaging and streaming, and removed obsolete CUDA build steps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> |
||
|
|
c62a22b552 |
feat: Controller support, performance enchancements, multi-stage images, fixes (#304)
## Description Oops.. another massive PR 🥲 This PR contains multiple improvements and changes. Firstly, thanks gst-wayland-display's PR [here](https://github.com/games-on-whales/gst-wayland-display/pull/20). NVIDIA path is now way more efficient than before. Secondly, adding controller support was a massive hurdle, requiring me to start another project [vimputti](https://github.com/DatCaptainHorse/vimputti) - which allows simple virtual controller inputs in isolated containers. Well, it's not simple, it includes LD_PRELOAD shims and other craziness, but the library API is simple to use.. Thirdly, split runner image into 3 separate stages, base + build + runtime, should help keep things in check in future, also added GitHub Actions CI builds for v2 to v4 builds (hopefully they pass..). Fourth, replaced the runner's runtime Steam patching with better and simpler bubblewrap patch, massive thanks to `games-on-whales` to figuring it out better! Fifth, relay for once needed some changes, the new changes are still mostly WIP, but I'll deal with them next time I have energy.. I'm spent now. Needed to include these changes as relay needed a minor change to allow rumble events to flow back to client peer. Sixth.. tons of package updates, minor code improvements and the usual. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * End-to-end gamepad/controller support (attach/detach, buttons, sticks, triggers, rumble) with client/server integration and virtual controller plumbing. * Optional Prometheus metrics endpoint and WebTransport support. * Background vimputti manager process added for controller handling. * **Improvements** * Multi-variant container image builds and streamlined runtime images. * Zero-copy video pipeline and encoder improvements for lower latency. * Updated Steam compat mapping and dependency/toolchain refreshes. * **Bug Fixes** * More robust GPU detection, input/fullscreen lifecycle, startup/entrypoint, and container runtime fixes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> |
||
|
|
5705029972 |
feat(play-standalone): Add PEER_URL env variable (#302)
## Description Adds PEER_URL env variable for setting peer URL (query param still takes priority if set). - Useful for self-hosters - Was a pain to figure out <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Support configuring the peer server URL via an environment variable, with automatic fallback to the URL parameter or a default. - Server-provided configuration is securely passed to the client to simplify deployment setup. - Chores - Excluded common build artifacts and IDE directories from container contexts to reduce image size and speed up builds. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> |
||
|
|
590fe5e196 |
feat(runner): Container detection and handling, video bit-depth flags and script updates (#303)
## Description Works in apptainer now.. podman is still the goat since apptainer needs docker treatment and even more.. - Added container detection so podman can be used to it's fullest, the non-sane ones are handled separately.. - Added video bit-depth option, cuz AV1 and 10-bit encoding go well together. - Some other package updates to nestri-server. - General tidying up of scripts to make multi-container-engine handling less of a pain. - Updated old wireplumber lua script to new json format. Further changes: - Removed unused debug arg from nestri-server. - Moved configs to config file folder rather than keeping them in containerfile. - Improved audio configs, moved some into wireplumber to keep things tidy. - Bit better arg handling in nestri-server. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Optional 10‑bit video support and auto‑launch of an app after display setup. * **Changes** * Standardized runtime/user env to NESTRI_* with updated home/cache paths and explicit LANG; password generation now logged. * Improved container/GPU detection and startup logging; reduced blanket root usage during startup; SSH setup surfaced. * WirePlumber/PipeWire moved to JSON configs; low‑latency clock and loopback audio policies added; audio capture defaults to PipeWire. * **Chores** * GStreamer/libp2p dependency upgrades and Rust toolchain pinned; NVIDIA driver capability exposed. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com> |
||
|
|
639616ce73 | feat: Add database | ||
|
|
9818165a90 | fix: Move more directories | ||
|
|
dc6a53e18d |
chore: Fix up the directories
Adds a basic standalone "play site" that mimics current one in apps/www. This is so self-hosters don't need to host whole site, but can just use small version of it. Yet to test so marking as draft, not at home currently so may take some time. Also might be good idea to make Caddy-powered container out of this later? <!-- This is an auto-generated comment: release notes by coderabbit.ai --> - New Features - Introduces a standalone Play site with server output, accessible on 0.0.0.0:3000. - Streams video via WebRTC into a canvas with continuous frame rendering. - Fullscreen and pointer lock support with optional keyboard lock for navigation keys. - Room-based routing with offline and loading states. - Responsive 16:9 canvas and improved default layout styling. - Chores - Adds a multi-stage container build for efficient runtime images and a lightweight init process. - Includes configuration and project setup for the standalone package. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: DatCaptainHorse <datcaptainhorse@users.noreply.github.com> |
||
|
|
8d18ac9044 |
build(deps-dev): bump the npm_and_yarn group across 2 directories with 1 update (#297)
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). Bumps the npm_and_yarn group with 1 update in the /apps/www directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). Updates `vite` from 6.0.15 to 6.1.6 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/releases">vite's releases</a>.</em></p> <blockquote> <h2>v6.1.6</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.5</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.4</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.3</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.2</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>create-vite@6.1.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/create-vite@6.1.1/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>plugin-legacy@6.1.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>create-vite@6.1.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/create-vite@6.1.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>plugin-legacy@6.1.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0-beta.2</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0-beta.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0-beta.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0-beta.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0-beta.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">vite's changelog</a>.</em></p> <blockquote> <h2><!-- raw HTML omitted -->6.1.6 (2025-04-30)<!-- raw HTML omitted --></h2> <ul> <li>fix: check static serve file inside sirv (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>) (<a href="https://github.com/vitejs/vite/commit/42079a078e2ff5a2bd209788b06f00cfc63a75c3">42079a0</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19965">#19965</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.5 (2025-04-10)<!-- raw HTML omitted --></h2> <ul> <li>fix: reject requests with <code>#</code> in request-target (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>) (<a href="https://github.com/vitejs/vite/commit/87cff1215bcba6bee2113e5b85bccfa5c20049d6">87cff12</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.4 (2025-04-03)<!-- raw HTML omitted --></h2> <ul> <li>fix: backport <a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>, fs check with svg and relative paths (<a href="https://github.com/vitejs/vite/commit/0aeccef739cca78d8903c91a424d02192d698e23">0aeccef</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.3 (2025-03-31)<!-- raw HTML omitted --></h2> <ul> <li>fix: fs check in transform middleware (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>) (<a href="https://github.com/vitejs/vite/commit/45b00c987c092b8032ebccdc4db17398e3b4d573">45b00c9</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.2 (2025-03-24)<!-- raw HTML omitted --></h2> <ul> <li>fix: fs raw query with query separators (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>) (<a href="https://github.com/vitejs/vite/commit/80381c38d6f068b12e6e928cd3c616bd1d64803c">80381c3</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.1 (2025-02-19)<!-- raw HTML omitted --></h2> <ul> <li>fix: ensure <code>.[cm]?[tj]sx?</code> static assets are JS mime (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19453">#19453</a>) (<a href="https://github.com/vitejs/vite/commit/e7ba55e7d57ad97ab43682b152159e29fa4b3753">e7ba55e</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19453">#19453</a></li> <li>fix: ignore <code>*.ipv4</code> address in cert (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19416">#19416</a>) (<a href="https://github.com/vitejs/vite/commit/973283bf84c3dca42e2e20a9f9b8761011878b8b">973283b</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19416">#19416</a></li> <li>fix(css): run rewrite plugin if postcss plugin exists (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19371">#19371</a>) (<a href="https://github.com/vitejs/vite/commit/bcdb51a1ac082f4e8ed6f820787d6745dfaa972d">bcdb51a</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19371">#19371</a></li> <li>fix(deps): bump tsconfck (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19375">#19375</a>) (<a href="https://github.com/vitejs/vite/commit/746a583d42592a31e1e8e80cc790a7c9e6acf58e">746a583</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19375">#19375</a></li> <li>fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19392">#19392</a>) (<a href="https://github.com/vitejs/vite/commit/60456a54fe90872dbd4bed332ecbd85bc88deb92">60456a5</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19392">#19392</a></li> <li>fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19440">#19440</a>) (<a href="https://github.com/vitejs/vite/commit/ccac73d9d0e92c7232f09207d1d6b893e823ed8e">ccac73d</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19440">#19440</a></li> <li>fix(html): ignore malformed src attrs (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19397">#19397</a>) (<a href="https://github.com/vitejs/vite/commit/aff7812f0aed059c05ca36c86bf907d25964119a">aff7812</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19397">#19397</a></li> <li>fix(worker): fix web worker type detection (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19462">#19462</a>) (<a href="https://github.com/vitejs/vite/commit/edc65eafa332b57ce44835deb7d7707e2d036c24">edc65ea</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19462">#19462</a></li> <li>refactor: remove custom .jxl mime (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19457">#19457</a>) (<a href="https://github.com/vitejs/vite/commit/0c854645bd17960abbe8f01b602d1a1da1a2b9fd">0c85464</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19457">#19457</a></li> <li>feat: add support for injecting debug IDs (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18763">#18763</a>) (<a href="https://github.com/vitejs/vite/commit/0ff556a6d9b55bff7cac17396ce7d4397becacaa">0ff556a</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/18763">#18763</a></li> <li>chore: update 6.1.0 changelog (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19363">#19363</a>) (<a href="https://github.com/vitejs/vite/commit/fa7c211bf3e51269f8a8601e5994fb3ebb6859f9">fa7c211</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19363">#19363</a></li> </ul> <h2>6.1.0 (2025-02-05)</h2> <h3>Features</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vitejs/vite/commit/3b2299f6f57be23867e0d793d3ec32b4dfb5eaaf"><code>3b2299f</code></a> release: v6.1.6</li> <li><a href="https://github.com/vitejs/vite/commit/42079a078e2ff5a2bd209788b06f00cfc63a75c3"><code>42079a0</code></a> fix: check static serve file inside sirv (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/c279023cc3b3fed9cfcb43938cd452aa9ce11995"><code>c279023</code></a> release: v6.1.5</li> <li><a href="https://github.com/vitejs/vite/commit/87cff1215bcba6bee2113e5b85bccfa5c20049d6"><code>87cff12</code></a> fix: reject requests with <code>#</code> in request-target (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/f4d34dc4cd1f9b6aab7aa83846082c8461f311a9"><code>f4d34dc</code></a> release: v6.1.4</li> <li><a href="https://github.com/vitejs/vite/commit/0aeccef739cca78d8903c91a424d02192d698e23"><code>0aeccef</code></a> fix: backport <a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>, fs check with svg and relative paths</li> <li><a href="https://github.com/vitejs/vite/commit/98d066a35501cee393432dfabc8d8012372b8d24"><code>98d066a</code></a> release: v6.1.3</li> <li><a href="https://github.com/vitejs/vite/commit/45b00c987c092b8032ebccdc4db17398e3b4d573"><code>45b00c9</code></a> fix: fs check in transform middleware (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/0044d54c6d773cb1a8a687b129c6c776180854ae"><code>0044d54</code></a> release: v6.1.2</li> <li><a href="https://github.com/vitejs/vite/commit/80381c38d6f068b12e6e928cd3c616bd1d64803c"><code>80381c3</code></a> fix: fs raw query with query separators (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li> <li>Additional commits viewable in <a href="https://github.com/vitejs/vite/commits/v6.1.6/packages/vite">compare view</a></li> </ul> </details> <br /> Updates `vite` from 6.0.15 to 6.1.6 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/releases">vite's releases</a>.</em></p> <blockquote> <h2>v6.1.6</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.5</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.5/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.4</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.3</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.2</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>create-vite@6.1.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/create-vite@6.1.1/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>plugin-legacy@6.1.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.1/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>create-vite@6.1.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/create-vite@6.1.0/packages/create-vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>plugin-legacy@6.1.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/plugin-legacy@6.1.0/packages/plugin-legacy/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0-beta.2</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0-beta.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0-beta.1</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0-beta.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> <h2>v6.1.0-beta.0</h2> <p>Please refer to <a href="https://github.com/vitejs/vite/blob/v6.1.0-beta.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a> for details.</p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/blob/v6.1.6/packages/vite/CHANGELOG.md">vite's changelog</a>.</em></p> <blockquote> <h2><!-- raw HTML omitted -->6.1.6 (2025-04-30)<!-- raw HTML omitted --></h2> <ul> <li>fix: check static serve file inside sirv (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>) (<a href="https://github.com/vitejs/vite/commit/42079a078e2ff5a2bd209788b06f00cfc63a75c3">42079a0</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19965">#19965</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.5 (2025-04-10)<!-- raw HTML omitted --></h2> <ul> <li>fix: reject requests with <code>#</code> in request-target (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>) (<a href="https://github.com/vitejs/vite/commit/87cff1215bcba6bee2113e5b85bccfa5c20049d6">87cff12</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19830">#19830</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.4 (2025-04-03)<!-- raw HTML omitted --></h2> <ul> <li>fix: backport <a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>, fs check with svg and relative paths (<a href="https://github.com/vitejs/vite/commit/0aeccef739cca78d8903c91a424d02192d698e23">0aeccef</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19782">#19782</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.3 (2025-03-31)<!-- raw HTML omitted --></h2> <ul> <li>fix: fs check in transform middleware (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>) (<a href="https://github.com/vitejs/vite/commit/45b00c987c092b8032ebccdc4db17398e3b4d573">45b00c9</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19761">#19761</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.2 (2025-03-24)<!-- raw HTML omitted --></h2> <ul> <li>fix: fs raw query with query separators (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>) (<a href="https://github.com/vitejs/vite/commit/80381c38d6f068b12e6e928cd3c616bd1d64803c">80381c3</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19702">#19702</a></li> </ul> <h2><!-- raw HTML omitted -->6.1.1 (2025-02-19)<!-- raw HTML omitted --></h2> <ul> <li>fix: ensure <code>.[cm]?[tj]sx?</code> static assets are JS mime (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19453">#19453</a>) (<a href="https://github.com/vitejs/vite/commit/e7ba55e7d57ad97ab43682b152159e29fa4b3753">e7ba55e</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19453">#19453</a></li> <li>fix: ignore <code>*.ipv4</code> address in cert (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19416">#19416</a>) (<a href="https://github.com/vitejs/vite/commit/973283bf84c3dca42e2e20a9f9b8761011878b8b">973283b</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19416">#19416</a></li> <li>fix(css): run rewrite plugin if postcss plugin exists (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19371">#19371</a>) (<a href="https://github.com/vitejs/vite/commit/bcdb51a1ac082f4e8ed6f820787d6745dfaa972d">bcdb51a</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19371">#19371</a></li> <li>fix(deps): bump tsconfck (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19375">#19375</a>) (<a href="https://github.com/vitejs/vite/commit/746a583d42592a31e1e8e80cc790a7c9e6acf58e">746a583</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19375">#19375</a></li> <li>fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19392">#19392</a>) (<a href="https://github.com/vitejs/vite/commit/60456a54fe90872dbd4bed332ecbd85bc88deb92">60456a5</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19392">#19392</a></li> <li>fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19440">#19440</a>) (<a href="https://github.com/vitejs/vite/commit/ccac73d9d0e92c7232f09207d1d6b893e823ed8e">ccac73d</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19440">#19440</a></li> <li>fix(html): ignore malformed src attrs (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19397">#19397</a>) (<a href="https://github.com/vitejs/vite/commit/aff7812f0aed059c05ca36c86bf907d25964119a">aff7812</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19397">#19397</a></li> <li>fix(worker): fix web worker type detection (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19462">#19462</a>) (<a href="https://github.com/vitejs/vite/commit/edc65eafa332b57ce44835deb7d7707e2d036c24">edc65ea</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19462">#19462</a></li> <li>refactor: remove custom .jxl mime (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19457">#19457</a>) (<a href="https://github.com/vitejs/vite/commit/0c854645bd17960abbe8f01b602d1a1da1a2b9fd">0c85464</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19457">#19457</a></li> <li>feat: add support for injecting debug IDs (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/18763">#18763</a>) (<a href="https://github.com/vitejs/vite/commit/0ff556a6d9b55bff7cac17396ce7d4397becacaa">0ff556a</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/18763">#18763</a></li> <li>chore: update 6.1.0 changelog (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19363">#19363</a>) (<a href="https://github.com/vitejs/vite/commit/fa7c211bf3e51269f8a8601e5994fb3ebb6859f9">fa7c211</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/19363">#19363</a></li> </ul> <h2>6.1.0 (2025-02-05)</h2> <h3>Features</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vitejs/vite/commit/3b2299f6f57be23867e0d793d3ec32b4dfb5eaaf"><code>3b2299f</code></a> release: v6.1.6</li> <li><a href="https://github.com/vitejs/vite/commit/42079a078e2ff5a2bd209788b06f00cfc63a75c3"><code>42079a0</code></a> fix: check static serve file inside sirv (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19965">#19965</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/c279023cc3b3fed9cfcb43938cd452aa9ce11995"><code>c279023</code></a> release: v6.1.5</li> <li><a href="https://github.com/vitejs/vite/commit/87cff1215bcba6bee2113e5b85bccfa5c20049d6"><code>87cff12</code></a> fix: reject requests with <code>#</code> in request-target (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19830">#19830</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/f4d34dc4cd1f9b6aab7aa83846082c8461f311a9"><code>f4d34dc</code></a> release: v6.1.4</li> <li><a href="https://github.com/vitejs/vite/commit/0aeccef739cca78d8903c91a424d02192d698e23"><code>0aeccef</code></a> fix: backport <a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19782">#19782</a>, fs check with svg and relative paths</li> <li><a href="https://github.com/vitejs/vite/commit/98d066a35501cee393432dfabc8d8012372b8d24"><code>98d066a</code></a> release: v6.1.3</li> <li><a href="https://github.com/vitejs/vite/commit/45b00c987c092b8032ebccdc4db17398e3b4d573"><code>45b00c9</code></a> fix: fs check in transform middleware (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19761">#19761</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/0044d54c6d773cb1a8a687b129c6c776180854ae"><code>0044d54</code></a> release: v6.1.2</li> <li><a href="https://github.com/vitejs/vite/commit/80381c38d6f068b12e6e928cd3c616bd1d64803c"><code>80381c3</code></a> fix: fs raw query with query separators (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/19702">#19702</a>)</li> <li>Additional commits viewable in <a href="https://github.com/vitejs/vite/commits/v6.1.6/packages/vite">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/nestrilabs/nestri/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
2db52f107e | 📝 docs: Start work on the README |