nylorun 0.28.0-beta → 0.29.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 +23 -0
- package/README.md +27 -1
- package/dist/client/doctor.d.ts +9 -0
- package/dist/client/doctor.js +12 -0
- package/dist/sandbox/cluster-file.d.ts +17 -0
- package/dist/sandbox/commands.d.ts +27 -1
- package/dist/sandbox/commands.js +167 -9
- package/dist/sandbox/substrate.d.ts +25 -0
- package/dist/sandbox/substrate.js +115 -0
- package/dist/stack/compose-file.d.ts +2 -0
- package/dist/stack/compose-file.js +13 -4
- package/dist/stack/env-file.d.ts +6 -0
- package/dist/stack/env-file.js +15 -1
- package/dist/stack/prepare.js +1 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# nylorun
|
|
2
2
|
|
|
3
|
+
## 0.29.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f87ba81: Pods on Agent Substrate (preview, ADR-138 Phase 1a). `nylorun local sandbox enable --context <name> --backend substrate` runs the Tenant's sandboxes as Agent Substrate v1.0 actors instead of agent-sandbox Sandboxes, on a Kubernetes 1.37 cluster with Substrate installed (Docker Desktop's kind cluster, or Substrate's own kind script). It creates the Tenant's namespace and the sandboxes service's RBAC, makes the Tenant's atespace with the service as its editor, gives actors a name for the edge in CoreDNS, and publishes the edge's two ports from the `sandboxes` service: a TLS front, in that container, through which actors reach the Harness API and the gates. The default backend stays agent-sandbox, and nothing changes for a Tenant that does not pass `--backend substrate`.
|
|
8
|
+
|
|
9
|
+
The sandboxes service drives Substrate's API: a template per pod incarnation, pinned by digest; an egress policy from the Agent's network (its preset, hosts and suffixes, which the core now sends with each pod); suspend and resume for `mode`; and the same `/v1/pods` contract, so the core is unchanged. Not yet on Substrate: keys injected at egress, builds and tags; those come in Phase 1b.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0c6634b: `GET /v1/tenant/sandbox`'s `cluster` names its `backend` (`agent-sandbox` or `substrate`) and, on Agent Substrate, its `atespace`. `nylorun doctor sandbox` prints the Tenant's pods line (backend, context, namespace, ready), and Studio's Sandbox section says Agent Substrate with the atespace.
|
|
14
|
+
- Pin core to the tested release.
|
|
15
|
+
- Pin agents to the tested release.
|
|
16
|
+
- Pin admin to the tested release.
|
|
17
|
+
- Pin runtime to the tested release.
|
|
18
|
+
- Pin studio to the tested release.
|
|
19
|
+
- Updated dependencies [0c6634b]
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- Updated dependencies
|
|
22
|
+
- @nylorun/core@0.39.0-beta
|
|
23
|
+
- @nylorun/agents@0.36.2-beta
|
|
24
|
+
- @nylorun/admin@0.19.15-beta
|
|
25
|
+
|
|
3
26
|
## 0.28.0-beta
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ nylorun local ls [--json] # the Tenants on this machine, with their sta
|
|
|
119
119
|
nylorun local delete <tenant> --yes # remove a Tenant: containers, volumes, Host root and vault key
|
|
120
120
|
nylorun local logs [service] [--tenant <name>] [-f] [--tail <n>] # postgres, restate, s2-lite, rustfs, gateway, runtime, harness, studio, sandboxes
|
|
121
121
|
nylorun local reset [--tenant <name>] [--yes] # delete the Tenant's volumes, Tenant directory and vault key; the next start creates it anew
|
|
122
|
-
nylorun local sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]
|
|
122
|
+
nylorun local sandbox enable --context <name> [--tenant <name>] [--backend agent-sandbox|substrate] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]
|
|
123
123
|
nylorun local sandbox disable [--tenant <name>] [--delete-namespace]
|
|
124
124
|
nylorun local sandbox status [--tenant <name>] [--json] # pods on a Kubernetes context (see Sandboxes)
|
|
125
125
|
nylorun local telemetry [status|enable|disable] # Studio's anonymous usage analytics
|
|
@@ -446,6 +446,32 @@ then run `enable` again.
|
|
|
446
446
|
the namespace and every sandbox in it). Sessions run in a pod when their
|
|
447
447
|
Agent's manifest has `runtime.kind` `pod`.
|
|
448
448
|
|
|
449
|
+
### Pods on Agent Substrate (preview)
|
|
450
|
+
|
|
451
|
+
`nylorun local sandbox enable --context <name> --backend substrate` runs the Tenant's pods as
|
|
452
|
+
[Agent Substrate](https://github.com/agent-substrate/substrate) v1.0 actors instead
|
|
453
|
+
([ADR-138](https://github.com/nylorun/factory/blob/main/decisions/engine/2026-11-09-substrate-replaces-agent-sandbox.md),
|
|
454
|
+
Phase 1). Actors suspend to a snapshot of their memory and resume where they stopped, which suits
|
|
455
|
+
sessions that are short and mostly asleep. The cluster needs Kubernetes 1.37 with Substrate v1.0
|
|
456
|
+
installed (its `ate-system` namespace and `api` Service): a kind cluster on Docker Desktop, made
|
|
457
|
+
with Substrate's own kind script, works. `enable` refuses a context without Substrate.
|
|
458
|
+
|
|
459
|
+
It only touches the context you name. It creates the namespace `nylorun-sbx-<tenant>` and a
|
|
460
|
+
ServiceAccount allowed only what the sandboxes service uses (its state ConfigMaps, a token for
|
|
461
|
+
itself, a port-forward to Substrate's API and Substrate's trust bundles), and adds a `hosts` entry
|
|
462
|
+
to CoreDNS so that actors resolve the Tenant's edge, `nylorun-sbx-<tenant>.edge.nylorun.internal`,
|
|
463
|
+
to the Docker host. The edge is a TLS front in the `sandboxes` container, published on two host
|
|
464
|
+
ports (8793 and 8794 by default), through which actors reach the Harness API and the gates; its
|
|
465
|
+
certificate is made for that name and its CA is given to each actor. Then, as one of Substrate's
|
|
466
|
+
owners (a 10-minute token for `ate-system/ate-client`, deleted after use), it makes the Tenant's
|
|
467
|
+
atespace with the sandboxes service as its editor.
|
|
468
|
+
|
|
469
|
+
Each Agent's network (preset, hosts, suffixes) becomes its actors' egress policy: only the edge
|
|
470
|
+
and the allowed hosts resolve and connect. Keys injected at egress, builds and tags are not on
|
|
471
|
+
Substrate yet. `nylorun local sandbox status` names the backend, the atespace and the edge.
|
|
472
|
+
`disable` works as for agent-sandbox; the atespace stays until you delete it with Substrate's
|
|
473
|
+
tools.
|
|
474
|
+
|
|
449
475
|
## Host root
|
|
450
476
|
|
|
451
477
|
The **Host root** is `~/.nylorun/tenants/<name>/`, or `NYLORUN_HOME`. It is
|
package/dist/client/doctor.d.ts
CHANGED
|
@@ -20,6 +20,15 @@ export type SandboxReport = {
|
|
|
20
20
|
environments?: number;
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
+
/** The Tenant's cluster for pod Agents (`nylorun local sandbox enable`), when it has one. */
|
|
24
|
+
cluster?: {
|
|
25
|
+
namespace: string;
|
|
26
|
+
context: string;
|
|
27
|
+
ready: boolean;
|
|
28
|
+
backend?: "agent-sandbox" | "substrate";
|
|
29
|
+
atespace?: string;
|
|
30
|
+
controllerVersion?: string;
|
|
31
|
+
} | null;
|
|
23
32
|
};
|
|
24
33
|
/**
|
|
25
34
|
* `nylorun doctor sandbox`: the Tenant's sandbox report (`GET /v1/tenant/sandbox`, Management API)
|
package/dist/client/doctor.js
CHANGED
|
@@ -59,6 +59,18 @@ export async function doctorSandbox(deps, options) {
|
|
|
59
59
|
.filter((item) => item !== undefined)
|
|
60
60
|
.join(", ") || "-",
|
|
61
61
|
]);
|
|
62
|
+
const cluster = report.cluster;
|
|
63
|
+
if (cluster)
|
|
64
|
+
rows.push([
|
|
65
|
+
"pods",
|
|
66
|
+
[
|
|
67
|
+
cluster.backend === "substrate"
|
|
68
|
+
? `Agent Substrate (preview), atespace ${cluster.atespace ?? "?"}`
|
|
69
|
+
: `agent-sandbox${cluster.controllerVersion ? ` ${cluster.controllerVersion}` : ""}`,
|
|
70
|
+
`on ${cluster.context}, namespace ${cluster.namespace}`,
|
|
71
|
+
cluster.ready ? "ready" : "not ready",
|
|
72
|
+
].join(", "),
|
|
73
|
+
]);
|
|
62
74
|
rows.push([
|
|
63
75
|
"selected",
|
|
64
76
|
report.backend
|
|
@@ -28,6 +28,23 @@ export interface ClusterFile {
|
|
|
28
28
|
probedAt: string;
|
|
29
29
|
};
|
|
30
30
|
enabledAt: string;
|
|
31
|
+
/** agent-sandbox when absent; substrate for Agent Substrate (ADR-138, preview). */
|
|
32
|
+
backend?: "agent-sandbox" | "substrate";
|
|
33
|
+
/** Agent Substrate: where the Tenant's actors live and the edge actors reach the Runtime by. */
|
|
34
|
+
substrate?: {
|
|
35
|
+
atespace: string;
|
|
36
|
+
systemNamespace: string;
|
|
37
|
+
apiService: string;
|
|
38
|
+
serviceAccount: string;
|
|
39
|
+
endpoint?: string;
|
|
40
|
+
version?: string;
|
|
41
|
+
edge: {
|
|
42
|
+
host: string;
|
|
43
|
+
harnessPort: number;
|
|
44
|
+
gatesPort: number;
|
|
45
|
+
};
|
|
46
|
+
workerSelector?: Record<string, string>;
|
|
47
|
+
};
|
|
31
48
|
}
|
|
32
49
|
export declare function sandboxesDir(root: string): string;
|
|
33
50
|
export declare function clusterFilePath(root: string): string;
|
|
@@ -3,7 +3,7 @@ import type { FetchLike } from "../stack/studio-login.js";
|
|
|
3
3
|
import { type ClusterFile } from "./cluster-file.js";
|
|
4
4
|
import { type Kube, type KubectlRunner } from "./kubectl.js";
|
|
5
5
|
import { type ProbePorts } from "./probe.js";
|
|
6
|
-
export declare const sandboxUsage = " sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]\n run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox v1.0.10)\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
|
+
export declare const sandboxUsage = " sandbox enable --context <name> [--tenant <name>] [--backend agent-sandbox|substrate] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]\n run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox v1.0.10),\n or as Agent Substrate actors (--backend substrate, preview: Kubernetes 1.37 with Substrate v1.0 installed)\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";
|
|
7
7
|
/** The default workload image (D36), pre-pulled with the Runtime image. */
|
|
8
8
|
export declare const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
9
9
|
export interface SandboxDeps {
|
|
@@ -21,6 +21,8 @@ export interface SandboxDeps {
|
|
|
21
21
|
* (seen on GKE right after the apply).
|
|
22
22
|
*/
|
|
23
23
|
export declare function waitForCrd(k: Kube, pollMs: number): Promise<void>;
|
|
24
|
+
/** The ServiceAccount token the token controller writes into the Secret. */
|
|
25
|
+
export declare function serviceAccountToken(k: Kube, namespace: string, pollMs: number): Promise<string>;
|
|
24
26
|
export interface InstallResult {
|
|
25
27
|
cluster: ClusterFile;
|
|
26
28
|
/** The ServiceAccount token. */
|
|
@@ -45,5 +47,29 @@ export declare function installCluster(deps: SandboxDeps, input: {
|
|
|
45
47
|
egress: number;
|
|
46
48
|
};
|
|
47
49
|
}): Promise<InstallResult>;
|
|
50
|
+
/**
|
|
51
|
+
* `enable --backend substrate` against one context (ADR-138, preview): checks Substrate is
|
|
52
|
+
* installed, makes the Tenant's namespace and RBAC, finds the Docker host's address as pods see
|
|
53
|
+
* it, and names the edge for actors in CoreDNS. The atespace is made later, by the sandboxes
|
|
54
|
+
* image's substrate-setup, once cluster.json is written.
|
|
55
|
+
*/
|
|
56
|
+
export declare function installSubstrate(deps: SandboxDeps, input: {
|
|
57
|
+
context: string;
|
|
58
|
+
tenant: string;
|
|
59
|
+
hostAddress?: string;
|
|
60
|
+
bindAddress?: string;
|
|
61
|
+
ports: {
|
|
62
|
+
harness: number;
|
|
63
|
+
gates: number;
|
|
64
|
+
egress: number;
|
|
65
|
+
};
|
|
66
|
+
edgePorts: {
|
|
67
|
+
harness: number;
|
|
68
|
+
gates: number;
|
|
69
|
+
};
|
|
70
|
+
}): Promise<{
|
|
71
|
+
cluster: ClusterFile;
|
|
72
|
+
token: string;
|
|
73
|
+
}>;
|
|
48
74
|
/** `nylorun local sandbox <enable|disable|status>`. */
|
|
49
75
|
export declare function runSandboxCommand(args: readonly string[], deps: SandboxDeps): Promise<number>;
|
package/dist/sandbox/commands.js
CHANGED
|
@@ -9,15 +9,21 @@ import { readStackEnv } from "../stack/prepare.js";
|
|
|
9
9
|
import { apiRoute, readClusterFile, removeClusterFiles, writeClusterFiles, } from "./cluster-file.js";
|
|
10
10
|
import { contexts, kube } from "./kubectl.js";
|
|
11
11
|
import { CONTROLLER_DEPLOYMENT, CONTROLLER_NAMESPACE, CONTROLLER_SHA256, CONTROLLER_URL, CONTROLLER_VERSION, networkPolicy, SANDBOX_API_VERSION, SANDBOX_CRD, tenantLabel, tenantManifests, tenantNamespace, TOKEN_SECRET, } from "./manifests.js";
|
|
12
|
-
import { assertIPv4, parseProbePorts, runProbe } from "./probe.js";
|
|
12
|
+
import { assertIPv4, defaultBind, parseNslookup, parseProbePorts, runProbe } from "./probe.js";
|
|
13
|
+
import { corefileWithEdge, edgeHost, PREFERRED_EDGE_PORTS, SUBSTRATE_API_SERVICE, SUBSTRATE_AUDIENCE, SUBSTRATE_NAMESPACE, SUBSTRATE_OWNER_ACCOUNT, substrateManifests, } from "./substrate.js";
|
|
14
|
+
import { PROBE_IMAGE, SERVICE_ACCOUNT } from "./manifests.js";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { rm, writeFile } from "node:fs/promises";
|
|
17
|
+
import { sandboxesDir } from "./cluster-file.js";
|
|
13
18
|
import { cliTuning } from "../tuning.js";
|
|
14
|
-
export const sandboxUsage = ` sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]
|
|
15
|
-
run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox ${CONTROLLER_VERSION})
|
|
19
|
+
export const sandboxUsage = ` sandbox enable --context <name> [--tenant <name>] [--backend agent-sandbox|substrate] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]
|
|
20
|
+
run the Tenant's sandboxes as pods on that kubeconfig context (agent-sandbox ${CONTROLLER_VERSION}),
|
|
21
|
+
or as Agent Substrate actors (--backend substrate, preview: Kubernetes 1.37 with Substrate v1.0 installed)
|
|
16
22
|
sandbox disable [--tenant <name>] [--delete-namespace]
|
|
17
23
|
stop the sandboxes service and forget the cluster
|
|
18
24
|
sandbox status [--tenant <name>] [--json]
|
|
19
25
|
the cluster, the sandboxes service's readiness and what it reports`;
|
|
20
|
-
const ENABLE_USAGE = "nylorun local sandbox enable --context <name> [--tenant <name>] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]";
|
|
26
|
+
const ENABLE_USAGE = "nylorun local sandbox enable --context <name> [--tenant <name>] [--backend agent-sandbox|substrate] [--host-address <ip>] [--bind-address <ip>] [--probe-ports <allowed>,<denied>] [--no-pull]";
|
|
21
27
|
/** The default workload image (D36), pre-pulled with the Runtime image. */
|
|
22
28
|
export const DEFAULT_SANDBOX_IMAGE = "python:3.13-slim";
|
|
23
29
|
/** Preferred host ports for the Harness API, gates and egress (pods reach them). */
|
|
@@ -82,7 +88,7 @@ export async function waitForCrd(k, pollMs) {
|
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
/** The ServiceAccount token the token controller writes into the Secret. */
|
|
85
|
-
async function serviceAccountToken(k, namespace, pollMs) {
|
|
91
|
+
export async function serviceAccountToken(k, namespace, pollMs) {
|
|
86
92
|
const deadline = Date.now() + cliTuning().sandbox.tokenWaitMs;
|
|
87
93
|
for (;;) {
|
|
88
94
|
const secret = await k.getJson(["secret", TOKEN_SECRET, "-n", namespace]);
|
|
@@ -153,14 +159,122 @@ export async function installCluster(deps, input) {
|
|
|
153
159
|
notPulled: probe.notPulled,
|
|
154
160
|
};
|
|
155
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* `enable --backend substrate` against one context (ADR-138, preview): checks Substrate is
|
|
164
|
+
* installed, makes the Tenant's namespace and RBAC, finds the Docker host's address as pods see
|
|
165
|
+
* it, and names the edge for actors in CoreDNS. The atespace is made later, by the sandboxes
|
|
166
|
+
* image's substrate-setup, once cluster.json is written.
|
|
167
|
+
*/
|
|
168
|
+
export async function installSubstrate(deps, input) {
|
|
169
|
+
const k = kube(deps.kubectl, input.context);
|
|
170
|
+
const pollMs = deps.pollMs ?? cliTuning().sandbox.pollMs;
|
|
171
|
+
const { server, caData } = await readKubeconfig(k);
|
|
172
|
+
let route;
|
|
173
|
+
try {
|
|
174
|
+
route = apiRoute(server);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new CliError(`Context ${input.context}: ${error instanceof Error ? error.message : String(error)}`, 6);
|
|
178
|
+
}
|
|
179
|
+
const service = await k.getJson([
|
|
180
|
+
"service", SUBSTRATE_API_SERVICE, "-n", SUBSTRATE_NAMESPACE,
|
|
181
|
+
]);
|
|
182
|
+
if (!service)
|
|
183
|
+
throw new CliError(`Refusing context ${input.context}: Agent Substrate is not installed (no ${SUBSTRATE_NAMESPACE}/${SUBSTRATE_API_SERVICE} Service). Install Substrate v1.0 first; see "Pods on Agent Substrate" in the CLI README.`, 6);
|
|
184
|
+
const namespace = tenantNamespace(input.tenant);
|
|
185
|
+
const existing = await k.getJson(["namespace", namespace]);
|
|
186
|
+
const owner = existing?.metadata?.labels?.["dev.nylorun.tenant"];
|
|
187
|
+
if (existing && owner !== tenantLabel(input.tenant))
|
|
188
|
+
throw new CliError(`Namespace ${namespace} exists in ${input.context} but belongs to ${owner ?? "something else"}.`, 6);
|
|
189
|
+
await k.apply("Creating the Tenant's namespace and RBAC", [...tenantManifests(namespace, input.tenant), ...substrateManifests(namespace)]);
|
|
190
|
+
const token = await serviceAccountToken(k, namespace, pollMs);
|
|
191
|
+
let hostAddress = input.hostAddress;
|
|
192
|
+
if (!hostAddress) {
|
|
193
|
+
const lookup = await k.run(["run", "nylorun-host-lookup", "-n", namespace, "--rm", "-i", "--quiet", "--restart=Never", `--image=${PROBE_IMAGE}`, "--", "nslookup", "host.docker.internal"], { timeoutMs: cliTuning().sandbox.probeExecMs * 3 });
|
|
194
|
+
hostAddress = parseNslookup(lookup.stdout);
|
|
195
|
+
if (!hostAddress)
|
|
196
|
+
throw new CliError(`Refusing context ${input.context}: pods cannot resolve host.docker.internal. Pass --host-address with the Docker host's address as pods reach it (172.17.0.1 for kind on Linux).`, 6);
|
|
197
|
+
}
|
|
198
|
+
const bindAddress = input.bindAddress ?? defaultBind(input.context, hostAddress);
|
|
199
|
+
const host = edgeHost(namespace);
|
|
200
|
+
const coredns = await k.getJson(["configmap", "coredns", "-n", "kube-system"]);
|
|
201
|
+
const corefile = coredns?.data?.Corefile;
|
|
202
|
+
const patched = corefile === undefined ? undefined : corefileWithEdge(corefile, host, hostAddress);
|
|
203
|
+
if (patched === undefined)
|
|
204
|
+
deps.stack.err(`Warning: could not add ${host} -> ${hostAddress} to CoreDNS in ${input.context}; actors resolve the edge only once that name resolves (add it to the cluster's DNS).`);
|
|
205
|
+
else if (patched !== corefile)
|
|
206
|
+
await k.check("Naming the edge in CoreDNS", ["patch", "configmap", "coredns", "-n", "kube-system", "--type", "merge", "-p", JSON.stringify({ data: { Corefile: patched } })]);
|
|
207
|
+
return {
|
|
208
|
+
cluster: {
|
|
209
|
+
context: input.context,
|
|
210
|
+
server,
|
|
211
|
+
dial: route.dial,
|
|
212
|
+
tlsServerName: route.tlsServerName,
|
|
213
|
+
caData,
|
|
214
|
+
namespace,
|
|
215
|
+
controllerVersion: "",
|
|
216
|
+
hostAddress,
|
|
217
|
+
bindAddress,
|
|
218
|
+
ports: input.ports,
|
|
219
|
+
networkPolicy: { enforced: false, probedAt: "" },
|
|
220
|
+
enabledAt: new Date().toISOString(),
|
|
221
|
+
backend: "substrate",
|
|
222
|
+
substrate: {
|
|
223
|
+
atespace: namespace,
|
|
224
|
+
systemNamespace: SUBSTRATE_NAMESPACE,
|
|
225
|
+
apiService: SUBSTRATE_API_SERVICE,
|
|
226
|
+
serviceAccount: SERVICE_ACCOUNT,
|
|
227
|
+
edge: { host, harnessPort: input.edgePorts.harness, gatesPort: input.edgePorts.gates },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
token,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* The edge's certificate and the Tenant's atespace, with the sandboxes image: `edge-cert` writes
|
|
235
|
+
* the CA and certificate next to cluster.json, and `substrate-setup` makes the atespace and its
|
|
236
|
+
* access policy as Substrate's owner client, with a 10-minute token minted from the kubeconfig.
|
|
237
|
+
*/
|
|
238
|
+
async function prepareSubstrate(deps, root, cluster, image) {
|
|
239
|
+
const { stack } = deps;
|
|
240
|
+
const dir = sandboxesDir(root);
|
|
241
|
+
const user = `${stack.uid}:${stack.gid}`;
|
|
242
|
+
const cert = await stack.docker.run([
|
|
243
|
+
"run", "--rm", "--user", user, "-v", `${dir}:/out`, image, "edge-cert", cluster.substrate.edge.host, "/out",
|
|
244
|
+
]);
|
|
245
|
+
if (cert.code !== 0)
|
|
246
|
+
throw new CliError(`Making the edge's certificate failed: ${cert.stderr.trim() || cert.stdout.trim()}`, 1);
|
|
247
|
+
const k = kube(deps.kubectl, cluster.context);
|
|
248
|
+
const owner = (await k.check("Minting Substrate's owner token", [
|
|
249
|
+
"create", "token", SUBSTRATE_OWNER_ACCOUNT, "-n", SUBSTRATE_NAMESPACE, "--audience", SUBSTRATE_AUDIENCE, "--duration", "10m",
|
|
250
|
+
])).trim();
|
|
251
|
+
const tokenFile = join(dir, "owner-token");
|
|
252
|
+
await writeFile(tokenFile, owner, { mode: 0o600 });
|
|
253
|
+
try {
|
|
254
|
+
const setup = await stack.docker.run([
|
|
255
|
+
"run", "--rm", "--user", user, "--add-host", "host.docker.internal:host-gateway",
|
|
256
|
+
"-v", `${dir}:/run/nylorun/sandboxes:ro`, "-e", "NYLORUN_SUBSTRATE_OWNER_TOKEN_FILE=/run/nylorun/sandboxes/owner-token",
|
|
257
|
+
image, "substrate-setup",
|
|
258
|
+
]);
|
|
259
|
+
if (setup.code !== 0)
|
|
260
|
+
throw new CliError(`Making the Tenant's atespace failed: ${setup.stderr.trim() || setup.stdout.trim()}`, 1);
|
|
261
|
+
stack.err(setup.stdout.trim());
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
await rm(tokenFile, { force: true });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
156
267
|
async function enable(deps, args) {
|
|
157
268
|
const { stack } = deps;
|
|
158
|
-
const flags = parseStackFlags(args, { booleans: ["--no-pull"], values: ["--context", "--tenant", "--host-address", "--bind-address", "--probe-ports"] }, ENABLE_USAGE);
|
|
269
|
+
const flags = parseStackFlags(args, { booleans: ["--no-pull"], values: ["--context", "--tenant", "--backend", "--host-address", "--bind-address", "--probe-ports"] }, ENABLE_USAGE);
|
|
159
270
|
if (flags.rest.length)
|
|
160
271
|
throw usageError(`Usage: ${ENABLE_USAGE}`);
|
|
161
272
|
const context = flags.values.get("--context");
|
|
162
273
|
if (!context)
|
|
163
274
|
throw usageError(`nylorun local sandbox enable needs --context <name>: the kubeconfig context to run pods in (never the current one implicitly). Usage: ${ENABLE_USAGE}`);
|
|
275
|
+
const backend = flags.values.get("--backend") ?? "agent-sandbox";
|
|
276
|
+
if (backend !== "agent-sandbox" && backend !== "substrate")
|
|
277
|
+
throw usageError(`--backend must be agent-sandbox or substrate, not ${backend}. Usage: ${ENABLE_USAGE}`);
|
|
164
278
|
const hostAddress = flags.values.get("--host-address");
|
|
165
279
|
const bindAddress = flags.values.get("--bind-address");
|
|
166
280
|
if (hostAddress)
|
|
@@ -188,6 +302,42 @@ async function enable(deps, args) {
|
|
|
188
302
|
gates: await pick(PREFERRED_PORTS.gates, persisted?.gatesPort),
|
|
189
303
|
egress: await pick(PREFERRED_PORTS.egress, persisted?.egressPort),
|
|
190
304
|
};
|
|
305
|
+
if (backend === "substrate") {
|
|
306
|
+
const edgePorts = {
|
|
307
|
+
harness: await pick(PREFERRED_EDGE_PORTS.harness, persisted?.edgeHarnessPort),
|
|
308
|
+
gates: await pick(PREFERRED_EDGE_PORTS.gates, persisted?.edgeGatesPort),
|
|
309
|
+
};
|
|
310
|
+
const images = stackImages(stack.env, { runtime: stack.runtimeVersion, studio: stack.studioVersion });
|
|
311
|
+
const installed = await installSubstrate(deps, {
|
|
312
|
+
context,
|
|
313
|
+
tenant: tenant.name,
|
|
314
|
+
...(hostAddress ? { hostAddress } : {}),
|
|
315
|
+
...(bindAddress ? { bindAddress } : {}),
|
|
316
|
+
ports,
|
|
317
|
+
edgePorts,
|
|
318
|
+
});
|
|
319
|
+
await writeClusterFiles(tenant.paths.root, installed.cluster, installed.token);
|
|
320
|
+
await prepareSubstrate(deps, tenant.paths.root, installed.cluster, images.sandboxes);
|
|
321
|
+
const settings = {
|
|
322
|
+
token: persisted?.token ?? randomBytes(32).toString("hex"),
|
|
323
|
+
image: images.sandboxes,
|
|
324
|
+
harnessPort: ports.harness,
|
|
325
|
+
gatesPort: ports.gates,
|
|
326
|
+
egressPort: ports.egress,
|
|
327
|
+
hostAddress: installed.cluster.hostAddress,
|
|
328
|
+
bind: installed.cluster.bindAddress,
|
|
329
|
+
edgeHarnessPort: edgePorts.harness,
|
|
330
|
+
edgeGatesPort: edgePorts.gates,
|
|
331
|
+
};
|
|
332
|
+
await restartTenant(stack, { name: tenant.name, sandboxes: settings });
|
|
333
|
+
const ready = await stack.docker.stream(tenant.compose("up", "--detach", "--wait", "--wait-timeout", String(cliTuning().sandbox.composeWaitSeconds), "sandboxes"));
|
|
334
|
+
if (ready !== 0)
|
|
335
|
+
throw new CliError(`The sandboxes service did not become ready (image ${images.sandboxes}). See "nylorun local logs sandboxes" and "nylorun local sandbox status".`, 7);
|
|
336
|
+
const c = installed.cluster;
|
|
337
|
+
stack.out(`Sandboxes ${tenant.name} enabled on ${c.context} (backend substrate, atespace ${c.substrate.atespace}; preview)`);
|
|
338
|
+
stack.out(`Actors reach the edge ${c.substrate.edge.host} (${c.hostAddress}) on ports ${edgePorts.harness} and ${edgePorts.gates}`);
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
191
341
|
if (probePorts && [probePorts.allowed, probePorts.denied].some((port) => taken.has(port)))
|
|
192
342
|
throw usageError(`--probe-ports ${probePortsFlag} overlaps the sandbox ports ${ports.harness}, ${ports.gates} and ${ports.egress}; pick two others.`);
|
|
193
343
|
const images = stackImages(stack.env, { runtime: stack.runtimeVersion, studio: stack.studioVersion });
|
|
@@ -283,9 +433,17 @@ async function status(deps, args) {
|
|
|
283
433
|
}
|
|
284
434
|
else {
|
|
285
435
|
stack.out(`Context ${cluster.context} (${cluster.server}, dialled at ${cluster.dial})`);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
436
|
+
if (cluster.backend === "substrate" && cluster.substrate) {
|
|
437
|
+
stack.out(`Namespace ${cluster.namespace} backend substrate (preview), atespace ${cluster.substrate.atespace}`);
|
|
438
|
+
stack.out(`Edge ${cluster.substrate.edge.host} ports ${cluster.substrate.edge.harnessPort}, ${cluster.substrate.edge.gatesPort} (bound on ${cluster.bindAddress}, actors reach ${cluster.hostAddress})`);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
stack.out(`Namespace ${cluster.namespace} agent-sandbox ${cluster.controllerVersion}`);
|
|
442
|
+
}
|
|
443
|
+
if (cluster.backend !== "substrate") {
|
|
444
|
+
stack.out(`Pods reach ${cluster.hostAddress} ports ${cluster.ports.harness}, ${cluster.ports.gates}, ${cluster.ports.egress} (bound on ${cluster.bindAddress})`);
|
|
445
|
+
stack.out(`Policy ${cluster.networkPolicy.enforced ? "enforced" : "NOT enforced"} (probed ${cluster.networkPolicy.probedAt})`);
|
|
446
|
+
}
|
|
289
447
|
stack.out(`Service ${ready ? "ready" : "not ready"}${info ? "" : " (no /v1/info answer)"}`);
|
|
290
448
|
}
|
|
291
449
|
return ready ? 0 : 3;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Substrate v1.0's install: its control plane's namespace, API Service and owner client. */
|
|
2
|
+
export declare const SUBSTRATE_NAMESPACE = "ate-system";
|
|
3
|
+
export declare const SUBSTRATE_API_SERVICE = "api";
|
|
4
|
+
export declare const SUBSTRATE_OWNER_ACCOUNT = "ate-client";
|
|
5
|
+
/** The audience ate-api-server accepts: its Service's DNS name. */
|
|
6
|
+
export declare const SUBSTRATE_AUDIENCE = "api.ate-system.svc";
|
|
7
|
+
/** Preferred host ports for the edge: the Harness API's and the gates' TLS fronts. */
|
|
8
|
+
export declare const PREFERRED_EDGE_PORTS: {
|
|
9
|
+
readonly harness: 8793;
|
|
10
|
+
readonly gates: 8794;
|
|
11
|
+
};
|
|
12
|
+
/** The name actors dial for the edge, unique to the Tenant's namespace. */
|
|
13
|
+
export declare function edgeHost(namespace: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* RBAC for the sandboxes service on Substrate: in the Tenant's namespace, its state ConfigMaps
|
|
16
|
+
* and a token for its own ServiceAccount (its Substrate identity); in Substrate's namespace, a
|
|
17
|
+
* port-forward to ate-api-server; cluster-wide, reading Substrate's trust bundles.
|
|
18
|
+
*/
|
|
19
|
+
export declare function substrateManifests(namespace: string): object[];
|
|
20
|
+
/**
|
|
21
|
+
* CoreDNS's Corefile with `host` resolving to `address` for actors: a `hosts` block in the
|
|
22
|
+
* default server block, marked so a later `enable` replaces it. Undefined when the Corefile has
|
|
23
|
+
* no `.:53` block or already has a `hosts` plugin of its own.
|
|
24
|
+
*/
|
|
25
|
+
export declare function corefileWithEdge(corefile: string, host: string, address: string): string | undefined;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `nylorun local sandbox enable --backend substrate` adds to a cluster that runs Agent
|
|
3
|
+
* Substrate (ADR-138, Phase 1: preview): the sandboxes service's access to Substrate's API, the
|
|
4
|
+
* Tenant's atespace (made by the sandboxes image's `substrate-setup`), and the name actors reach
|
|
5
|
+
* the edge by, in CoreDNS.
|
|
6
|
+
*/
|
|
7
|
+
import { SERVICE_ACCOUNT } from "./manifests.js";
|
|
8
|
+
/** Substrate v1.0's install: its control plane's namespace, API Service and owner client. */
|
|
9
|
+
export const SUBSTRATE_NAMESPACE = "ate-system";
|
|
10
|
+
export const SUBSTRATE_API_SERVICE = "api";
|
|
11
|
+
export const SUBSTRATE_OWNER_ACCOUNT = "ate-client";
|
|
12
|
+
/** The audience ate-api-server accepts: its Service's DNS name. */
|
|
13
|
+
export const SUBSTRATE_AUDIENCE = `${SUBSTRATE_API_SERVICE}.${SUBSTRATE_NAMESPACE}.svc`;
|
|
14
|
+
/** Preferred host ports for the edge: the Harness API's and the gates' TLS fronts. */
|
|
15
|
+
export const PREFERRED_EDGE_PORTS = { harness: 8793, gates: 8794 };
|
|
16
|
+
/** The name actors dial for the edge, unique to the Tenant's namespace. */
|
|
17
|
+
export function edgeHost(namespace) {
|
|
18
|
+
return `${namespace}.edge.nylorun.internal`;
|
|
19
|
+
}
|
|
20
|
+
const MANAGED = { "app.kubernetes.io/managed-by": "nylorun" };
|
|
21
|
+
/**
|
|
22
|
+
* RBAC for the sandboxes service on Substrate: in the Tenant's namespace, its state ConfigMaps
|
|
23
|
+
* and a token for its own ServiceAccount (its Substrate identity); in Substrate's namespace, a
|
|
24
|
+
* port-forward to ate-api-server; cluster-wide, reading Substrate's trust bundles.
|
|
25
|
+
*/
|
|
26
|
+
export function substrateManifests(namespace) {
|
|
27
|
+
const subject = [{ kind: "ServiceAccount", name: SERVICE_ACCOUNT, namespace }];
|
|
28
|
+
const name = `nylorun-sandboxes-${namespace}`;
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
32
|
+
kind: "Role",
|
|
33
|
+
metadata: { name: `${SERVICE_ACCOUNT}-substrate`, namespace, labels: MANAGED },
|
|
34
|
+
rules: [
|
|
35
|
+
{ apiGroups: [""], resources: ["configmaps"], verbs: ["get", "list", "watch", "create", "update", "delete"] },
|
|
36
|
+
{ apiGroups: [""], resources: ["serviceaccounts/token"], resourceNames: [SERVICE_ACCOUNT], verbs: ["create"] },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
41
|
+
kind: "RoleBinding",
|
|
42
|
+
metadata: { name: `${SERVICE_ACCOUNT}-substrate`, namespace, labels: MANAGED },
|
|
43
|
+
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: `${SERVICE_ACCOUNT}-substrate` },
|
|
44
|
+
subjects: subject,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
48
|
+
kind: "Role",
|
|
49
|
+
metadata: { name, namespace: SUBSTRATE_NAMESPACE, labels: MANAGED },
|
|
50
|
+
rules: [
|
|
51
|
+
{ apiGroups: [""], resources: ["services"], resourceNames: [SUBSTRATE_API_SERVICE], verbs: ["get"] },
|
|
52
|
+
{ apiGroups: [""], resources: ["pods"], verbs: ["get", "list"] },
|
|
53
|
+
{ apiGroups: [""], resources: ["pods/portforward"], verbs: ["create", "get"] },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
58
|
+
kind: "RoleBinding",
|
|
59
|
+
metadata: { name, namespace: SUBSTRATE_NAMESPACE, labels: MANAGED },
|
|
60
|
+
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name },
|
|
61
|
+
subjects: subject,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
65
|
+
kind: "ClusterRole",
|
|
66
|
+
metadata: { name, labels: MANAGED },
|
|
67
|
+
rules: [{ apiGroups: ["certificates.k8s.io"], resources: ["clustertrustbundles"], verbs: ["get", "list", "watch"] }],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
71
|
+
kind: "ClusterRoleBinding",
|
|
72
|
+
metadata: { name, labels: MANAGED },
|
|
73
|
+
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "ClusterRole", name },
|
|
74
|
+
subjects: subject,
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
const BEGIN = " # nylorun edge (nylorun local sandbox enable --backend substrate)";
|
|
79
|
+
const END = " # end nylorun edge";
|
|
80
|
+
/**
|
|
81
|
+
* CoreDNS's Corefile with `host` resolving to `address` for actors: a `hosts` block in the
|
|
82
|
+
* default server block, marked so a later `enable` replaces it. Undefined when the Corefile has
|
|
83
|
+
* no `.:53` block or already has a `hosts` plugin of its own.
|
|
84
|
+
*/
|
|
85
|
+
export function corefileWithEdge(corefile, host, address) {
|
|
86
|
+
const lines = corefile.split("\n");
|
|
87
|
+
const start = lines.indexOf(BEGIN);
|
|
88
|
+
if (start >= 0) {
|
|
89
|
+
const end = lines.indexOf(END, start);
|
|
90
|
+
if (end < 0)
|
|
91
|
+
return undefined;
|
|
92
|
+
const ours = lines.slice(start + 1, end).filter((line) => /^\s+\d+\.\d+\.\d+\.\d+\s+\S/.test(line));
|
|
93
|
+
const entries = new Map(ours.map((line) => [line.trim().split(/\s+/)[1], line.trim().split(/\s+/)[0]]));
|
|
94
|
+
entries.set(host, address);
|
|
95
|
+
lines.splice(start, end - start + 1, ...block(entries));
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
if (lines.some((line) => /^\s*hosts(\s|$)/.test(line)))
|
|
99
|
+
return undefined;
|
|
100
|
+
const server = lines.findIndex((line) => /^\.:53\s*\{/.test(line.trim()));
|
|
101
|
+
if (server < 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
lines.splice(server + 1, 0, ...block(new Map([[host, address]])));
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
106
|
+
function block(entries) {
|
|
107
|
+
return [
|
|
108
|
+
BEGIN,
|
|
109
|
+
" hosts {",
|
|
110
|
+
...[...entries].map(([host, address]) => ` ${address} ${host}`),
|
|
111
|
+
" fallthrough",
|
|
112
|
+
" }",
|
|
113
|
+
END,
|
|
114
|
+
];
|
|
115
|
+
}
|
|
@@ -74,6 +74,8 @@
|
|
|
74
74
|
*/
|
|
75
75
|
export declare function renderComposeFile(project: string, name: string, options?: {
|
|
76
76
|
sandboxes?: true;
|
|
77
|
+
/** The sandboxes container serves the edge (backend substrate, ADR-138). */
|
|
78
|
+
sandboxEdge?: true;
|
|
77
79
|
harness?: "remote" | "in-process";
|
|
78
80
|
restateUi?: true;
|
|
79
81
|
identity?: true;
|
|
@@ -327,7 +327,7 @@ ${healthcheck("runtime")} restart: unless-stopped
|
|
|
327
327
|
healthcheck: # /healthz needs no session; Host studio:3000 is accepted
|
|
328
328
|
test: ["CMD", "node", "-e", "require('http').get({port:3000,path:'/healthz',headers:{host:'studio:3000'}},r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"]
|
|
329
329
|
${healthcheck("studio")} restart: unless-stopped
|
|
330
|
-
${remote ? harnessService(project) : ""}${sandboxes ? sandboxesService(project) : ""}
|
|
330
|
+
${remote ? harnessService(project) : ""}${sandboxes ? sandboxesService(project, options.sandboxEdge === true) : ""}
|
|
331
331
|
networks:
|
|
332
332
|
default:
|
|
333
333
|
name: ${project}
|
|
@@ -412,6 +412,15 @@ const SANDBOXES_GATES_PORT = ` ports:
|
|
|
412
412
|
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun local sandbox enable}:4100" # gates, for sandbox pods (run tokens)
|
|
413
413
|
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EGRESS_PORT:?run nylorun local sandbox enable}:4200" # egress-gate, for sandbox pods (egress tokens)
|
|
414
414
|
`;
|
|
415
|
+
/**
|
|
416
|
+
* The edge (backend substrate, ADR-138): Substrate actors reach the Harness API and the gates
|
|
417
|
+
* over TLS that Substrate passes through; the sandboxes container terminates it and forwards to
|
|
418
|
+
* runtime:4200 and gateway:4100 on \`default\`.
|
|
419
|
+
*/
|
|
420
|
+
const SANDBOXES_EDGE_PORTS = `
|
|
421
|
+
ports:
|
|
422
|
+
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EDGE_HARNESS_PORT:?run nylorun local sandbox enable}:8443" # the edge: the Harness API, for actors
|
|
423
|
+
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EDGE_GATES_PORT:?run nylorun local sandbox enable}:8444" # the edge: the gates, for actors`;
|
|
415
424
|
/** egress-gate (F7.2): pods' CONNECT proxy in the gateway. */
|
|
416
425
|
const SANDBOXES_GATEWAY_ENV = `
|
|
417
426
|
# egress-gate: the only way out of a sandbox pod, to the hosts its spec allows.
|
|
@@ -427,9 +436,9 @@ const SANDBOXES_RUNTIME_MOUNT = `
|
|
|
427
436
|
* (NYLORUN_SANDBOX_*_PORT in .env), the Harness API's, the gates' and egress-gate's, are
|
|
428
437
|
* published by the runtime and the gateway (above).
|
|
429
438
|
*/
|
|
430
|
-
function sandboxesService(project) {
|
|
439
|
+
function sandboxesService(project, edge) {
|
|
431
440
|
return `
|
|
432
|
-
sandboxes: #
|
|
441
|
+
sandboxes: # the sandboxes driver; the only holder of the cluster credentials; not published${edge ? ", but for its edge" : ""}
|
|
433
442
|
image: \${NYLORUN_SANDBOXES_IMAGE:?run nylorun local sandbox enable}
|
|
434
443
|
container_name: ${project}-sandboxes
|
|
435
444
|
labels: *tenant
|
|
@@ -443,7 +452,7 @@ function sandboxesService(project) {
|
|
|
443
452
|
volumes:
|
|
444
453
|
# cluster.json (API server, CA, namespace) and the ServiceAccount token, read-only.
|
|
445
454
|
- \${NYLORUN_HOST_ROOT:?run nylorun start}/sandboxes:/run/nylorun/sandboxes:ro
|
|
446
|
-
networks: [default] # the cluster's API server, through host.docker.internal
|
|
455
|
+
networks: [default] # the cluster's API server, through host.docker.internal${edge ? SANDBOXES_EDGE_PORTS : ""}
|
|
447
456
|
healthcheck: # /ready: informers synced and the API server answers
|
|
448
457
|
test: ["CMD", "/sandboxes", "healthcheck"]
|
|
449
458
|
${healthcheck("sandboxes")} restart: unless-stopped
|
package/dist/stack/env-file.d.ts
CHANGED
|
@@ -74,6 +74,12 @@ export interface SandboxStackEnv {
|
|
|
74
74
|
hostAddress: string;
|
|
75
75
|
/** Where the pod-facing ports are published (127.0.0.1 on Docker Desktop). */
|
|
76
76
|
bind: string;
|
|
77
|
+
/**
|
|
78
|
+
* With the Substrate backend (ADR-138): the edge's host ports, the TLS front actors reach
|
|
79
|
+
* the Harness API and the gates through, served by the sandboxes container.
|
|
80
|
+
*/
|
|
81
|
+
edgeHarnessPort?: number;
|
|
82
|
+
edgeGatesPort?: number;
|
|
77
83
|
}
|
|
78
84
|
export type HarnessMode = "remote" | "in-process";
|
|
79
85
|
export declare function renderEnvFile(env: StackEnv): string;
|
package/dist/stack/env-file.js
CHANGED
|
@@ -13,6 +13,8 @@ const SANDBOX_KEYS = {
|
|
|
13
13
|
egressPort: "NYLORUN_SANDBOX_EGRESS_PORT",
|
|
14
14
|
hostAddress: "NYLORUN_SANDBOX_HOST_ADDRESS",
|
|
15
15
|
bind: "NYLORUN_SANDBOX_BIND",
|
|
16
|
+
edgeHarnessPort: "NYLORUN_SANDBOX_EDGE_HARNESS_PORT",
|
|
17
|
+
edgeGatesPort: "NYLORUN_SANDBOX_EDGE_GATES_PORT",
|
|
16
18
|
};
|
|
17
19
|
const KEYS = {
|
|
18
20
|
runtimePort: "NYLORUN_PORT",
|
|
@@ -120,6 +122,13 @@ export function renderEnvFile(env) {
|
|
|
120
122
|
sandboxLine("egressPort"),
|
|
121
123
|
sandboxLine("hostAddress"),
|
|
122
124
|
sandboxLine("bind"),
|
|
125
|
+
...(sandboxes.edgeHarnessPort && sandboxes.edgeGatesPort
|
|
126
|
+
? [
|
|
127
|
+
"# The edge (backend substrate): the TLS ports actors reach the Runtime through.",
|
|
128
|
+
sandboxLine("edgeHarnessPort"),
|
|
129
|
+
sandboxLine("edgeGatesPort"),
|
|
130
|
+
]
|
|
131
|
+
: []),
|
|
123
132
|
"",
|
|
124
133
|
]
|
|
125
134
|
: []),
|
|
@@ -161,7 +170,12 @@ function parseSandboxes(values) {
|
|
|
161
170
|
return undefined;
|
|
162
171
|
if (!hostAddress || !IPV4.test(hostAddress) || !bind || !IPV4.test(bind))
|
|
163
172
|
return undefined;
|
|
164
|
-
|
|
173
|
+
const edgeHarnessPort = port(values.get(SANDBOX_KEYS.edgeHarnessPort));
|
|
174
|
+
const edgeGatesPort = port(values.get(SANDBOX_KEYS.edgeGatesPort));
|
|
175
|
+
return {
|
|
176
|
+
token, image, harnessPort, gatesPort, egressPort, hostAddress, bind,
|
|
177
|
+
...(edgeHarnessPort && edgeGatesPort ? { edgeHarnessPort, edgeGatesPort } : {}),
|
|
178
|
+
};
|
|
165
179
|
}
|
|
166
180
|
export function parsePersisted(text) {
|
|
167
181
|
const values = parseEnvLines(text);
|
package/dist/stack/prepare.js
CHANGED
|
@@ -102,6 +102,7 @@ export async function prepareStack(input) {
|
|
|
102
102
|
await writeFileMode(paths.compose, renderComposeFile(input.project, input.name, {
|
|
103
103
|
harness: env.harness,
|
|
104
104
|
...(env.sandboxes ? { sandboxes: true } : {}),
|
|
105
|
+
...(env.sandboxes?.edgeHarnessPort ? { sandboxEdge: true } : {}),
|
|
105
106
|
...(env.restateUi ? { restateUi: true } : {}),
|
|
106
107
|
// The trusted issuers, when the operator wrote an identity file; a change takes a restart.
|
|
107
108
|
...(existsSync(paths.identity) ? { identity: true } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nylorun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose, and act on a Tenant through its Runtime and Management APIs",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"prepack": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nylorun/admin": "0.19.
|
|
34
|
-
"@nylorun/agents": "0.36.
|
|
35
|
-
"@nylorun/core": "0.
|
|
33
|
+
"@nylorun/admin": "0.19.15-beta",
|
|
34
|
+
"@nylorun/agents": "0.36.2-beta",
|
|
35
|
+
"@nylorun/core": "0.39.0-beta"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@earendil-works/pi-ai": "1.3.0",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"nylo": "dist/nylo.js"
|
|
47
47
|
},
|
|
48
48
|
"nylorun": {
|
|
49
|
-
"runtime": "0.
|
|
50
|
-
"studio": "0.35.
|
|
49
|
+
"runtime": "0.45.0-beta",
|
|
50
|
+
"studio": "0.35.8-beta"
|
|
51
51
|
}
|
|
52
52
|
}
|