mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
chore: Update Readme
This commit is contained in:
@@ -4,6 +4,10 @@ TEST_DATABASE_URL=
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_URL=
|
||||
DATABASE_HOST=
|
||||
DATABASE_PORT=
|
||||
DATABASE_USER=
|
||||
ADMIN_SHARED_SECRET=
|
||||
SSH_AUTH_KEY=
|
||||
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
|
||||
12
README.md
Normal file
12
README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
<p align="center">
|
||||
<img src="./wordmark.svg" alt="Nestri logo" width="200" />
|
||||
</p>
|
||||
|
||||
# Nestri
|
||||
|
||||
Cloud game streaming platform — play your games from any device via QUIC low-latency streams.
|
||||
|
||||
- **Streaming core** — QUIC-based relay, game machines, pairing codes
|
||||
- **Games** — Steam-linked catalog with per-machine depot downloads
|
||||
- **Auth** — Steam / SSH login via a self-hosted OpenAuth issuer
|
||||
- **Infra** — Cloudflare Workers + Postgres, deployed with [Alchemy](https://alchemy.run)
|
||||
@@ -17,10 +17,10 @@ const Database = Effect.gen(function* () {
|
||||
return yield* Cloudflare.Hyperdrive.Connection('db', {
|
||||
origin: {
|
||||
scheme: 'postgres',
|
||||
host: 'public-nestri-pg-1-atdogthbymao.db.upclouddatabases.com',
|
||||
port: 11569,
|
||||
host: process.env.DATABASE_HOST ?? 'localhost',
|
||||
port: Number(process.env.DATABASE_PORT ?? 5432),
|
||||
database,
|
||||
user: 'upadmin',
|
||||
user: process.env.DATABASE_USER ?? 'postgres',
|
||||
password: Redacted.make(process.env.DATABASE_PASSWORD!)
|
||||
},
|
||||
dev: {
|
||||
|
||||
44
apps/api/README.md
Normal file
44
apps/api/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# apps/api
|
||||
|
||||
The public HTTP API for Nestri — a [Hono](https://hono.dev) app deployed as a Cloudflare Worker.
|
||||
|
||||
## What it does
|
||||
|
||||
Exposes the JSON API consumed by frontends and other clients. Every route is a thin wrapper that
|
||||
validates input, delegates to a domain function in [`@nestri/core`](../../packages/core/README.md),
|
||||
and returns `{ data: ... }`. All business logic lives in the core package.
|
||||
|
||||
Routes:
|
||||
|
||||
| Prefix | Purpose |
|
||||
| ----------------- | ------------------------------------------------------------- |
|
||||
| `/` | Health check |
|
||||
| `/user` | Current user profile, fingerprints, linked accounts |
|
||||
| `/steam` | Link / sync / unlink a Steam account |
|
||||
| `/library` | Owned games with playtime |
|
||||
| `/games` | Game catalog |
|
||||
| `/pairing-code` | Device pairing codes |
|
||||
| `/machine` | Host machines |
|
||||
| `/access-token` | Short-lived access tokens |
|
||||
| `/doc` | Generated OpenAPI spec |
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
app/
|
||||
index.ts # Hono entrypoint: middleware, routes, error handler, /doc
|
||||
middleware/auth.ts # Bearer JWT + admin shared-secret auth → Actor
|
||||
routes/*.ts # Thin route namespaces (UserApi, SteamApi, ...)
|
||||
utils/ # ErrorResponses, Result(), validator wrapping
|
||||
test/ # Route tests (Vitest/Bun)
|
||||
```
|
||||
|
||||
## Key details
|
||||
|
||||
- Auth: `Authorization: Bearer <JWT>` verified against `@nestri/auth`; or the `x-nestri-admin-token` header (see `ADMIN_SHARED_SECRET`).
|
||||
- Errors: centralized `VisibleError` → typed JSON responses.
|
||||
- The API worker receives its bindings (`AUTH`, `HYPERDRIVE`, `STEAM_API_KEY`, `ADMIN_SHARED_SECRET`) from Alchemy — see `alchemy.run.ts` at the repo root.
|
||||
|
||||
## Running
|
||||
|
||||
Run via the root Alchemy setup (`bun alchemy.run.ts --dev`). Needs a Postgres database and an auth worker; see the root README for full dev setup.
|
||||
32
apps/auth/README.md
Normal file
32
apps/auth/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# apps/auth
|
||||
|
||||
The authentication worker for Nestri — a Cloudflare Worker built on
|
||||
[`@nestri/auth`](../../packages/auth/README.md) (OpenAuth-style issuer).
|
||||
|
||||
## What it does
|
||||
|
||||
Hosts the OpenID Connect / OAuth issuer and the login UI:
|
||||
|
||||
- **Steam OAuth** — the primary login flow. After Steam redirects back, the worker fetches the
|
||||
player's profile, creates (or finds) the `User` + `LinkedAccount` rows in Postgres, auto-creates a
|
||||
personal team on first login, and issues a JWT `user` subject containing `{ userID, linkedAccountID }`.
|
||||
- **SSH login** — authenticates a device via its SSH fingerprint (keyed by `SSH_AUTH_KEY`),
|
||||
resolving the identity through `Steam.resolveSshIdentity` in `@nestri/core`.
|
||||
|
||||
## Key details
|
||||
|
||||
- Signing keys are generated at runtime and persisted in the `AuthStorage` KV namespace.
|
||||
- JWT subjects are defined in `@nestri/core/auth/subjects`.
|
||||
- The API worker calls this worker via a service binding (`AUTH`), verified through `AUTH_ISSUER_URL`.
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
src/index.ts # Worker entrypoint: issuer config + success callbacks (steam, ssh)
|
||||
test/ # Worker tests
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Deployed through Alchemy (`apps/auth` worker in `alchemy.run.ts` at the repo root) with bindings
|
||||
`AuthStorage` (KV), `HYPERDRIVE` (Postgres), `STEAM_API_KEY`, `SSH_AUTH_KEY`.
|
||||
46
packages/auth/README.md
Normal file
46
packages/auth/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# packages/auth (`@nestri/auth`)
|
||||
|
||||
Framework-agnostic OpenAuth implementation for Nestri — the OAuth/OIDC **issuer**, **client**,
|
||||
**subjects**, and the login **UI**. A vendored/forked build of **OpenAuth**.
|
||||
|
||||
## What it does
|
||||
|
||||
Everything needed to run your own authentication provider:
|
||||
|
||||
- **`issuer.ts`** — the authorization server: routes for `/authorize`, `/callback`, `/token`,
|
||||
`/userinfo`, `.well-known/*`, plus the login UI (React renderer).
|
||||
- **`client.ts`** — `createClient` to verify JWTs against the issuer ("who is this token?").
|
||||
- **`subject.ts`** — typed JWT subjects (`zod` schemas for the token payload).
|
||||
- **`provider/*`** — drop-in OAuth/OIDC providers (steam, discord, github, google, apple,
|
||||
microsoft, slack, spotify, twitch, x, yahoo, facebook, linkedin, cognito, keycloak, jumpcloud,
|
||||
oauth2, oidc, password, ssh, code, arctic).
|
||||
- **`storage/*`** — persistence adapters for keys/sessions/codes: `memory`, `cloudflare` (KV),
|
||||
`aws`, `dynamo`.
|
||||
- **`ui/*`** — the login page components (forms, password, code, theme, CSS).
|
||||
- **`jwt.ts`, `keys.ts`, `pkce.ts`, `random.ts`** — signing, keypair management, PKCE, randomness.
|
||||
|
||||
## Usage
|
||||
|
||||
Consumed by the [`apps/auth`](../../apps/auth/README.md) worker, e.g.:
|
||||
|
||||
```ts
|
||||
import { issuer } from '@nestri/auth/index';
|
||||
import { CloudflareStorage } from '@nestri/auth/storage/cloudflare';
|
||||
import { SteamProvider } from '@nestri/auth/provider/steam';
|
||||
```
|
||||
|
||||
The API uses `createClient` (from `@openauth/openauth/client`) or the bundled `client.ts` to verify
|
||||
tokens against the issuer URL.
|
||||
|
||||
## Scripts
|
||||
|
||||
```sh
|
||||
bun test # run tests
|
||||
bun run build # build (see script/build.ts)
|
||||
```
|
||||
|
||||
## Note
|
||||
|
||||
`@openauthjs` is the upstream project; this package's exports are meant to be API-compatible with a
|
||||
pinned preference toward tree-shaking-friendly imports. Prefer importing subpaths over the barrel
|
||||
(`@nestri/auth/index`).
|
||||
60
packages/core/README.md
Normal file
60
packages/core/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# packages/core
|
||||
|
||||
`@nestri/core` — the **domain layer** for Nestri. All business logic, database access, and
|
||||
serialization lives here. The API and auth workers are thin pass-through translation layers on top.
|
||||
|
||||
## What it contains
|
||||
|
||||
| Area | Files | Purpose |
|
||||
| ---- | ----- | ------- |
|
||||
| **db** | `db/index.ts`, `db/types.ts`, `db/test.ts` | Drizzle + Postgres (`Database.use/transaction`), ULID column helpers |
|
||||
| **users** | `user/*` | Users, linked accounts, fingerprints, library |
|
||||
| **teams** | `team/*` | Teams + membership with roles (`team_member`) |
|
||||
| **games** | `game/*` | Game catalog, depot content, per-host downloads |
|
||||
| **steam** | `steam/index.ts` | Steam API integration & SSH identity resolution |
|
||||
| **auth** | `auth/subjects.ts` | JWT subjects shared with the auth worker |
|
||||
| **infra** | `env.ts`, `context.ts`, `actor.ts`, `fn.ts`, `id.ts`, `error.ts`, `examples.ts` | Environment, Actor model, zod-typed `fn()` wrappers, IDs, error types, examples |
|
||||
| **migrations** | `migrations/` | Drizzle-kit SQL migrations for Postgres schema |
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Domain namespaces** (`user/`, `team/`, ...) expose typed `fn()` functions that validate input
|
||||
with a Zod schema and serialize DB rows inside the function boundary — the API routes never see raw table rows.
|
||||
- **Actor model**: `Actor.userID`, `Actor.type`, ... pull the current authenticated identity from
|
||||
`AsyncLocalStorage` (set by the API middleware / auth worker) without passing it through call chains.
|
||||
- **Soft delete**: every table has `time_deleted`; queries filter with `isNull(table.timeDeleted)`.
|
||||
- **IDs**: ULIDs via `Identifier.ascending('user')` → `usr_...`.
|
||||
- Tables are defined in `*.sql.ts` files (drizzle) with namespaces in `index.ts`.
|
||||
- Environment is read through `Env.get()`, init by worker bindings.
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
src/
|
||||
├── actor.ts, env.ts, id.ts, fn.ts, error.ts, examples.ts
|
||||
├── db/
|
||||
├── auth/
|
||||
├── user/ (user.sql.ts, linked-account.*, fingerprint.*, library.*, index.ts)
|
||||
├── team/ (team.sql.ts, member.*, index.ts)
|
||||
├── game/ (game.sql.ts, depot.*, download.*, index.ts)
|
||||
├── steam/ (index.ts)
|
||||
├── pairing-code/
|
||||
├── access-token/
|
||||
└── machine/
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
```sh
|
||||
bun run db:push # push schema (drizzle-kit)
|
||||
bun run db # open drizzle-kit
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { Team } from '@nestri/core/team/index';
|
||||
import { Database } from '@nestri/core/db/index';
|
||||
|
||||
const team = await Team.fromID('tem_...');
|
||||
```
|
||||
43
wordmark.svg
Normal file
43
wordmark.svg
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="120"
|
||||
height="24"
|
||||
viewBox="0 0 31.749999 6.3499999"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><g
|
||||
id="layer1"><g
|
||||
id="g1"
|
||||
transform="matrix(1.0384818,0,0,1.0384818,-0.26119451,0.00464605)"><path
|
||||
d="m 93.240234,43.240234 v 3.34961 l -0.40039,-0.357422 c -2.096687,-1.875287 -4.792416,-2.933207 -7.59961,-2.990235 v 3.572266 c 4.331915,0.124459 7.820855,3.613398 7.945313,7.945313 h 3.574219 V 43.240234 Z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:none;stroke-width:0.0714435;stroke-dasharray:none"
|
||||
id="path27"
|
||||
transform="matrix(0.44279029,0,0,0.44279029,-36.976573,-18.649472)" /><path
|
||||
d="m 85.240234,47.292969 v 3.642578 c 2.057061,0.11945 3.704769,1.767158 3.824219,3.824219 h 3.642578 C 92.583101,50.689065 89.310935,47.4169 85.240234,47.292969 Z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:none;stroke-width:0.0714435;stroke-dasharray:none"
|
||||
id="path25"
|
||||
transform="matrix(0.44279029,0,0,0.44279029,-36.976573,-18.649472)" /><path
|
||||
d="m 85.240234,51.416016 v 3.34375 h 3.34375 c -0.117719,-1.795413 -1.548337,-3.226031 -3.34375,-3.34375 z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:none;stroke-width:0.0714435;stroke-dasharray:none"
|
||||
id="path26"
|
||||
transform="matrix(0.44279029,0,0,0.44279029,-36.976573,-18.649472)" /></g><path
|
||||
d="m 6.3553899,0.52902957 v 0.1682512 1.53946343 h 1.6989362 0.00878 1.7077191 V 2.4050039 H 8.064776 a 1.7064502,1.7064502 0 0 0 -4.35e-4,0 1.7064502,1.7064502 0 0 0 -0.00131,0 v 0.011399 a 1.7109739,1.7109739 0 0 1 -0.0013,-0.011399 H 6.3551932 v 1.5394593 0.1684904 h 0.00261 A 1.7064502,1.7064502 0 0 0 7.3305504,5.6521827 1.7064502,1.7064502 0 0 0 8.0629013,5.818063 v 0.00263 H 9.7706262 11.478346 V 5.6521776 4.1129494 H 9.7706244 8.0629097 v -0.168495 h 1.5280697 0.1715786 0.00802 0.1682601 a 1.7109739,1.7109739 0 0 0 1.5292529,-1.539459 1.7109739,1.7109739 0 0 0 0.0079,-0.1625607 1.7109739,1.7109739 0 0 0 0,-0.00569 1.7109739,1.7109739 0 0 0 -0.976066,-1.53946152 1.7109739,1.7109739 0 0 0 -0.7292697,-0.1658802 v -0.00217 H 8.0628921 Z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-dasharray:none"
|
||||
id="path8" /><path
|
||||
d="m 13.709845,0.52614613 v 0.002175 a 1.7119553,1.7119553 0 0 0 -0.729689,0.16598235 1.7119553,1.7119553 0 0 0 -0.976632,1.54034282 1.7119553,1.7119553 0 0 0 0,0.00569 1.7119553,1.7119553 0 0 0 0.0079,0.162659 1.7119553,1.7119553 0 0 0 1.530129,1.5403426 h 0.168355 0.0081 0.171679 1.528947 v 0.168587 H 13.709919 12.001222 V 5.6520324 5.820626 h 1.708697 1.708701 v -0.00263 a 1.7074292,1.7074292 0 0 0 0.732769,-0.1659739 1.7074292,1.7074292 0 0 0 0.973313,-1.5401077 1.7074292,1.7074292 0 0 0 0,-0.00131 1.7074292,1.7074292 0 0 0 -0.0081,-0.167164 1.7074292,1.7074292 0 0 0 -1.696823,-1.5403427 1.7119553,1.7119553 0 0 1 -0.0013,0.011382 v -0.011382 a 1.7074292,1.7074292 0 0 0 -0.0013,0 h -4.35e-4 -1.707038 v -0.168328 h 1.708702 0.0088 1.69991 V 0.69442656 0.52607662 h -1.708693 z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
|
||||
id="path1" /><path
|
||||
d="M 17.649994,0.52916663 V 2.1732606 h 1.814408 0.0095 v 0.1796921 0.012166 1.6319287 0.1799455 1.6438406 h 0.784918 1.038868 V 4.1769923 a 1.8224328,1.8224328 0 0 0 0,-0.00151 1.8224328,1.8224328 0 0 0 -0.0079,-0.1687946 1.8224328,1.8224328 0 0 0 -7.34e-4,-0.00964 h 0.0085 V 2.3529527 2.1732606 h 1.821252 0.0026 V 0.52916663 h -1.044966 -0.778835 -0.791 -1.032787 z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
|
||||
id="path1-2" /><path
|
||||
d="M 23.644282,0.52916392 V 0.69714688 2.2341075 h 1.696176 0.0087 1.704944 v 0.1679826 h -1.551414 -0.152345 c -4.4e-4,0 -7.34e-4,0 -0.0012,0 v 0.012561 c -4.25e-4,-0.00412 -7.79e-4,-0.00837 -0.0012,-0.012561 -0.472042,6.912e-4 -0.899231,0.1934204 -1.207377,0.5041854 -0.268576,0.270858 -0.446712,0.6314329 -0.485705,1.0327745 -0.0054,0.054867 -0.008,0.1105228 -0.008,0.1667991 0,4.401e-4 -2e-6,9.809e-4 0,0.00145 h -0.0025 v 1.536728 0.1682203 h 1.704942 V 5.8095798 5.6439665 4.1697918 l 1.14579,1.1981514 0.264176,0.2760233 0.160875,0.168219 h 0.134103 1.704942 0.153767 l -0.153703,-0.1537667 -0.01446,-0.014453 -1.536723,-1.536723 -0.153767,-0.153768 -0.0055,-0.00546 c 0.0018,0 0.0037,-2.316e-4 0.0055,-2.364e-4 0.05714,-1.855e-4 0.113724,-0.00315 0.169404,-0.00873 0.807873,-0.082186 1.449216,-0.7273834 1.525589,-1.5369611 0.0051,-0.053473 0.0076,-0.1075037 0.0076,-0.1622959 0,-0.00194 7e-6,-0.00376 0,-0.0057 -0.0024,-0.6779645 -0.399622,-1.26300854 -0.973719,-1.53694086 -0.220934,-0.10543433 -0.468068,-0.1647717 -0.729009,-0.1656172 v -0.002364 h -1.704942 z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-dasharray:none"
|
||||
id="path8-0" /><path
|
||||
d="m 29.435832,0.5290966 v 1.4644007 0.179692 0.1796919 0.01217 1.6319272 0.1799453 1.6438394 h 0.784916 1.038868 v -1.643844 a 1.8224314,1.8224314 0 0 0 0,-0.00153 1.8224314,1.8224314 0 0 0 -0.0077,-0.1687941 1.8224314,1.8224314 0 0 0 -7.87e-4,-0.00964 h 0.0085 V 2.3528812 2.1731893 0.5290966 H 30.46863 Z"
|
||||
style="fill:#ff4f14;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
|
||||
id="path1-22" /></g></svg>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
Reference in New Issue
Block a user