nylorun 0.8.0-beta → 0.9.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.md +45 -15
- package/dist/cli.js +12 -2
- package/dist/project/link.d.ts +13 -7
- package/dist/project/link.js +13 -2
- package/dist/stack/commands.d.ts +20 -2
- package/dist/stack/commands.js +76 -23
- package/dist/stack/compose-file.d.ts +5 -2
- package/dist/stack/compose-file.js +19 -6
- package/dist/stack/env-file.d.ts +3 -14
- package/dist/stack/env-file.js +6 -36
- package/dist/stack/index.d.ts +6 -0
- package/dist/stack/index.js +12 -1
- package/dist/stack/keys.d.ts +4 -0
- package/dist/stack/keys.js +72 -0
- package/dist/stack/mcp.d.ts +8 -0
- package/dist/stack/mcp.js +114 -0
- package/dist/stack/operator-keys.d.ts +39 -0
- package/dist/stack/operator-keys.js +146 -0
- package/dist/stack/paths.d.ts +17 -0
- package/dist/stack/paths.js +4 -0
- package/dist/stack/prepare.d.ts +2 -4
- package/dist/stack/prepare.js +4 -4
- package/dist/stack/sandbox.js +1 -1
- package/package.json +4 -4
- package/dist/project/derived-key.d.ts +0 -8
- package/dist/project/derived-key.js +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# nylorun
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- bd478ee: **Subject tokens, browser keys and derived principals leave open source (F9 I3, protocol 7).** Upgrade these with the Runtime: they speak protocol 7, which this Runtime serves beside 4, 5 and 6; protocol 6 clients keep working on every route that remains. Browsers and apps present a trusted issuer's token (F9 I2), servers an operator key (F9 I1), and a person's own credentials come from the operator's credential resolver (F9 C1). See MIGRATION.md.
|
|
8
|
+
|
|
9
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`): subject tokens are gone.** `POST /v1/tokens`, `POST /v1/access/revocations`, subject-token verification, revocation epochs and the `subject.revoked` stream signal are removed; the routes answer `404`, and a JWT no trusted issuer signed is the opaque `404`. A stream opened with a token now ends only at its expiry (`event: nylorun.closed`, reason `token_expired`; `StreamClosedFrame` loses `revoked`). Migration `0010_oss_auth_removals` drops the `subject_epochs`, `subject_usage` and `publishable_keys` tables. The `token` caller is a trusted issuer's token only: it loses `role`, `limits` and `epoch`, and `issuer` is always set; `GET /v1/me` no longer reports `via: token`. Core drops `SUBJECT_TOKEN_TYPE`, `SUBJECT_TOKEN_AUDIENCE`, `subjectTokenIssuer` (the Runtime's own tokens use `tenantTokenIssuer`, same value), `CreateTokenRequest`/`Response`, `RevokeSubject*`, `SubjectTokenClaims`, `TOKEN_TTL_MIN_SECONDS` and `TOKEN_TTL_DEFAULT_SECONDS`; `TOKEN_SCOPES` is `agents:read`, `sessions:own`, `sandboxes:write`.
|
|
10
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`): the access policy and role limits are gone.** `GET`/`PUT /v1/access/policy` answer `404`, a turn is never refused with `429 limit_exceeded` for a role (limit requests at your proxy), and an existing `access.policy` setting is ignored. Core drops `AccessPolicy*`, `AccessRole*`, `RoleLimits*`, `DEFAULT_ACCESS_POLICY`, `PutAccessPolicyRequest` and `ROLE_NAME_PATTERN`.
|
|
11
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`): publishable keys and the Runtime's CORS are gone.** `/v1/access/publishable-keys*` answer `404`, `Nylorun-Key` is ignored, `NYLORUN_BROWSER_ACCESS` and `browserAccess` (host.json, `createHost`, `startEphemeralRuntime`) are removed, and the Runtime sends no CORS headers. A request with `Origin` reaches the Tenant routes of the public listener with no toggle: a trusted issuer's token is served, an application key or delivery token is `403 origin_rejected`; admin routes, `/health`, `/ready` and the operator listener still refuse `Origin`. Any `OPTIONS` request is `204` with `Allow` and no CORS header: the operator's proxy answers preflights. Route declarations lose `browser` (`x-nylorun-browser` leaves the OpenAPI document) and the `publishable` credential; the token security scheme is `issuerToken`. Core drops `PUBLISHABLE_KEY_HEADER`, `PUBLISHABLE_KEY_PATTERN`, `PUBLISHABLE_KEY_ID_PATTERN`, `tenantOfPublishableKey`, `newPublishableKey`, `newPublishableKeyId`, `PublishableKey*`, `OriginEntrySchema`, `originAllowed` and `LOOPBACK_ORIGIN_WILDCARDS`.
|
|
12
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`, `@nylorun/agents`): vault routes are application-only, and `vaults:own` is retired.** Every `/v1/vaults` route takes an application key acting for no one (operator keys, Studio); a request acting for a subject or a trusted issuer's token gets `403 scope_required`. `vaults:own` leaves `SUBJECT_SCOPES`, `TOKEN_SCOPES` and the issuer scopes; `Nylorun-Scopes` may still name it and it grants nothing. Existing user vaults stay attachable to their own owner's sessions. `client.createVault` takes `{ scope: "installation" }` or `ownerUserId`, and `client.listVaults()` takes an optional owner.
|
|
13
|
+
- **Breaking (`@nylorun/runtime`, `@nylorun/core`, `@nylorun/admin`, `nylorun`): derived principals are gone.** Only Studio's key is derived from the admin key. `NYLORUN_DERIVED_PRINCIPALS` is ignored and no longer written, `hostPrincipals` takes no `derived`, `startEphemeralRuntime` no `derivedPrincipals`, and `@nylorun/admin` drops `deriveTenantKey`, `PROJECT_PRINCIPAL_ID` and `admin.deriveTenantKey`. Keys an earlier Host derived stay in the database and keep working as ordinary keys; replace them with operator keys. Core renames `DERIVED_PRINCIPAL_ID_PATTERN` to `APPLICATION_KEY_ID_PATTERN`.
|
|
14
|
+
- **Breaking (`@nylorun/agents`):** `createTokenEndpoint`, `createBrowserClient` and the `@nylorun/agents/browser` entry, `client.tokens`, `TokensClient`, `PublishableKeysClient`, `client.access.getPolicy`/`putPolicy`/`revokeSubject`/`publishableKeys` and `Destination.publishableKey` are removed. `client.access.signingKeys` and `client.access.jwks()` stay, and `Destination.token` takes a trusted issuer's tokens.
|
|
15
|
+
- **Breaking (`@nylorun/cli`):** `nylo access policy|keys|revoke|token` are removed and name what replaces them; `nylo access signing-keys list|rotate|revoke` stays.
|
|
16
|
+
- **Breaking (`@nylorun/core`, `@nylorun/agents`, `@nylorun/studio`): the Studio embed message `open.babai` is now `open.session`** (`{ sessionId }`): it asks the embedding app to open that session in its own UI. Studio sends no such message itself.
|
|
17
|
+
- **Breaking (`nylorun`): Studio embedding is opt-in.** No origin may frame Studio by default; list your app's origins with `nylorun start --studio-embed-origin <origin>`. A Tenant's `.env` from an earlier nylorun keeps the origins it had until `--studio-embed-origin-reset`.
|
|
18
|
+
- `PROTOCOL_VERSION` is 7 and `HOST_PROTOCOL` 4–7; the Host features `subject-tokens`, `browser-access` and `derived-principals` are removed. `@nylorun/studio` and `@nylorun/create-agent` speak protocol 7.
|
|
19
|
+
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- 8773586: **Installation vaults and a credential resolver (F9 C1).** A session's MCP credential now comes from its attached vaults, then from the operator's own credential resolver; nothing changes for existing vaults, and the protocol stays at 6.
|
|
23
|
+
|
|
24
|
+
- Installation vaults: `POST /v1/vaults` takes `scope: "installation"` (no `ownerUserId`) from an application key acting for no one; a request acting for a subject gets `403`. The vault is owned by `installation`, now a reserved subject like `host`. Any session may attach one and select its credentials. `GET /v1/vaults` from an application key lists them after the named person's vaults, and lists only them without `ownerUserId`; a request acting for a subject never sees one (the opaque `404`). The host model vault stays hidden and unattachable. Migration 0008 adds the scope.
|
|
25
|
+
- The credential resolver: `NYLORUN_RESOLVER_URL` and `NYLORUN_RESOLVER_TOKEN` on the gateway (`TenantConfig.resolver` and `startEphemeralRuntime({ resolver })` in process). When the session's vaults hold nothing for a remote MCP server's URL, the Runtime POSTs `{ owner, session, turn, target: { kind: "mcp", server, agent, url } }` with the resolver's bearer: `200 { headers, expiresAt? }` is used, `404` goes without a credential, and anything else or no answer within 5 s refuses the server with `credential_unavailable`. Owner and turn come from the session row. Answers are cached per owner and URL until `expiresAt`, at most 5 minutes (60 s without one), and concurrent misses share one request. See DEPLOYMENT.md, Credentials.
|
|
26
|
+
- `nylorun`: the gateway's Compose service passes `NYLORUN_RESOLVER_URL` and `NYLORUN_RESOLVER_TOKEN` from the shell that runs `nylorun start` (unset by default).
|
|
27
|
+
- Studio: the Vault page is now **Connections**, and creates installation vaults.
|
|
28
|
+
|
|
29
|
+
- e1bfb4b: **MCP OAuth connect for installation vaults (F9 C2).** The installation can sign in once to a remote MCP server that uses OAuth, and every session that attaches the vault uses the credential. Additive; the protocol stays at 6.
|
|
30
|
+
|
|
31
|
+
- `POST /v1/vaults/{vaultId}/oauth/start` (application keys acting for no one, installation vaults only) takes `{ url, server, clientId? }` and answers `{ authorizeUrl, expiresAt }`. The Runtime discovers the server's authorization server (RFC 9728, then RFC 8414), registers itself (RFC 7591) unless `clientId` names a registered client, and starts an S256 PKCE sign-in whose `state` works once, for ten minutes. A server without registration and no `clientId` is `400 oauth_client_required`.
|
|
32
|
+
- `GET /v1/oauth/callback` takes the browser back: anonymous and unversioned like an artifact link, it exchanges the code and stores an `oauth` credential bound to the URL, named after `server` (connecting again rotates it), and answers a small HTML page. An unknown, used or expired `state` is `oauth_state_invalid`; the authorization server's refusal is `oauth_failed`. The callback's base is `NYLORUN_PUBLIC_URL` (`TenantConfig.publicUrl`), else the start request's origin.
|
|
33
|
+
- Every OAuth step runs in the gateway's keys module (F9-D14): the runtime container never sees a token, the PKCE verifier or a client secret, and makes no outbound call. Migration 0009 adds `oauth_pending`, with the verifier and secret sealed under the vault key. `Keys` gains `startOAuth` and `finishOAuth`.
|
|
34
|
+
- OAuth requests, including refresh of every OAuth vault credential, now go through `guardedFetch` (`tenant/outbound.ts`): the `NYLORUN_ENDPOINT_*` address policy checked on the address connected to, no redirects, a bounded answer. With `NYLORUN_ENDPOINT_PRIVATE=refuse` a token endpoint on a private address is refused, where refresh used to call it.
|
|
35
|
+
- `@nylorun/core`: `ERROR_CODES` adds `oauth_client_required`, `oauth_state_invalid` and `oauth_failed`; `StartOAuthRequestSchema` and `StartOAuthResponseSchema`.
|
|
36
|
+
- `nylorun mcp connect <url> --server <name> [--vault <id>] [--client-id <id>]`: creates the installation vault `mcp` if needed, opens the sign-in page and waits up to 10 minutes for the credential. See DEPLOYMENT.md, "Connecting a remote MCP server with OAuth".
|
|
37
|
+
|
|
38
|
+
- 5cfaed9: **Operator keys, and project links on them (F9 I1).** The Admin API manages the Tenant's application keys by name: `PUT /v1/admin/keys/{id}` creates a key or rotates it and returns it once, `GET /v1/admin/keys` lists every key's id, role and issue time (never the keys), and `DELETE /v1/admin/keys/{id}` removes one. A rotated or deleted key stops authenticating on its next request. Ids follow `^[a-z][a-z0-9-]{0,31}$`; `studio` (derived from the admin key) is refused. Keys keep today's format (64 hex) and only their SHA-256 is stored, in the existing principals table (no migration). Host feature `operator-keys` (additive, protocol unchanged); core adds the `OperatorKey`, `ListOperatorKeysResponse`, `PutOperatorKeyResponse` and `DeleteOperatorKeyResponse` schemas.
|
|
39
|
+
|
|
40
|
+
- `@nylorun/admin`: `admin.keys.put(id)`, `admin.keys.list()` and `admin.keys.delete(id)`; they refuse with `incompatible_host` when the Host lacks `operator-keys`.
|
|
41
|
+
- `nylorun key put|list|rm <id>` manages a running local Tenant's keys; `put` prints the key once on stdout.
|
|
42
|
+
- `nylorun start` no longer derives the project's key. It keeps `.nylorun/credentials.json` while its key still reaches the Tenant (one authenticated read); otherwise it gives the project the operator key `project`, which the Host root keeps in `project-credentials.json` (0600) so every checkout linked to the Tenant shares it. An existing derived project key keeps working and is adopted.
|
|
43
|
+
- `nylorun sandbox` uses the linked project's key, or the operator key `cli` it puts once and keeps in `<Host root>/cli-credentials.json` (0600).
|
|
44
|
+
- The ephemeral Runtime (`startEphemeralRuntime`) no longer registers the derived `project` principal; pass `derivedPrincipals: ["project"]` to keep it. Derived principals (`NYLORUN_DERIVED_PRINCIPALS`, `deriveTenantKey`) still work on a Host.
|
|
45
|
+
|
|
46
|
+
- b906050: **Studio for a team (F9 S1).** Studio can sit behind a sign-in proxy such as oauth2-proxy. A request with no Studio session that carries a JWT, in `X-Forwarded-Access-Token` or as an `Authorization` bearer that is not a Studio session, signs in when the Runtime's `GET /v1/me` verifies it and reports the `studio` scope: Studio sets its usual signed session cookie, which records the subject for its write log and ends no later than the token (at most the usual 30 days), and serves the request. Studio never trusts the header unverified and keeps nothing else. A token without the scope gets `403` naming `studio`; a token the Runtime refuses, or one that is not a JWT, gets `401`. Admitted people get Studio's Tenant-wide view; the CLI sign-in and embedding are unchanged, and a valid embed bearer session still wins. New `NYLORUN_STUDIO_ALLOWED_HOSTS`: extra `Host` values Studio serves, comma-separated (`studio.acme.dev`); its state changes accept that host's `https` and `http` origins, and the cookie is `Secure` when the proxy sends `X-Forwarded-Proto: https`. The default is unchanged: only `localhost` and `127.0.0.1` on the published port. `nylorun start` passes `NYLORUN_STUDIO_ALLOWED_HOSTS` from its environment to the Studio container.
|
|
47
|
+
- c0b604e: **Trusted issuers (F9 I2).** The Tenant API accepts JWTs from the operator's own identity provider as bearers, configured in an identity file: `NYLORUN_IDENTITY_FILE` names a YAML file listing each issuer's `name`, `issuer`, `audience`, `jwks` URL or static PEM `keys`, a `subject` template over scalar claims (`u:{sub}`), `scopes` from a claim or a fixed list, `allowedScopes`, an optional `agents` allowlist, optional `sandboxes` grant templates (`{org_id}/*`) and `maxLifetime`. A malformed file stops the boot, naming the issuer and the field; a subject template without a claim is malformed. A bearer whose unverified `iss` names an issuer is verified with RS256, ES256 or EdDSA only, up to 16 KiB, `aud` matching, `exp` and `iat` required and `exp − iat` within `maxLifetime`, and becomes a token caller (`role: issuer:<name>`) with the issuer's scopes, agents and rendered sandbox grants; subject revocation does not reach it. JWKS keys are fetched only from the configured URL (no redirects), cached by `kid`, refetched at most once a minute for an unknown `kid`; while a JWKS is unreachable cached keys keep working and a new `kid` is `401 issuer_unavailable` (new error code). A browser request with an issuer token needs no publishable key (CORS comes from the operator's proxy). New: `GET /v1/me` reports the subject, scopes, agents, sandbox grants and `via` (`application:<id>`, `subject`, `token` or `issuer:<name>`) of any credential; the issuer-only scope `studio`; `ISSUER_SCOPES`, `CALLER_SCOPES` and `MeResponseSchema` in `@nylorun/core/contracts`; `parseIdentityFile` and `createTrustedIssuers` in `@nylorun/runtime`, and an `issuers` option on `startEphemeralRuntime`; Host feature `trusted-issuers` (additive, protocol unchanged). `nylorun start` sets `NYLORUN_IDENTITY_FILE=/nylorun/identity.yaml` on the runtime container when `<Host root>/identity.yaml` exists.
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Pin core to the tested release.
|
|
52
|
+
- Pin runtime to the tested release.
|
|
53
|
+
- Pin studio to the tested release.
|
|
54
|
+
- Updated dependencies [8773586]
|
|
55
|
+
- Updated dependencies [e1bfb4b]
|
|
56
|
+
- Updated dependencies [5cfaed9]
|
|
57
|
+
- Updated dependencies [bd478ee]
|
|
58
|
+
- Updated dependencies [c0b604e]
|
|
59
|
+
- @nylorun/core@0.13.0-beta
|
|
60
|
+
|
|
3
61
|
## 0.8.0-beta
|
|
4
62
|
|
|
5
63
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -46,6 +46,10 @@ nylorun ls [--json] # the Tenants on this machine, with their sta
|
|
|
46
46
|
nylorun delete <tenant> --yes # remove a Tenant: containers, volumes, Host root and vault key
|
|
47
47
|
nylorun sandbox ls [--tenant <name>] [--label <key=value>]... [--json] # the running Tenant's sandboxes
|
|
48
48
|
nylorun sandbox rm <id> [--tenant <name>] # delete a sandbox and its files
|
|
49
|
+
nylorun key put <id> [--tenant <name>] # create or rotate the Tenant's operator key <id>; prints it once
|
|
50
|
+
nylorun key list [--tenant <name>] [--json] # the Tenant's keys: id, role, when issued
|
|
51
|
+
nylorun key rm <id> [--tenant <name>] # delete a key: it stops working at once
|
|
52
|
+
nylorun mcp connect <url> --server <name> [--vault <id>] [--client-id <id>] [--tenant <name>] [--no-open] # sign the Tenant in to a remote MCP server with OAuth
|
|
49
53
|
nylorun doctor [--json] # prerequisites and the Tenant's health
|
|
50
54
|
nylorun telemetry [status|enable|disable] # Studio's anonymous usage analytics
|
|
51
55
|
nylorun sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]
|
|
@@ -137,8 +141,12 @@ its own `.gitignore` of `*`):
|
|
|
137
141
|
- `link.json`: `{ "format": 3, "tenant", "tenantId", "hostUrl", "hostId" }`.
|
|
138
142
|
`tenant` is the local Tenant's name; the Tenant id is information only:
|
|
139
143
|
nothing in a request selects a Tenant.
|
|
140
|
-
- `credentials.json` (mode 0600):
|
|
141
|
-
|
|
144
|
+
- `credentials.json` (mode 0600): `{ "format": 1, "applicationKey",
|
|
145
|
+
"principalId" }`, the operator key `project`. A later `start` keeps the file
|
|
146
|
+
while its key still reaches the Tenant (one authenticated read); otherwise it
|
|
147
|
+
writes the Tenant's `project` key, which the Host root keeps in
|
|
148
|
+
`project-credentials.json` (mode 0600) so every checkout linked to the Tenant
|
|
149
|
+
shares one key, or puts a new one through the Admin API.
|
|
142
150
|
|
|
143
151
|
Later starts reuse the Tenant and rewrite the link only when the Tenant's name,
|
|
144
152
|
URL, Host or id changed (after `nylorun reset`, for example). When it writes a
|
|
@@ -154,9 +162,28 @@ both share it. `--no-link` starts a Tenant without linking the working
|
|
|
154
162
|
directory (scripts). A link of an older nylorun (format 0 to 2) counts as no
|
|
155
163
|
link: `start` replaces it.
|
|
156
164
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
No key is derived from the admin key but Studio's. A project holds the operator
|
|
166
|
+
key `project`; a key an older nylorun derived for it keeps working while it
|
|
167
|
+
authenticates and is replaced by the operator key otherwise.
|
|
168
|
+
|
|
169
|
+
## Operator keys
|
|
170
|
+
|
|
171
|
+
`nylorun key put <id>` creates the running Tenant's key `<id>` (or rotates it:
|
|
172
|
+
the previous key stops working at once) and prints it once on stdout.
|
|
173
|
+
`nylorun key list [--json]` shows each key's id, role and when it was issued,
|
|
174
|
+
never the keys; `nylorun key rm <id>` deletes one. Ids match
|
|
175
|
+
`^[a-z][a-z0-9-]{0,31}$`; `studio` belongs to Studio and is refused. Give each
|
|
176
|
+
app server its own key. `nylorun sandbox` and `nylorun mcp` use the linked
|
|
177
|
+
project's key, or the key `cli` they put once and keep in
|
|
178
|
+
`<Host root>/cli-credentials.json` (mode 0600).
|
|
179
|
+
|
|
180
|
+
`nylorun mcp connect <url> --server <name>` signs the running Tenant in to a
|
|
181
|
+
remote MCP server that uses OAuth: it opens the server's sign-in page in the
|
|
182
|
+
browser and waits until the Runtime has stored the credential in the
|
|
183
|
+
installation vault `mcp` (created if needed; `--vault <id>` picks another),
|
|
184
|
+
which sessions attach with `vaultIds`. Pass `--client-id` when the server does
|
|
185
|
+
not let clients register themselves. See
|
|
186
|
+
[Connecting a remote MCP server with OAuth](../DEPLOYMENT.md#connecting-a-remote-mcp-server-with-oauth).
|
|
160
187
|
|
|
161
188
|
## The containers
|
|
162
189
|
|
|
@@ -212,14 +239,16 @@ out of another.
|
|
|
212
239
|
|
|
213
240
|
### Embedding Studio in a desktop app
|
|
214
241
|
|
|
215
|
-
A desktop
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
`~/.nylorun/tenants/<name>/docker/.env`) may frame it
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`
|
|
222
|
-
|
|
242
|
+
A desktop or web app can show Studio inside its own window, in an iframe loaded
|
|
243
|
+
from Studio's URL (`studio.url` in `nylorun status --json`). Embedding is
|
|
244
|
+
opt-in: only exact origins listed in `NYLORUN_STUDIO_FRAME_ANCESTORS` (in
|
|
245
|
+
`~/.nylorun/tenants/<name>/docker/.env`) may frame it, and the list is empty by
|
|
246
|
+
default. Add the app's origins once with `nylorun start --studio-embed-origin
|
|
247
|
+
<origin>` (for example `app://localhost`, or a dev server's
|
|
248
|
+
`http://localhost:1420`); the list is kept across starts until
|
|
249
|
+
`--studio-embed-origin-reset`, and `nylorun status` shows it under `Embeds`.
|
|
250
|
+
Wildcards are refused. A Tenant started by an older nylorun keeps the origins it
|
|
251
|
+
had.
|
|
223
252
|
|
|
224
253
|
The app's backend mints a single-use login token for the Tenant with
|
|
225
254
|
`mintStudioLoginToken` from `@nylorun/admin` (it needs the admin key), and its
|
|
@@ -279,8 +308,9 @@ The **Host root** is `~/.nylorun/tenants/<name>/`, or `NYLORUN_HOME`. It is
|
|
|
279
308
|
bind-mounted into the Runtime and Studio containers, and holds `tenant.json`
|
|
280
309
|
(`{ "format": 1, "name", "project"? }`: the Tenant's name and the project it
|
|
281
310
|
was created for), `host.json` (the client-facing host and port),
|
|
282
|
-
`host-credentials.json` (the admin key, mode 0600),
|
|
283
|
-
`
|
|
311
|
+
`host-credentials.json` (the admin key, mode 0600), `project-credentials.json`
|
|
312
|
+
and `cli-credentials.json` (the operator keys `project` and `cli`, mode 0600, when
|
|
313
|
+
they were put), the Docker Compose files in `docker/`, the Tenant directory `tenant/` (homes, logs) and `keys/vault-kek`,
|
|
284
314
|
the Tenant's vault key, which only the gateway container mounts. The Tenant's data lives in its Postgres, Restate and S2 volumes.
|
|
285
315
|
|
|
286
316
|
`nylorun reset` deletes the selected Tenant's volumes, `tenant/` and `keys/`, and keeps
|
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
import "./baseline.js";
|
|
3
3
|
import { baselineEnv } from "./baseline.js";
|
|
4
4
|
import { CliError } from "./errors.js";
|
|
5
|
-
import { isStackCommand, sandboxCommand, sandboxUsage, stackCommand, stackUsage, studioCommand, } from "./stack/index.js";
|
|
6
|
-
const usage = `nylorun <up|down|start|stop|status|logs|studio|reset|ls|delete|sandbox|doctor|telemetry>
|
|
5
|
+
import { isStackCommand, keyCommand, keyUsage, mcpCommand, mcpUsage, sandboxCommand, sandboxUsage, stackCommand, stackUsage, studioCommand, } from "./stack/index.js";
|
|
6
|
+
const usage = `nylorun <up|down|start|stop|status|logs|studio|reset|ls|delete|sandbox|key|mcp|doctor|telemetry>
|
|
7
7
|
|
|
8
8
|
Local Tenants (Docker Compose), one per project:
|
|
9
9
|
${stackUsage}
|
|
10
10
|
${sandboxUsage}
|
|
11
|
+
${keyUsage}
|
|
12
|
+
${mcpUsage}
|
|
11
13
|
doctor [--json] check Node, Docker and Compose v2, and the Tenant's health
|
|
12
14
|
telemetry [status|enable|disable] Studio's anonymous usage analytics (on unless disabled,
|
|
13
15
|
NYLORUN_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1 or CI)
|
|
@@ -50,6 +52,14 @@ async function main() {
|
|
|
50
52
|
process.exitCode = await sandboxCommand(args, baselineEnv());
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
55
|
+
if (command === "mcp") {
|
|
56
|
+
process.exitCode = await mcpCommand(args, baselineEnv());
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (command === "key") {
|
|
60
|
+
process.exitCode = await keyCommand(args, baselineEnv());
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
53
63
|
if (isStackCommand(command)) {
|
|
54
64
|
process.exitCode = await stackCommand(command, args, baselineEnv());
|
|
55
65
|
return;
|
package/dist/project/link.d.ts
CHANGED
|
@@ -22,13 +22,19 @@ export declare function writeProjectLink(projectRoot: string, link: {
|
|
|
22
22
|
hostId: string;
|
|
23
23
|
tenantId: string;
|
|
24
24
|
}): Promise<void>;
|
|
25
|
-
/**
|
|
26
|
-
export
|
|
25
|
+
/** An application key and its principal: `credentials.json`'s content (format 1). */
|
|
26
|
+
export interface KeyCredentials {
|
|
27
27
|
applicationKey: string;
|
|
28
28
|
principalId: string;
|
|
29
|
-
}
|
|
29
|
+
}
|
|
30
|
+
/** The Project's application key and principal, or undefined when absent or unreadable. */
|
|
31
|
+
export declare function readProjectCredentials(projectRoot: string): Promise<KeyCredentials | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* A credentials file (`{ format: 1, applicationKey, principalId }`): the Project's, or one the
|
|
34
|
+
* nylorun commands keep in the Host root. Undefined when absent or unreadable.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readCredentialsFile(path: string): Promise<KeyCredentials | undefined>;
|
|
30
37
|
/** `.nylorun/credentials.json` (format 1, mode 0600). */
|
|
31
|
-
export declare function writeProjectCredentials(projectRoot: string, credentials:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}): Promise<void>;
|
|
38
|
+
export declare function writeProjectCredentials(projectRoot: string, credentials: KeyCredentials): Promise<void>;
|
|
39
|
+
/** A credentials file (format 1), written atomically with mode 0600. */
|
|
40
|
+
export declare function writeCredentialsFile(path: string, credentials: KeyCredentials): Promise<void>;
|
package/dist/project/link.js
CHANGED
|
@@ -69,8 +69,15 @@ export async function writeProjectLink(projectRoot, link) {
|
|
|
69
69
|
}
|
|
70
70
|
/** The Project's application key and principal, or undefined when absent or unreadable. */
|
|
71
71
|
export async function readProjectCredentials(projectRoot) {
|
|
72
|
+
return await readCredentialsFile(credentialsPath(projectRoot));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A credentials file (`{ format: 1, applicationKey, principalId }`): the Project's, or one the
|
|
76
|
+
* nylorun commands keep in the Host root. Undefined when absent or unreadable.
|
|
77
|
+
*/
|
|
78
|
+
export async function readCredentialsFile(path) {
|
|
72
79
|
try {
|
|
73
|
-
const value = JSON.parse(await readFile(
|
|
80
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
74
81
|
return typeof value.applicationKey === "string" && typeof value.principalId === "string"
|
|
75
82
|
? { applicationKey: value.applicationKey, principalId: value.principalId }
|
|
76
83
|
: undefined;
|
|
@@ -82,7 +89,11 @@ export async function readProjectCredentials(projectRoot) {
|
|
|
82
89
|
/** `.nylorun/credentials.json` (format 1, mode 0600). */
|
|
83
90
|
export async function writeProjectCredentials(projectRoot, credentials) {
|
|
84
91
|
await ensureProjectDir(projectRoot);
|
|
85
|
-
await
|
|
92
|
+
await writeCredentialsFile(credentialsPath(projectRoot), credentials);
|
|
93
|
+
}
|
|
94
|
+
/** A credentials file (format 1), written atomically with mode 0600. */
|
|
95
|
+
export async function writeCredentialsFile(path, credentials) {
|
|
96
|
+
await writePrivate(path, {
|
|
86
97
|
format: 1,
|
|
87
98
|
applicationKey: credentials.applicationKey,
|
|
88
99
|
principalId: credentials.principalId,
|
package/dist/stack/commands.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { HarnessMode, SandboxStackEnv } from "./env-file.js";
|
|
|
4
4
|
import { type StackPaths } from "./paths.js";
|
|
5
5
|
import type { PortProbe } from "./ports.js";
|
|
6
6
|
import { type FetchLike } from "./studio-login.js";
|
|
7
|
+
import { type AdminEndpoint } from "./operator-keys.js";
|
|
7
8
|
export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2-lite", "rustfs", "gateway", "runtime", "harness", "studio"];
|
|
8
9
|
export declare const stackUsage: string;
|
|
9
10
|
export interface StackDeps {
|
|
@@ -128,14 +129,31 @@ export interface StackEndpoints {
|
|
|
128
129
|
/** This call started the Runtime (it was not running before). */
|
|
129
130
|
started: boolean;
|
|
130
131
|
}
|
|
131
|
-
/** The running Tenant's API, reached
|
|
132
|
+
/** The running Tenant's API, reached with an operator key (`nylorun sandbox`). */
|
|
132
133
|
export interface TenantApi {
|
|
133
134
|
/** The Tenant's name. */
|
|
134
135
|
name: string;
|
|
135
136
|
runtimeUrl: string;
|
|
136
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* The linked project's key (`.nylorun/credentials.json`) when it reaches the Tenant, else
|
|
139
|
+
* the operator key `cli` the Host root keeps (`cli-credentials.json`).
|
|
140
|
+
*/
|
|
137
141
|
applicationKey: string;
|
|
138
142
|
}
|
|
143
|
+
/** The selected Tenant's Admin API while it runs: `nylorun key` and `runningTenantApi`. */
|
|
144
|
+
export interface RunningAdmin extends AdminEndpoint {
|
|
145
|
+
/** The Tenant's name. */
|
|
146
|
+
name: string;
|
|
147
|
+
runtimeUrl: string;
|
|
148
|
+
paths: StackPaths;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The selected Tenant's Admin API while it runs; exit 3 when it is not running, 7 when it is
|
|
152
|
+
* not open.
|
|
153
|
+
*/
|
|
154
|
+
export declare function runningAdmin(deps: StackDeps, options?: {
|
|
155
|
+
name?: string;
|
|
156
|
+
}): Promise<RunningAdmin>;
|
|
139
157
|
/**
|
|
140
158
|
* The selected Tenant's API while it runs: commands that read or change the Tenant's data never
|
|
141
159
|
* start it. Exit 3 when it is not running, 7 when it is not open.
|
package/dist/stack/commands.js
CHANGED
|
@@ -3,8 +3,7 @@ import { mkdir, readFile, rm } from "node:fs/promises";
|
|
|
3
3
|
import { basename, resolve } from "node:path";
|
|
4
4
|
import { compareVersions, PROTOCOL_HEADER, PROTOCOL_VERSION, } from "@nylorun/core/compatibility";
|
|
5
5
|
import { CliError } from "../errors.js";
|
|
6
|
-
import {
|
|
7
|
-
import { readProjectCredentials, readProjectLink, writeProjectCredentials, writeProjectLink, } from "../project/link.js";
|
|
6
|
+
import { credentialsPath, readCredentialsFile, readProjectCredentials, readProjectLink, writeProjectCredentials, writeProjectLink, } from "../project/link.js";
|
|
8
7
|
import { findProjectRoot } from "../project/root.js";
|
|
9
8
|
import { seedTenant } from "../project/seed.js";
|
|
10
9
|
import { readTelemetry, STUDIO_ANALYTICS_ID, TELEMETRY_NOTICE, telemetryDecision, writeTelemetry, } from "../telemetry.js";
|
|
@@ -15,6 +14,7 @@ import { stackPaths } from "./paths.js";
|
|
|
15
14
|
import { prepareStack, readStackEnv } from "./prepare.js";
|
|
16
15
|
import { assertTenantName, chooseTenantName, DEFAULT_TENANT, defaultNylorunRoot, listTenants, moveStackRoots, readTenantRecord, removeOldProxy, sanitizeTenantName, tenantRoot, tenantsDir, writeTenantRecord, } from "./stacks.js";
|
|
17
16
|
import { mintStudioLogin, studioOrigin } from "./studio-login.js";
|
|
17
|
+
import { CLI_KEY_ID, hostKey, keyAuthenticates, PROJECT_KEY_ID, } from "./operator-keys.js";
|
|
18
18
|
export const STACK_SERVICES = [
|
|
19
19
|
"postgres",
|
|
20
20
|
"restate",
|
|
@@ -392,9 +392,6 @@ async function bringUp(ctx, options) {
|
|
|
392
392
|
runtimeVersion: overridden ? undefined : deps.runtimeVersion,
|
|
393
393
|
ports: deps.ports,
|
|
394
394
|
reserved: await reservedPorts(ctx),
|
|
395
|
-
...(deps.env.NYLORUN_DERIVED_PRINCIPALS?.trim()
|
|
396
|
-
? { derivedPrincipals: deps.env.NYLORUN_DERIVED_PRINCIPALS }
|
|
397
|
-
: {}),
|
|
398
395
|
...(options.studioEmbedOrigins ? { studioEmbedOrigins: options.studioEmbedOrigins } : {}),
|
|
399
396
|
...(analytics ? { studioAnalyticsId: STUDIO_ANALYTICS_ID } : {}),
|
|
400
397
|
...(options.sandboxes !== undefined ? { sandboxes: options.sandboxes } : {}),
|
|
@@ -487,13 +484,17 @@ async function openLogin(ctx, login) {
|
|
|
487
484
|
}
|
|
488
485
|
/**
|
|
489
486
|
* Link the project to the Tenant: `.nylorun/link.json` (format 3) and
|
|
490
|
-
* `.nylorun/credentials.json
|
|
491
|
-
*
|
|
492
|
-
*
|
|
487
|
+
* `.nylorun/credentials.json`. The link is rewritten only when it is older, or the Tenant's
|
|
488
|
+
* name, URL, Host or id changed; a new link seeds the Tenant from the project's `.env`.
|
|
489
|
+
*
|
|
490
|
+
* The credentials file is kept while its key still reaches the Tenant (one authenticated
|
|
491
|
+
* read). Otherwise it gets the operator key `project` (F9 I1): the one the Host root keeps for
|
|
492
|
+
* the projects linked to this Tenant (`project-credentials.json`), or a new one put through
|
|
493
|
+
* the Admin API. Every checkout linked to the Tenant shares it, so linking one never rotates
|
|
494
|
+
* another's key.
|
|
493
495
|
*/
|
|
494
496
|
async function linkProject(ctx, started, tenantId) {
|
|
495
497
|
const { deps, projectDir, link } = ctx;
|
|
496
|
-
const applicationKey = deriveTenantKey(started.adminKey, tenantId, PROJECT_PRINCIPAL_ID);
|
|
497
498
|
const fresh = link?.format !== 3 ||
|
|
498
499
|
link.tenant !== ctx.name ||
|
|
499
500
|
link.hostUrl !== started.runtimeUrl ||
|
|
@@ -506,14 +507,37 @@ async function linkProject(ctx, started, tenantId) {
|
|
|
506
507
|
hostId: started.hostId,
|
|
507
508
|
tenantId,
|
|
508
509
|
});
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
510
|
+
const existing = await readProjectCredentials(projectDir);
|
|
511
|
+
const admin = {
|
|
512
|
+
fetch: deps.fetch,
|
|
513
|
+
adminUrl: started.adminUrl,
|
|
514
|
+
adminKey: started.adminKey,
|
|
515
|
+
};
|
|
516
|
+
const keyOptions = {
|
|
517
|
+
admin,
|
|
518
|
+
runtimeUrl: started.runtimeUrl,
|
|
519
|
+
id: PROJECT_KEY_ID,
|
|
520
|
+
file: ctx.paths.projectCredentials,
|
|
521
|
+
lock: ctx.paths.keysLock,
|
|
522
|
+
};
|
|
523
|
+
let applicationKey;
|
|
524
|
+
if (existing && (await keyAuthenticates(deps.fetch, started.runtimeUrl, existing.applicationKey))) {
|
|
525
|
+
applicationKey = existing.applicationKey;
|
|
526
|
+
// A project key from before operator keys becomes the one the Host root keeps.
|
|
527
|
+
if (existing.principalId === PROJECT_KEY_ID &&
|
|
528
|
+
(await readCredentialsFile(ctx.paths.projectCredentials))?.applicationKey !== applicationKey)
|
|
529
|
+
await hostKey({ ...keyOptions, adopt: existing });
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
const key = await hostKey(keyOptions);
|
|
533
|
+
applicationKey = key.applicationKey;
|
|
513
534
|
await writeProjectCredentials(projectDir, {
|
|
514
|
-
applicationKey,
|
|
515
|
-
principalId:
|
|
535
|
+
applicationKey: key.applicationKey,
|
|
536
|
+
principalId: key.principalId,
|
|
516
537
|
});
|
|
538
|
+
if (existing && !fresh)
|
|
539
|
+
deps.err(`Replaced the project's key, which no longer reaches Tenant ${ctx.name} (.nylorun/credentials.json).`);
|
|
540
|
+
}
|
|
517
541
|
if (!fresh)
|
|
518
542
|
return;
|
|
519
543
|
deps.err(`Linked ${projectDir} to Tenant ${ctx.name} (.nylorun/link.json, .nylorun/credentials.json).`);
|
|
@@ -971,11 +995,7 @@ async function ensureSelected(ctx, options) {
|
|
|
971
995
|
started: !runtimeWasUp,
|
|
972
996
|
};
|
|
973
997
|
}
|
|
974
|
-
|
|
975
|
-
* The selected Tenant's API while it runs: commands that read or change the Tenant's data never
|
|
976
|
-
* start it. Exit 3 when it is not running, 7 when it is not open.
|
|
977
|
-
*/
|
|
978
|
-
export async function runningTenantApi(deps, options = {}) {
|
|
998
|
+
async function runningSelected(deps, options) {
|
|
979
999
|
const ctx = await selectStack(deps, options);
|
|
980
1000
|
requireStackFiles(ctx);
|
|
981
1001
|
await dockerPreflight(deps.docker);
|
|
@@ -986,11 +1006,44 @@ export async function runningTenantApi(deps, options = {}) {
|
|
|
986
1006
|
if (!tenant?.id || tenant.state !== "open")
|
|
987
1007
|
throw new CliError(`Tenant ${ctx.name} is not open. See "nylorun status".`, 7);
|
|
988
1008
|
return {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1009
|
+
ctx,
|
|
1010
|
+
admin: {
|
|
1011
|
+
name: ctx.name,
|
|
1012
|
+
runtimeUrl: running.runtimeUrl,
|
|
1013
|
+
paths: ctx.paths,
|
|
1014
|
+
fetch: deps.fetch,
|
|
1015
|
+
adminUrl: running.adminUrl,
|
|
1016
|
+
adminKey: running.adminKey,
|
|
1017
|
+
},
|
|
992
1018
|
};
|
|
993
1019
|
}
|
|
1020
|
+
/**
|
|
1021
|
+
* The selected Tenant's Admin API while it runs; exit 3 when it is not running, 7 when it is
|
|
1022
|
+
* not open.
|
|
1023
|
+
*/
|
|
1024
|
+
export async function runningAdmin(deps, options = {}) {
|
|
1025
|
+
return (await runningSelected(deps, options)).admin;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* The selected Tenant's API while it runs: commands that read or change the Tenant's data never
|
|
1029
|
+
* start it. Exit 3 when it is not running, 7 when it is not open.
|
|
1030
|
+
*/
|
|
1031
|
+
export async function runningTenantApi(deps, options = {}) {
|
|
1032
|
+
const { ctx, admin } = await runningSelected(deps, options);
|
|
1033
|
+
if (ctx.projectDir && ctx.link?.format === 3 && ctx.link.tenant === ctx.name) {
|
|
1034
|
+
const project = await readCredentialsFile(credentialsPath(ctx.projectDir));
|
|
1035
|
+
if (project && (await keyAuthenticates(deps.fetch, admin.runtimeUrl, project.applicationKey)))
|
|
1036
|
+
return { name: ctx.name, runtimeUrl: admin.runtimeUrl, applicationKey: project.applicationKey };
|
|
1037
|
+
}
|
|
1038
|
+
const key = await hostKey({
|
|
1039
|
+
admin,
|
|
1040
|
+
runtimeUrl: admin.runtimeUrl,
|
|
1041
|
+
id: CLI_KEY_ID,
|
|
1042
|
+
file: ctx.paths.cliCredentials,
|
|
1043
|
+
lock: ctx.paths.keysLock,
|
|
1044
|
+
});
|
|
1045
|
+
return { name: ctx.name, runtimeUrl: admin.runtimeUrl, applicationKey: key.applicationKey };
|
|
1046
|
+
}
|
|
994
1047
|
/**
|
|
995
1048
|
* Start the selected Tenant unless it is already running (`nylorun studio`): the
|
|
996
1049
|
* `start` code path without its output, its Tenant wait or the Project link. Compose
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
* database with C collation (`--locale=C`) and runs with `wal_level=logical`: the stream
|
|
14
14
|
* relay feeds s2-lite from the record over logical replication (Durable Streams), and
|
|
15
15
|
* `max_slot_wal_keep_size` caps the WAL a stuck relay can hold. The Runtime creates the
|
|
16
|
-
* Tenant on its first start, with its name (`NYLORUN_TENANT_NAME`)
|
|
17
|
-
* principals of `NYLORUN_DERIVED_PRINCIPALS` (`project` for the Project link).
|
|
16
|
+
* Tenant on its first start, with its name (`NYLORUN_TENANT_NAME`).
|
|
18
17
|
*
|
|
19
18
|
* Restate signs requests to the Worker endpoint with the private key in
|
|
20
19
|
* `docker/restate-identity.pem`, mounted read-only; the Runtime gets the public
|
|
@@ -68,9 +67,13 @@
|
|
|
68
67
|
* `default` carries egress and the published ports (runtime, gateway, Studio, sandboxes).
|
|
69
68
|
* Restate's admin port (its UI, unauthenticated) is published, and Restate joins `default`,
|
|
70
69
|
* only with `restateUi` (`nylorun start --restate-ui`).
|
|
70
|
+
*
|
|
71
|
+
* With `identity` (when `<Host root>/identity.yaml` exists) the runtime reads its trusted issuers
|
|
72
|
+
* from it, through the Host root mount (NYLORUN_IDENTITY_FILE=/nylorun/identity.yaml).
|
|
71
73
|
*/
|
|
72
74
|
export declare function renderComposeFile(project: string, name: string, options?: {
|
|
73
75
|
sandboxes?: true;
|
|
74
76
|
harness?: "remote" | "in-process";
|
|
75
77
|
restateUi?: true;
|
|
78
|
+
identity?: true;
|
|
76
79
|
}): string;
|
|
@@ -14,8 +14,7 @@ import { PINNED_IMAGES } from "./images.js";
|
|
|
14
14
|
* database with C collation (`--locale=C`) and runs with `wal_level=logical`: the stream
|
|
15
15
|
* relay feeds s2-lite from the record over logical replication (Durable Streams), and
|
|
16
16
|
* `max_slot_wal_keep_size` caps the WAL a stuck relay can hold. The Runtime creates the
|
|
17
|
-
* Tenant on its first start, with its name (`NYLORUN_TENANT_NAME`)
|
|
18
|
-
* principals of `NYLORUN_DERIVED_PRINCIPALS` (`project` for the Project link).
|
|
17
|
+
* Tenant on its first start, with its name (`NYLORUN_TENANT_NAME`).
|
|
19
18
|
*
|
|
20
19
|
* Restate signs requests to the Worker endpoint with the private key in
|
|
21
20
|
* `docker/restate-identity.pem`, mounted read-only; the Runtime gets the public
|
|
@@ -69,9 +68,13 @@ import { PINNED_IMAGES } from "./images.js";
|
|
|
69
68
|
* `default` carries egress and the published ports (runtime, gateway, Studio, sandboxes).
|
|
70
69
|
* Restate's admin port (its UI, unauthenticated) is published, and Restate joins `default`,
|
|
71
70
|
* only with `restateUi` (`nylorun start --restate-ui`).
|
|
71
|
+
*
|
|
72
|
+
* With `identity` (when `<Host root>/identity.yaml` exists) the runtime reads its trusted issuers
|
|
73
|
+
* from it, through the Host root mount (NYLORUN_IDENTITY_FILE=/nylorun/identity.yaml).
|
|
72
74
|
*/
|
|
73
75
|
export function renderComposeFile(project, name, options = {}) {
|
|
74
76
|
const sandboxes = options.sandboxes === true;
|
|
77
|
+
const identity = options.identity === true;
|
|
75
78
|
const remote = (options.harness ?? "remote") === "remote";
|
|
76
79
|
const restateUi = options.restateUi === true;
|
|
77
80
|
return `# Written by \`nylorun start\`; rewritten on every start. Settings live in .env.
|
|
@@ -174,7 +177,11 @@ ${restateUi ? RESTATE_UI : RESTATE_CLOSED} healthcheck:
|
|
|
174
177
|
NYLORUN_OBJECT_STORE_ACCESS_KEY: nylorun
|
|
175
178
|
NYLORUN_OBJECT_STORE_SECRET_KEY: \${NYLORUN_OBJECT_STORE_SECRET_KEY:?run nylorun start}
|
|
176
179
|
# Action endpoints on this machine: \`localhost\` in a registered URL means the Docker host.
|
|
177
|
-
NYLORUN_ENDPOINT_LOOPBACK: docker-host
|
|
180
|
+
NYLORUN_ENDPOINT_LOOPBACK: docker-host
|
|
181
|
+
# Your credential resolver, for people's own MCP credentials (DEPLOYMENT.md, Credentials);
|
|
182
|
+
# unset by default. Set both in the shell that runs \`nylorun start\`.
|
|
183
|
+
NYLORUN_RESOLVER_URL: \${NYLORUN_RESOLVER_URL:-}
|
|
184
|
+
NYLORUN_RESOLVER_TOKEN: \${NYLORUN_RESOLVER_TOKEN:-}${sandboxes ? SANDBOXES_GATEWAY_ENV : ""}
|
|
178
185
|
extra_hosts:
|
|
179
186
|
host.docker.internal: host-gateway # model servers, MCP servers and Action endpoints on this machine
|
|
180
187
|
volumes:
|
|
@@ -206,7 +213,6 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
206
213
|
NYLORUN_PACKING: combined
|
|
207
214
|
# The Tenant the Runtime creates on its first start (later starts open it).
|
|
208
215
|
NYLORUN_TENANT_NAME: \${NYLORUN_TENANT_NAME:?run nylorun start}
|
|
209
|
-
NYLORUN_DERIVED_PRINCIPALS: \${NYLORUN_DERIVED_PRINCIPALS:-project}
|
|
210
216
|
# Model calls, remote MCP calls and deliveries go through the gateway, and vault writes
|
|
211
217
|
# and token signing through its keys service: this container never reads a credential
|
|
212
218
|
# or the vault key.
|
|
@@ -242,7 +248,7 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
242
248
|
NYLORUN_HARNESS: \${NYLORUN_HARNESS:-remote}
|
|
243
249
|
NYLORUN_HARNESS_LISTEN_PORT: "4200"
|
|
244
250
|
NYLORUN_HARNESS_ALLOWED_HOSTS: runtime:4200${sandboxes ? SANDBOXES_HARNESS_HOST : ""}
|
|
245
|
-
NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}
|
|
251
|
+
NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
|
|
246
252
|
extra_hosts:
|
|
247
253
|
host.docker.internal: host-gateway # the Docker host, also on Linux Docker Engine
|
|
248
254
|
volumes:
|
|
@@ -287,10 +293,13 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
|
|
|
287
293
|
# Browsers share cookies across ports of one host: a cookie per Tenant keeps the
|
|
288
294
|
# sessions of two Studios on localhost apart.
|
|
289
295
|
NYLORUN_STUDIO_SESSION_COOKIE: nylorun_studio_${name}
|
|
290
|
-
# Exact origins that may frame Studio (
|
|
296
|
+
# Exact origins that may frame Studio (none by default: embedding is opt-in); kept in .env.
|
|
291
297
|
NYLORUN_STUDIO_FRAME_ANCESTORS: \${NYLORUN_STUDIO_FRAME_ANCESTORS:-}
|
|
292
298
|
# Studio's anonymous usage analytics; empty when telemetry is off.
|
|
293
299
|
NYLORUN_STUDIO_ANALYTICS_ID: \${NYLORUN_STUDIO_ANALYTICS_ID:-}
|
|
300
|
+
# Extra Host values Studio serves behind a sign-in proxy (studio.acme.dev),
|
|
301
|
+
# comma-separated; from the environment of nylorun start.
|
|
302
|
+
NYLORUN_STUDIO_ALLOWED_HOSTS: \${NYLORUN_STUDIO_ALLOWED_HOSTS:-}
|
|
294
303
|
volumes:
|
|
295
304
|
- \${NYLORUN_HOST_ROOT}/host-credentials.json:/run/nylorun/host-credentials.json:ro
|
|
296
305
|
networks: [default]
|
|
@@ -373,6 +382,10 @@ function harnessService(project) {
|
|
|
373
382
|
* The runtime reaches the sandboxes service on the Compose network with its token, and serves
|
|
374
383
|
* the Harness API to sandbox pods (F7.2) on the Docker host's address.
|
|
375
384
|
*/
|
|
385
|
+
/** The identity file (`<Host root>/identity.yaml`): the trusted issuers, read at boot. */
|
|
386
|
+
const IDENTITY_RUNTIME_ENV = `
|
|
387
|
+
# Trusted issuers (<Host root>/identity.yaml): JWTs from the operator's identity provider.
|
|
388
|
+
NYLORUN_IDENTITY_FILE: /nylorun/identity.yaml`;
|
|
376
389
|
const SANDBOXES_RUNTIME_ENV = `
|
|
377
390
|
# The sandboxes service (nylorun sandbox enable): pods on the Tenant's cluster.
|
|
378
391
|
NYLORUN_SANDBOXES_URL: http://sandboxes:4300
|
package/dist/stack/env-file.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Origins that may embed Studio by default:
|
|
3
|
-
*
|
|
2
|
+
* Origins that may embed Studio by default: none. Embedding is opt-in: the operator lists
|
|
3
|
+
* the exact origins of the app that frames Studio (`--studio-embed-origin`, Studio §8.9).
|
|
4
4
|
*/
|
|
5
|
-
export declare const DEFAULT_STUDIO_FRAME_ANCESTORS: readonly [
|
|
5
|
+
export declare const DEFAULT_STUDIO_FRAME_ANCESTORS: readonly string[];
|
|
6
6
|
/**
|
|
7
7
|
* Settings in `docker/.env` (mode 0600). Ports, the password and the gates token
|
|
8
8
|
* persist; the Restate identity public key is derived from `restate-identity.pem`.
|
|
@@ -57,11 +57,6 @@ export interface StackEnv {
|
|
|
57
57
|
studioAnalyticsId: string;
|
|
58
58
|
/** The Tenant's name, which the Runtime gives the Tenant it creates. */
|
|
59
59
|
tenantName: string;
|
|
60
|
-
/**
|
|
61
|
-
* Derived principals the Runtime registers on its Tenant, comma-separated (persists);
|
|
62
|
-
* always includes `project`, the Project link's principal.
|
|
63
|
-
*/
|
|
64
|
-
derivedPrincipals: string;
|
|
65
60
|
/** Present once `nylorun sandbox enable` ran for the Tenant (F7.2). */
|
|
66
61
|
sandboxes?: SandboxStackEnv;
|
|
67
62
|
}
|
|
@@ -84,11 +79,6 @@ export interface SandboxStackEnv {
|
|
|
84
79
|
}
|
|
85
80
|
export type HarnessMode = "remote" | "in-process";
|
|
86
81
|
export declare function renderEnvFile(env: StackEnv): string;
|
|
87
|
-
/**
|
|
88
|
-
* `NYLORUN_DERIVED_PRINCIPALS`: comma-separated principal ids, as the Runtime accepts them
|
|
89
|
-
* (not `studio`), with `project` first so the Project link's key always works.
|
|
90
|
-
*/
|
|
91
|
-
export declare function parseDerivedPrincipals(raw: string, source: string): string[];
|
|
92
82
|
/** Parse KEY=value lines; comments and blank lines are skipped. */
|
|
93
83
|
export declare function parseEnvLines(text: string): Map<string, string>;
|
|
94
84
|
/** The settings that persist across starts, from an existing .env. */
|
|
@@ -106,7 +96,6 @@ export interface PersistedStackEnv {
|
|
|
106
96
|
objectStoreSecretKey?: string;
|
|
107
97
|
/** Validated origins; absent when the line is missing (an older .env). */
|
|
108
98
|
studioFrameAncestors?: string[];
|
|
109
|
-
derivedPrincipals?: string[];
|
|
110
99
|
sandboxes?: SandboxStackEnv;
|
|
111
100
|
}
|
|
112
101
|
export declare function parsePersisted(text: string): PersistedStackEnv;
|