nylorun 0.7.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +95 -25
  3. package/dist/cli.js +17 -2
  4. package/dist/doctor.d.ts +1 -1
  5. package/dist/doctor.js +10 -0
  6. package/dist/project/link.d.ts +13 -7
  7. package/dist/project/link.js +13 -2
  8. package/dist/sandbox/cluster-file.d.ts +47 -0
  9. package/dist/sandbox/cluster-file.js +55 -0
  10. package/dist/sandbox/commands.d.ts +41 -0
  11. package/dist/sandbox/commands.js +281 -0
  12. package/dist/sandbox/kubectl.d.ts +39 -0
  13. package/dist/sandbox/kubectl.js +70 -0
  14. package/dist/sandbox/manifests.d.ts +41 -0
  15. package/dist/sandbox/manifests.js +138 -0
  16. package/dist/sandbox/probe.d.ts +44 -0
  17. package/dist/sandbox/probe.js +137 -0
  18. package/dist/stack/commands.d.ts +71 -1
  19. package/dist/stack/commands.js +209 -38
  20. package/dist/stack/compose-file.d.ts +47 -7
  21. package/dist/stack/compose-file.js +223 -18
  22. package/dist/stack/env-file.d.ts +50 -14
  23. package/dist/stack/env-file.js +87 -40
  24. package/dist/stack/host-files.js +11 -1
  25. package/dist/stack/images.d.ts +8 -2
  26. package/dist/stack/images.js +8 -2
  27. package/dist/stack/index.d.ts +9 -0
  28. package/dist/stack/index.js +18 -1
  29. package/dist/stack/keys.d.ts +4 -0
  30. package/dist/stack/keys.js +72 -0
  31. package/dist/stack/mcp.d.ts +8 -0
  32. package/dist/stack/mcp.js +114 -0
  33. package/dist/stack/operator-keys.d.ts +39 -0
  34. package/dist/stack/operator-keys.js +146 -0
  35. package/dist/stack/paths.d.ts +32 -0
  36. package/dist/stack/paths.js +11 -0
  37. package/dist/stack/prepare.d.ts +12 -6
  38. package/dist/stack/prepare.js +24 -6
  39. package/dist/stack/sandbox.d.ts +4 -0
  40. package/dist/stack/sandbox.js +106 -0
  41. package/package.json +5 -5
  42. package/dist/project/derived-key.d.ts +0 -8
  43. package/dist/project/derived-key.js +0 -18
@@ -6,16 +6,15 @@ import { PINNED_IMAGES } from "./images.js";
6
6
  * network and ports. Container, network and volume names are global on the Docker engine, so
7
7
  * every one is `<project>-<role>` (`nylorun-shop-studio`, network `nylorun-shop`, volume
8
8
  * `nylorun-shop-postgres`), labelled `dev.nylorun.tenant: <name>`; they are written here, from
9
- * the project and the Tenant's name. Everything else that varies (ports, the Postgres password,
10
- * UID/GID, the Host root, the images) comes from `docker/.env`.
9
+ * the project and the Tenant's name. Everything else that varies (ports, the Postgres password and the other
10
+ * secrets, UID/GID, the Host root, the images) comes from `docker/.env`.
11
11
  *
12
12
  * The Tenant's state is its Postgres database, executed through Restate, with its
13
13
  * history in s2-lite; the Runtime's /ready checks all three. Postgres initialises the
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`) and the derived
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
@@ -26,8 +25,8 @@ import { PINNED_IMAGES } from "./images.js";
26
25
  * which alone reads model credentials, and the Tool Gate (F4.1), which alone
27
26
  * holds remote MCP connections and their credentials and POSTs every Action
28
27
  * delivery. Every model call, remote MCP call and delivery of the loop crosses it
29
- * (NYLORUN_GATES_URL, with NYLORUN_GATES_TOKEN from `.env`); stdio MCP servers
30
- * still run in the runtime container. The gateway also runs keys (F4.2): the only
28
+ * (NYLORUN_GATES_URL, with NYLORUN_GATES_TOKEN from `.env`, or a run's token from the
29
+ * harness); MCP stdio servers run in the harness container. The gateway also runs keys (F4.2): the only
31
30
  * holder of the vault key, it runs every vault write that touches a secret and signs
32
31
  * every token (NYLORUN_KEYS_URL). The gateway mounts only the Host's Tenant directory
33
32
  * (`tenant/`) and its keys directory (`keys/`, the vault key), read-only: never
@@ -36,8 +35,48 @@ import { PINNED_IMAGES } from "./images.js";
36
35
  * Restate's private key and `.env`. The runtime does not wait for the gateway: while
37
36
  * it is down, model and MCP calls fail, deliveries are retried, vault writes and
38
37
  * token minting answer 503, and the session takes the next message.
38
+ *
39
+ * The Object store (blueprint D35) is RustFS, single node and single drive, pinned by digest,
40
+ * on the `rustfs` volume; it is not published. Its credential is the access key `nylorun` and
41
+ * the secret key NYLORUN_OBJECT_STORE_SECRET_KEY from `.env`, and only the runtime and the
42
+ * gateway receive it (NYLORUN_OBJECT_STORE_*). The runtime creates the bucket at boot and
43
+ * reaches the store through its `s3` BlobStore.
44
+ *
45
+ * With `sandboxes` (after `nylorun sandbox enable`, F7.2) the `sandboxes` service runs the
46
+ * agent-sandbox driver: the only container holding the cluster credentials
47
+ * (`<Host root>/sandboxes`, read-only), not published, reached by the runtime alone with
48
+ * NYLORUN_SANDBOXES_TOKEN. The runtime gets an empty read-only mount over that directory.
49
+ * Sandbox pods reach the stack only on the Docker host's address (NYLORUN_SANDBOX_HOST_ADDRESS),
50
+ * where three ports are published on NYLORUN_SANDBOX_BIND: the runtime's Harness API listener
51
+ * (NYLORUN_SANDBOX_HARNESS_PORT → 4200: pods join and connect with host tokens), the
52
+ * gateway's gates (NYLORUN_SANDBOX_GATES_PORT → 4100: model and MCP calls with run tokens) and
53
+ * egress-gate (NYLORUN_SANDBOX_EGRESS_PORT → 4200 in the gateway, `--service gates,keys,egress`:
54
+ * CONNECT with the egress token, to the hosts the sandbox spec allows). The two HTTP listeners
55
+ * accept that address as a Host.
56
+ *
57
+ * The harness (F6.2, `harness: "remote"`, the default) runs every agent turn, MCP stdio server
58
+ * and workspace in its own container: the runtime image with `--service harness`, connected to
59
+ * core's Harness API (`ws://runtime:4200/nylorun/harness/v1`) with NYLORUN_HARNESS_TOKEN, its
60
+ * only credential, and calling models and remote MCP servers through the gateway with each
61
+ * run's token. It mounts only the Tenant's `sandboxes/`, `plugin-data/`, `home/` and `tmp/`
62
+ * under `/harness`, and the Host root's `plugins/` read-only at its own path (plugin roots name
63
+ * paths on this machine; the runtime mounts it there too). NYLORUN_HARNESS=in-process in `.env`
64
+ * rolls back to turns in the runtime container, without the harness service.
65
+ *
66
+ * Three networks: `store` (internal: no egress) joins Postgres, s2-lite, Restate and RustFS to
67
+ * the runtime and the gateway; `harness` joins the harness to the runtime and the gateway only;
68
+ * `default` carries egress and the published ports (runtime, gateway, Studio, sandboxes).
69
+ * Restate's admin port (its UI, unauthenticated) is published, and Restate joins `default`,
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).
39
74
  */
40
- export function renderComposeFile(project, name) {
75
+ export function renderComposeFile(project, name, options = {}) {
76
+ const sandboxes = options.sandboxes === true;
77
+ const identity = options.identity === true;
78
+ const remote = (options.harness ?? "remote") === "remote";
79
+ const restateUi = options.restateUi === true;
41
80
  return `# Written by \`nylorun start\`; rewritten on every start. Settings live in .env.
42
81
  name: ${project}
43
82
 
@@ -58,6 +97,7 @@ services:
58
97
  POSTGRES_INITDB_ARGS: "--locale=C" # C collation for the whole database
59
98
  volumes:
60
99
  - postgres:/var/lib/postgresql/data
100
+ networks: [store]
61
101
  healthcheck:
62
102
  test: ["CMD-SHELL", "pg_isready -U nylorun -d nylorun"]
63
103
  interval: 2s
@@ -75,9 +115,7 @@ services:
75
115
  volumes:
76
116
  - restate:/restate-data
77
117
  - \${NYLORUN_HOST_ROOT:?run nylorun start}/docker/restate-identity.pem:/run/nylorun/restate-identity.pem:ro
78
- ports:
79
- - "127.0.0.1:\${NYLORUN_RESTATE_PORT:?run nylorun start}:9070" # Restate UI and admin, for debugging
80
- healthcheck:
118
+ ${restateUi ? RESTATE_UI : RESTATE_CLOSED} healthcheck:
81
119
  test: ["CMD", "curl", "-fsS", "http://localhost:9070/health"]
82
120
  interval: 2s
83
121
  timeout: 5s
@@ -94,15 +132,36 @@ services:
94
132
  # (e.g. /data) is root-owned and s2-lite cannot write it.
95
133
  volumes:
96
134
  - s2-lite:/home/nonroot
135
+ networks: [store]
97
136
  # The s2 image has no shell or HTTP client, so it has no health check;
98
137
  # the Runtime's /ready covers it.
99
138
  restart: unless-stopped
100
139
 
101
- gateway: # the Model and Tool Gates and keys: the vault key, credentials, signing, outbound calls; not published
140
+ rustfs: # the Object store (D35); not published, only the runtime and the gateway hold its credential
141
+ image: ${PINNED_IMAGES.rustfs}
142
+ container_name: ${project}-rustfs
143
+ labels: *tenant
144
+ environment:
145
+ RUSTFS_ACCESS_KEY: nylorun
146
+ RUSTFS_SECRET_KEY: \${NYLORUN_OBJECT_STORE_SECRET_KEY:?run nylorun start}
147
+ RUSTFS_CONSOLE_ENABLE: "false" # no web console: clients go through the Runtime
148
+ RUSTFS_OBS_LOG_DIRECTORY: "" # log to stdout (nylorun logs rustfs)
149
+ # The image runs as uid 10001 and owns /data, which Docker copies into the new volume.
150
+ volumes:
151
+ - rustfs:/data
152
+ networks: [store]
153
+ healthcheck:
154
+ test: ["CMD", "curl", "-fsS", "http://localhost:9000/health"]
155
+ interval: 2s
156
+ timeout: 5s
157
+ retries: 30
158
+ restart: unless-stopped
159
+
160
+ gateway: # the Model and Tool Gates and keys: the vault key, credentials, signing, outbound calls; not published${sandboxes ? ", but for sandbox pods" : ""}
102
161
  image: \${NYLORUN_RUNTIME_IMAGE:?run nylorun start}
103
162
  container_name: ${project}-gateway
104
163
  labels: *tenant
105
- command: ["--service", "gates,keys"]
164
+ command: ["--service", "gates,keys${sandboxes ? ",egress" : ""}"]
106
165
  user: "\${NYLORUN_UID:?run nylorun start}:\${NYLORUN_GID:?run nylorun start}"
107
166
  depends_on:
108
167
  postgres: { condition: service_healthy }
@@ -110,17 +169,27 @@ services:
110
169
  NYLORUN_HOME: /nylorun
111
170
  NYLORUN_PACKING: combined
112
171
  NYLORUN_GATES_LISTEN_PORT: "4100"
113
- NYLORUN_GATES_ALLOWED_HOSTS: gateway:4100
172
+ NYLORUN_GATES_ALLOWED_HOSTS: gateway:4100${sandboxes ? SANDBOXES_GATES_HOST : ""}
114
173
  NYLORUN_GATES_TOKEN: \${NYLORUN_GATES_TOKEN:?run nylorun start}
115
174
  NYLORUN_DATABASE_URL: postgres://nylorun:\${NYLORUN_POSTGRES_PASSWORD}@postgres:5432/nylorun
175
+ # The Object store, where model-gate will read file parts (F8.1).
176
+ NYLORUN_OBJECT_STORE_ENDPOINT: http://rustfs:9000
177
+ NYLORUN_OBJECT_STORE_ACCESS_KEY: nylorun
178
+ NYLORUN_OBJECT_STORE_SECRET_KEY: \${NYLORUN_OBJECT_STORE_SECRET_KEY:?run nylorun start}
116
179
  # Action endpoints on this machine: \`localhost\` in a registered URL means the Docker host.
117
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 : ""}
118
185
  extra_hosts:
119
186
  host.docker.internal: host-gateway # model servers, MCP servers and Action endpoints on this machine
120
187
  volumes:
121
188
  # The Tenant's homes and its vault key only, read-only.
122
189
  - \${NYLORUN_HOST_ROOT:?run nylorun start}/tenant:/nylorun/tenant:ro
123
190
  - \${NYLORUN_HOST_ROOT:?run nylorun start}/keys:/nylorun/keys:ro
191
+ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness reaches its gates on \`harness\`.
192
+ networks: [default, store, harness]
124
193
  healthcheck:
125
194
  test: ["CMD", "node", "-e", "fetch('http://localhost:4100/ready').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
126
195
  interval: 2s
@@ -138,12 +207,12 @@ services:
138
207
  postgres: { condition: service_healthy }
139
208
  restate: { condition: service_healthy }
140
209
  s2-lite: { condition: service_started }
210
+ rustfs: { condition: service_healthy }
141
211
  environment:
142
212
  NYLORUN_HOME: /nylorun
143
213
  NYLORUN_PACKING: combined
144
214
  # The Tenant the Runtime creates on its first start (later starts open it).
145
215
  NYLORUN_TENANT_NAME: \${NYLORUN_TENANT_NAME:?run nylorun start}
146
- NYLORUN_DERIVED_PRINCIPALS: \${NYLORUN_DERIVED_PRINCIPALS:-project}
147
216
  # Model calls, remote MCP calls and deliveries go through the gateway, and vault writes
148
217
  # and token signing through its keys service: this container never reads a credential
149
218
  # or the vault key.
@@ -168,8 +237,18 @@ services:
168
237
  NYLORUN_S2_ENDPOINT: http://s2-lite:80
169
238
  NYLORUN_S2_TOKEN: ignored # s2-lite has no access tokens yet
170
239
  NYLORUN_WORKSPACE_STORE_URL: file:///workspaces
240
+ # The Object store, through the BlobStore seam's s3 adapter; the runtime creates the bucket.
241
+ NYLORUN_OBJECT_STORE_ENDPOINT: http://rustfs:9000
242
+ NYLORUN_OBJECT_STORE_ACCESS_KEY: nylorun
243
+ NYLORUN_OBJECT_STORE_SECRET_KEY: \${NYLORUN_OBJECT_STORE_SECRET_KEY:?run nylorun start}
171
244
  # Action endpoints on this machine: \`localhost\` in a registered URL means the Docker host.
172
245
  NYLORUN_ENDPOINT_LOOPBACK: docker-host
246
+ # Agent turns, MCP servers and workspaces run in the harness container, which connects to
247
+ # this listener with NYLORUN_HARNESS_TOKEN. NYLORUN_HARNESS=in-process in .env rolls back.
248
+ NYLORUN_HARNESS: \${NYLORUN_HARNESS:-remote}
249
+ NYLORUN_HARNESS_LISTEN_PORT: "4200"
250
+ NYLORUN_HARNESS_ALLOWED_HOSTS: runtime:4200${sandboxes ? SANDBOXES_HARNESS_HOST : ""}
251
+ NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
173
252
  extra_hosts:
174
253
  host.docker.internal: host-gateway # the Docker host, also on Linux Docker Engine
175
254
  volumes:
@@ -183,11 +262,14 @@ services:
183
262
  - type: tmpfs
184
263
  target: /nylorun/docker
185
264
  read_only: true
186
- tmpfs: { size: 4096, mode: 0755 } # empty, and listable by the runtime user
265
+ tmpfs: { size: 4096, mode: 0755 } # empty, and listable by the runtime user${sandboxes ? SANDBOXES_RUNTIME_MOUNT : ""}
187
266
  - workspaces:/workspaces
267
+ # Plugin roots on this machine, at their own path (as for the harness), for NYLORUN_HARNESS=in-process.
268
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/plugins:\${NYLORUN_HOST_ROOT:?run nylorun start}/plugins:ro
269
+ networks: [default, store, harness]
188
270
  ports:
189
271
  - "127.0.0.1:\${NYLORUN_PORT:?run nylorun start}:4000" # Tenant API, SSE, browsers
190
- - "127.0.0.1:\${NYLORUN_ADMIN_PORT:?run nylorun start}:4001" # Admin API (operators only)
272
+ - "127.0.0.1:\${NYLORUN_ADMIN_PORT:?run nylorun start}:4001" # Admin API (operators only)${sandboxes ? SANDBOXES_RUNTIME_PORT : ""}
191
273
  healthcheck: # liveness: a Tenant that cannot open is reported by nylorun start from the Admin status, not by a 300 s wait
192
274
  test: ["CMD", "node", "-e", "fetch('http://localhost:4000/health').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
193
275
  interval: 2s
@@ -211,12 +293,16 @@ services:
211
293
  # Browsers share cookies across ports of one host: a cookie per Tenant keeps the
212
294
  # sessions of two Studios on localhost apart.
213
295
  NYLORUN_STUDIO_SESSION_COOKIE: nylorun_studio_${name}
214
- # Exact origins that may frame Studio (Babai Desktop); kept in .env.
296
+ # Exact origins that may frame Studio (none by default: embedding is opt-in); kept in .env.
215
297
  NYLORUN_STUDIO_FRAME_ANCESTORS: \${NYLORUN_STUDIO_FRAME_ANCESTORS:-}
216
298
  # Studio's anonymous usage analytics; empty when telemetry is off.
217
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:-}
218
303
  volumes:
219
304
  - \${NYLORUN_HOST_ROOT}/host-credentials.json:/run/nylorun/host-credentials.json:ro
305
+ networks: [default]
220
306
  ports:
221
307
  - "127.0.0.1:\${NYLORUN_STUDIO_PORT:?run nylorun start}:3000" # opened as http://localhost:<port>
222
308
  healthcheck: # /healthz needs no session; Host studio:3000 is accepted
@@ -225,16 +311,135 @@ services:
225
311
  timeout: 5s
226
312
  retries: 30
227
313
  restart: unless-stopped
228
-
314
+ ${remote ? harnessService(project) : ""}${sandboxes ? sandboxesService(project) : ""}
229
315
  networks:
230
316
  default:
231
317
  name: ${project}
232
318
  labels: *tenant
319
+ store: # the stores: no egress, no published port
320
+ name: ${project}-store
321
+ internal: true
322
+ labels: *tenant
323
+ harness: # the harness reaches the runtime's Harness API and the gateway only (egress for MCP servers and bash)
324
+ name: ${project}-harness
325
+ labels: *tenant
233
326
 
234
327
  volumes:
235
328
  postgres: { name: ${project}-postgres, labels: *tenant }
236
329
  restate: { name: ${project}-restate, labels: *tenant }
237
330
  s2-lite: { name: ${project}-s2-lite, labels: *tenant }
331
+ rustfs: { name: ${project}-rustfs, labels: *tenant }
238
332
  workspaces: { name: ${project}-workspaces, labels: *tenant }
239
333
  `;
240
334
  }
335
+ /** Restate on the stores' network only: its admin port (the UI) is not published. */
336
+ const RESTATE_CLOSED = ` networks: [store] # admin and UI not published: nylorun start --restate-ui
337
+ `;
338
+ /** Restate's admin port and UI on loopback (`--restate-ui`): it must join `default` to be published. */
339
+ const RESTATE_UI = ` networks: [store, default]
340
+ ports:
341
+ - "127.0.0.1:\${NYLORUN_RESTATE_PORT:?run nylorun start}:9070" # Restate UI and admin (unauthenticated): nylorun start --restate-ui
342
+ `;
343
+ /**
344
+ * The harness service (F6.2): agent turns, MCP stdio servers and workspaces, apart from core.
345
+ * It holds the harness token only; models and remote MCP servers are reached through the
346
+ * gateway with each run's token. Healthy once connected to core's Harness API.
347
+ */
348
+ function harnessService(project) {
349
+ return `
350
+ harness: # agent turns, MCP servers and workspaces; holds only the harness token; not published
351
+ image: \${NYLORUN_RUNTIME_IMAGE:?run nylorun start}
352
+ container_name: ${project}-harness
353
+ labels: *tenant
354
+ command: ["--service", "harness"]
355
+ user: "\${NYLORUN_UID:?run nylorun start}:\${NYLORUN_GID:?run nylorun start}"
356
+ depends_on:
357
+ runtime: { condition: service_healthy }
358
+ gateway: { condition: service_healthy }
359
+ environment:
360
+ NYLORUN_HARNESS_URL: ws://runtime:4200/nylorun/harness/v1
361
+ NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}
362
+ NYLORUN_GATES_URL: http://gateway:4100
363
+ NYLORUN_HARNESS_ROOT: /harness
364
+ volumes:
365
+ # The Tenant's workspaces, plugin data and the homes of MCP stdio servers; nothing else.
366
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/tenant/sandboxes:/harness/sandboxes
367
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/tenant/plugin-data:/harness/plugin-data
368
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/tenant/home:/harness/home
369
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/tenant/tmp:/harness/tmp
370
+ # Plugin roots on this machine, read-only, at their own path: agents name them so.
371
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/plugins:\${NYLORUN_HOST_ROOT:?run nylorun start}/plugins:ro
372
+ networks: [harness]
373
+ healthcheck: # healthy once connected to core's Harness API
374
+ test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:4300/health').then(r=>r.json()).then(b=>process.exit(b.connected?0:1),()=>process.exit(1))"]
375
+ interval: 2s
376
+ timeout: 5s
377
+ retries: 90
378
+ restart: unless-stopped
379
+ `;
380
+ }
381
+ /**
382
+ * The runtime reaches the sandboxes service on the Compose network with its token, and serves
383
+ * the Harness API to sandbox pods (F7.2) on the Docker host's address.
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`;
389
+ const SANDBOXES_RUNTIME_ENV = `
390
+ # The sandboxes service (nylorun sandbox enable): pods on the Tenant's cluster.
391
+ NYLORUN_SANDBOXES_URL: http://sandboxes:4300
392
+ NYLORUN_SANDBOXES_TOKEN: \${NYLORUN_SANDBOXES_TOKEN:?run nylorun sandbox enable}
393
+ NYLORUN_SANDBOX_HARNESS_IMAGE: \${NYLORUN_RUNTIME_IMAGE:?run nylorun start} # pods copy the engine from it`;
394
+ /** Pods join and connect to the Harness API at the Docker host's address. */
395
+ const SANDBOXES_HARNESS_HOST = ",\${NYLORUN_SANDBOX_HOST_ADDRESS:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_HARNESS_PORT:?run nylorun sandbox enable}";
396
+ /** The Harness API, published for sandbox pods. */
397
+ const SANDBOXES_RUNTIME_PORT = `
398
+ - "\${NYLORUN_SANDBOX_BIND:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_HARNESS_PORT:?run nylorun sandbox enable}:4200" # Harness API, for sandbox pods`;
399
+ /** The gates accept the pods' Host; their calls carry run tokens. */
400
+ const SANDBOXES_GATES_HOST = ",\${NYLORUN_SANDBOX_HOST_ADDRESS:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun sandbox enable}";
401
+ /** The gates and egress-gate, published for sandbox pods. */
402
+ const SANDBOXES_GATES_PORT = ` ports:
403
+ - "\${NYLORUN_SANDBOX_BIND:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun sandbox enable}:4100" # gates, for sandbox pods (run tokens)
404
+ - "\${NYLORUN_SANDBOX_BIND:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_EGRESS_PORT:?run nylorun sandbox enable}:4200" # egress-gate, for sandbox pods (egress tokens)
405
+ `;
406
+ /** egress-gate (F7.2): pods' CONNECT proxy in the gateway. */
407
+ const SANDBOXES_GATEWAY_ENV = `
408
+ # egress-gate: the only way out of a sandbox pod, to the hosts its spec allows.
409
+ NYLORUN_EGRESS_LISTEN_PORT: "4200"`;
410
+ /** Empty and read-only over the cluster credentials: only the sandboxes service reads them. */
411
+ const SANDBOXES_RUNTIME_MOUNT = `
412
+ - type: tmpfs
413
+ target: /nylorun/sandboxes
414
+ read_only: true
415
+ tmpfs: { size: 4096, mode: 0755 } # empty, and listable by the runtime user`;
416
+ /**
417
+ * The sandboxes service. The pod-facing ports `nylorun sandbox enable` records
418
+ * (NYLORUN_SANDBOX_*_PORT in .env), the Harness API's, the gates' and egress-gate's, are
419
+ * published by the runtime and the gateway (above).
420
+ */
421
+ function sandboxesService(project) {
422
+ return `
423
+ sandboxes: # agent-sandbox driver; the only holder of the cluster credentials; not published
424
+ image: \${NYLORUN_SANDBOXES_IMAGE:?run nylorun sandbox enable}
425
+ container_name: ${project}-sandboxes
426
+ labels: *tenant
427
+ user: "\${NYLORUN_UID:?run nylorun start}:\${NYLORUN_GID:?run nylorun start}" # reads the 0600 token
428
+ environment:
429
+ NYLORUN_SANDBOXES_TOKEN: \${NYLORUN_SANDBOXES_TOKEN:?run nylorun sandbox enable}
430
+ NYLORUN_SANDBOXES_DIR: /run/nylorun/sandboxes
431
+ NYLORUN_SANDBOXES_LISTEN: ":4300"
432
+ extra_hosts:
433
+ host.docker.internal: host-gateway # the API server when the kubeconfig names this machine's loopback
434
+ volumes:
435
+ # cluster.json (API server, CA, namespace) and the ServiceAccount token, read-only.
436
+ - \${NYLORUN_HOST_ROOT:?run nylorun start}/sandboxes:/run/nylorun/sandboxes:ro
437
+ networks: [default] # the cluster's API server, through host.docker.internal
438
+ healthcheck: # /ready: informers synced and the API server answers
439
+ test: ["CMD", "/sandboxes", "healthcheck"]
440
+ interval: 5s
441
+ timeout: 5s
442
+ retries: 24
443
+ restart: unless-stopped
444
+ `;
445
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Origins that may embed Studio by default: Babai Desktop's `nylorun` scheme on
3
- * macOS and Linux, and its WebView2 origin on Windows (Studio §8.9).
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 ["nylorun://localhost", "http://nylorun.localhost"];
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`.
@@ -14,11 +14,32 @@ export interface StackEnv {
14
14
  adminPort: number;
15
15
  /** Published Studio port (loopback). */
16
16
  studioPort: number;
17
- /** Published Restate UI and admin port (loopback). */
17
+ /** Restate UI and admin port (loopback), published only with `restateUi`. */
18
18
  restatePort: number;
19
+ /**
20
+ * `NYLORUN_RESTATE_UI`: `1` publishes Restate's UI and admin port (unauthenticated) for this
21
+ * start (`nylorun start --restate-ui`); `0` keeps it closed. Decided again on every start.
22
+ */
23
+ restateUi: boolean;
19
24
  postgresPassword: string;
20
25
  /** The token the runtime presents to the gateway (`NYLORUN_GATES_TOKEN`), 32 bytes as hex. */
21
26
  gatesToken: string;
27
+ /**
28
+ * The harness's credential for core's Harness API (`NYLORUN_HARNESS_TOKEN`), 32 bytes as hex:
29
+ * given only to the runtime (which checks it) and the harness.
30
+ */
31
+ harnessToken: string;
32
+ /**
33
+ * `NYLORUN_HARNESS`: `remote` (the harness container runs agent turns, MCP servers and
34
+ * workspaces) or `in-process` (the runtime does, without a harness container). Kept across
35
+ * starts; edit `.env` to roll back.
36
+ */
37
+ harness: HarnessMode;
38
+ /**
39
+ * RustFS's secret key (`NYLORUN_OBJECT_STORE_SECRET_KEY`), 32 bytes as hex: the Object store's
40
+ * credential, given only to the runtime and the gateway.
41
+ */
42
+ objectStoreSecretKey: string;
22
43
  /** `publickeyv1_...` of `docker/restate-identity.pem`. */
23
44
  restateIdentityKey: string;
24
45
  uid: number;
@@ -36,18 +57,28 @@ export interface StackEnv {
36
57
  studioAnalyticsId: string;
37
58
  /** The Tenant's name, which the Runtime gives the Tenant it creates. */
38
59
  tenantName: string;
39
- /**
40
- * Derived principals the Runtime registers on its Tenant, comma-separated (persists);
41
- * always includes `project`, the Project link's principal.
42
- */
43
- derivedPrincipals: string;
60
+ /** Present once `nylorun sandbox enable` ran for the Tenant (F7.2). */
61
+ sandboxes?: SandboxStackEnv;
44
62
  }
45
- export declare function renderEnvFile(env: StackEnv): string;
46
63
  /**
47
- * `NYLORUN_DERIVED_PRINCIPALS`: comma-separated principal ids, as the Runtime accepts them
48
- * (not `studio`), with `project` first so the Project link's key always works.
64
+ * The sandboxes service's settings (`nylorun sandbox enable`): its bearer token (only the
65
+ * runtime presents it), its image, and the host ports pods reach (Harness API, gates,
66
+ * egress) on the host address, published on the bind address. The ports persist.
49
67
  */
50
- export declare function parseDerivedPrincipals(raw: string, source: string): string[];
68
+ export interface SandboxStackEnv {
69
+ /** NYLORUN_SANDBOXES_TOKEN, 32 bytes as hex. */
70
+ token: string;
71
+ image: string;
72
+ harnessPort: number;
73
+ gatesPort: number;
74
+ egressPort: number;
75
+ /** The Docker host as pods reach it (192.168.65.254 on Docker Desktop). */
76
+ hostAddress: string;
77
+ /** Where the pod-facing ports are published (127.0.0.1 on Docker Desktop). */
78
+ bind: string;
79
+ }
80
+ export type HarnessMode = "remote" | "in-process";
81
+ export declare function renderEnvFile(env: StackEnv): string;
51
82
  /** Parse KEY=value lines; comments and blank lines are skipped. */
52
83
  export declare function parseEnvLines(text: string): Map<string, string>;
53
84
  /** The settings that persist across starts, from an existing .env. */
@@ -56,10 +87,15 @@ export interface PersistedStackEnv {
56
87
  adminPort?: number;
57
88
  studioPort?: number;
58
89
  restatePort?: number;
90
+ /** Whether the last start published Restate's UI (`NYLORUN_RESTATE_UI=1`). */
91
+ restateUi?: boolean;
59
92
  postgresPassword?: string;
60
93
  gatesToken?: string;
94
+ harnessToken?: string;
95
+ harness?: HarnessMode;
96
+ objectStoreSecretKey?: string;
61
97
  /** Validated origins; absent when the line is missing (an older .env). */
62
98
  studioFrameAncestors?: string[];
63
- derivedPrincipals?: string[];
99
+ sandboxes?: SandboxStackEnv;
64
100
  }
65
101
  export declare function parsePersisted(text: string): PersistedStackEnv;