nylorun 0.2.1-beta → 0.3.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 +84 -0
- package/README.md +35 -1
- package/dist/stack/commands.d.ts +7 -0
- package/dist/stack/commands.js +90 -11
- package/dist/stack/compose-file.d.ts +4 -1
- package/dist/stack/compose-file.js +19 -3
- package/dist/stack/env-file.d.ts +13 -1
- package/dist/stack/env-file.js +28 -0
- package/dist/stack/host-files.d.ts +7 -3
- package/dist/stack/host-files.js +5 -3
- package/dist/stack/images.d.ts +2 -0
- package/dist/stack/images.js +10 -6
- package/dist/stack/ports.d.ts +2 -0
- package/dist/stack/ports.js +2 -0
- package/dist/stack/prepare.d.ts +10 -1
- package/dist/stack/prepare.js +19 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# nylorun
|
|
2
2
|
|
|
3
|
+
## 0.3.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e28b8a9: **New projects serve their tools as an Action endpoint.** Everything Nylorun ships now uses Action endpoints instead of executors. `connectAgents` still works, but it is deprecated.
|
|
8
|
+
|
|
9
|
+
- **Starter (`create-agent`).** `src/main.ts` serves the agents with `createActionHandler` on `http://localhost:3001/nylorun/actions` (`PORT`, `NYLORUN_ACTIONS_URL`) and registers it. `npm run dev` and `npm start` work as before. The README explains which URL the Runtime must reach.
|
|
10
|
+
- **Local stack (`nylorun`).** The Runtime container sets `NYLORUN_ENDPOINT_LOOPBACK=docker-host` and maps `host.docker.internal` to the Docker host, so a `localhost` endpoint on the developer's machine is reachable, on Linux Docker Engine too.
|
|
11
|
+
- **Examples.** The AG-UI and browser-direct apps serve their Action endpoint at `/nylorun/actions` beside their other routes, and `register(origin)` replaces `connection.ready`.
|
|
12
|
+
- **Docs.** The README, `DEPLOYMENT.md` and the `@nylorun/agents` README describe Action endpoints.
|
|
13
|
+
|
|
14
|
+
- 50d0fb5: **The Admin API on its own listener.** A Runtime can serve the Admin API on an operator listener, so the port that faces browsers and reverse proxies serves the Tenant API alone. The stack does this by default.
|
|
15
|
+
|
|
16
|
+
- **Runtime.** With an operator listener (`adminPort` in `host.json`, or `NYLORUN_ADMIN_LISTEN_PORT`, `NYLORUN_ADMIN_LISTEN_HOST` and `NYLORUN_ADMIN_ALLOWED_HOSTS` in a container), the public listener answers `/v1/admin/**` with the opaque `404` and the operator listener serves the Admin API, Host shutdown and the Tenant API, never to browsers. Each checks `Host` against its own port. `/ready` needs both listening; a taken port on either exits with code 98. Without one, a single listener serves everything as before.
|
|
17
|
+
- **Stack.** `nylorun start` publishes the operator port on loopback (`NYLORUN_ADMIN_PORT`, default 8788), writes it to `host.json` as `adminPort`, and points Studio at `runtime:4001`. `nylorun status` prints it.
|
|
18
|
+
- **Admin client.** Reads `adminPort` from `host.json` and sends Admin API requests there (`admin.adminUrl`); `admin.url` stays the Tenant API URL. A `host.json` without `adminPort` keeps working.
|
|
19
|
+
|
|
20
|
+
- 9546ac7: **The stream relay, ready to wire in.** The Runtime gains the relay that will feed S2 from a Postgres record of session events over logical replication (Durable Streams v1); nothing uses it yet.
|
|
21
|
+
|
|
22
|
+
- **Relay core** (`streams/relay/`): per-session pumps appending with `matchSeq`, acknowledgements only after S2 has the rows, refills from the record on a gap, reconciliation after a new or lost slot, and rows of an old basin generation dropped.
|
|
23
|
+
- **Change source** (`adapters/replication/pgoutput.ts`): a persistent `pgoutput` slot, one active process per slot, always resumed from the confirmed position; a pending reconciliation is kept in `nylorun_streams.relay_slots` so a crash cannot skip it.
|
|
24
|
+
- **Shared schema** (`nylorun_streams`): `session_events`, `session_log_heads`, `relay_slots` and the `nylorun_stream_relay` publication, migrated by the Host.
|
|
25
|
+
- **Basin generations**: `basinOf(tenantId, generation)` names a Tenant's later basins (`<basin>-<g base36>`).
|
|
26
|
+
- **Local stack**: Postgres runs with `wal_level=logical` and `max_slot_wal_keep_size=4GB`. `nylorun start` recreates the Postgres container once; its data volume is kept. `DEPLOYMENT.md` lists the settings for a Postgres you run yourself.
|
|
27
|
+
|
|
28
|
+
- 9d52189: **Embedding Studio in a desktop app.** Studio can be shown inside a desktop app such as Babai Desktop, in an iframe loaded from its URL and signed in by `postMessage` with a token limited to one Tenant.
|
|
29
|
+
|
|
30
|
+
- **`nylorun`.** The local stack lets Babai's origins frame Studio: `NYLORUN_STUDIO_FRAME_ANCESTORS` in `stack/.env` defaults to `nylorun://localhost http://nylorun.localhost` and is passed to the Studio container. `nylorun start --studio-embed-origin <origin>` (repeatable) adds an exact origin, such as a desktop app's dev server, and keeps it across starts until `--studio-embed-origin-reset`. Wildcards are refused. `nylorun status` lists the origins under `Embeds`, and `status --json` as `studio.embedOrigins`.
|
|
31
|
+
- **`@nylorun/admin`.** `mintStudioLoginToken({ studioUrl, adminKey, tenant?, subject? })` mints a single-use Studio login token from an app's backend. With `tenant`, the session it leads to reaches only that Tenant.
|
|
32
|
+
- **Studio.** `POST /_studio/sessions` exchanges such a token for a one-hour bearer session kept in the frame's memory; dashboard pages send `frame-ancestors` from the allowlist instead of `X-Frame-Options: DENY`; `?embed=1` hides Studio's branding, follows the app's theme and routes, and reports its own; `/tenants/:tenant/sessions/:session` opens a session by id. The cookie login of `nylorun studio` is unchanged.
|
|
33
|
+
|
|
34
|
+
The dashboard routes `/tenants/:tenant`, `/tenants/:tenant/agents/:agent`, `/tenants/:tenant/agents/:agent/sessions/:session`, `/tenants/:tenant/sessions/:session`, `/tenants/:tenant/vault` and `/tenants/:tenant/settings` are now a public contract for embedders: removing or changing one is a breaking change.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- 4282d5f: **Breaking: executors are removed; protocol 3.** The Runtime delivers every Action (tool, hook, `fn`, `verify`) to the Action endpoint an agent registers. It no longer offers Actions for executors to claim. See `MIGRATION.md`, "Action endpoints replace executors".
|
|
39
|
+
|
|
40
|
+
- **Core.** `PROTOCOL_VERSION` is 3 and a Host serves only protocol 3. `action-endpoints` is a protocol feature instead of an optional Host feature. Removed:
|
|
41
|
+
|
|
42
|
+
- the executor, claim and Action-list schemas;
|
|
43
|
+
- `action_result` from `SessionCommandSchema`;
|
|
44
|
+
- the `claimed` Action status, `claimId` and `leaseExpiresAt`.
|
|
45
|
+
|
|
46
|
+
New: `ActionResultReceiptSchema`, the receipt of `POST /v1/actions/:id/result`, which is `AcceptedResponse` without `requestId`.
|
|
47
|
+
|
|
48
|
+
- **Runtime.** Removed:
|
|
49
|
+
|
|
50
|
+
- `PUT`/`GET /v1/executors`, `DELETE /v1/executors/:agentId`, `GET /v1/executors/connect`;
|
|
51
|
+
- `GET /v1/actions`, `POST /v1/actions/:id/claim`, and the executor form of `POST /v1/actions/:id/heartbeat`;
|
|
52
|
+
- the `tenant/work` stream;
|
|
53
|
+
- claim expiry;
|
|
54
|
+
- executor credentials.
|
|
55
|
+
|
|
56
|
+
`POST /v1/actions/:id/sandbox/:tool` takes the delivery token only. Tenant summaries report `inFlightDeliveries` instead of `connectedExecutors`. Tenant status lists each agent's endpoint and has an `endpoints` check instead of `executors`.
|
|
57
|
+
|
|
58
|
+
Postgres migration 6 drops the `executors` table. Actions an executor had claimed are handled like lost deliveries: a tool becomes `uncertain`, and a hook, `fn` or `verify` is delivered again.
|
|
59
|
+
|
|
60
|
+
- **Agents.** Removed: `connectAgents`, the `@nylorun/agents/executor` subpath, derived executor keys and `NYLORUN_EXECUTOR_KEY`. Serve agents with `createActionHandler` and call `register({ url })`.
|
|
61
|
+
- **CLI.** Executor keys are gone from Project credentials. `nylo tenant endpoints` shows each endpoint and its health.
|
|
62
|
+
|
|
63
|
+
- c0f4623: `nylorun up`/`start` refuses to downgrade the shared stack. When this release pins a Runtime older than `host.json` `runtimeVersion` or the running Runtime's `/health` version, it exits 5 before changing the stack files or containers, and names the remedy: update nylorun, or pass the new `--allow-downgrade` flag. `nylorun studio` applies the same check when it starts the stack. With `NYLORUN_RUNTIME_IMAGE` set, the check is skipped and `host.json` keeps its recorded `runtimeVersion`.
|
|
64
|
+
- Pin core to the tested release.
|
|
65
|
+
- Pin runtime to the tested release.
|
|
66
|
+
- Pin studio to the tested release.
|
|
67
|
+
- Updated dependencies [c7614a4]
|
|
68
|
+
- Updated dependencies [679c488]
|
|
69
|
+
- Updated dependencies [c85cd9e]
|
|
70
|
+
- Updated dependencies [4282d5f]
|
|
71
|
+
- Updated dependencies [82d95ef]
|
|
72
|
+
- Updated dependencies [f48f12f]
|
|
73
|
+
- Updated dependencies [50d0fb5]
|
|
74
|
+
- Updated dependencies [50d0fb5]
|
|
75
|
+
- Updated dependencies [c121144]
|
|
76
|
+
- Updated dependencies [6ab4c59]
|
|
77
|
+
- Updated dependencies [6ab4c59]
|
|
78
|
+
- Updated dependencies [2ab8ed1]
|
|
79
|
+
- Updated dependencies [c121144]
|
|
80
|
+
- Updated dependencies [9546ac7]
|
|
81
|
+
- Updated dependencies [9546ac7]
|
|
82
|
+
- Updated dependencies [9d52189]
|
|
83
|
+
- Updated dependencies [50d0fb5]
|
|
84
|
+
- Updated dependencies [18468d9]
|
|
85
|
+
- @nylorun/core@0.9.0-beta
|
|
86
|
+
|
|
3
87
|
## 0.2.1-beta
|
|
4
88
|
|
|
5
89
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -29,7 +29,8 @@ npx nylorun doctor # Node 24+, Docker, Compose v2, and the stack
|
|
|
29
29
|
## Commands
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
nylorun up|start [--no-studio] [--no-open]
|
|
32
|
+
nylorun up|start [--no-studio] [--no-open] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset]
|
|
33
|
+
# set up (first run) and start the stack; print the Runtime and Studio URLs; open Studio signed in
|
|
33
34
|
nylorun down|stop # stop the containers; keep volumes
|
|
34
35
|
nylorun status [--json] # services, endpoints, Runtime health
|
|
35
36
|
nylorun logs [service] [-f] [--tail <n>] # postgres, restate, s2, runtime, studio
|
|
@@ -57,6 +58,14 @@ CI). Ports publish on loopback only: the Runtime on `8787`, Studio on `4161`
|
|
|
57
58
|
and the Restate UI on `9070`, or free ports chosen on the first start and kept
|
|
58
59
|
in `.env`. While the Host has no Tenant, `up` says how to create one.
|
|
59
60
|
|
|
61
|
+
`up` never downgrades the stack on its own. Every project and app on the
|
|
62
|
+
machine shares it, and a Runtime older than a Tenant's schema quarantines that
|
|
63
|
+
Tenant. `up` records the pinned Runtime version in `host.json`
|
|
64
|
+
(`runtimeVersion`) and exits 5 when that version is older than the one recorded
|
|
65
|
+
there or the one running. Update nylorun, or pass `--allow-downgrade` to start
|
|
66
|
+
the older Runtime anyway. With `NYLORUN_RUNTIME_IMAGE` set, the image's version
|
|
67
|
+
is unknown: `up` does not check it and keeps the recorded version.
|
|
68
|
+
|
|
60
69
|
`up` prints Studio as `http://localhost:<port>`. Studio has no password: in a
|
|
61
70
|
terminal (not in CI, and not with `--no-open`), `up` asks the Studio container
|
|
62
71
|
for a single-use login token with the admin key (`POST /_studio/login-tokens`)
|
|
@@ -68,6 +77,29 @@ browser in the same way; `nylorun studio --no-open` prints the login URL (it
|
|
|
68
77
|
works once, for two minutes) instead. Inside a linked project, `nylorun studio`
|
|
69
78
|
reads `.nylorun/link.json` (never writes it) and lands on that project's Tenant.
|
|
70
79
|
|
|
80
|
+
### Embedding Studio in a desktop app
|
|
81
|
+
|
|
82
|
+
A desktop app such as Babai Desktop can show Studio inside its own window, in
|
|
83
|
+
an iframe loaded from Studio's URL (`studio.url` in `nylorun status --json`).
|
|
84
|
+
Only exact origins listed in `NYLORUN_STUDIO_FRAME_ANCESTORS` (in
|
|
85
|
+
`<Host root>/stack/.env`) may frame it. The default is Babai's
|
|
86
|
+
`nylorun://localhost http://nylorun.localhost`; `nylorun status` lists them
|
|
87
|
+
under `Embeds`. While building such an app, add its dev server once with
|
|
88
|
+
`nylorun start --studio-embed-origin http://localhost:1420`; the list is kept
|
|
89
|
+
across starts until `--studio-embed-origin-reset`. Wildcards are refused.
|
|
90
|
+
|
|
91
|
+
The app's backend mints a single-use login token limited to one Tenant with
|
|
92
|
+
`mintStudioLoginToken` from `@nylorun/admin` (it needs the admin key), and its
|
|
93
|
+
page passes the token to Studio by `postMessage`. The message contract is
|
|
94
|
+
`@nylorun/agents/studio-embed`.
|
|
95
|
+
|
|
96
|
+
## Sandboxes
|
|
97
|
+
|
|
98
|
+
A session gets a sandbox when it is opened with one, or when the Tenant's
|
|
99
|
+
default names one (`PUT /v1/tenant/sandbox`). The stack runs them on the
|
|
100
|
+
Runtime's **virtual** backend: an emulated shell in the Runtime process, with no
|
|
101
|
+
extra containers. It is not a VM or container boundary.
|
|
102
|
+
|
|
71
103
|
## Host root
|
|
72
104
|
|
|
73
105
|
The **Host root** is `NYLORUN_HOME` or `~/.nylorun`. It is bind-mounted into the
|
|
@@ -86,6 +118,7 @@ each with its own Tenant.
|
|
|
86
118
|
| 2 | Usage error, or a removed or moved command |
|
|
87
119
|
| 3 | `status`: the Runtime is not answering; `stop`/`logs`: no stack yet |
|
|
88
120
|
| 4 | The Runtime port is held by another Host |
|
|
121
|
+
| 5 | `up`/`start`/`studio`: this nylorun pins a Runtime older than the one the Host last ran or is running; update nylorun, or `up --allow-downgrade` |
|
|
89
122
|
| 7 | The stack or Studio did not become ready |
|
|
90
123
|
| 130 / 143 | SIGINT / SIGTERM |
|
|
91
124
|
|
|
@@ -95,6 +128,7 @@ each with its own Tenant.
|
|
|
95
128
|
| --- | --- |
|
|
96
129
|
| Docker missing or not running | Install or start Docker Desktop, OrbStack or Colima; `nylorun doctor` checks |
|
|
97
130
|
| `426` from the Runtime | Upgrade nylorun (`npx nylorun@latest up`), or pin a matching older set |
|
|
131
|
+
| `Refusing to downgrade` (exit 5) | Upgrade nylorun (`npx nylorun@latest up`); `--allow-downgrade` only if you accept quarantined Tenants |
|
|
98
132
|
| Port conflict | Change `NYLORUN_PORT` / `NYLORUN_STUDIO_PORT` in `<Host root>/stack/.env` |
|
|
99
133
|
| Logs | `nylorun logs runtime -f` |
|
|
100
134
|
| Studio login expired | `nylorun studio` |
|
package/dist/stack/commands.d.ts
CHANGED
|
@@ -35,10 +35,13 @@ interface Flags {
|
|
|
35
35
|
rest: string[];
|
|
36
36
|
booleans: Set<string>;
|
|
37
37
|
values: Map<string, string>;
|
|
38
|
+
/** Options that may repeat, in order. */
|
|
39
|
+
lists: Map<string, string[]>;
|
|
38
40
|
}
|
|
39
41
|
export declare function parseStackFlags(args: readonly string[], allowed: {
|
|
40
42
|
booleans?: readonly string[];
|
|
41
43
|
values?: readonly string[];
|
|
44
|
+
lists?: readonly string[];
|
|
42
45
|
aliases?: Record<string, string>;
|
|
43
46
|
}, usage: string): Flags;
|
|
44
47
|
/** Printed by `start` when it does not open Studio itself. */
|
|
@@ -51,6 +54,8 @@ export interface StackStatus {
|
|
|
51
54
|
state: "running" | "stopped" | "absent";
|
|
52
55
|
runtime: {
|
|
53
56
|
url?: string;
|
|
57
|
+
/** The Admin API (operator listener), when the stack publishes one. */
|
|
58
|
+
adminUrl?: string;
|
|
54
59
|
healthy: boolean;
|
|
55
60
|
version?: string;
|
|
56
61
|
hostId?: string;
|
|
@@ -59,6 +64,8 @@ export interface StackStatus {
|
|
|
59
64
|
studio: {
|
|
60
65
|
url?: string;
|
|
61
66
|
state: string;
|
|
67
|
+
/** Exact origins that may show Studio in a frame (Studio §8.9). */
|
|
68
|
+
embedOrigins?: string[];
|
|
62
69
|
};
|
|
63
70
|
restate: {
|
|
64
71
|
url?: string;
|
package/dist/stack/commands.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { readFile, rm, mkdir } from "node:fs/promises";
|
|
3
|
-
import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
|
|
3
|
+
import { compareVersions, PROTOCOL_HEADER, PROTOCOL_VERSION, } from "@nylorun/core/compatibility";
|
|
4
4
|
import { CliError } from "../errors.js";
|
|
5
5
|
import { resolveHome } from "../home.js";
|
|
6
6
|
import { dockerPreflight, parseComposePs, } from "./docker.js";
|
|
7
7
|
import { readAdminKey, readHostConfig, STACK_CLIENT_HOST } from "./host-files.js";
|
|
8
|
-
import { stackImages } from "./images.js";
|
|
8
|
+
import { runtimeImageOverridden, stackImages } from "./images.js";
|
|
9
9
|
import { stackPaths } from "./paths.js";
|
|
10
10
|
import { prepareStack, readStackEnv } from "./prepare.js";
|
|
11
11
|
import { mintStudioLogin, studioOrigin } from "./studio-login.js";
|
|
12
12
|
export const STACK_SERVICES = ["postgres", "restate", "s2", "runtime", "studio"];
|
|
13
13
|
const CORE_SERVICES = ["postgres", "restate", "s2", "runtime"];
|
|
14
14
|
const DEFAULT_PROJECT = "nylorun";
|
|
15
|
-
export const stackUsage = ` up|start [--no-studio] [--no-open]
|
|
15
|
+
export const stackUsage = ` up|start [--no-studio] [--no-open] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset]
|
|
16
|
+
set up the stack on first run, then start it; print the Runtime and Studio URLs and open Studio signed in (in a terminal).
|
|
17
|
+
refuses a Runtime older than the Host last ran unless --allow-downgrade.
|
|
18
|
+
--studio-embed-origin lets one more exact origin show Studio in a frame (a desktop app's development server); kept across starts until --studio-embed-origin-reset
|
|
16
19
|
down|stop stop the stack's containers; keep volumes
|
|
17
20
|
status [--json] [--env] services, endpoints and Runtime health (--env: the linked Project's variables)
|
|
18
21
|
logs [service] [-f] [--tail <n>] stack logs (${STACK_SERVICES.join(", ")})
|
|
@@ -51,6 +54,7 @@ function requireStackFiles(ctx) {
|
|
|
51
54
|
export function parseStackFlags(args, allowed, usage) {
|
|
52
55
|
const booleans = new Set();
|
|
53
56
|
const values = new Map();
|
|
57
|
+
const lists = new Map();
|
|
54
58
|
const rest = [];
|
|
55
59
|
for (let index = 0; index < args.length; index += 1) {
|
|
56
60
|
const raw = args[index];
|
|
@@ -70,9 +74,16 @@ export function parseStackFlags(args, allowed, usage) {
|
|
|
70
74
|
values.set(arg, value);
|
|
71
75
|
continue;
|
|
72
76
|
}
|
|
77
|
+
if (allowed.lists?.includes(arg)) {
|
|
78
|
+
const value = args[++index];
|
|
79
|
+
if (value === undefined || value.startsWith("-"))
|
|
80
|
+
throw usageError(`${arg} requires a value.`);
|
|
81
|
+
lists.set(arg, [...(lists.get(arg) ?? []), value]);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
73
84
|
throw usageError(`Unknown option ${raw}. Usage: ${usage}`);
|
|
74
85
|
}
|
|
75
|
-
return { rest, booleans, values };
|
|
86
|
+
return { rest, booleans, values, lists };
|
|
76
87
|
}
|
|
77
88
|
async function sleep(ms) {
|
|
78
89
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
@@ -135,9 +146,52 @@ function isUp(services, name) {
|
|
|
135
146
|
const service = services.find((s) => s.service === name);
|
|
136
147
|
return service?.state === "running" && (service.health === "" || service.health === "healthy");
|
|
137
148
|
}
|
|
149
|
+
const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
150
|
+
/** `version` is a SemVer older than `than` (prereleases sort before releases). */
|
|
151
|
+
function isOlder(version, than) {
|
|
152
|
+
return (typeof than === "string" &&
|
|
153
|
+
SEMVER.test(version) &&
|
|
154
|
+
SEMVER.test(than) &&
|
|
155
|
+
compareVersions(version, than) < 0);
|
|
156
|
+
}
|
|
157
|
+
/** The version of this Host's Runtime when it answers on the stack's port. */
|
|
158
|
+
async function runningRuntimeVersion(ctx, hostId) {
|
|
159
|
+
const persisted = await readStackEnv(ctx.paths);
|
|
160
|
+
if (!persisted?.runtimePort || typeof hostId !== "string")
|
|
161
|
+
return undefined;
|
|
162
|
+
const health = await fetchHealth(ctx.deps, `http://${STACK_CLIENT_HOST}:${persisted.runtimePort}`);
|
|
163
|
+
return health?.status === "ok" && health.hostId === hostId ? health.version : undefined;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Refuse to start a Runtime older than the one the Host last ran (host.json
|
|
167
|
+
* `runtimeVersion`) or the one running now: Compose would replace it, every
|
|
168
|
+
* client on the machine shares the stack, and a Runtime older than a Tenant's
|
|
169
|
+
* schema quarantines that Tenant. Skipped when `NYLORUN_RUNTIME_IMAGE` names
|
|
170
|
+
* the image.
|
|
171
|
+
*/
|
|
172
|
+
async function refuseDowngrade(ctx, allowDowngrade) {
|
|
173
|
+
const pinned = ctx.deps.runtimeVersion;
|
|
174
|
+
const host = await readHostConfig(ctx.paths);
|
|
175
|
+
const newer = [];
|
|
176
|
+
if (isOlder(pinned, host?.runtimeVersion))
|
|
177
|
+
newer.push(`the Host last ran Runtime ${host.runtimeVersion} (${ctx.paths.config})`);
|
|
178
|
+
const running = await runningRuntimeVersion(ctx, host?.hostId);
|
|
179
|
+
if (isOlder(pinned, running) && running !== host?.runtimeVersion)
|
|
180
|
+
newer.push(`Runtime ${running} is running`);
|
|
181
|
+
if (newer.length === 0)
|
|
182
|
+
return;
|
|
183
|
+
if (allowDowngrade) {
|
|
184
|
+
ctx.deps.err(`Warning: downgrading to Runtime ${pinned}: ${newer.join(" and ")}. Tenants a newer Runtime migrated are quarantined.`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
throw new CliError(`Refusing to downgrade: this nylorun pins Runtime ${pinned}, but ${newer.join(" and ")}. Every project and app on this machine shares the stack, and a Runtime older than a Tenant's schema quarantines that Tenant. Update nylorun (npx nylorun@latest up), or run "nylorun start --allow-downgrade" to start Runtime ${pinned} anyway.`, 5);
|
|
188
|
+
}
|
|
138
189
|
async function bringUp(ctx, options) {
|
|
139
190
|
const { deps } = ctx;
|
|
140
191
|
await refuseLauncherRuntime(ctx);
|
|
192
|
+
const overridden = runtimeImageOverridden(deps.env);
|
|
193
|
+
if (!overridden)
|
|
194
|
+
await refuseDowngrade(ctx, options.allowDowngrade ?? false);
|
|
141
195
|
const prepared = await prepareStack({
|
|
142
196
|
paths: ctx.paths,
|
|
143
197
|
images: stackImages(deps.env, {
|
|
@@ -146,8 +200,10 @@ async function bringUp(ctx, options) {
|
|
|
146
200
|
}),
|
|
147
201
|
uid: deps.uid,
|
|
148
202
|
gid: deps.gid,
|
|
149
|
-
|
|
203
|
+
// An overriding image's version is unknown: keep the recorded one.
|
|
204
|
+
runtimeVersion: overridden ? undefined : deps.runtimeVersion,
|
|
150
205
|
ports: deps.ports,
|
|
206
|
+
...(options.studioEmbedOrigins ? { studioEmbedOrigins: options.studioEmbedOrigins } : {}),
|
|
151
207
|
});
|
|
152
208
|
if (prepared.firstRun)
|
|
153
209
|
deps.err(`Wrote ${ctx.paths.compose} and ${ctx.paths.env} (Runtime port ${prepared.env.runtimePort}, Studio port ${prepared.env.studioPort}).`);
|
|
@@ -204,13 +260,24 @@ async function openLogin(ctx, login) {
|
|
|
204
260
|
}
|
|
205
261
|
/** Printed by `start` while the Host has no Tenant: nylorun never creates one. */
|
|
206
262
|
export const TENANT_HINT = "No Tenant yet. Create one in Studio, or run `npx @nylorun/cli tenant create` in your project.";
|
|
263
|
+
const START_USAGE = "nylorun start [--no-studio] [--no-open] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset]";
|
|
207
264
|
async function start(ctx, args) {
|
|
208
|
-
const
|
|
209
|
-
|
|
265
|
+
const flags = parseStackFlags(args, {
|
|
266
|
+
booleans: ["--no-studio", "--no-open", "--allow-downgrade", "--studio-embed-origin-reset"],
|
|
267
|
+
lists: ["--studio-embed-origin"],
|
|
268
|
+
}, START_USAGE);
|
|
210
269
|
if (flags.rest.length)
|
|
211
|
-
throw usageError(`Usage: ${
|
|
270
|
+
throw usageError(`Usage: ${START_USAGE}`);
|
|
212
271
|
await dockerPreflight(ctx.deps.docker);
|
|
213
|
-
const
|
|
272
|
+
const embedOrigins = flags.lists.get("--studio-embed-origin") ?? [];
|
|
273
|
+
const resetEmbed = flags.booleans.has("--studio-embed-origin-reset");
|
|
274
|
+
const started = await bringUp(ctx, {
|
|
275
|
+
studio: !flags.booleans.has("--no-studio"),
|
|
276
|
+
allowDowngrade: flags.booleans.has("--allow-downgrade"),
|
|
277
|
+
...(embedOrigins.length || resetEmbed
|
|
278
|
+
? { studioEmbedOrigins: { add: embedOrigins, reset: resetEmbed } }
|
|
279
|
+
: {}),
|
|
280
|
+
});
|
|
214
281
|
ctx.deps.out(`Runtime ${started.runtimeUrl}`);
|
|
215
282
|
if (started.studioStarted) {
|
|
216
283
|
ctx.deps.out(`Studio ${studioOrigin(started.studioPort)}`);
|
|
@@ -280,8 +347,12 @@ async function stackStatus(ctx) {
|
|
|
280
347
|
const health = runtimeUrl ? await fetchHealth(ctx.deps, runtimeUrl) : undefined;
|
|
281
348
|
const host = await readHostConfig(ctx.paths);
|
|
282
349
|
const healthy = health?.status === "ok" && (host === undefined || health.hostId === host.hostId);
|
|
283
|
-
|
|
284
|
-
|
|
350
|
+
// The Admin API answers on the operator port; an older stack has only the Runtime port.
|
|
351
|
+
const adminUrl = persisted.adminPort
|
|
352
|
+
? `http://${STACK_CLIENT_HOST}:${persisted.adminPort}`
|
|
353
|
+
: runtimeUrl;
|
|
354
|
+
const tenants = healthy && adminUrl
|
|
355
|
+
? await adminTenantCount(ctx.deps, adminUrl, await readAdminKey(ctx.paths))
|
|
285
356
|
: undefined;
|
|
286
357
|
const studio = services.find((s) => s.service === "studio");
|
|
287
358
|
return {
|
|
@@ -289,6 +360,7 @@ async function stackStatus(ctx) {
|
|
|
289
360
|
state: services.some((s) => s.state === "running") ? "running" : "stopped",
|
|
290
361
|
runtime: {
|
|
291
362
|
...(runtimeUrl ? { url: runtimeUrl } : {}),
|
|
363
|
+
...(persisted.adminPort && adminUrl ? { adminUrl } : {}),
|
|
292
364
|
healthy,
|
|
293
365
|
...(health?.version ? { version: health.version } : {}),
|
|
294
366
|
...(health?.hostId ? { hostId: health.hostId } : {}),
|
|
@@ -297,6 +369,9 @@ async function stackStatus(ctx) {
|
|
|
297
369
|
studio: {
|
|
298
370
|
...(persisted.studioPort ? { url: studioOrigin(persisted.studioPort) } : {}),
|
|
299
371
|
state: studio ? [studio.state, studio.health].filter(Boolean).join(", ") : "absent",
|
|
372
|
+
...(persisted.studioFrameAncestors
|
|
373
|
+
? { embedOrigins: persisted.studioFrameAncestors }
|
|
374
|
+
: {}),
|
|
300
375
|
},
|
|
301
376
|
restate: persisted.restatePort
|
|
302
377
|
? { url: `http://${STACK_CLIENT_HOST}:${persisted.restatePort}` }
|
|
@@ -326,7 +401,11 @@ async function status(ctx, args) {
|
|
|
326
401
|
? `healthy, ${result.runtime.version ?? "?"}, ${result.runtime.hostId ?? "?"}${result.runtime.tenants !== undefined ? `, ${result.runtime.tenants} Tenant(s)` : ""}`
|
|
327
402
|
: "not answering";
|
|
328
403
|
out(`Runtime ${result.runtime.url ?? "?"} ${runtimeDetail}`);
|
|
404
|
+
if (result.runtime.adminUrl)
|
|
405
|
+
out(`Admin API ${result.runtime.adminUrl} (operators only, never proxied)`);
|
|
329
406
|
out(`Studio ${result.studio.url ?? "?"} ${result.studio.state} (log in with "nylorun studio")`);
|
|
407
|
+
if (result.studio.embedOrigins?.length)
|
|
408
|
+
out(`Embeds ${result.studio.embedOrigins.join(" ")} (may show Studio in a frame)`);
|
|
330
409
|
if (result.restate.url)
|
|
331
410
|
out(`Restate UI ${result.restate.url}`);
|
|
332
411
|
out(`Services ${STACK_SERVICES.map((name) => {
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* Host root, the Runtime and Studio images) comes from `stack/.env`.
|
|
6
6
|
*
|
|
7
7
|
* Tenants are Postgres schemas, executed through Restate, with their history in
|
|
8
|
-
* s2-lite; the Runtime's /ready checks all three.
|
|
8
|
+
* s2-lite; the Runtime's /ready checks all three. Postgres runs with
|
|
9
|
+
* `wal_level=logical`: the stream relay feeds s2-lite from the record over logical
|
|
10
|
+
* replication (Durable Streams), and `max_slot_wal_keep_size` caps the WAL a stuck
|
|
11
|
+
* relay can hold.
|
|
9
12
|
*
|
|
10
13
|
* Restate signs requests to the Worker endpoint with the private key in
|
|
11
14
|
* `stack/restate-identity.pem`, mounted read-only; the Runtime gets the public
|
|
@@ -6,7 +6,10 @@ import { PINNED_IMAGES } from "./images.js";
|
|
|
6
6
|
* Host root, the Runtime and Studio images) comes from `stack/.env`.
|
|
7
7
|
*
|
|
8
8
|
* Tenants are Postgres schemas, executed through Restate, with their history in
|
|
9
|
-
* s2-lite; the Runtime's /ready checks all three.
|
|
9
|
+
* s2-lite; the Runtime's /ready checks all three. Postgres runs with
|
|
10
|
+
* `wal_level=logical`: the stream relay feeds s2-lite from the record over logical
|
|
11
|
+
* replication (Durable Streams), and `max_slot_wal_keep_size` caps the WAL a stuck
|
|
12
|
+
* relay can hold.
|
|
10
13
|
*
|
|
11
14
|
* Restate signs requests to the Worker endpoint with the private key in
|
|
12
15
|
* `stack/restate-identity.pem`, mounted read-only; the Runtime gets the public
|
|
@@ -19,6 +22,8 @@ name: nylorun
|
|
|
19
22
|
services:
|
|
20
23
|
postgres: # Session Store
|
|
21
24
|
image: ${PINNED_IMAGES.postgres}
|
|
25
|
+
# Logical replication feeds the stream relay; a stuck slot is capped at 4 GB of WAL.
|
|
26
|
+
command: ["postgres", "-c", "wal_level=logical", "-c", "max_slot_wal_keep_size=4GB"]
|
|
22
27
|
environment:
|
|
23
28
|
POSTGRES_USER: nylorun
|
|
24
29
|
POSTGRES_PASSWORD: \${NYLORUN_POSTGRES_PASSWORD:?run nylorun start}
|
|
@@ -77,6 +82,10 @@ services:
|
|
|
77
82
|
# published port as clients on this machine address it.
|
|
78
83
|
NYLORUN_ALLOWED_HOSTS: runtime:4000,localhost:\${NYLORUN_PORT},127.0.0.1:\${NYLORUN_PORT}
|
|
79
84
|
NYLORUN_PUBLIC_URL: http://localhost:\${NYLORUN_PORT} # reported by /v1/admin/status
|
|
85
|
+
# The Admin API on its own listener, published on loopback only; Studio reaches it on
|
|
86
|
+
# the stack network. Port 4000 serves the Tenant API alone.
|
|
87
|
+
NYLORUN_ADMIN_LISTEN_PORT: "4001"
|
|
88
|
+
NYLORUN_ADMIN_ALLOWED_HOSTS: runtime:4001,localhost:\${NYLORUN_ADMIN_PORT},127.0.0.1:\${NYLORUN_ADMIN_PORT}
|
|
80
89
|
NYLORUN_DATABASE_URL: postgres://nylorun:\${NYLORUN_POSTGRES_PASSWORD}@postgres:5432/nylorun
|
|
81
90
|
NYLORUN_RESTATE_INGRESS_URL: http://restate:8080
|
|
82
91
|
NYLORUN_RESTATE_ADMIN_URL: http://restate:9070
|
|
@@ -85,11 +94,16 @@ services:
|
|
|
85
94
|
NYLORUN_S2_ENDPOINT: http://s2:80
|
|
86
95
|
NYLORUN_S2_TOKEN: ignored # s2-lite has no access tokens yet
|
|
87
96
|
NYLORUN_WORKSPACE_STORE_URL: file:///workspaces
|
|
97
|
+
# Action endpoints on this machine: \`localhost\` in a registered URL means the Docker host.
|
|
98
|
+
NYLORUN_ENDPOINT_LOOPBACK: docker-host
|
|
99
|
+
extra_hosts:
|
|
100
|
+
host.docker.internal: host-gateway # the Docker host, also on Linux Docker Engine
|
|
88
101
|
volumes:
|
|
89
102
|
- \${NYLORUN_HOST_ROOT:?run nylorun start}:/nylorun # Host root
|
|
90
103
|
- workspaces:/workspaces
|
|
91
104
|
ports:
|
|
92
|
-
- "127.0.0.1:\${NYLORUN_PORT:?run nylorun start}:4000" # Tenant API,
|
|
105
|
+
- "127.0.0.1:\${NYLORUN_PORT:?run nylorun start}:4000" # Tenant API, SSE, browsers
|
|
106
|
+
- "127.0.0.1:\${NYLORUN_ADMIN_PORT:?run nylorun start}:4001" # Admin API (operators only)
|
|
93
107
|
healthcheck:
|
|
94
108
|
test: ["CMD", "node", "-e", "fetch('http://localhost:4000/ready').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"]
|
|
95
109
|
interval: 2s
|
|
@@ -103,11 +117,13 @@ services:
|
|
|
103
117
|
depends_on:
|
|
104
118
|
runtime: { condition: service_healthy }
|
|
105
119
|
environment:
|
|
106
|
-
NYLORUN_RUNTIME_URL: http://runtime:
|
|
120
|
+
NYLORUN_RUNTIME_URL: http://runtime:4001 # the operator listener: Admin and Tenant API
|
|
107
121
|
NYLORUN_ADMIN_KEY_FILE: /run/nylorun/host-credentials.json
|
|
108
122
|
PORT: "3000"
|
|
109
123
|
# Studio's Host check accepts localhost/127.0.0.1 on the published port.
|
|
110
124
|
NYLORUN_STUDIO_PUBLIC_PORT: \${NYLORUN_STUDIO_PORT}
|
|
125
|
+
# Exact origins that may frame Studio (Babai Desktop); kept in .env.
|
|
126
|
+
NYLORUN_STUDIO_FRAME_ANCESTORS: \${NYLORUN_STUDIO_FRAME_ANCESTORS:-}
|
|
111
127
|
volumes:
|
|
112
128
|
- \${NYLORUN_HOST_ROOT}/host-credentials.json:/run/nylorun/host-credentials.json:ro
|
|
113
129
|
ports:
|
package/dist/stack/env-file.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
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).
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_STUDIO_FRAME_ANCESTORS: readonly ["nylorun://localhost", "http://nylorun.localhost"];
|
|
1
6
|
/**
|
|
2
7
|
* Settings in `stack/.env` (mode 0600). Ports and the password persist; the
|
|
3
8
|
* Restate identity public key is derived from `restate-identity.pem`.
|
|
4
9
|
*/
|
|
5
10
|
export interface StackEnv {
|
|
6
|
-
/** Published Runtime port (loopback). */
|
|
11
|
+
/** Published Runtime port (loopback): the Tenant API. */
|
|
7
12
|
runtimePort: number;
|
|
13
|
+
/** Published operator port (loopback): the Admin API. */
|
|
14
|
+
adminPort: number;
|
|
8
15
|
/** Published Studio port (loopback). */
|
|
9
16
|
studioPort: number;
|
|
10
17
|
/** Published Restate UI and admin port (loopback). */
|
|
@@ -18,6 +25,8 @@ export interface StackEnv {
|
|
|
18
25
|
hostRoot: string;
|
|
19
26
|
runtimeImage: string;
|
|
20
27
|
studioImage: string;
|
|
28
|
+
/** Exact origins that may frame Studio, separated by spaces (persists). */
|
|
29
|
+
studioFrameAncestors: string;
|
|
21
30
|
}
|
|
22
31
|
export declare function renderEnvFile(env: StackEnv): string;
|
|
23
32
|
/** Parse KEY=value lines; comments and blank lines are skipped. */
|
|
@@ -25,8 +34,11 @@ export declare function parseEnvLines(text: string): Map<string, string>;
|
|
|
25
34
|
/** The settings that persist across starts, from an existing .env. */
|
|
26
35
|
export interface PersistedStackEnv {
|
|
27
36
|
runtimePort?: number;
|
|
37
|
+
adminPort?: number;
|
|
28
38
|
studioPort?: number;
|
|
29
39
|
restatePort?: number;
|
|
30
40
|
postgresPassword?: string;
|
|
41
|
+
/** Validated origins; absent when the line is missing (an older .env). */
|
|
42
|
+
studioFrameAncestors?: string[];
|
|
31
43
|
}
|
|
32
44
|
export declare function parsePersisted(text: string): PersistedStackEnv;
|
package/dist/stack/env-file.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { parseFrameAncestors } from "@nylorun/core/contracts";
|
|
1
2
|
import { CliError } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Origins that may embed Studio by default: Babai Desktop's `nylorun` scheme on
|
|
5
|
+
* macOS and Linux, and its WebView2 origin on Windows (Studio §8.9).
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_STUDIO_FRAME_ANCESTORS = [
|
|
8
|
+
"nylorun://localhost",
|
|
9
|
+
"http://nylorun.localhost",
|
|
10
|
+
];
|
|
2
11
|
const KEYS = {
|
|
3
12
|
runtimePort: "NYLORUN_PORT",
|
|
13
|
+
adminPort: "NYLORUN_ADMIN_PORT",
|
|
4
14
|
studioPort: "NYLORUN_STUDIO_PORT",
|
|
5
15
|
restatePort: "NYLORUN_RESTATE_PORT",
|
|
6
16
|
postgresPassword: "NYLORUN_POSTGRES_PASSWORD",
|
|
@@ -10,6 +20,7 @@ const KEYS = {
|
|
|
10
20
|
hostRoot: "NYLORUN_HOST_ROOT",
|
|
11
21
|
runtimeImage: "NYLORUN_RUNTIME_IMAGE",
|
|
12
22
|
studioImage: "NYLORUN_STUDIO_IMAGE",
|
|
23
|
+
studioFrameAncestors: "NYLORUN_STUDIO_FRAME_ANCESTORS",
|
|
13
24
|
};
|
|
14
25
|
/** Compose .env values: single quotes keep a value literal (no interpolation). */
|
|
15
26
|
function quote(key, value) {
|
|
@@ -34,6 +45,7 @@ export function renderEnvFile(env) {
|
|
|
34
45
|
"",
|
|
35
46
|
"# Published on 127.0.0.1; clients use http://localhost:<port>.",
|
|
36
47
|
line("runtimePort"),
|
|
48
|
+
line("adminPort"),
|
|
37
49
|
line("studioPort"),
|
|
38
50
|
line("restatePort"),
|
|
39
51
|
"",
|
|
@@ -52,6 +64,10 @@ export function renderEnvFile(env) {
|
|
|
52
64
|
line("runtimeImage"),
|
|
53
65
|
line("studioImage"),
|
|
54
66
|
"",
|
|
67
|
+
"# Exact origins that may show Studio in a frame (Babai Desktop). Kept across",
|
|
68
|
+
"# starts; change with nylorun start --studio-embed-origin <origin>.",
|
|
69
|
+
line("studioFrameAncestors"),
|
|
70
|
+
"",
|
|
55
71
|
].join("\n");
|
|
56
72
|
}
|
|
57
73
|
/** Parse KEY=value lines; comments and blank lines are skipped. */
|
|
@@ -80,6 +96,9 @@ export function parsePersisted(text) {
|
|
|
80
96
|
const runtimePort = port(values.get(KEYS.runtimePort));
|
|
81
97
|
if (runtimePort)
|
|
82
98
|
out.runtimePort = runtimePort;
|
|
99
|
+
const adminPort = port(values.get(KEYS.adminPort));
|
|
100
|
+
if (adminPort)
|
|
101
|
+
out.adminPort = adminPort;
|
|
83
102
|
const studioPort = port(values.get(KEYS.studioPort));
|
|
84
103
|
if (studioPort)
|
|
85
104
|
out.studioPort = studioPort;
|
|
@@ -89,5 +108,14 @@ export function parsePersisted(text) {
|
|
|
89
108
|
const password = values.get(KEYS.postgresPassword);
|
|
90
109
|
if (password && /^[A-Za-z0-9]{16,}$/.test(password))
|
|
91
110
|
out.postgresPassword = password;
|
|
111
|
+
const ancestors = values.get(KEYS.studioFrameAncestors);
|
|
112
|
+
if (ancestors !== undefined) {
|
|
113
|
+
try {
|
|
114
|
+
out.studioFrameAncestors = parseFrameAncestors(ancestors);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
throw new CliError(`${KEYS.studioFrameAncestors} in stack/.env: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
92
120
|
return out;
|
|
93
121
|
}
|
|
@@ -13,6 +13,8 @@ export interface HostConfigFile {
|
|
|
13
13
|
hostId: string;
|
|
14
14
|
host: string;
|
|
15
15
|
port: number;
|
|
16
|
+
/** The operator listener's published port (the Admin API); absent on older stacks. */
|
|
17
|
+
adminPort?: number;
|
|
16
18
|
runtimeVersion?: string;
|
|
17
19
|
[field: string]: unknown;
|
|
18
20
|
}
|
|
@@ -21,12 +23,14 @@ export declare function newHostId(): string;
|
|
|
21
23
|
export declare function ensureHostLayout(paths: StackPaths): Promise<void>;
|
|
22
24
|
export declare function readHostConfig(paths: StackPaths): Promise<Record<string, unknown> | undefined>;
|
|
23
25
|
/**
|
|
24
|
-
* Write host.json with the published port
|
|
25
|
-
* of an existing file
|
|
26
|
+
* Write host.json with the published port and the Runtime version that runs
|
|
27
|
+
* the Host. Keeps the hostId and unknown fields of an existing file, and its
|
|
28
|
+
* runtimeVersion when `runtimeVersion` is undefined; refuses a newer format.
|
|
26
29
|
*/
|
|
27
30
|
export declare function writeStackHostConfig(paths: StackPaths, input: {
|
|
28
31
|
port: number;
|
|
29
|
-
|
|
32
|
+
adminPort?: number;
|
|
33
|
+
runtimeVersion: string | undefined;
|
|
30
34
|
}): Promise<HostConfigFile>;
|
|
31
35
|
export declare function readAdminKey(paths: StackPaths): Promise<string | undefined>;
|
|
32
36
|
/** Create host-credentials.json (mode 0600) if missing; always leave it 0600. */
|
package/dist/stack/host-files.js
CHANGED
|
@@ -46,8 +46,9 @@ export async function readHostConfig(paths) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Write host.json with the published port
|
|
50
|
-
* of an existing file
|
|
49
|
+
* Write host.json with the published port and the Runtime version that runs
|
|
50
|
+
* the Host. Keeps the hostId and unknown fields of an existing file, and its
|
|
51
|
+
* runtimeVersion when `runtimeVersion` is undefined; refuses a newer format.
|
|
51
52
|
*/
|
|
52
53
|
export async function writeStackHostConfig(paths, input) {
|
|
53
54
|
const existing = (await readHostConfig(paths)) ?? {};
|
|
@@ -64,7 +65,8 @@ export async function writeStackHostConfig(paths, input) {
|
|
|
64
65
|
hostId,
|
|
65
66
|
host: STACK_CLIENT_HOST,
|
|
66
67
|
port: input.port,
|
|
67
|
-
|
|
68
|
+
...(input.adminPort === undefined ? {} : { adminPort: input.adminPort }),
|
|
69
|
+
...(input.runtimeVersion !== undefined ? { runtimeVersion: input.runtimeVersion } : {}),
|
|
68
70
|
};
|
|
69
71
|
await writeAtomic(paths.config, `${JSON.stringify(config, null, 2)}\n`);
|
|
70
72
|
return config;
|
package/dist/stack/images.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface StackImages {
|
|
|
16
16
|
restate: string;
|
|
17
17
|
s2: string;
|
|
18
18
|
}
|
|
19
|
+
/** `NYLORUN_RUNTIME_IMAGE` names the Runtime image, so its version is unknown here. */
|
|
20
|
+
export declare function runtimeImageOverridden(env: Readonly<Record<string, string | undefined>>): boolean;
|
|
19
21
|
export declare function stackImages(env: Readonly<Record<string, string | undefined>>, versions?: {
|
|
20
22
|
runtime: string;
|
|
21
23
|
studio: string;
|
package/dist/stack/images.js
CHANGED
|
@@ -10,18 +10,22 @@ export const PINNED_IMAGES = {
|
|
|
10
10
|
restate: "docker.restate.dev/restatedev/restate:1.7.12",
|
|
11
11
|
s2: "ghcr.io/s2-streamstore/s2:0.43.0",
|
|
12
12
|
};
|
|
13
|
+
function override(env, name) {
|
|
14
|
+
const value = env[name]?.trim();
|
|
15
|
+
return value ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
/** `NYLORUN_RUNTIME_IMAGE` names the Runtime image, so its version is unknown here. */
|
|
18
|
+
export function runtimeImageOverridden(env) {
|
|
19
|
+
return override(env, "NYLORUN_RUNTIME_IMAGE") !== undefined;
|
|
20
|
+
}
|
|
13
21
|
export function stackImages(env, versions = {
|
|
14
22
|
runtime: pinnedVersion("runtime"),
|
|
15
23
|
studio: pinnedVersion("studio"),
|
|
16
24
|
}) {
|
|
17
|
-
const override = (name) => {
|
|
18
|
-
const value = env[name]?.trim();
|
|
19
|
-
return value ? value : undefined;
|
|
20
|
-
};
|
|
21
25
|
return {
|
|
22
|
-
runtime: override("NYLORUN_RUNTIME_IMAGE") ??
|
|
26
|
+
runtime: override(env, "NYLORUN_RUNTIME_IMAGE") ??
|
|
23
27
|
`ghcr.io/nylorun/runtime:${versions.runtime}`,
|
|
24
|
-
studio: override("NYLORUN_STUDIO_IMAGE") ??
|
|
28
|
+
studio: override(env, "NYLORUN_STUDIO_IMAGE") ??
|
|
25
29
|
`ghcr.io/nylorun/studio:${versions.studio}`,
|
|
26
30
|
...PINNED_IMAGES,
|
|
27
31
|
};
|
package/dist/stack/ports.d.ts
CHANGED
package/dist/stack/ports.js
CHANGED
package/dist/stack/prepare.d.ts
CHANGED
|
@@ -23,6 +23,15 @@ export declare function prepareStack(input: {
|
|
|
23
23
|
images: StackImages;
|
|
24
24
|
uid: number;
|
|
25
25
|
gid: number;
|
|
26
|
-
|
|
26
|
+
/** Recorded in host.json; undefined keeps the recorded version. */
|
|
27
|
+
runtimeVersion: string | undefined;
|
|
27
28
|
ports: PortProbe;
|
|
29
|
+
/**
|
|
30
|
+
* Changes to the origins that may embed Studio: `reset` goes back to the
|
|
31
|
+
* defaults, `add` appends (for example a desktop app's dev server).
|
|
32
|
+
*/
|
|
33
|
+
studioEmbedOrigins?: {
|
|
34
|
+
add?: readonly string[];
|
|
35
|
+
reset?: boolean;
|
|
36
|
+
};
|
|
28
37
|
}): Promise<PreparedStack>;
|
package/dist/stack/prepare.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { chmod, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import { renderComposeFile } from "./compose-file.js";
|
|
4
|
-
import {
|
|
4
|
+
import { parseFrameAncestors } from "@nylorun/core/contracts";
|
|
5
|
+
import { CliError } from "../errors.js";
|
|
6
|
+
import { DEFAULT_STUDIO_FRAME_ANCESTORS, parsePersisted, renderEnvFile, } from "./env-file.js";
|
|
5
7
|
import { ensureHostCredentials, ensureHostLayout, writeStackHostConfig, } from "./host-files.js";
|
|
6
8
|
import { choosePort, DEFAULT_PORTS } from "./ports.js";
|
|
7
9
|
import { ensureIdentityKey } from "./restate-identity.js";
|
|
@@ -38,12 +40,26 @@ export async function prepareStack(input) {
|
|
|
38
40
|
const taken = new Set();
|
|
39
41
|
const runtimePort = await choosePort(input.ports, DEFAULT_PORTS.runtime, persisted.runtimePort, taken);
|
|
40
42
|
taken.add(runtimePort);
|
|
43
|
+
const adminPort = await choosePort(input.ports, DEFAULT_PORTS.admin, persisted.adminPort, taken);
|
|
44
|
+
taken.add(adminPort);
|
|
41
45
|
const studioPort = await choosePort(input.ports, DEFAULT_PORTS.studio, persisted.studioPort, taken);
|
|
42
46
|
taken.add(studioPort);
|
|
43
47
|
const restatePort = await choosePort(input.ports, DEFAULT_PORTS.restate, persisted.restatePort, taken);
|
|
44
48
|
const identity = await ensureIdentityKey(paths.restateIdentity, writeFileMode);
|
|
49
|
+
let added;
|
|
50
|
+
try {
|
|
51
|
+
added = parseFrameAncestors((input.studioEmbedOrigins?.add ?? []).join(" "));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new CliError(`--studio-embed-origin: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
55
|
+
}
|
|
56
|
+
const base = input.studioEmbedOrigins?.reset || persisted.studioFrameAncestors === undefined
|
|
57
|
+
? [...DEFAULT_STUDIO_FRAME_ANCESTORS]
|
|
58
|
+
: persisted.studioFrameAncestors;
|
|
59
|
+
const studioFrameAncestors = [...new Set([...base, ...added])].join(" ");
|
|
45
60
|
const env = {
|
|
46
61
|
runtimePort,
|
|
62
|
+
adminPort,
|
|
47
63
|
studioPort,
|
|
48
64
|
restatePort,
|
|
49
65
|
postgresPassword: persisted.postgresPassword ?? randomBytes(24).toString("hex"),
|
|
@@ -53,10 +69,12 @@ export async function prepareStack(input) {
|
|
|
53
69
|
hostRoot: paths.root,
|
|
54
70
|
runtimeImage: input.images.runtime,
|
|
55
71
|
studioImage: input.images.studio,
|
|
72
|
+
studioFrameAncestors,
|
|
56
73
|
};
|
|
57
74
|
const { adminKey } = await ensureHostCredentials(paths);
|
|
58
75
|
const host = await writeStackHostConfig(paths, {
|
|
59
76
|
port: runtimePort,
|
|
77
|
+
adminPort,
|
|
60
78
|
runtimeVersion: input.runtimeVersion,
|
|
61
79
|
});
|
|
62
80
|
await writeFileMode(paths.env, renderEnvFile(env), 0o600);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nylorun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Set up and run the local Nylorun stack (Runtime and Studio) with Docker Compose",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"prepack": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nylorun/core": "0.
|
|
33
|
+
"@nylorun/core": "0.9.0-beta"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"nylorun": "dist/cli.js"
|
|
42
42
|
},
|
|
43
43
|
"nylorun": {
|
|
44
|
-
"runtime": "0.
|
|
45
|
-
"studio": "0.
|
|
44
|
+
"runtime": "0.14.0-beta",
|
|
45
|
+
"studio": "0.14.0-beta"
|
|
46
46
|
}
|
|
47
47
|
}
|