smolmachines 1.22.2 → 1.25.1
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/README.md +72 -0
- package/dist/binding.d.ts +97 -3
- package/dist/generated/smolfleet.d.ts +10863 -1988
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/machine.d.ts +47 -8
- package/dist/machine.js +61 -7
- package/dist/native.d.ts +10 -2
- package/dist/transport.d.ts +44 -1
- package/dist/transport.js +305 -23
- package/dist/types.d.ts +104 -2
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -87,6 +87,33 @@ try {
|
|
|
87
87
|
}
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
+
For a local VM whose published service starts through `exec()`, set
|
|
91
|
+
`waitForPorts: false` on creation. The SDK still waits for the guest agent,
|
|
92
|
+
so `exec()` is safe; call `waitUntilReady()` once the service is listening.
|
|
93
|
+
The default keeps waiting for all published ports. This option is local-only.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const m = await Machine.create(
|
|
97
|
+
{ image: 'node:24-alpine', ports: [{ host: 18080, guest: 8080 }], waitForPorts: false },
|
|
98
|
+
{ target: 'local' },
|
|
99
|
+
);
|
|
100
|
+
try {
|
|
101
|
+
await m.exec(['sh', '-c',
|
|
102
|
+
`nohup node -e 'require("http").createServer((_, res) => res.end("ok"))
|
|
103
|
+
.listen(8080, "0.0.0.0")' >/tmp/server.log 2>&1 &`,
|
|
104
|
+
]);
|
|
105
|
+
await m.waitUntilReady();
|
|
106
|
+
console.log(await (await m.fetch(8080)).text());
|
|
107
|
+
} finally {
|
|
108
|
+
await m.delete();
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
When reconnecting to clean up a local machine whose published service has
|
|
113
|
+
stopped, `Machine.connect(name, { target: 'local', waitForPorts: false })`
|
|
114
|
+
attaches after the agent is ready, so `delete()` can run without a listener.
|
|
115
|
+
The default continues waiting for all published ports.
|
|
116
|
+
|
|
90
117
|
To reach a service **inside** the VM, use the authenticated connect bridge —
|
|
91
118
|
**no Cloudflare/localhost.run tunnel, no public exposure, no egress allow-list.**
|
|
92
119
|
Have the worker LISTEN on a published port and connect *inbound*:
|
|
@@ -249,6 +276,51 @@ const session = await Machine.restoreCheckpoint('prepared.smolcheckpoint', 'sess
|
|
|
249
276
|
allow lists on the host. A restored machine keeps its checkpoint's backend, so
|
|
250
277
|
an allow list on a TSI checkpoint is refused rather than left unenforced.
|
|
251
278
|
|
|
279
|
+
### API keys the workload uses without seeing
|
|
280
|
+
|
|
281
|
+
Bind a credential to the hosts it is for. The guest variable holds a
|
|
282
|
+
placeholder, and the real value is substituted only in the headers of HTTPS
|
|
283
|
+
requests to those hosts:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const machine = await Machine.create({
|
|
287
|
+
image: 'alpine:3.20',
|
|
288
|
+
credentials: [
|
|
289
|
+
{
|
|
290
|
+
name: 'notion',
|
|
291
|
+
envVar: 'NOTION_API_KEY',
|
|
292
|
+
hosts: ['api.notion.com', 'files.notion.com'],
|
|
293
|
+
value: process.env.NOTION_API_KEY,
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
await machine.exec(['sh', '-c', 'curl -H "Authorization: Bearer $NOTION_API_KEY" https://api.notion.com/v1/users/me']);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
- Hosts are exact names; list each subdomain.
|
|
301
|
+
- Locally the value stays in this process's memory. Without `value`, this
|
|
302
|
+
process's own `envVar` is read at each start, which is how a machine reopened
|
|
303
|
+
by a later process gets it. `methods: ['GET', 'HEAD']` limits a read-only token.
|
|
304
|
+
- On the cloud, a credential with a `value` is stored for your account under
|
|
305
|
+
`name`; one without refers to a credential already stored there.
|
|
306
|
+
|
|
307
|
+
## Cache disks (cloud)
|
|
308
|
+
|
|
309
|
+
A cache disk is a disk image many machines start from, each through its own
|
|
310
|
+
copy-on-write layer: dependencies, build caches, datasets. Publish a stopped
|
|
311
|
+
machine's cache as the next version, and later machines start from it.
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { CacheDisk, Machine } from "smolmachines";
|
|
315
|
+
|
|
316
|
+
await CacheDisk.create({ name: "deps" }); // v0: empty
|
|
317
|
+
const m = await Machine.create({ image: "node:22", cacheDisk: { cache: "deps" } });
|
|
318
|
+
await m.exec(["sh", "-c", "cd /cache && npm install"]);
|
|
319
|
+
await m.stop();
|
|
320
|
+
await m.publishCacheDisk(); // v1
|
|
321
|
+
// Every machine created with cacheDisk: { cache: "deps" } now starts from v1.
|
|
322
|
+
```
|
|
323
|
+
|
|
252
324
|
## Building from source
|
|
253
325
|
|
|
254
326
|
This package's native core lives alongside it (Rust, `src/*.rs`) and links the
|
package/dist/binding.d.ts
CHANGED
|
@@ -36,6 +36,47 @@ export interface MachineConfig {
|
|
|
36
36
|
persistent?: boolean
|
|
37
37
|
/** If true, every start uses cloneable, memfd-backed guest RAM. */
|
|
38
38
|
forkable?: boolean
|
|
39
|
+
/**
|
|
40
|
+
* If true, the VM outlives this process: its boot subprocess does not
|
|
41
|
+
* watch the parent, so it keeps running when this process exits or
|
|
42
|
+
* crashes, and a later process reattaches with `connect`. Remembered by
|
|
43
|
+
* the machine, so later starts are detached too.
|
|
44
|
+
*/
|
|
45
|
+
detached?: boolean
|
|
46
|
+
/**
|
|
47
|
+
* Caller metadata stored with the machine and returned by `list`; the
|
|
48
|
+
* engine never interprets it.
|
|
49
|
+
*/
|
|
50
|
+
labels?: Record<string, string>
|
|
51
|
+
/**
|
|
52
|
+
* Credentials the workload uses without seeing them, as the CLI's
|
|
53
|
+
* `--credential`.
|
|
54
|
+
*/
|
|
55
|
+
credentials?: Array<CredentialConfig>
|
|
56
|
+
}
|
|
57
|
+
/** One machine from this host's shared machine database. */
|
|
58
|
+
export interface MachineSummary {
|
|
59
|
+
/** Machine name. */
|
|
60
|
+
name: string
|
|
61
|
+
/**
|
|
62
|
+
* Lifecycle state as `state()` reports it: `running`, `stopped`,
|
|
63
|
+
* `frozen`, `paused`, `failed`, ...
|
|
64
|
+
*/
|
|
65
|
+
state: string
|
|
66
|
+
/** OCI image the machine boots, when it is an image machine. */
|
|
67
|
+
image?: string
|
|
68
|
+
/** Caller metadata given at create. */
|
|
69
|
+
labels: Record<string, string>
|
|
70
|
+
/** Host PID of the VM process while it is running. */
|
|
71
|
+
pid?: number
|
|
72
|
+
/** Whether the record outlives the process that created it. */
|
|
73
|
+
persistent: boolean
|
|
74
|
+
/** Whether the VM outlives the process that starts it. */
|
|
75
|
+
detached: boolean
|
|
76
|
+
/** Whether it starts as a live branch source. */
|
|
77
|
+
branchable: boolean
|
|
78
|
+
/** Creation time, seconds since the Unix epoch. */
|
|
79
|
+
createdAt: number
|
|
39
80
|
}
|
|
40
81
|
/** Whether this host can run local machines, from the engine's own checks. */
|
|
41
82
|
export interface HostAvailability {
|
|
@@ -89,6 +130,26 @@ export interface MachineResources {
|
|
|
89
130
|
/** Overlay disk size in GiB, or unset while it has the default size. */
|
|
90
131
|
overlayGib?: number
|
|
91
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* A credential the workload uses without seeing it: the guest variable
|
|
135
|
+
* `env_var` holds a placeholder, and the host substitutes the real value in
|
|
136
|
+
* request headers of HTTPS requests to `hosts` only.
|
|
137
|
+
*/
|
|
138
|
+
export interface CredentialConfig {
|
|
139
|
+
/** Binding name. */
|
|
140
|
+
name: string
|
|
141
|
+
/** Guest environment variable that holds the placeholder. */
|
|
142
|
+
envVar: string
|
|
143
|
+
/** Exact host names the value may be sent to. */
|
|
144
|
+
hosts: Array<string>
|
|
145
|
+
/** HTTP methods the value may be used with; all supported ones when unset. */
|
|
146
|
+
methods?: Array<string>
|
|
147
|
+
/**
|
|
148
|
+
* The value, held in this process's memory only. When unset, this
|
|
149
|
+
* process's own variable named `env_var` is read at each start.
|
|
150
|
+
*/
|
|
151
|
+
value?: string
|
|
152
|
+
}
|
|
92
153
|
/** VM resource allocation. */
|
|
93
154
|
export interface VmResourcesConfig {
|
|
94
155
|
/** Number of vCPUs (default: src/data/resources.rs default). */
|
|
@@ -123,6 +184,22 @@ export interface VmResourcesConfig {
|
|
|
123
184
|
* CUDA toolkit needed in the image). Local target only (default: false).
|
|
124
185
|
*/
|
|
125
186
|
cuda?: boolean
|
|
187
|
+
/**
|
|
188
|
+
* Network backend: `"tsi"` (outbound-only, the default for plain network
|
|
189
|
+
* access) or `"virtio-net"`. A machine that will be checkpointed and have
|
|
190
|
+
* its egress policy changed later needs virtio-net, since the policy is
|
|
191
|
+
* enforced by virtio-net's host-side stack and a restore keeps the backend.
|
|
192
|
+
*/
|
|
193
|
+
networkBackend?: string
|
|
194
|
+
}
|
|
195
|
+
/** A replacement outbound network policy for a stopped machine. */
|
|
196
|
+
export interface EgressPolicyConfig {
|
|
197
|
+
/** Outbound network access at all. */
|
|
198
|
+
network: boolean
|
|
199
|
+
/** Addresses the machine may reach; empty means no address restriction. */
|
|
200
|
+
cidrs?: Array<string>
|
|
201
|
+
/** Host names (`api.github.com`, or `*.github.com` for subdomains only). */
|
|
202
|
+
hosts?: Array<string>
|
|
126
203
|
}
|
|
127
204
|
/** Options for executing a command. */
|
|
128
205
|
export interface ExecOptions {
|
|
@@ -218,8 +295,25 @@ export declare class NapiMachine {
|
|
|
218
295
|
* process — backs the SDK's local `Machine.connect()`.
|
|
219
296
|
*/
|
|
220
297
|
static connect(name: string, interceptorAddress?: string | undefined | null, interceptorToken?: string | undefined | null): NapiMachine
|
|
221
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* Every machine in this host's shared machine database, in name order.
|
|
300
|
+
* The database is shared with the CLI and every other embedder on the
|
|
301
|
+
* host, so machines other processes made are listed too — `labels` tell
|
|
302
|
+
* a process's own apart after it restarts.
|
|
303
|
+
*/
|
|
304
|
+
static list(): Promise<Array<MachineSummary>>
|
|
305
|
+
/**
|
|
306
|
+
* Create a stopped machine from a portable live checkpoint on disk.
|
|
307
|
+
* `keep_identity` restores it as the same machine going back in time, so
|
|
308
|
+
* its first start skips the identity reset a clone needs.
|
|
309
|
+
*/
|
|
222
310
|
static restoreCheckpoint(name: string, artifact: string, keepIdentity?: boolean | undefined | null): NapiMachine
|
|
311
|
+
/**
|
|
312
|
+
* Replace this stopped machine's outbound network policy, e.g. to give a
|
|
313
|
+
* machine just restored from a shared checkpoint its own allow list
|
|
314
|
+
* before it first boots.
|
|
315
|
+
*/
|
|
316
|
+
setEgressPolicy(policy: EgressPolicyConfig): void
|
|
223
317
|
/** Export a stored checkpoint directory as one portable checkpoint file. */
|
|
224
318
|
static exportCheckpoint(source: string, output: string): number
|
|
225
319
|
/** Remove objects that no retained checkpoint in a local store references. */
|
|
@@ -265,12 +359,12 @@ export declare class NapiMachine {
|
|
|
265
359
|
* live RAM + disks (same host). `ports` are `{ host, guest }` inbound
|
|
266
360
|
* forwards for the clone. Returns a handle to the running clone.
|
|
267
361
|
*/
|
|
268
|
-
fork(name: string, ports?: Array<PortMappingConfig> | undefined | null, checkpointable?: boolean | undefined | null): Promise<NapiMachine>
|
|
362
|
+
fork(name: string, ports?: Array<PortMappingConfig> | undefined | null, checkpointable?: boolean | undefined | null, freezeSource?: boolean | undefined | null): Promise<NapiMachine>
|
|
269
363
|
/**
|
|
270
364
|
* Fork many clones from one retained snapshot and boot them in bounded
|
|
271
365
|
* parallel waves. Transactional: an error removes every clone in this call.
|
|
272
366
|
*/
|
|
273
|
-
forkBatch(names: Array<string>, ports?: Array<PortMappingConfig> | undefined | null, parallel?: number | undefined | null): Promise<Array<NapiMachine>>
|
|
367
|
+
forkBatch(names: Array<string>, ports?: Array<PortMappingConfig> | undefined | null, parallel?: number | undefined | null, freezeSource?: boolean | undefined | null): Promise<Array<NapiMachine>>
|
|
274
368
|
/** Execute a command directly in the VM (not in a container). */
|
|
275
369
|
exec(command: Array<string>, options?: ExecOptions | undefined | null): Promise<ExecResult>
|
|
276
370
|
/**
|