nylorun 0.6.0-beta → 0.8.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 +61 -0
- package/README.md +77 -73
- package/dist/cli.js +7 -2
- package/dist/doctor.d.ts +2 -4
- package/dist/doctor.js +12 -19
- package/dist/sandbox/cluster-file.d.ts +47 -0
- package/dist/sandbox/cluster-file.js +55 -0
- package/dist/sandbox/commands.d.ts +41 -0
- package/dist/sandbox/commands.js +281 -0
- package/dist/sandbox/kubectl.d.ts +39 -0
- package/dist/sandbox/kubectl.js +70 -0
- package/dist/sandbox/manifests.d.ts +41 -0
- package/dist/sandbox/manifests.js +138 -0
- package/dist/sandbox/probe.d.ts +44 -0
- package/dist/sandbox/probe.js +137 -0
- package/dist/stack/commands.d.ts +56 -9
- package/dist/stack/commands.js +167 -123
- package/dist/stack/compose-file.d.ts +42 -5
- package/dist/stack/compose-file.js +208 -21
- package/dist/stack/env-file.d.ts +48 -6
- package/dist/stack/env-file.js +81 -9
- package/dist/stack/host-files.js +11 -1
- package/dist/stack/images.d.ts +8 -4
- package/dist/stack/images.js +8 -3
- package/dist/stack/index.d.ts +4 -2
- package/dist/stack/index.js +7 -3
- package/dist/stack/paths.d.ts +15 -0
- package/dist/stack/paths.js +7 -0
- package/dist/stack/ports.d.ts +0 -2
- package/dist/stack/ports.js +0 -2
- package/dist/stack/prepare.d.ts +10 -4
- package/dist/stack/prepare.js +20 -3
- package/dist/stack/sandbox.d.ts +4 -0
- package/dist/stack/sandbox.js +106 -0
- package/dist/stack/stacks.d.ts +8 -0
- package/dist/stack/stacks.js +19 -0
- package/dist/stack/studio-login.d.ts +2 -2
- package/dist/stack/studio-login.js +2 -2
- package/package.json +5 -5
- package/dist/stack/proxy.d.ts +0 -78
- package/dist/stack/proxy.js +0 -224
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# nylorun
|
|
2
2
|
|
|
3
|
+
## 0.8.0-beta
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- b352feb: **File artifacts, message parts and capability links (protocol 6).** Upgrade these with the Runtime: they speak protocol 6, which this Runtime serves beside 4 and 5. See MIGRATION.md.
|
|
8
|
+
|
|
9
|
+
- **`@nylorun/runtime`: file artifacts.** A file is an artifact: an `af_` id, a name and numbered immutable versions, with rows in the new `artifacts` and `artifact_versions` tables (migration `0004_artifacts`) and bytes in the Object store (`BlobStore`). `POST /v1/artifacts?name=&sessionId=` uploads a file in one streamed request and `POST /v1/artifacts/{id}/versions` adds a version, within the Tenant's limits (`GET`/`PUT /v1/tenant/artifacts`: 100 MiB per file and 10 GiB in all by default); a body past either is `413 limit_exceeded`, refused mid-stream with nothing stored. `GET /v1/artifacts` lists them (by session), `GET /v1/artifacts/{id}` reads one with its versions, `GET /v1/artifacts/{id}/versions/{n|latest}/content` downloads through the Runtime with HTTP Range (`206`, `Content-Range`, `416`), and `DELETE /v1/artifacts/{id}` deletes it with its bytes. A session's artifacts go with it on a sessions reset, and appear in its history as `artifact.created`, `artifact.version.created` and `artifact.deleted` (new in the event catalog). A subject reaches only the artifacts of their own sessions.
|
|
10
|
+
- **`@nylorun/runtime`: capability links.** `POST /v1/artifacts/{id}/links` mints a short-lived path, `/v1/artifact-links/<token>`, that downloads one version (with Range) with no credential and no `Nylorun-Protocol`: an ES256 JWT (`typ: nylorun-artifact+jwt`) signed with the Tenant's signing key, at most 15 minutes, and dead once the artifact is deleted. The Host logs its path without the token.
|
|
11
|
+
- **Breaking (`@nylorun/core`, `@nylorun/runtime`): message `parts`.** A user message may carry `parts`: `text`, and `file` by `artifactId` (and `version`, else the latest, pinned when the message is accepted). Model-gate reads the file from the Object store: an image goes to the model as image input, a text file as text, and any other file fails the call (`invalid_request`). The record and the transcript hold only the reference. The gateway builds its `BlobStore` from `NYLORUN_OBJECT_STORE_*` (the Tenant's `fs` store without it). `PROTOCOL_VERSION` is 6; `HOST_PROTOCOL` is 4–6 and protocol 6 clients require the feature `artifacts`.
|
|
12
|
+
- **`@nylorun/runtime`: `save_artifact`.** A session with a sandbox gets the `nylorun.artifacts` capability, whose `save_artifact` tool saves a sandbox file (`path`) or text (`content`) as an artifact of the session, carrying the turn and tool call on its event.
|
|
13
|
+
- **Breaking (`@nylorun/runtime`): `MediaStore` is removed.** `MediaStore` and `localMedia` leave `@nylorun/runtime/node`, and `piModel` takes `files` (a resolver from an artifact reference to its bytes) instead of `media`; images are file artifacts. The image checks (`decodeImageBase64`, `validateImageBytes`, `IMAGE_MEDIA_TYPES`, `MAX_IMAGE_BYTES`) stay.
|
|
14
|
+
- **`@nylorun/agents`:** `client.artifacts` (`upload`, `uploadVersion`, `list`, `get`, `download` with a range, `link`, `delete`), also on `BrowserClient`, and `session.inputParts(parts)`. A request with a body of its own type keeps it.
|
|
15
|
+
- `@nylorun/admin`, `@nylorun/cli`, `nylorun`, `@nylorun/studio`: speak protocol 6.
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- e58e6ec: **egress-gate: pod sandboxes reach only the hosts their spec allows (F7.2).** A new `egress` service runs in the gateway's process (`--service gates,keys,egress`) on `NYLORUN_EGRESS_LISTEN_PORT` (default 4200). It is a CONNECT proxy: each tunnel needs an egress token (`Proxy-Authorization`, Basic with the token as password or Bearer) for a live sandbox at its current host epoch, a host name (no IP literal) that the sandbox's `network.allow` names exactly or by `*.suffix`, port 443 or 80, and a name that resolves to a public address; the gate connects to that checked address and pipes bytes, with no TLS interception and no credential injection. Plain HTTP is answered `405`; a sandbox holds at most 64 tunnels; idle tunnels close after 5 minutes; refusals are logged, never emitted as events. Egress tokens (`typ: nylorun-egress+jwt`), minted with each host token at a pod's join, are accepted nowhere else; egress-gate also refuses a token of a sandbox that is deleted, lost or expired, or of a pod other than the one that joined. Sandbox spec resolution lets `pod` sandboxes allow `*.suffix` hosts within the Tenant's ceiling (virtual ones keep exact names). With sandboxes enabled, `nylorun` runs the gateway with `egress` and publishes it on `NYLORUN_SANDBOX_BIND` at `NYLORUN_SANDBOX_EGRESS_PORT`, next to the gates.
|
|
20
|
+
- 186ec3f: **A local Tenant has an Object store: RustFS, behind the Runtime's new `BlobStore` seam.** `nylorun start` adds a `rustfs` container (RustFS 1.0.1, single node and single drive, pinned by digest) on the `nylorun-<tenant>-rustfs` volume, unpublished and without its console. Its secret key is generated once into `docker/.env` (`NYLORUN_OBJECT_STORE_SECRET_KEY`), and only the `runtime` and `gateway` containers receive the credential (`NYLORUN_OBJECT_STORE_ENDPOINT`, `_ACCESS_KEY`, `_SECRET_KEY`); the runtime creates the bucket at boot. The Runtime reaches the store through `BlobStore` (put with a streamed body and a size cap, get with a byte range, head, delete, list by prefix) with an `s3` adapter over the plain S3 API and an `fs` adapter, which a Runtime without `NYLORUN_OBJECT_STORE_ENDPOINT` (embedded, ephemeral, tests) uses under the Tenant directory's `blobs/`. Nothing stores files there yet; file artifacts build on it.
|
|
21
|
+
- 3fd1cda: **The harness container, the network split, and Restate's UI closed by default (F6.2).** A local Tenant now runs agent turns, stdio MCP servers and workspaces in a `harness` container: the runtime image as `--service harness`, connected to the runtime's Harness API (`ws://runtime:4200/nylorun/harness/v1`, `NYLORUN_HARNESS=remote`) with `NYLORUN_HARNESS_TOKEN`, which `nylorun start` generates once and keeps in `.env`. The harness holds no other credential, mounts only the Tenant directory's `sandboxes/`, `plugin-data/`, `home/` and `tmp/` under `/harness`, publishes no port and is healthy once connected; `nylorun status` and `nylorun doctor` report it, and `nylorun logs harness` shows its log. `NYLORUN_HARNESS=in-process` in `.env` rolls back to turns in the runtime container (the harness service is then removed).
|
|
22
|
+
|
|
23
|
+
- Plugin roots: the Host root's `plugins/` directory is mounted read-only at its own path into the harness and runtime containers, so a stdio MCP server from a plugin under `~/.nylorun/tenants/<name>/plugins/` runs in the harness.
|
|
24
|
+
- Networks: `<project>-store` (internal) joins Postgres, s2-lite, Restate and RustFS to the runtime and the gateway only; `<project>-harness` joins the harness to the runtime and the gateway only; the default network keeps egress and the published ports.
|
|
25
|
+
- Restate's admin API and UI (unauthenticated) are no longer published. `nylorun start --restate-ui` (or `NYLORUN_RESTATE_UI=1`) publishes them on loopback for that start and prints the URL.
|
|
26
|
+
|
|
27
|
+
- 678e085: **Pod sandboxes (F7.2, second part).** With `nylorun sandbox enable`, a sandbox resource of kind `pod` (`PUT /v1/sandboxes/{id}` with `kind: "pod"`, an `image`, `*.suffix` hosts, `storage` and `lifecycle.ttl`) is an agent-sandbox pod on the Tenant's cluster, created at once. The turns of the sessions attached to it run in the pod: the engine is copied from the Runtime image into the pod (the Runtime image now carries tini for it), waits until the pod's NetworkPolicy is in force, exchanges its join token for a host token at the Harness API listener (`POST /nylorun/harness/v1/host/join`, published for pods on the Docker host's address, with the gates), and serves its sandbox alone. New: `POST /v1/sandboxes/{id}/stop` and `/reset`; the Tenant's `limits.ttl`, `lifecycle.onExpiry`, `lifecycle.stopGrace` and `placement`; idle stop; lifecycle events `sandbox.running`, `.suspended`, `.expired`, `.relaunched`, `.lost`, `.reset` and `.failed`; error codes `placement_refused`, `sandbox_lost` and `sandbox_expired`; `cluster` in `GET /v1/tenant/sandbox`; Host feature `sandbox-pods` (additive, protocol unchanged). Without a cluster, kind `pod` is `409 sandbox_unavailable` (it was 400). Migration `0007_sandbox_pods` adds the pod lifecycle columns to `sandbox_resources`.
|
|
28
|
+
- 926711b: **Sandboxes are a resource (F7.1, blueprint D39; Host feature `sandboxes`).** A sandbox has its own id, a kind, a spec and labels, and outlives the sessions attached to it. Additive: protocol 5 is unchanged.
|
|
29
|
+
|
|
30
|
+
- `PUT /v1/sandboxes/{id}` creates a sandbox or finds the one with that id (get-or-create in one call); `GET /v1/sandboxes/{id}`, `GET /v1/sandboxes?label=key=value` (repeatable), `GET /v1/sandboxes/{id}/events` and `DELETE /v1/sandboxes/{id}`. Ids are `/`-separated segments (`team-a/proj-42`), sent percent-encoded as one path segment. Only kind `virtual` runs; `pod` is refused with `sandbox_unavailable`. The spec is resolved against the Tenant's limits and fixed once the sandbox exists; labels can change.
|
|
31
|
+
- A session attaches with `sandbox: { id }` and shares the sandbox's `/workspace` with every other session attached to it. Turns are serial per sandbox: a second session's turn is refused with `409 sandbox_busy` while another runs. Deleting a session (a sessions reset) only detaches it. Deleting a sandbox is refused while a turn runs in it; afterwards an attached session's next turn is refused with `sandbox_unavailable` until a sandbox with that id exists again.
|
|
32
|
+
- Subject tokens carry an `sbx` claim: `POST /v1/tokens` takes `sandboxes`, exact ids or prefixes ending in `/*` (at most 16). A token reaches only the sandboxes they match, checked when a session attaches and at every turn start (`403 sandbox_not_granted`); any other sandbox is the 404 of a missing one. The new scope `sandboxes:write` lets a role create and delete the sandboxes its grants reach. Application keys reach every sandbox.
|
|
33
|
+
- The Tenant holds at most `limits.sandboxes` sandboxes (`PUT /v1/tenant/sandbox`, default 100); one more is `409 limit_exceeded`.
|
|
34
|
+
- Lifecycle events (`sandbox.created`, `sandbox.attached`, `sandbox.detached`, `sandbox.deleted`) go to the sandbox's own stream in the record, through the record module; the session's log records `sandbox.attached`. The sandbox stream is not relayed to S2.
|
|
35
|
+
- New error codes `sandbox_not_granted`, `sandbox_busy` and `sandbox_unavailable`; the session view gains `sandboxId` and `sandboxSource: "sandbox"`. Migration `0004_sandbox_resources` adds `sandbox_resources`, `nylorun_streams.sandbox_events` and the sessions' `sandbox_id` column.
|
|
36
|
+
- `@nylorun/agents`: `client.sandboxes` with `ensure(id, spec)`, `get`, `list({ labels })`, `delete`, `events`, and `forSession({ session, spec })`, which creates a sandbox for one session, opens the session on it, and deletes it with `release()`. It replaces sharing through another session (`sandbox: { session }` and the view's `sandboxOwnerId`, now deprecated). `client.tokens.create` takes `sandboxes`.
|
|
37
|
+
- `nylorun sandbox ls [--label key=value]... [--json]` and `nylorun sandbox rm <id>` list and delete the running local Tenant's sandboxes.
|
|
38
|
+
|
|
39
|
+
- db045cd: **The sandboxes service and `nylorun sandbox enable` (F7.2, first part).** A new image, `ghcr.io/nylorun/sandboxes`, versioned with the Runtime, drives agent-sandbox v1.0.5 Sandboxes in one namespace per Tenant: `PUT`, `GET ?wait=` and `DELETE /v1/pods/{name}` with an operation id, `/ready` and `/v1/info`, behind a bearer token only the runtime container holds. `nylorun sandbox enable --context <name>` installs into that kubeconfig context only (the pinned controller when absent, the namespace `nylorun-sbx-<tenant>`, a ServiceAccount whose Role covers Sandbox lifecycle and join Secrets, no NetworkPolicy or exec rights), refuses a cluster whose NetworkPolicy it cannot prove enforced, records `<Host root>/sandboxes/cluster.json` and the token, and adds the `sandboxes` service to the Tenant; `nylorun sandbox disable` and `nylorun sandbox status` remove and report it. Sessions do not run in pods yet: that comes with the Harness API and egress-gate.
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- Pin core to the tested release.
|
|
44
|
+
- Pin runtime to the tested release.
|
|
45
|
+
- Pin studio to the tested release.
|
|
46
|
+
- Updated dependencies [b352feb]
|
|
47
|
+
- Updated dependencies [6077272]
|
|
48
|
+
- Updated dependencies [b6bf1f5]
|
|
49
|
+
- Updated dependencies [8ed4ea6]
|
|
50
|
+
- Updated dependencies [678e085]
|
|
51
|
+
- Updated dependencies [926711b]
|
|
52
|
+
- @nylorun/core@0.12.0-beta
|
|
53
|
+
|
|
54
|
+
## 0.7.0-beta
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- 9af30d8: **The Studio proxy is removed, and Restate runs with its own memory defaults.** Each Tenant's Studio is at `http://localhost:<port>` again (`nylorun studio --tenant <name>` opens it signed in): its own session cookie already keeps it apart from other Studios, so the proxy only added a container and a second address. The 256 MiB RocksDB cap did not lower Restate's memory, so it is gone. The first command of this release removes the proxy that 0.6 started (`nylorun-proxy` and `~/.nylorun/proxy/`); `NYLORUN_PROXY_PORT`, `NYLORUN_PROXY_DISABLED`, `studio.proxyUrl` and Studio's `NYLORUN_STUDIO_PUBLIC_ORIGINS` are gone.
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- Pin studio to the tested release.
|
|
63
|
+
|
|
3
64
|
## 0.6.0-beta
|
|
4
65
|
|
|
5
66
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -34,17 +34,23 @@ npx nylorun doctor # Node 24+, Docker, Compose v2, and the Tenan
|
|
|
34
34
|
## Commands
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
nylorun up|start [--tenant <name>] [--no-link] [--no-studio] [--no-open] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset]
|
|
37
|
+
nylorun up|start [--tenant <name>] [--no-link] [--no-studio] [--no-open] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset] [--restate-ui]
|
|
38
38
|
# create (first run) and start the Tenant; link the project; print the URLs; open Studio signed in
|
|
39
|
-
|
|
39
|
+
# --restate-ui (or NYLORUN_RESTATE_UI=1): publish Restate's UI on loopback for this start
|
|
40
|
+
nylorun down|stop [--tenant <name> | --all] # stop the containers (--all: every Tenant's); keep volumes
|
|
40
41
|
nylorun status [--tenant <name>] [--json] # the Tenant, its Host root and id, services, endpoints, Runtime health
|
|
41
|
-
nylorun logs [service] [--tenant <name>] [-f] [--tail <n>] # postgres, restate, s2-lite, gateway, runtime, studio
|
|
42
|
+
nylorun logs [service] [--tenant <name>] [-f] [--tail <n>] # postgres, restate, s2-lite, rustfs, gateway, runtime, harness, studio, sandboxes
|
|
42
43
|
nylorun studio [--tenant <name>] [--no-open] # sign a browser in to Studio on the Tenant; starts it if needed
|
|
43
44
|
nylorun reset [--tenant <name>] [--yes] # delete the Tenant's volumes, Tenant directory and vault key; the next start creates it anew
|
|
44
45
|
nylorun ls [--json] # the Tenants on this machine, with their state, memory and URLs
|
|
45
46
|
nylorun delete <tenant> --yes # remove a Tenant: containers, volumes, Host root and vault key
|
|
47
|
+
nylorun sandbox ls [--tenant <name>] [--label <key=value>]... [--json] # the running Tenant's sandboxes
|
|
48
|
+
nylorun sandbox rm <id> [--tenant <name>] # delete a sandbox and its files
|
|
46
49
|
nylorun doctor [--json] # prerequisites and the Tenant's health
|
|
47
50
|
nylorun telemetry [status|enable|disable] # Studio's anonymous usage analytics
|
|
51
|
+
nylorun sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]
|
|
52
|
+
nylorun sandbox disable [--tenant <name>] [--delete-namespace]
|
|
53
|
+
nylorun sandbox status [--tenant <name>] [--json] # pods on a Kubernetes context (see Sandboxes)
|
|
48
54
|
```
|
|
49
55
|
|
|
50
56
|
`up` and `down` are the Docker Compose spellings of `start` and `stop`: `down`
|
|
@@ -82,29 +88,29 @@ projects and volumes.
|
|
|
82
88
|
|
|
83
89
|
Tenants run side by side, each on its own ports, containers, network and
|
|
84
90
|
volumes ([The containers](#the-containers)); a running Tenant uses about
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
1.2 GB. Each Studio is on its own port, `http://localhost:<port>`, with its own
|
|
92
|
+
session cookie, so two Studios in one browser stay signed in;
|
|
93
|
+
`nylorun studio --tenant <name>` opens any Tenant's Studio signed in. `nylorun ls`
|
|
87
94
|
lists them with their state, the memory their containers use (`MEMORY`: the sum
|
|
88
95
|
of `docker stats` over the containers labelled `dev.nylorun.tenant=<name>`, `-`
|
|
89
96
|
when stopped; `memoryBytes` in `--json`, `null` when stopped or unknown), the
|
|
90
97
|
Runtime's URL and Studio's:
|
|
91
98
|
|
|
92
99
|
```text
|
|
93
|
-
TENANT STATE MEMORY RUNTIME STUDIO
|
|
94
|
-
api running
|
|
95
|
-
default stopped - http://localhost:8787 http://
|
|
100
|
+
TENANT STATE MEMORY RUNTIME STUDIO PROJECT
|
|
101
|
+
api running 1.2 GB http://localhost:8790 http://localhost:4162 /Users/me/api
|
|
102
|
+
default stopped - http://localhost:8787 http://localhost:4161 -
|
|
96
103
|
```
|
|
97
104
|
|
|
98
105
|
When other Tenants are running, `start` says so after its summary, on stderr
|
|
99
106
|
(without the size when Docker does not report it):
|
|
100
107
|
|
|
101
108
|
```text
|
|
102
|
-
Also running: default, api (about
|
|
109
|
+
Also running: default, api (about 2.4 GB). "nylorun stop --all" stops them all.
|
|
103
110
|
```
|
|
104
111
|
|
|
105
|
-
`nylorun stop --all` stops every running Tenant on this machine
|
|
106
|
-
|
|
107
|
-
`stop` of one Tenant leaves the proxy running.
|
|
112
|
+
`nylorun stop --all` stops every running Tenant on this machine, keeping their
|
|
113
|
+
volumes; it cannot be combined with `--tenant` (exit 2).
|
|
108
114
|
|
|
109
115
|
A Tenant created by nylorun 0.5 keeps its data in volumes Compose named
|
|
110
116
|
`<project>_postgres`, `<project>_restate`, `<project>_s2` and
|
|
@@ -157,77 +163,52 @@ The Runtime registers the derived principals of `NYLORUN_DERIVED_PRINCIPALS`
|
|
|
157
163
|
`nylorun start` writes `compose.yaml` and `.env` (mode 0600) under
|
|
158
164
|
`~/.nylorun/tenants/<name>/docker/` on every start; `.env` keeps the ports and
|
|
159
165
|
secrets chosen on the first run. The services are `postgres`, `restate`,
|
|
160
|
-
`s2-lite`, `gateway`, `runtime` and `studio`. Container, network and volume
|
|
166
|
+
`s2-lite`, `rustfs`, `gateway`, `runtime`, `harness` and `studio`. Container, network and volume
|
|
161
167
|
names are global on the Docker engine, so each carries the Tenant's Compose
|
|
162
168
|
project, and each has the label `dev.nylorun.tenant: <name>`. For Tenant `shop`:
|
|
163
169
|
|
|
164
170
|
| Thing | Name |
|
|
165
171
|
| --- | --- |
|
|
166
|
-
| Containers | `nylorun-shop-postgres`, `nylorun-shop-restate`, `nylorun-shop-s2-lite`, `nylorun-shop-gateway`, `nylorun-shop-runtime`, `nylorun-shop-studio` |
|
|
167
|
-
|
|
|
168
|
-
| Volumes | `nylorun-shop-postgres`, `nylorun-shop-restate`, `nylorun-shop-s2-lite`, `nylorun-shop-workspaces` |
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
| Containers | `nylorun-shop-postgres`, `nylorun-shop-restate`, `nylorun-shop-s2-lite`, `nylorun-shop-rustfs`, `nylorun-shop-gateway`, `nylorun-shop-runtime`, `nylorun-shop-harness`, `nylorun-shop-studio` |
|
|
173
|
+
| Networks | `nylorun-shop` (egress, published ports), `nylorun-shop-store` (internal: the stores), `nylorun-shop-harness` (the harness, the runtime and the gateway) |
|
|
174
|
+
| Volumes | `nylorun-shop-postgres`, `nylorun-shop-restate`, `nylorun-shop-s2-lite`, `nylorun-shop-rustfs`, `nylorun-shop-workspaces` |
|
|
175
|
+
|
|
176
|
+
The `harness` container (the Runtime image as `--service harness`) runs agent
|
|
177
|
+
turns, stdio MCP servers and workspaces, apart from the `runtime` container. It
|
|
178
|
+
holds only the harness token (`NYLORUN_HARNESS_TOKEN` in `.env`), mounts only
|
|
179
|
+
the Tenant directory's `sandboxes/`, `plugin-data/`, `home/` and `tmp/`, and the
|
|
180
|
+
Host root's `plugins/` read-only at its own path: put a plugin whose stdio MCP
|
|
181
|
+
server the agent runs under `~/.nylorun/tenants/<name>/plugins/` and load it from
|
|
182
|
+
there. `NYLORUN_HARNESS=in-process` in `.env` rolls back to running them in the
|
|
183
|
+
`runtime` container (no `harness` container); `nylorun status` shows which.
|
|
184
|
+
|
|
185
|
+
Postgres initialises the Tenant's database with C collation. Restate runs with
|
|
186
|
+
its own defaults. A running Tenant uses about 1.2 GB, most of it Restate; stop
|
|
187
|
+
the Tenants you are not using (`nylorun stop`, or `nylorun stop --all`). The Runtime and Studio images are pinned by
|
|
174
188
|
this release (`package.json` `nylorun.runtime` and `nylorun.studio`);
|
|
175
189
|
`NYLORUN_RUNTIME_IMAGE` and `NYLORUN_STUDIO_IMAGE` override them (local builds,
|
|
176
190
|
CI). Ports publish on loopback only: the Runtime on `8787`, its operator
|
|
177
|
-
listener (Admin API) on `8788
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
`
|
|
182
|
-
|
|
183
|
-
`
|
|
184
|
-
|
|
191
|
+
listener (Admin API) on `8788` and Studio on `4161`, or free ports chosen on the
|
|
192
|
+
Tenant's first start, avoiding the ports other Tenants keep, and kept in its
|
|
193
|
+
`.env`. Restate's UI and admin API (unauthenticated) are not published; `nylorun
|
|
194
|
+
start --restate-ui` (or `NYLORUN_RESTATE_UI=1`) publishes them for that start on
|
|
195
|
+
`9070` (or the port kept in `.env`), and the next start without it closes them.
|
|
196
|
+
|
|
197
|
+
`start`, `status`, `ls` and `nylorun studio` give Studio as
|
|
198
|
+
`http://localhost:<port>` (`studio.url` in `status --json`). Studio has no
|
|
185
199
|
password: in a terminal (not in CI, and not with `--no-open`), `start` asks the
|
|
186
200
|
Studio container for a single-use login token with the admin key (`POST
|
|
187
|
-
/_studio/login-tokens
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`nylorun studio`, which signs a browser in the same way; `nylorun studio
|
|
201
|
+
/_studio/login-tokens`) and opens `http://localhost:<port>/login?token=…` on the
|
|
202
|
+
Tenant's page in the browser. That sets a session cookie for 30 days, which
|
|
203
|
+
survives Studio restarts, so the printed URL keeps working in that browser. The
|
|
204
|
+
token itself is never printed unless no browser starts. Otherwise `start` says
|
|
205
|
+
to run `nylorun studio`, which signs a browser in the same way; `nylorun studio
|
|
193
206
|
--no-open` prints the login URL (it works once, for two minutes) instead.
|
|
194
207
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
One small Caddy container per machine (`nylorun-proxy`, Compose project
|
|
201
|
-
`nylorun-proxy`, label `dev.nylorun.proxy: "true"`) gives every Tenant's Studio
|
|
202
|
-
the address `http://<name>.localhost:<port>`. Browsers keep cookies per host,
|
|
203
|
-
not per port, so separate hosts keep the Studios' sessions apart. The proxy is
|
|
204
|
-
for browsers only: programs keep using the Runtime's `http://localhost:<port>`.
|
|
205
|
-
It holds no Tenant data; its files are in `~/.nylorun/proxy/` (`compose.yaml`,
|
|
206
|
-
`Caddyfile`, and `.env` with `NYLORUN_PROXY_PORT`, 4160 or a free port chosen
|
|
207
|
-
on its first start and kept; Caddy's own state is tmpfs). Its image is pinned
|
|
208
|
-
by this release (`caddy:2.11.6`).
|
|
209
|
-
|
|
210
|
-
- Caddy listens on port 80 in the container, published at `NYLORUN_PROXY_PORT`
|
|
211
|
-
on `127.0.0.1` and `[::1]` (macOS resolves `*.localhost` to `::1` only); when
|
|
212
|
-
Docker refuses `::1` (IPv6 off), it publishes on `127.0.0.1` alone and says
|
|
213
|
-
so once.
|
|
214
|
-
- It routes every Tenant under `~/.nylorun/tenants/`: it joins each Tenant's
|
|
215
|
-
network and reaches `<project>-studio:3000`, passing the browser's `Host`
|
|
216
|
-
through, which Studio checks. A stopped Tenant answers 502 naming
|
|
217
|
-
`nylorun start --tenant <name>`; an unknown host 404 naming `nylorun ls`.
|
|
218
|
-
- `start` rewrites its files, brings it up (only when Studio starts: not with
|
|
219
|
-
`--no-studio`), joins every Tenant's network and reloads Caddy; Studio learns
|
|
220
|
-
its proxy origin from `NYLORUN_STUDIO_PUBLIC_ORIGINS`, written to the
|
|
221
|
-
Tenant's `.env` on every start. A proxy that does not come up never fails
|
|
222
|
-
`start`: it warns once and prints Studio's own URL.
|
|
223
|
-
- `reset` and `delete` detach it from the Tenant's network first; `delete`
|
|
224
|
-
removes the Tenant's route. `stop --all` stops it.
|
|
225
|
-
- `NYLORUN_PROXY_DISABLED=1` turns it off: Studio is `http://localhost:<port>`.
|
|
226
|
-
A Tenant under `NYLORUN_HOME` or `NYLORUN_COMPOSE_PROJECT` (the repository's
|
|
227
|
-
smokes) does not use it either.
|
|
228
|
-
|
|
229
|
-
`nylorun doctor` reports it on a `proxy` row: running (on which loopbacks and
|
|
230
|
-
port), stopped, not created yet, or disabled.
|
|
208
|
+
Browsers share cookies across the ports of one host, so each Tenant's Studio
|
|
209
|
+
sets its own session cookie, `nylorun_studio_<name>`
|
|
210
|
+
(`NYLORUN_STUDIO_SESSION_COOKIE`): signing in to one Studio does not sign you
|
|
211
|
+
out of another.
|
|
231
212
|
|
|
232
213
|
### Embedding Studio in a desktop app
|
|
233
214
|
|
|
@@ -269,6 +250,29 @@ default names one (`PUT /v1/tenant/sandbox`). A local Tenant runs them on the
|
|
|
269
250
|
Runtime's **virtual** backend: an emulated shell in the Runtime process, with no
|
|
270
251
|
extra containers. It is not a VM or container boundary.
|
|
271
252
|
|
|
253
|
+
A sandbox can also be a resource with its own id (`PUT /v1/sandboxes/{id}`,
|
|
254
|
+
`client.sandboxes` in `@nylorun/agents`) that sessions attach to and that
|
|
255
|
+
outlives them. `nylorun sandbox ls` lists the running Tenant's sandboxes with
|
|
256
|
+
their state, attached sessions and labels; `nylorun sandbox rm <id>` deletes one
|
|
257
|
+
and its files (refused while a turn runs in it). Neither starts a stopped Tenant
|
|
258
|
+
(exit 3).
|
|
259
|
+
|
|
260
|
+
### Pods on a cluster (preview)
|
|
261
|
+
|
|
262
|
+
`nylorun sandbox enable --context <name>` prepares a Kubernetes context for the
|
|
263
|
+
Tenant's sandboxes as pods ([agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox)
|
|
264
|
+
v1.0.5): Docker Desktop's cluster (`--context docker-desktop`) or kind. It only
|
|
265
|
+
touches the context you name. It installs the pinned agent-sandbox controller
|
|
266
|
+
when the cluster has none (and refuses another version), creates the namespace
|
|
267
|
+
`nylorun-sbx-<tenant>` with a ServiceAccount limited to Sandbox lifecycle, proves
|
|
268
|
+
with a probe that the cluster enforces NetworkPolicy (and refuses it otherwise),
|
|
269
|
+
lets pods reach only three ports on the Docker host, and adds the `sandboxes`
|
|
270
|
+
service to the Tenant: the one container holding the cluster credentials
|
|
271
|
+
(`<Host root>/sandboxes/`). On kind under Linux, pass `--host-address 172.17.0.1`.
|
|
272
|
+
`nylorun sandbox disable` removes the service (`--delete-namespace` also deletes
|
|
273
|
+
the namespace and every sandbox in it). Sessions keep using the virtual backend
|
|
274
|
+
in this release.
|
|
275
|
+
|
|
272
276
|
## Host root
|
|
273
277
|
|
|
274
278
|
The **Host root** is `~/.nylorun/tenants/<name>/`, or `NYLORUN_HOME`. It is
|
|
@@ -291,7 +295,7 @@ containers, volumes and Host root: the vault key (KEK) and all its data go with
|
|
|
291
295
|
| 0 | Success |
|
|
292
296
|
| 1 | Generic failure, including a missing Docker or Compose v2 |
|
|
293
297
|
| 2 | Usage error (including no Tenant selected, or `delete` without `--yes`), or a removed or moved command |
|
|
294
|
-
| 3 | `status`: the Runtime is not answering; `stop`/`logs`: no Tenant yet; `delete`: no such Tenant; `start`: a Tenant created by nylorun 0.5 (reset it) |
|
|
298
|
+
| 3 | `status`: the Runtime is not answering; `sandbox`: the Tenant is not running; `stop`/`logs`: no Tenant yet; `delete`: no such Tenant; `start`: a Tenant created by nylorun 0.5 (reset it) |
|
|
295
299
|
| 4 | The Runtime port is held by another Host |
|
|
296
300
|
| 5 | `up`/`start`/`studio`: this nylorun pins a Runtime older than the one that last ran the Tenant; update nylorun, or `start --allow-downgrade` |
|
|
297
301
|
| 7 | The containers, the Tenant or Studio did not become ready (an unavailable Tenant's cause is printed) |
|
|
@@ -308,7 +312,7 @@ containers, volumes and Host root: the vault key (KEK) and all its data go with
|
|
|
308
312
|
| Port conflict | Change `NYLORUN_PORT` / `NYLORUN_STUDIO_PORT` in `~/.nylorun/tenants/<name>/docker/.env` |
|
|
309
313
|
| Logs | `nylorun logs runtime -f` |
|
|
310
314
|
| Studio login expired | `nylorun studio` |
|
|
311
|
-
|
|
|
315
|
+
| Which Studio is which | `nylorun ls` lists each Tenant's Studio URL; `nylorun studio --tenant <name>` opens one signed in |
|
|
312
316
|
| `Tenant … was created by nylorun 0.5` (exit 3) | `nylorun reset --tenant <name>` (its data starts fresh) |
|
|
313
317
|
|
|
314
318
|
See [MIGRATION.md](../MIGRATION.md).
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import "./baseline.js";
|
|
3
3
|
import { baselineEnv } from "./baseline.js";
|
|
4
4
|
import { CliError } from "./errors.js";
|
|
5
|
-
import { isStackCommand, stackCommand, stackUsage, studioCommand, } from "./stack/index.js";
|
|
6
|
-
const usage = `nylorun <up|down|start|stop|status|logs|studio|reset|ls|delete|doctor|telemetry>
|
|
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>
|
|
7
7
|
|
|
8
8
|
Local Tenants (Docker Compose), one per project:
|
|
9
9
|
${stackUsage}
|
|
10
|
+
${sandboxUsage}
|
|
10
11
|
doctor [--json] check Node, Docker and Compose v2, and the Tenant's health
|
|
11
12
|
telemetry [status|enable|disable] Studio's anonymous usage analytics (on unless disabled,
|
|
12
13
|
NYLORUN_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1 or CI)
|
|
@@ -45,6 +46,10 @@ async function main() {
|
|
|
45
46
|
process.exitCode = await studioCommand(args, baselineEnv());
|
|
46
47
|
return;
|
|
47
48
|
}
|
|
49
|
+
if (command === "sandbox") {
|
|
50
|
+
process.exitCode = await sandboxCommand(args, baselineEnv());
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
48
53
|
if (isStackCommand(command)) {
|
|
49
54
|
process.exitCode = await stackCommand(command, args, baselineEnv());
|
|
50
55
|
return;
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Check, type
|
|
1
|
+
import { type Check, type StackDeps, type StackStatus } from "./stack/index.js";
|
|
2
2
|
export interface StackDoctorReport {
|
|
3
3
|
node: {
|
|
4
4
|
version: string;
|
|
@@ -6,11 +6,9 @@ export interface StackDoctorReport {
|
|
|
6
6
|
};
|
|
7
7
|
docker: Check;
|
|
8
8
|
compose?: Check;
|
|
9
|
-
tenant?: Pick<StackStatus, "name" | "project" | "home" | "state" | "runtime" | "tenant" | "studio" | "gateway">;
|
|
9
|
+
tenant?: Pick<StackStatus, "name" | "project" | "home" | "state" | "runtime" | "tenant" | "studio" | "gateway" | "harness">;
|
|
10
10
|
/** Why no Tenant was checked: none is selected here (a project without a link). */
|
|
11
11
|
noTenant?: string;
|
|
12
|
-
/** The machine's Studio proxy (`http://<tenant>.localhost:<port>`); never a failure. */
|
|
13
|
-
proxy?: ProxyStatus;
|
|
14
12
|
}
|
|
15
13
|
/**
|
|
16
14
|
* `nylorun doctor`: check the prerequisites of a local Tenant (Node 24+,
|
package/dist/doctor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CliError } from "./errors.js";
|
|
2
|
-
import { checkDocker, defaultStackDeps,
|
|
2
|
+
import { checkDocker, defaultStackDeps, readStackStatus, } from "./stack/index.js";
|
|
3
3
|
/** Oldest Node nylorun and the developer's application run on. */
|
|
4
4
|
const MIN_NODE_MAJOR = 24;
|
|
5
5
|
/**
|
|
@@ -29,6 +29,7 @@ export async function doctorStack(options) {
|
|
|
29
29
|
...(status.tenant ? { tenant: status.tenant } : {}),
|
|
30
30
|
studio: status.studio,
|
|
31
31
|
gateway: status.gateway,
|
|
32
|
+
harness: status.harness,
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
catch (error) {
|
|
@@ -36,11 +37,11 @@ export async function doctorStack(options) {
|
|
|
36
37
|
throw error;
|
|
37
38
|
report.noTenant = error.message;
|
|
38
39
|
}
|
|
39
|
-
report.proxy = await readProxyReport(deps);
|
|
40
40
|
}
|
|
41
41
|
const broken = report.tenant?.state === "running" &&
|
|
42
42
|
(!report.tenant.runtime.healthy ||
|
|
43
43
|
!report.tenant.gateway.healthy ||
|
|
44
|
+
(report.tenant.harness.mode === "remote" && !report.tenant.harness.healthy) ||
|
|
44
45
|
report.tenant.tenant?.cause !== undefined);
|
|
45
46
|
const failed = !nodeOk || !checks.docker.ok || checks.compose?.ok === false || broken;
|
|
46
47
|
if (options.json) {
|
|
@@ -89,6 +90,14 @@ export async function doctorStack(options) {
|
|
|
89
90
|
? `✓ ${local.gateway.state} · combined packing (runtime: core,loop; gateway: gates)`
|
|
90
91
|
: `✗ ${local.gateway.state}: model calls fail; see nylorun logs gateway`,
|
|
91
92
|
]);
|
|
93
|
+
rows.push([
|
|
94
|
+
"harness",
|
|
95
|
+
local.harness.mode === "in-process"
|
|
96
|
+
? "- in-process (NYLORUN_HARNESS=in-process in .env)"
|
|
97
|
+
: local.harness.healthy
|
|
98
|
+
? `✓ ${local.harness.state} · remote (agent turns, MCP servers, workspaces)`
|
|
99
|
+
: `✗ ${local.harness.state}: turns do not run; see nylorun logs harness`,
|
|
100
|
+
]);
|
|
92
101
|
if (local.tenant)
|
|
93
102
|
rows.push([
|
|
94
103
|
"tenant id",
|
|
@@ -96,25 +105,9 @@ export async function doctorStack(options) {
|
|
|
96
105
|
? `✓ ${local.tenant.id ?? "?"} open`
|
|
97
106
|
: `✗ ${local.tenant.id ?? "?"} unavailable${local.tenant.cause ? `: ${local.tenant.cause.code}: ${local.tenant.cause.repair}` : " (opening)"}`,
|
|
98
107
|
]);
|
|
99
|
-
rows.push(["studio", `${local.studio.
|
|
108
|
+
rows.push(["studio", `${local.studio.url ?? "?"} · ${local.studio.state}`]);
|
|
100
109
|
}
|
|
101
110
|
}
|
|
102
|
-
const proxy = report.proxy;
|
|
103
|
-
if (proxy) {
|
|
104
|
-
const on = proxy.port
|
|
105
|
-
? `127.0.0.1:${proxy.port}${proxy.ipv6 ? ` and [::1]:${proxy.port}` : ""}`
|
|
106
|
-
: "";
|
|
107
|
-
rows.push([
|
|
108
|
-
"proxy",
|
|
109
|
-
proxy.state === "running"
|
|
110
|
-
? `✓ running on ${on} (http://<tenant>.localhost:${proxy.port ?? "?"})`
|
|
111
|
-
: proxy.state === "disabled"
|
|
112
|
-
? "- disabled (NYLORUN_PROXY_DISABLED): Studio is on each Tenant's own port"
|
|
113
|
-
: proxy.state === "stopped"
|
|
114
|
-
? `- stopped${on ? ` (${on})` : ""}: nylorun start starts it`
|
|
115
|
-
: "- not created yet: nylorun start creates it",
|
|
116
|
-
]);
|
|
117
|
-
}
|
|
118
111
|
const width = Math.max(...rows.map(([key]) => key.length)) + 2;
|
|
119
112
|
for (const [key, value] of rows)
|
|
120
113
|
log(` ${key.padEnd(width)}${value}`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<Host root>/sandboxes/`: what the sandboxes service mounts read-only. `cluster.json`
|
|
3
|
+
* records the cluster `nylorun sandbox enable` installed into; `token` is the
|
|
4
|
+
* ServiceAccount token (0600). Only the sandboxes container reads them: the runtime
|
|
5
|
+
* container sees an empty mount there.
|
|
6
|
+
*/
|
|
7
|
+
export interface ClusterFile {
|
|
8
|
+
context: string;
|
|
9
|
+
/** The API server URL in the kubeconfig. */
|
|
10
|
+
server: string;
|
|
11
|
+
/** host:port the sandboxes container dials. */
|
|
12
|
+
dial: string;
|
|
13
|
+
/** The name the API server's certificate is verified against. */
|
|
14
|
+
tlsServerName: string;
|
|
15
|
+
/** The cluster CA, base64 PEM. */
|
|
16
|
+
caData: string;
|
|
17
|
+
namespace: string;
|
|
18
|
+
controllerVersion: string;
|
|
19
|
+
hostAddress: string;
|
|
20
|
+
bindAddress: string;
|
|
21
|
+
ports: {
|
|
22
|
+
harness: number;
|
|
23
|
+
gates: number;
|
|
24
|
+
egress: number;
|
|
25
|
+
};
|
|
26
|
+
networkPolicy: {
|
|
27
|
+
enforced: boolean;
|
|
28
|
+
probedAt: string;
|
|
29
|
+
};
|
|
30
|
+
enabledAt: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function sandboxesDir(root: string): string;
|
|
33
|
+
export declare function clusterFilePath(root: string): string;
|
|
34
|
+
/** Sandboxes are enabled for the Tenant under `root`. */
|
|
35
|
+
export declare function sandboxesEnabled(root: string): boolean;
|
|
36
|
+
export declare function readClusterFile(root: string): Promise<ClusterFile | undefined>;
|
|
37
|
+
export declare function writeClusterFiles(root: string, cluster: ClusterFile, token: string): Promise<void>;
|
|
38
|
+
export declare function removeClusterFiles(root: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* How the sandboxes container reaches the API server. One on this machine's loopback
|
|
41
|
+
* (Docker Desktop's `https://127.0.0.1:<port>`) is dialled at `host.docker.internal:<port>`
|
|
42
|
+
* while the certificate is still verified against the kubeconfig's host (Experiment 2, Q3).
|
|
43
|
+
*/
|
|
44
|
+
export declare function apiRoute(server: string): {
|
|
45
|
+
dial: string;
|
|
46
|
+
tlsServerName: string;
|
|
47
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
export function sandboxesDir(root) {
|
|
6
|
+
return join(root, "sandboxes");
|
|
7
|
+
}
|
|
8
|
+
export function clusterFilePath(root) {
|
|
9
|
+
return join(sandboxesDir(root), "cluster.json");
|
|
10
|
+
}
|
|
11
|
+
/** Sandboxes are enabled for the Tenant under `root`. */
|
|
12
|
+
export function sandboxesEnabled(root) {
|
|
13
|
+
return existsSync(clusterFilePath(root));
|
|
14
|
+
}
|
|
15
|
+
export async function readClusterFile(root) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(await readFile(clusterFilePath(root), "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function writeFileMode(path, text, mode) {
|
|
24
|
+
const temporary = `${path}.${randomBytes(8).toString("hex")}.tmp`;
|
|
25
|
+
await writeFile(temporary, text, { mode });
|
|
26
|
+
await rename(temporary, path);
|
|
27
|
+
await chmod(path, mode);
|
|
28
|
+
}
|
|
29
|
+
export async function writeClusterFiles(root, cluster, token) {
|
|
30
|
+
const dir = sandboxesDir(root);
|
|
31
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
32
|
+
await chmod(dir, 0o700);
|
|
33
|
+
await writeFileMode(join(dir, "token"), token, 0o600);
|
|
34
|
+
await writeFileMode(clusterFilePath(root), `${JSON.stringify(cluster, null, 2)}\n`, 0o600);
|
|
35
|
+
}
|
|
36
|
+
export async function removeClusterFiles(root) {
|
|
37
|
+
await rm(sandboxesDir(root), { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
const LOOPBACK = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
40
|
+
/**
|
|
41
|
+
* How the sandboxes container reaches the API server. One on this machine's loopback
|
|
42
|
+
* (Docker Desktop's `https://127.0.0.1:<port>`) is dialled at `host.docker.internal:<port>`
|
|
43
|
+
* while the certificate is still verified against the kubeconfig's host (Experiment 2, Q3).
|
|
44
|
+
*/
|
|
45
|
+
export function apiRoute(server) {
|
|
46
|
+
const url = new URL(server);
|
|
47
|
+
if (url.protocol !== "https:")
|
|
48
|
+
throw new Error(`the API server ${server} is not https`);
|
|
49
|
+
const port = url.port || "443";
|
|
50
|
+
const host = url.hostname;
|
|
51
|
+
const bare = host.replace(/^\[|\]$/g, "");
|
|
52
|
+
if (LOOPBACK.has(host) || LOOPBACK.has(bare))
|
|
53
|
+
return { dial: `host.docker.internal:${port}`, tlsServerName: bare };
|
|
54
|
+
return { dial: `${host}:${port}`, tlsServerName: bare };
|
|
55
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type StackDeps } from "../stack/commands.js";
|
|
2
|
+
import type { FetchLike } from "../stack/studio-login.js";
|
|
3
|
+
import { type ClusterFile } from "./cluster-file.js";
|
|
4
|
+
import { type KubectlRunner } from "./kubectl.js";
|
|
5
|
+
export declare const sandboxUsage = " sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--no-pull]\n run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox v1.0.5)\n sandbox disable [--tenant <name>] [--delete-namespace]\n stop the sandboxes service and forget the cluster\n sandbox status [--tenant <name>] [--json]\n the cluster, the sandboxes service's readiness and what it reports";
|
|
6
|
+
/** The default workload image (D36), pre-pulled with the Runtime image. */
|
|
7
|
+
export declare const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
8
|
+
export interface SandboxDeps {
|
|
9
|
+
stack: StackDeps;
|
|
10
|
+
kubectl: KubectlRunner;
|
|
11
|
+
/** Fetches the controller manifest; defaults to the stack's fetch. */
|
|
12
|
+
fetch?: FetchLike;
|
|
13
|
+
/** Poll interval and NetworkPolicy wait, for tests. */
|
|
14
|
+
pollMs?: number;
|
|
15
|
+
policyTimeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface InstallResult {
|
|
18
|
+
cluster: ClusterFile;
|
|
19
|
+
/** The ServiceAccount token. */
|
|
20
|
+
token: string;
|
|
21
|
+
/** Images the pre-pull could not pull. */
|
|
22
|
+
notPulled: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Steps 2–8 of `enable` against one context: controller, namespace and RBAC, NetworkPolicy
|
|
26
|
+
* probe, the namespace's policy, pre-pull. Returns what cluster.json records.
|
|
27
|
+
*/
|
|
28
|
+
export declare function installCluster(deps: SandboxDeps, input: {
|
|
29
|
+
context: string;
|
|
30
|
+
tenant: string;
|
|
31
|
+
hostAddress?: string;
|
|
32
|
+
bindAddress?: string;
|
|
33
|
+
prePull: readonly string[];
|
|
34
|
+
ports: {
|
|
35
|
+
harness: number;
|
|
36
|
+
gates: number;
|
|
37
|
+
egress: number;
|
|
38
|
+
};
|
|
39
|
+
}): Promise<InstallResult>;
|
|
40
|
+
/** `nylorun sandbox <enable|disable|status>`. */
|
|
41
|
+
export declare function runSandboxCommand(args: readonly string[], deps: SandboxDeps): Promise<number>;
|