nylorun 0.5.0-beta → 0.6.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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # nylorun
2
2
 
3
+ ## 0.6.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 0cbd5c9: **Breaking (`nylorun`): readable Docker names, several Tenants on one machine, and Studio at `http://<name>.localhost:4160`.** See MIGRATION.md.
8
+
9
+ - **Breaking (`nylorun`): names.** Every container, the network and every volume carries the Tenant's Compose project: containers `nylorun-<name>-postgres`, `-restate`, `-s2-lite`, `-gateway`, `-runtime`, `-studio` (were `…-1`), network `nylorun-<name>` (was `nylorun-<name>_default`), volumes `nylorun-<name>-postgres`, `-restate`, `-s2-lite`, `-workspaces` (were `nylorun-<name>_postgres`, …), each labelled `dev.nylorun.tenant: <name>`. The Compose service `s2` is now `s2-lite` (`nylorun logs s2-lite`).
10
+ - **Breaking (`nylorun`): Tenants created by 0.5 start fresh.** `start` on a Tenant whose data is in the old volumes exits 3 without starting it and names them; `nylorun reset --tenant <name>` starts it anew. `reset` and `delete` also remove the old volumes and the old network.
11
+ - **Studio proxy.** One Caddy container per machine (`nylorun-proxy`, files in `~/.nylorun/proxy/`) gives each Tenant's Studio the address `http://<name>.localhost:<port>` (`NYLORUN_PROXY_PORT`, 4160 or a free port chosen once), on `127.0.0.1` and `[::1]` (IPv4 only, saying so, when Docker refuses `::1`). It holds no Tenant data and routes browsers only. `start` brings it up when Studio starts, prints Studio's proxy URL and signs in there (on Studio's own port when the proxy does not answer); a proxy failure never fails `start`. `status` shows both URLs, `ls` and `nylorun studio` the proxy's. `NYLORUN_PROXY_DISABLED=1` turns it off; a Tenant under `NYLORUN_HOME` or `NYLORUN_COMPOSE_PROJECT` does not use it. `doctor` has a `proxy` row.
12
+ - **Several Tenants.** Restate's RocksDB memory is capped at 256 MiB, so a Tenant uses about 600–700 MB (was about 1.3 GB). `nylorun ls` has a `MEMORY` column (`memoryBytes` in `--json`). `start` names the other running Tenants and their memory. `nylorun stop --all` stops every running Tenant and the proxy, keeping their volumes.
13
+ - **`@nylorun/studio`.** `NYLORUN_STUDIO_PUBLIC_ORIGINS` lists exact `http:` origins Studio also serves (Host and `Origin` checks); a login token's URL is on the origin it was minted on; `NYLORUN_STUDIO_SESSION_COOKIE` names the session cookie (default `nylorun_studio_session`; `nylorun` sets `nylorun_studio_<name>`, so Studios on one host keep their own sessions); the `421` answer lists the served origins.
14
+
15
+ ### Patch Changes
16
+
17
+ - Pin runtime to the tested release.
18
+ - Pin studio to the tested release.
19
+
3
20
  ## 0.5.0-beta
4
21
 
5
22
  ### Minor Changes
package/README.md CHANGED
@@ -36,12 +36,12 @@ npx nylorun doctor # Node 24+, Docker, Compose v2, and the Tenan
36
36
  ```sh
37
37
  nylorun up|start [--tenant <name>] [--no-link] [--no-studio] [--no-open] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset]
38
38
  # create (first run) and start the Tenant; link the project; print the URLs; open Studio signed in
39
- nylorun down|stop [--tenant <name>] # stop the containers; keep volumes
39
+ nylorun down|stop [--tenant <name> | --all] # stop the containers (--all: every Tenant's, and the Studio proxy); keep volumes
40
40
  nylorun status [--tenant <name>] [--json] # the Tenant, its Host root and id, services, endpoints, Runtime health
41
- nylorun logs [service] [--tenant <name>] [-f] [--tail <n>] # postgres, restate, s2, gateway, runtime, studio
41
+ nylorun logs [service] [--tenant <name>] [-f] [--tail <n>] # postgres, restate, s2-lite, gateway, runtime, studio
42
42
  nylorun studio [--tenant <name>] [--no-open] # sign a browser in to Studio on the Tenant; starts it if needed
43
43
  nylorun reset [--tenant <name>] [--yes] # delete the Tenant's volumes, Tenant directory and vault key; the next start creates it anew
44
- nylorun ls [--json] # the Tenants on this machine
44
+ nylorun ls [--json] # the Tenants on this machine, with their state, memory and URLs
45
45
  nylorun delete <tenant> --yes # remove a Tenant: containers, volumes, Host root and vault key
46
46
  nylorun doctor [--json] # prerequisites and the Tenant's health
47
47
  nylorun telemetry [status|enable|disable] # Studio's anonymous usage analytics
@@ -78,6 +78,42 @@ name). nylorun 0.4 kept Host roots in `~/.nylorun/stacks/`: the first command
78
78
  of this release moves them to `~/.nylorun/tenants/`, keeping their Compose
79
79
  projects and volumes.
80
80
 
81
+ ### Several Tenants
82
+
83
+ Tenants run side by side, each on its own ports, containers, network and
84
+ volumes ([The containers](#the-containers)); a running Tenant uses about
85
+ 600–700 MB. Each Studio has its own address through the
86
+ [Studio proxy](#the-studio-proxy), `http://<name>.localhost:4160`. `nylorun ls`
87
+ lists them with their state, the memory their containers use (`MEMORY`: the sum
88
+ of `docker stats` over the containers labelled `dev.nylorun.tenant=<name>`, `-`
89
+ when stopped; `memoryBytes` in `--json`, `null` when stopped or unknown), the
90
+ Runtime's URL and Studio's:
91
+
92
+ ```text
93
+ TENANT STATE MEMORY RUNTIME STUDIO PROJECT
94
+ api running 652 MB http://localhost:8790 http://api.localhost:4160 /Users/me/api
95
+ default stopped - http://localhost:8787 http://default.localhost:4160 -
96
+ ```
97
+
98
+ When other Tenants are running, `start` says so after its summary, on stderr
99
+ (without the size when Docker does not report it):
100
+
101
+ ```text
102
+ Also running: default, api (about 1.3 GB). "nylorun stop --all" stops them all.
103
+ ```
104
+
105
+ `nylorun stop --all` stops every running Tenant on this machine and the Studio
106
+ proxy, keeping their volumes; it cannot be combined with `--tenant` (exit 2).
107
+ `stop` of one Tenant leaves the proxy running.
108
+
109
+ A Tenant created by nylorun 0.5 keeps its data in volumes Compose named
110
+ `<project>_postgres`, `<project>_restate`, `<project>_s2` and
111
+ `<project>_workspaces`; this release names them `<project>-postgres`, … (see
112
+ [The containers](#the-containers)). `start` on such a Tenant exits 3 without
113
+ starting it, naming the old volumes: run `nylorun reset --tenant <name>` to
114
+ start it fresh. `reset` and `delete` also remove the old volumes (and the old
115
+ network `<project>_default`).
116
+
81
117
  A Tenant refuses only a Runtime older than its own database: `start` records
82
118
  the pinned Runtime version in `host.json` (`runtimeVersion`) and exits 5 when
83
119
  this nylorun pins an older one. Update nylorun, or pass `--allow-downgrade`
@@ -119,25 +155,79 @@ The Runtime registers the derived principals of `NYLORUN_DERIVED_PRINCIPALS`
119
155
  ## The containers
120
156
 
121
157
  `nylorun start` writes `compose.yaml` and `.env` (mode 0600) under
122
- `~/.nylorun/tenants/<name>/docker/` on the first run and reuses them after that: `postgres`,
123
- `restate`, `s2`, `gateway`, `runtime` and `studio`. Postgres initialises the
124
- Tenant's database with C collation. The Runtime and Studio images are pinned by
158
+ `~/.nylorun/tenants/<name>/docker/` on every start; `.env` keeps the ports and
159
+ secrets chosen on the first run. The services are `postgres`, `restate`,
160
+ `s2-lite`, `gateway`, `runtime` and `studio`. Container, network and volume
161
+ names are global on the Docker engine, so each carries the Tenant's Compose
162
+ project, and each has the label `dev.nylorun.tenant: <name>`. For Tenant `shop`:
163
+
164
+ | Thing | Name |
165
+ | --- | --- |
166
+ | Containers | `nylorun-shop-postgres`, `nylorun-shop-restate`, `nylorun-shop-s2-lite`, `nylorun-shop-gateway`, `nylorun-shop-runtime`, `nylorun-shop-studio` |
167
+ | Network | `nylorun-shop` |
168
+ | Volumes | `nylorun-shop-postgres`, `nylorun-shop-restate`, `nylorun-shop-s2-lite`, `nylorun-shop-workspaces` |
169
+
170
+ Postgres initialises the Tenant's database with C collation. Restate's RocksDB
171
+ memory is capped at 256 MiB (`RESTATE_ROCKSDB_TOTAL_MEMORY_SIZE`; Restate's
172
+ default is 2 GiB), so several Tenants fit on a laptop: a Tenant uses about
173
+ 600–700 MB, down from about 1.3 GB. The Runtime and Studio images are pinned by
125
174
  this release (`package.json` `nylorun.runtime` and `nylorun.studio`);
126
175
  `NYLORUN_RUNTIME_IMAGE` and `NYLORUN_STUDIO_IMAGE` override them (local builds,
127
176
  CI). Ports publish on loopback only: the Runtime on `8787`, its operator
128
177
  listener (Admin API) on `8788`, Studio on `4161` and the Restate UI on `9070`,
129
178
  or free ports chosen on the Tenant's first start, avoiding the ports other
130
- Tenants keep, and kept in its `.env`.
131
-
132
- `start` prints Studio as `http://localhost:<port>`. Studio has no password: in a
133
- terminal (not in CI, and not with `--no-open`), `start` asks the Studio container
134
- for a single-use login token with the admin key (`POST /_studio/login-tokens`)
135
- and opens `http://localhost:<port>/login?token=…` on the Tenant's page in the
136
- browser. That sets a session cookie for 30 days, which survives Studio
137
- restarts, so the printed URL keeps working in that browser. The token itself is
138
- never printed unless no browser starts. Otherwise `start` says to run `nylorun
139
- studio`, which signs a browser in the same way; `nylorun studio --no-open`
140
- prints the login URL (it works once, for two minutes) instead.
179
+ Tenants and the Studio proxy keep, and kept in its `.env`.
180
+
181
+ `start`, `ls` and `nylorun studio` give Studio as `http://<name>.localhost:4160`,
182
+ its address through the Studio proxy (below); `status` adds Studio's own
183
+ `http://localhost:<port>` (`--json` has both: `studio.url` is Studio's own port,
184
+ which an embedding app frames, and `studio.proxyUrl` the proxy's). Studio has no
185
+ password: in a terminal (not in CI, and not with `--no-open`), `start` asks the
186
+ Studio container for a single-use login token with the admin key (`POST
187
+ /_studio/login-tokens`, through the proxy; on Studio's own port, saying so,
188
+ when the proxy does not answer) and opens `…/login?token=…` on the Tenant's
189
+ page in the browser. That sets a session cookie for 30 days, which survives
190
+ Studio restarts, so the printed URL keeps working in that browser. The token
191
+ itself is never printed unless no browser starts. Otherwise `start` says to run
192
+ `nylorun studio`, which signs a browser in the same way; `nylorun studio
193
+ --no-open` prints the login URL (it works once, for two minutes) instead.
194
+
195
+ Each Tenant's Studio sets its own session cookie, `nylorun_studio_<name>`
196
+ (`NYLORUN_STUDIO_SESSION_COOKIE`).
197
+
198
+ ### The Studio proxy
199
+
200
+ One small Caddy container per machine (`nylorun-proxy`, Compose project
201
+ `nylorun-proxy`, label `dev.nylorun.proxy: "true"`) gives every Tenant's Studio
202
+ the address `http://<name>.localhost:<port>`. Browsers keep cookies per host,
203
+ not per port, so separate hosts keep the Studios' sessions apart. The proxy is
204
+ for browsers only: programs keep using the Runtime's `http://localhost:<port>`.
205
+ It holds no Tenant data; its files are in `~/.nylorun/proxy/` (`compose.yaml`,
206
+ `Caddyfile`, and `.env` with `NYLORUN_PROXY_PORT`, 4160 or a free port chosen
207
+ on its first start and kept; Caddy's own state is tmpfs). Its image is pinned
208
+ by this release (`caddy:2.11.6`).
209
+
210
+ - Caddy listens on port 80 in the container, published at `NYLORUN_PROXY_PORT`
211
+ on `127.0.0.1` and `[::1]` (macOS resolves `*.localhost` to `::1` only); when
212
+ Docker refuses `::1` (IPv6 off), it publishes on `127.0.0.1` alone and says
213
+ so once.
214
+ - It routes every Tenant under `~/.nylorun/tenants/`: it joins each Tenant's
215
+ network and reaches `<project>-studio:3000`, passing the browser's `Host`
216
+ through, which Studio checks. A stopped Tenant answers 502 naming
217
+ `nylorun start --tenant <name>`; an unknown host 404 naming `nylorun ls`.
218
+ - `start` rewrites its files, brings it up (only when Studio starts: not with
219
+ `--no-studio`), joins every Tenant's network and reloads Caddy; Studio learns
220
+ its proxy origin from `NYLORUN_STUDIO_PUBLIC_ORIGINS`, written to the
221
+ Tenant's `.env` on every start. A proxy that does not come up never fails
222
+ `start`: it warns once and prints Studio's own URL.
223
+ - `reset` and `delete` detach it from the Tenant's network first; `delete`
224
+ removes the Tenant's route. `stop --all` stops it.
225
+ - `NYLORUN_PROXY_DISABLED=1` turns it off: Studio is `http://localhost:<port>`.
226
+ A Tenant under `NYLORUN_HOME` or `NYLORUN_COMPOSE_PROJECT` (the repository's
227
+ smokes) does not use it either.
228
+
229
+ `nylorun doctor` reports it on a `proxy` row: running (on which loopbacks and
230
+ port), stopped, not created yet, or disabled.
141
231
 
142
232
  ### Embedding Studio in a desktop app
143
233
 
@@ -201,7 +291,7 @@ containers, volumes and Host root: the vault key (KEK) and all its data go with
201
291
  | 0 | Success |
202
292
  | 1 | Generic failure, including a missing Docker or Compose v2 |
203
293
  | 2 | Usage error (including no Tenant selected, or `delete` without `--yes`), or a removed or moved command |
204
- | 3 | `status`: the Runtime is not answering; `stop`/`logs`: no Tenant yet; `delete`: no such Tenant |
294
+ | 3 | `status`: the Runtime is not answering; `stop`/`logs`: no Tenant yet; `delete`: no such Tenant; `start`: a Tenant created by nylorun 0.5 (reset it) |
205
295
  | 4 | The Runtime port is held by another Host |
206
296
  | 5 | `up`/`start`/`studio`: this nylorun pins a Runtime older than the one that last ran the Tenant; update nylorun, or `start --allow-downgrade` |
207
297
  | 7 | The containers, the Tenant or Studio did not become ready (an unavailable Tenant's cause is printed) |
@@ -218,5 +308,7 @@ containers, volumes and Host root: the vault key (KEK) and all its data go with
218
308
  | Port conflict | Change `NYLORUN_PORT` / `NYLORUN_STUDIO_PORT` in `~/.nylorun/tenants/<name>/docker/.env` |
219
309
  | Logs | `nylorun logs runtime -f` |
220
310
  | Studio login expired | `nylorun studio` |
311
+ | `http://<name>.localhost:<port>` does not answer | `nylorun doctor` (the `proxy` row); `nylorun start` brings the proxy up; Studio's own port is in `nylorun status` |
312
+ | `Tenant … was created by nylorun 0.5` (exit 3) | `nylorun reset --tenant <name>` (its data starts fresh) |
221
313
 
222
314
  See [MIGRATION.md](../MIGRATION.md).
package/dist/doctor.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Check, type StackDeps, type StackStatus } from "./stack/index.js";
1
+ import { type Check, type ProxyStatus, type StackDeps, type StackStatus } from "./stack/index.js";
2
2
  export interface StackDoctorReport {
3
3
  node: {
4
4
  version: string;
@@ -9,6 +9,8 @@ export interface StackDoctorReport {
9
9
  tenant?: Pick<StackStatus, "name" | "project" | "home" | "state" | "runtime" | "tenant" | "studio" | "gateway">;
10
10
  /** Why no Tenant was checked: none is selected here (a project without a link). */
11
11
  noTenant?: string;
12
+ /** The machine's Studio proxy (`http://<tenant>.localhost:<port>`); never a failure. */
13
+ proxy?: ProxyStatus;
12
14
  }
13
15
  /**
14
16
  * `nylorun doctor`: check the prerequisites of a local Tenant (Node 24+,
package/dist/doctor.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CliError } from "./errors.js";
2
- import { checkDocker, defaultStackDeps, readStackStatus, } from "./stack/index.js";
2
+ import { checkDocker, defaultStackDeps, readProxyReport, readStackStatus, } from "./stack/index.js";
3
3
  /** Oldest Node nylorun and the developer's application run on. */
4
4
  const MIN_NODE_MAJOR = 24;
5
5
  /**
@@ -36,6 +36,7 @@ export async function doctorStack(options) {
36
36
  throw error;
37
37
  report.noTenant = error.message;
38
38
  }
39
+ report.proxy = await readProxyReport(deps);
39
40
  }
40
41
  const broken = report.tenant?.state === "running" &&
41
42
  (!report.tenant.runtime.healthy ||
@@ -95,9 +96,25 @@ export async function doctorStack(options) {
95
96
  ? `✓ ${local.tenant.id ?? "?"} open`
96
97
  : `✗ ${local.tenant.id ?? "?"} unavailable${local.tenant.cause ? `: ${local.tenant.cause.code}: ${local.tenant.cause.repair}` : " (opening)"}`,
97
98
  ]);
98
- rows.push(["studio", `${local.studio.url ?? "?"} · ${local.studio.state}`]);
99
+ rows.push(["studio", `${local.studio.proxyUrl ?? local.studio.url ?? "?"} · ${local.studio.state}`]);
99
100
  }
100
101
  }
102
+ const proxy = report.proxy;
103
+ if (proxy) {
104
+ const on = proxy.port
105
+ ? `127.0.0.1:${proxy.port}${proxy.ipv6 ? ` and [::1]:${proxy.port}` : ""}`
106
+ : "";
107
+ rows.push([
108
+ "proxy",
109
+ proxy.state === "running"
110
+ ? `✓ running on ${on} (http://<tenant>.localhost:${proxy.port ?? "?"})`
111
+ : proxy.state === "disabled"
112
+ ? "- disabled (NYLORUN_PROXY_DISABLED): Studio is on each Tenant's own port"
113
+ : proxy.state === "stopped"
114
+ ? `- stopped${on ? ` (${on})` : ""}: nylorun start starts it`
115
+ : "- not created yet: nylorun start creates it",
116
+ ]);
117
+ }
101
118
  const width = Math.max(...rows.map(([key]) => key.length)) + 2;
102
119
  for (const [key, value] of rows)
103
120
  log(` ${key.padEnd(width)}${value}`);
@@ -1,8 +1,9 @@
1
1
  import type { HostTenant } from "@nylorun/core/contracts";
2
2
  import { type ComposeService, type DockerRunner } from "./docker.js";
3
3
  import type { PortProbe } from "./ports.js";
4
+ import { type ProxyStatus } from "./proxy.js";
4
5
  import { type FetchLike } from "./studio-login.js";
5
- export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2", "gateway", "runtime", "studio"];
6
+ export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2-lite", "gateway", "runtime", "studio"];
6
7
  export declare const stackUsage: string;
7
8
  export interface StackDeps {
8
9
  /** Environment snapshot (NYLORUN_HOME, NYLORUN_TENANT, image overrides, NYLORUN_COMPOSE_PROJECT). */
@@ -73,7 +74,10 @@ export interface StackStatus {
73
74
  /** The Tenant as the Runtime reports it, while it answers. */
74
75
  tenant?: StackTenant;
75
76
  studio: {
77
+ /** Studio's own port: what an embedding app frames (same site as its own localhost). */
76
78
  url?: string;
79
+ /** Through the Studio proxy, when it serves the Tenant. */
80
+ proxyUrl?: string;
77
81
  state: string;
78
82
  /** Exact origins that may show Studio in a frame (Studio §8.9). */
79
83
  embedOrigins?: string[];
@@ -88,6 +92,8 @@ export interface StackStatus {
88
92
  };
89
93
  services: ComposeService[];
90
94
  }
95
+ /** The Studio proxy, as data (`nylorun doctor`). */
96
+ export declare function readProxyReport(deps: StackDeps): Promise<ProxyStatus>;
91
97
  /** What `nylorun status` reports, as data (`nylorun doctor`). */
92
98
  export declare function readStackStatus(deps: StackDeps, options?: {
93
99
  name?: string;
@@ -105,6 +111,8 @@ export interface StackEndpoints {
105
111
  hostId: string;
106
112
  adminKey: string;
107
113
  studioPort: number;
114
+ /** Through the proxy when it serves the Tenant, else Studio's own port. */
115
+ studioUrl: string;
108
116
  /** Studio is running and healthy. */
109
117
  studioUp: boolean;
110
118
  /** This call started the Runtime (it was not running before). */
@@ -127,7 +135,7 @@ export declare function tenantStudioPath(tenantId: string): string;
127
135
  * Mint a fresh Studio login URL on a running Tenant, landing on `next`.
128
136
  * Undefined (after a warning) when Studio does not answer.
129
137
  */
130
- export declare function studioLoginUrl(deps: StackDeps, running: Pick<StackEndpoints, "studioPort" | "adminKey">, next?: string): Promise<string | undefined>;
138
+ export declare function studioLoginUrl(deps: StackDeps, running: Pick<StackEndpoints, "studioPort" | "studioUrl" | "adminKey">, next?: string): Promise<string | undefined>;
131
139
  /** `nylorun studio`, landing on `next` when given, else on the Tenant's page. */
132
140
  export declare function runStudioCommand(args: readonly string[], deps: StackDeps, options?: {
133
141
  next?: string;