next-lxd 1.0.0

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 ADDED
@@ -0,0 +1,780 @@
1
+ # next-lxd
2
+
3
+ **Production-grade LXD client for Node.js** — wraps the LXD REST API (`/1.0`) with a typed, class-based interface, plus native SFTP support via `next-lxd-sftp`.
4
+
5
+ > **Status:** v0.1.0 — MIT
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install next-lxd
13
+ ```
14
+
15
+ Requires Node.js 18+ (ES2022).
16
+
17
+ ### Dependencies
18
+
19
+ | Package | Purpose |
20
+ |---|---|
21
+ | `next-lxd-sftp` | Native Node-API addon for SFTP file operations |
22
+ | `undici` | HTTP client (used internally) |
23
+
24
+ ---
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { Client } from 'next-lxd';
30
+
31
+ const client = new Client({
32
+ url: 'https://10.0.0.1:8443',
33
+ cert: fs.readFileSync('client.crt'),
34
+ key: fs.readFileSync('client.key'),
35
+ allowInsecure: true,
36
+ });
37
+
38
+ // Test the connection
39
+ const server = await client.connectionTest();
40
+ console.log(server.metadata.server_version);
41
+
42
+ // Access resource collections via client properties
43
+ const instances = client.instances;
44
+ const images = client.images;
45
+ const networks = client.networks;
46
+ const profiles = client.profiles;
47
+ const projects = client.projects;
48
+ const storagePools = client.storagePools;
49
+ const certificates = client.certificates;
50
+ const operations = client.operations;
51
+ const cluster = client.cluster;
52
+ const warnings = client.warnings;
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Architecture
58
+
59
+ The package follows a two-level pattern:
60
+
61
+ - **Collection classes** (`Instances`, `Images`, `Networks`, etc.) — list resources, fetch individual resources by name, and create new resources (POST).
62
+ - **Resource classes** (`Instance`, `Image`, `Network`, etc.) — represent a single LXD resource and expose its CRUD operations and sub-resources.
63
+
64
+ All classes extend the abstract `Resource` base class, which holds a reference to the `Client`.
65
+
66
+ ---
67
+
68
+ ## API Reference
69
+
70
+ ### `Client`
71
+
72
+ The entry point. Creates an HTTPS agent with mutual TLS authentication and exposes all LXD resource collections.
73
+
74
+ ```typescript
75
+ import { Client, ClientOptions, RequestOptions, RawResponse } from 'next-lxd';
76
+ ```
77
+
78
+ #### Constructor
79
+
80
+ ```typescript
81
+ new Client(options: ClientOptions)
82
+ ```
83
+
84
+ | Option | Type | Description |
85
+ |---|---|---|
86
+ | `url` | `string` | LXD server URL (e.g. `https://10.0.0.1:8443`) |
87
+ | `cert` | `string \| Buffer` | TLS client certificate |
88
+ | `key` | `string \| Buffer` | TLS client private key |
89
+ | `ca` | `string \| Buffer` (optional) | Custom CA certificate |
90
+ | `password` | `string` (optional) | Trust password (for `POST /1.0/certificates`) |
91
+ | `allowInsecure` | `boolean` (optional) | Skip TLS verification (`rejectUnauthorized: false`) |
92
+
93
+ #### Properties
94
+
95
+ | Property | Type | Description |
96
+ |---|---|---|
97
+ | `url` | `URL` | Parsed server URL |
98
+ | `clientInfo` | `object` | Certificate/key metadata (read-only mirror) |
99
+
100
+ #### Methods
101
+
102
+ | Method | Description |
103
+ |---|---|
104
+ | `request<T>(options)` | Performs an HTTPS request and returns the parsed JSON body of type `T` |
105
+ | `requestRaw<T>(options)` | Performs an HTTPS request and returns `RawResponse<T>` (status, headers, body) |
106
+ | `connectionTest()` | GET `/1.0` — returns `LxdServerResponse` with server info |
107
+
108
+ #### `request` / `requestRaw` Options
109
+
110
+ ```typescript
111
+ {
112
+ method?: string; // default 'GET'
113
+ path: string; // e.g. '/1.0/instances'
114
+ headers?: Record<string, any>;
115
+ body?: unknown; // serialized as JSON
116
+ }
117
+ ```
118
+
119
+ #### Collections (auto-instantiated)
120
+
121
+ Each collection is a public readonly property on the `Client` instance:
122
+
123
+ | Property | Class |
124
+ |---|---|
125
+ | `client.instances` | `Instances` |
126
+ | `client.images` | `Images` |
127
+ | `client.networks` | `Networks` |
128
+ | `client.profiles` | `Profiles` |
129
+ | `client.projects` | `Projects` |
130
+ | `client.storagePools` | `StoragePools` |
131
+ | `client.certificates` | `Certificates` |
132
+ | `client.operations` | `Operations` |
133
+ | `client.cluster` | `Cluster` |
134
+ | `client.warnings` | `Warnings` |
135
+
136
+ ---
137
+
138
+ ### `Instances`
139
+
140
+ Collection class — access via `client.instances`.
141
+
142
+ | Method | HTTP | Path |
143
+ |---|---|---|
144
+ | `list(project?)` | GET | `/1.0/instances?project=...` |
145
+ | `get(name, project?)` | GET | `/1.0/instances/{name}` → returns `Instance` |
146
+ | `post(config, project?, target?)` | POST | `/1.0/instances` — creates an instance and returns the `Instance` object |
147
+
148
+ ---
149
+
150
+ ### `Instance`
151
+
152
+ Represents a single instance. Returned by `Instances.get()` / `Instances.post()`.
153
+
154
+ #### Properties
155
+
156
+ | Property | Type |
157
+ |---|---|
158
+ | `name` | `string` |
159
+ | `status` | `string` |
160
+ | `metadata` | `LxdInstanceResponse['metadata']` |
161
+ | `project` | `string \| undefined` |
162
+
163
+ #### Methods (direct CRUD)
164
+
165
+ | Method | HTTP | Path |
166
+ |---|---|---|
167
+ | `delete()` | DELETE | `/1.0/instances/{name}` |
168
+ | `put(data, project?)` | PUT | `/1.0/instances/{name}` — full replace |
169
+ | `patch(data, project?)` | PATCH | `/1.0/instances/{name}` — partial update |
170
+ | `post(data, project?)` | POST | `/1.0/instances/{name}` — rename/migrate |
171
+ | `rebuild(data, project?)` | POST | `/1.0/instances/{name}/rebuild` |
172
+
173
+ #### Nested sub-resources
174
+
175
+ ##### `instance.console`
176
+
177
+ | Method | HTTP | Path |
178
+ |---|---|---|
179
+ | `get()` | GET | `/1.0/instances/{name}/console` |
180
+ | `post({height, width, type})` | POST | `/1.0/instances/{name}/console` |
181
+
182
+ ##### `instance.exec(data)` — single method
183
+
184
+ | Method | HTTP | Path |
185
+ |---|---|---|
186
+ | `exec(data)` | POST | `/1.0/instances/{name}/exec` |
187
+
188
+ See `LxdInstanceExecRequest` for the request shape (command, environment, interactive, etc.).
189
+
190
+ ##### `instance.logs`
191
+
192
+ | Method | HTTP | Path |
193
+ |---|---|---|
194
+ | `get()` | GET | `/1.0/instances/{name}/logs` — list log files |
195
+ | `file(filename).get()` | GET | `/1.0/instances/{name}/logs/{filename}` |
196
+ | `file(filename).delete()` | DELETE | `/1.0/instances/{name}/logs/{filename}` |
197
+ | `execOutput.get()` | GET | `/1.0/instances/{name}/logs/exec-output` |
198
+
199
+ ##### `instance.state`
200
+
201
+ | Method | HTTP | Path |
202
+ |---|---|---|
203
+ | `get(project?)` | GET | `/1.0/instances/{name}/state` — CPU, memory, network, processes |
204
+ | `put(data, project?)` | PUT | `/1.0/instances/{name}/state` — start/stop/restart/freeze |
205
+
206
+ ##### `instance.files`
207
+
208
+ | Method | HTTP | Path | Notes |
209
+ |---|---|---|---|
210
+ | `file(path).get()` | GET | `/1.0/instances/{name}/files?path=...` | Returns headers + body |
211
+ | `file(path).delete()` | DELETE | `/1.0/instances/{name}/files?path=...` | |
212
+ | `file(path).head()` | HEAD | `/1.0/instances/{name}/files?path=...` | Returns headers only |
213
+ | `file(path).post(body, headers?)` | POST | `/1.0/instances/{name}/files?path=...` | Upload. Headers: `X-LXD-uid`, `X-LXD-gid`, `X-LXD-mode`, `X-LXD-type`, `X-LXD-write` |
214
+
215
+ ##### `instance.snapshots`
216
+
217
+ | Method | HTTP | Path |
218
+ |---|---|---|
219
+ | `list(project?)` | GET | `/1.0/instances/{name}/snapshots` |
220
+ | `get(snapshotName, project?)` | GET | `/1.0/instances/{name}/snapshots/{snapshot}` |
221
+ | `post(data, project?)` | POST | `/1.0/instances/{name}/snapshots` — create snapshot |
222
+ | `snapshot(name).put(data, project?)` | PUT | full update |
223
+ | `snapshot(name).patch(data, project?)` | PATCH | partial update |
224
+ | `snapshot(name).delete(project?)` | DELETE | |
225
+ | `snapshot(name).post(data, project?)` | POST | rename/restore |
226
+
227
+ ##### `instance.backups`
228
+
229
+ | Method | HTTP | Path |
230
+ |---|---|---|
231
+ | `list(project?)` | GET | `/1.0/instances/{name}/backups` |
232
+ | `get(backupName, project?)` | GET | `/1.0/instances/{name}/backups/{backup}` |
233
+ | `post(data, project?)` | POST | `/1.0/instances/{name}/backups` — create backup |
234
+ | `backup(name).delete(project?)` | DELETE | |
235
+ | `backup(name).post(data, project?)` | POST | rename |
236
+ | `backup(name).export(project?)` | GET raw | download backup as raw response |
237
+
238
+ ##### `instance.metaData`
239
+
240
+ | Method | HTTP | Path |
241
+ |---|---|---|
242
+ | `get()` | GET | `/1.0/instances/{name}/metadata` |
243
+ | `put(data, project?)` | PUT | |
244
+ | `patch(data, project?)` | PATCH | |
245
+
246
+ ---
247
+
248
+ #### SFTP (native addon)
249
+
250
+ The `instance.sftp` namespace provides secure file transfer directly into the instance's filesystem via the `next-lxd-sftp` native addon.
251
+
252
+ > **Note:** The native addon (`next-lxd-sftp`) must be installed and built for your platform.
253
+
254
+ ```typescript
255
+ // 1. Connect — returns a sessionId
256
+ const { sessionId } = await instance.sftp.connect();
257
+
258
+ // 2. Use the session
259
+ const session = instance.sftp.session(sessionId);
260
+
261
+ // Directory operations
262
+ const entries = await session.readDir('/root');
263
+ await session.mkdir('/root/mydir');
264
+ await session.mkdirAll('/root/a/b/c');
265
+ await session.removeDir('/root/olddir');
266
+
267
+ // File operations
268
+ const file = await session.open('/root/file.txt'); // or openFile / create
269
+ await file.write(Buffer.from('hello'));
270
+ const data: Buffer = await file.read(1024);
271
+ await file.close();
272
+
273
+ // Stat / metadata
274
+ const stat = await session.stat('/root/file.txt');
275
+ await session.chmod('/root/file.txt', 0o755);
276
+ await session.chown('/root/file.txt', 1000, 1000);
277
+ await session.chtimes('/root/file.txt', new Date(), new Date());
278
+
279
+ // Path utilities
280
+ const real = await session.realPath('/root/../root');
281
+ const link = await session.readLink('/root/symlink');
282
+ const wd = await session.getWd();
283
+ const matches = await session.glob('*.txt');
284
+
285
+ // Remove / rename
286
+ await session.remove('/root/trash.txt');
287
+ await session.rename('/root/a.txt', '/root/b.txt');
288
+ await session.posixRename('/root/a.txt', '/root/b.txt');
289
+
290
+ // 3. Disconnect when done
291
+ await session.disconnect();
292
+ ```
293
+
294
+ **Available session methods:**
295
+
296
+ | Method | Description |
297
+ |---|---|
298
+ | `disconnect()` | Close the SFTP session |
299
+ | `readDir(path)` | List directory entries |
300
+ | `stat(path)` | Get file/dir attributes |
301
+ | `lStat(path)` | Get attributes (no follow symlink) |
302
+ | `readLink(path)` | Read symlink target |
303
+ | `realPath(path)` | Resolve to absolute path |
304
+ | `getWd()` | Get working directory |
305
+ | `glob(pattern)` | Glob pattern matching |
306
+ | `open(path)` | Open file for read/write |
307
+ | `openFile(path, flags?)` | Open file with flags |
308
+ | `create(path)` | Create new file |
309
+ | `closeFile(fileId)` | Close file handle |
310
+ | `remove(path)` | Delete file |
311
+ | `removeDir(path)` | Delete empty directory |
312
+ | `rename(oldPath, newPath)` | Rename file/dir |
313
+ | `posixRename(oldPath, newPath)` | POSIX rename (overwrite if exists) |
314
+ | `mkdir(path)` | Create directory |
315
+ | `mkdirAll(path)` | Create directory (parents too) |
316
+ | `chmod(path, mode)` | Change permissions |
317
+ | `chown(path, uid, gid)` | Change owner/group |
318
+ | `chtimes(path, atime, mtime)` | Change access/modify times |
319
+
320
+ **File handle methods** (returned by `open`, `openFile`, `create`):
321
+
322
+ | Method | Description |
323
+ |---|---|
324
+ | `read(length?)` | Read up to `length` bytes → `Buffer` |
325
+ | `write(data)` | Write `Buffer`, `Uint8Array`, or `string` |
326
+ | `close()` | Close the file handle |
327
+
328
+ ---
329
+
330
+ ### `Images`
331
+
332
+ Collection class — access via `client.images`.
333
+
334
+ | Method | HTTP | Path |
335
+ |---|---|---|
336
+ | `list(project?)` | GET | `/1.0/images?project=...` |
337
+ | `get(fingerprint, project?)` | GET | `/1.0/images/{fingerprint}` → returns `Image` |
338
+ | `post(config, project?)` | POST | `/1.0/images` — create (returns `LxdBaseResponse`) |
339
+
340
+ ---
341
+
342
+ ### `Image`
343
+
344
+ Represents a single image. Returned by `Images.get()`.
345
+
346
+ #### Properties
347
+
348
+ | Property | Type |
349
+ |---|---|
350
+ | `name` | `string` (fingerprint) |
351
+ | `metadata` | `LxdImageResponse['metadata']` |
352
+
353
+ #### Methods
354
+
355
+ | Method | HTTP | Path |
356
+ |---|---|---|
357
+ | `delete()` | DELETE | `/1.0/images/{fingerprint}` |
358
+ | `put(data)` | PUT | `/1.0/images/{fingerprint}` — full replace |
359
+ | `patch(data)` | PATCH | `/1.0/images/{fingerprint}` — partial update |
360
+ | `refresh()` | POST | `/1.0/images/{fingerprint}/refresh` |
361
+ | `export()` | GET raw | `/1.0/images/{fingerprint}/export` — download as raw response |
362
+ | `secret()` | POST | `/1.0/images/{fingerprint}/secret` — returns a secret for public download |
363
+
364
+ #### `image.aliases`
365
+
366
+ | Method | HTTP | Path |
367
+ |---|---|---|
368
+ | `list()` | GET | `/1.0/images/{fingerprint}/aliases` |
369
+ | `create(data)` | POST | `/1.0/images/{fingerprint}/aliases` |
370
+ | `entry(name).get()` | GET | `/1.0/images/aliases/{name}` |
371
+ | `entry(name).put(data)` | PUT | |
372
+ | `entry(name).patch(data)` | PATCH | |
373
+ | `entry(name).delete()` | DELETE | |
374
+
375
+ ---
376
+
377
+ ### `Networks`
378
+
379
+ Collection class — access via `client.networks`.
380
+
381
+ | Method | HTTP | Path |
382
+ |---|---|---|
383
+ | `list(project?)` | GET | `/1.0/networks?project=...` |
384
+ | `get(name, project?)` | GET | `/1.0/networks/{name}` → returns `Network` |
385
+ | `post(config, project?)` | POST | `/1.0/networks` — create and return `Network` |
386
+
387
+ ---
388
+
389
+ ### `Network`
390
+
391
+ Represents a single network. Returned by `Networks.get()` / `Networks.post()`.
392
+
393
+ #### Properties
394
+
395
+ | Property | Type |
396
+ |---|---|
397
+ | `name` | `string` |
398
+ | `type` | `string` |
399
+ | `managed` | `boolean` |
400
+ | `metadata` | `LxdNetworkResponse['metadata']` |
401
+
402
+ #### Methods
403
+
404
+ | Method | HTTP | Path |
405
+ |---|---|---|
406
+ | `delete(project?)` | DELETE | `/1.0/networks/{name}` |
407
+ | `put(data, project?)` | PUT | full replace |
408
+ | `patch(data, project?)` | PATCH | partial update |
409
+ | `state(project?)` | GET | `/1.0/networks/{name}/state` |
410
+ | `leases(project?)` | GET | `/1.0/networks/{name}/leases` |
411
+
412
+ #### Sub-resources
413
+
414
+ **`network.forwards`** — network address forwards:
415
+
416
+ `list()`, `get(listenAddress)`, `post(data)`, `put(listenAddress, data)`, `delete(listenAddress)`
417
+
418
+ **`network.acls`** — network ACLs:
419
+
420
+ `list()`, `get(aclName)`, `post(data)`, `put(aclName, data)`, `delete(aclName)`
421
+
422
+ **`network.peers`** — network peers:
423
+
424
+ `list()`, `get(peerName)`, `put(peerName, data)`, `delete(peerName)`
425
+
426
+ ---
427
+
428
+ ### `Profiles`
429
+
430
+ Collection class — access via `client.profiles`.
431
+
432
+ | Method | HTTP | Path |
433
+ |---|---|---|
434
+ | `list(project?)` | GET | `/1.0/profiles?project=...` |
435
+ | `get(name, project?)` | GET | `/1.0/profiles/{name}` → returns `Profile` |
436
+ | `post(config, project?)` | POST | `/1.0/profiles` — create and return `Profile` |
437
+
438
+ ---
439
+
440
+ ### `Profile`
441
+
442
+ Represents a single profile. Returned by `Profiles.get()` / `Profiles.post()`.
443
+
444
+ #### Properties
445
+
446
+ | Property | Type |
447
+ |---|---|
448
+ | `name` | `string` |
449
+ | `description` | `string` |
450
+ | `metadata` | `LxdProfileResponse['metadata']` |
451
+
452
+ #### Methods
453
+
454
+ | Method | HTTP | Path |
455
+ |---|---|---|
456
+ | `delete(project?)` | DELETE | `/1.0/profiles/{name}` |
457
+ | `put(data, project?)` | PUT | full replace |
458
+ | `patch(data, project?)` | PATCH | partial update |
459
+ | `post(data, project?)` | POST | rename |
460
+
461
+ ---
462
+
463
+ ### `Projects`
464
+
465
+ Collection class — access via `client.projects`.
466
+
467
+ | Method | HTTP | Path |
468
+ |---|---|---|
469
+ | `list(project?)` | GET | `/1.0/projects?project=...` |
470
+ | `get(name, project?)` | GET | `/1.0/projects/{name}` → returns `Project` |
471
+ | `post(config, project?)` | POST | `/1.0/projects` — create and return `Project` |
472
+
473
+ ---
474
+
475
+ ### `Project`
476
+
477
+ Represents a single project. Returned by `Projects.get()` / `Projects.post()`.
478
+
479
+ #### Properties
480
+
481
+ | Property | Type |
482
+ |---|---|
483
+ | `name` | `string` |
484
+ | `description` | `string` |
485
+ | `metadata` | `LxdProjectResponse['metadata']` |
486
+
487
+ #### Methods
488
+
489
+ | Method | HTTP | Path |
490
+ |---|---|---|
491
+ | `delete(force?)` | DELETE | `/1.0/projects/{name}?force=1` |
492
+ | `put(data)` | PUT | full replace |
493
+ | `patch(data)` | PATCH | partial update |
494
+ | `post(data)` | POST | rename |
495
+ | `state()` | GET | `/1.0/projects/{name}/state` — resource usage |
496
+
497
+ ---
498
+
499
+ ### `StoragePools`
500
+
501
+ Collection class — access via `client.storagePools`.
502
+
503
+ | Method | HTTP | Path |
504
+ |---|---|---|
505
+ | `list(project?)` | GET | `/1.0/storage-pools?project=...` |
506
+ | `get(name, project?)` | GET | `/1.0/storage-pools/{name}` → returns `StoragePool` |
507
+ | `post(config, project?)` | POST | `/1.0/storage-pools` — create and return `StoragePool` |
508
+
509
+ ---
510
+
511
+ ### `StoragePool`
512
+
513
+ Represents a single storage pool. Returned by `StoragePools.get()` / `StoragePools.post()`.
514
+
515
+ #### Properties
516
+
517
+ | Property | Type |
518
+ |---|---|
519
+ | `name` | `string` |
520
+ | `driver` | `string` |
521
+ | `metadata` | `LxdStoragePoolResponse['metadata']` |
522
+
523
+ #### Methods
524
+
525
+ | Method | HTTP | Path |
526
+ |---|---|---|
527
+ | `delete()` | DELETE | `/1.0/storage-pools/{name}` |
528
+ | `put(data)` | PUT | full replace |
529
+ | `patch(data)` | PATCH | partial update |
530
+ | `resources()` | GET | `/1.0/storage-pools/{name}/resources` |
531
+
532
+ #### Sub-resources: `storagePool.volumes`
533
+
534
+ | Method | HTTP | Path |
535
+ |---|---|---|
536
+ | `list(project?)` | GET | `/1.0/storage-pools/{name}/volumes` |
537
+ | `get(type, name, project?)` | GET | `/1.0/storage-pools/{name}/volumes/{type}/{name}` |
538
+ | `post(type, data, project?)` | POST | create volume |
539
+ | `put(type, name, data, project?)` | PUT | update volume |
540
+ | `patch(type, name, data, project?)` | PATCH | partial update volume |
541
+ | `delete(type, name, project?)` | DELETE | |
542
+ | `rename(type, name, data, project?)` | POST | rename volume |
543
+
544
+ **`storagePool.volumes.snapshots`** — volume snapshot operations:
545
+
546
+ `list(type, volumeName)`, `get(type, volumeName, snapshotName)`, `post(type, volumeName, data)`, `put(type, volumeName, snapshotName, data)`, `delete(type, volumeName, snapshotName)`, `rename(type, volumeName, snapshotName, data)`
547
+
548
+ ---
549
+
550
+ ### `Certificates`
551
+
552
+ Collection class — access via `client.certificates`.
553
+
554
+ | Method | HTTP | Path |
555
+ |---|---|---|
556
+ | `list()` | GET | `/1.0/certificates` |
557
+ | `get(fingerprint)` | GET | `/1.0/certificates/{fingerprint}` → returns `Certificate` |
558
+ | `post(config)` | POST | `/1.0/certificates` — add certificate |
559
+
560
+ ---
561
+
562
+ ### `Certificate`
563
+
564
+ Represents a single certificate. Returned by `Certificates.get()`.
565
+
566
+ | Property | Type |
567
+ |---|---|
568
+ | `fingerprint` | `string` |
569
+ | `metadata` | `LxdCertificateResponse['metadata']` |
570
+
571
+ #### Methods
572
+
573
+ | Method | HTTP | Path |
574
+ |---|---|---|
575
+ | `delete()` | DELETE | `/1.0/certificates/{fingerprint}` |
576
+ | `put(data)` | PUT | full replace |
577
+ | `patch(data)` | PATCH | partial update |
578
+
579
+ ---
580
+
581
+ ### `Operations`
582
+
583
+ Collection class — access via `client.operations`.
584
+
585
+ | Method | HTTP | Path |
586
+ |---|---|---|
587
+ | `list(project?)` | GET | `/1.0/operations?project=...` |
588
+ | `get(id)` | GET | `/1.0/operations/{id}` → returns `Operation` |
589
+
590
+ ---
591
+
592
+ ### `Operation`
593
+
594
+ Represents a single operation. Returned by `Operations.get()`.
595
+
596
+ | Property | Type |
597
+ |---|---|
598
+ | `id` | `string` |
599
+ | `metadata` | `LxdOperationResponse['metadata']` |
600
+
601
+ #### Methods
602
+
603
+ | Method | HTTP | Path |
604
+ |---|---|---|
605
+ | `wait(timeout?)` | GET | `/1.0/operations/{id}/wait?timeout=...` — blocks until complete |
606
+ | `cancel()` | DELETE | `/1.0/operations/{id}` |
607
+ | `websocketUrl(secret?)` | — | Returns the WebSocket URL for this operation (optionally with `?secret=...`) |
608
+
609
+ ---
610
+
611
+ ### `Cluster`
612
+
613
+ Collection class — access via `client.cluster`.
614
+
615
+ | Method | HTTP | Path |
616
+ |---|---|---|
617
+ | `get()` | GET | `/1.0/cluster` |
618
+ | `put(data)` | PUT | `/1.0/cluster` — bootstrap/join |
619
+ | `updateCertificate(data)` | PUT | `/1.0/cluster/certificate` |
620
+
621
+ #### Sub-resources
622
+
623
+ **`cluster.members`:**
624
+
625
+ `list()`, `get(name)`, `put(name, data)`, `patch(name, data)`, `rename(name, data)`, `delete(name)`, `state(name)`, `evacuate(name, data?)`, `restore(name, data?)`, `joinToken(data)`
626
+
627
+ **`cluster.groups`:**
628
+
629
+ `list()`, `get(name)`, `post(data)`, `put(name, data)`, `patch(name, data)`, `rename(name, data)`, `delete(name)`
630
+
631
+ ---
632
+
633
+ ### `Warnings`
634
+
635
+ Collection class — access via `client.warnings`.
636
+
637
+ | Method | HTTP | Path |
638
+ |---|---|---|
639
+ | `list(project?)` | GET | `/1.0/warnings?project=...` |
640
+ | `get(uuid)` | GET | `/1.0/warnings/{uuid}` |
641
+ | `put(uuid, data)` | PUT | `/1.0/warnings/{uuid}` |
642
+ | `delete(uuid)` | DELETE | `/1.0/warnings/{uuid}` |
643
+
644
+ ---
645
+
646
+ ## Type Exports
647
+
648
+ Key types (import from `next-lxd`):
649
+
650
+ ```typescript
651
+ // Base response wrapper
652
+ LxdBaseResponse<T>
653
+
654
+ // Server
655
+ LxdServerResponse, LxdServerMetadata, LxdEnvironment
656
+
657
+ // Instances
658
+ InstanceCreateRequest, LxdInstanceResponse, LxdInstanceStateResponse,
659
+ LxdInstanceStatePut, LxdInstanceExecRequest, LxdInstanceExecResponse,
660
+ LxdInstanceConsoleResponse, LxdInstancePost, LxdInstancePutRequest,
661
+ LxdInstanceRebuildPost, LxdInstanceDeleteResponse
662
+
663
+ // Instance snapshots
664
+ LxdInstanceSnapshotsResponse, LxdInstanceSnapshotResponse,
665
+ LxdInstanceSnapshotsPost, LxdInstanceSnapshotPut, LxdInstanceSnapshotPost
666
+
667
+ // Instance backups
668
+ LxdInstanceBackupsResponse, LxdInstanceBackupResponse,
669
+ LxdInstanceBackupsPost, LxdInstanceBackupPost
670
+
671
+ // Images
672
+ LxdImageResponse, LxdImagesResponse, LxdImagePut, LxdImagesPost,
673
+ LxdImageAliasesResponse, LxdImageAliasResponse,
674
+ LxdImageAliasesPost, LxdImageAliasesEntryPut
675
+
676
+ // Networks
677
+ LxdNetworkResponse, LxdNetworksResponse, LxdNetworkPut, LxdNetworksPost,
678
+ LxdNetworkLeasesResponse, LxdNetworkForwardsResponse, LxdNetworkForwardResponse,
679
+ LxdNetworkForwardsPost, LxdNetworkForwardPut,
680
+ LxdNetworkACLsResponse, LxdNetworkACLResponse, LxdNetworkACLsPost, LxdNetworkACLPut,
681
+ LxdNetworkPeersResponse, LxdNetworkPeerResponse, LxdNetworkPeerPut
682
+
683
+ // Profiles
684
+ LxdProfileResponse, LxdProfilesResponse, LxdProfilePut, LxdProfilesPost, LxdProfilePost
685
+
686
+ // Projects
687
+ LxdProjectResponse, LxdProjectsResponse, LxdProjectPut, LxdProjectsPost, LxdProjectPost,
688
+ LxdProjectStateResponse
689
+
690
+ // Storage pools
691
+ LxdStoragePoolResponse, LxdStoragePoolsResponse, LxdStoragePoolPut, LxdStoragePoolsPost,
692
+ LxdStorageVolumesResponse, LxdStorageVolumeResponse, LxdStorageVolumesPost,
693
+ LxdStorageVolumePut, LxdStorageVolumePost,
694
+ LxdStorageVolumeSnapshotsResponse, LxdStorageVolumeSnapshotResponse,
695
+ LxdStorageVolumeSnapshotsPost, LxdStorageVolumeSnapshotPut, LxdStorageVolumeSnapshotPost
696
+
697
+ // Certificates
698
+ LxdCertificateResponse, LxdCertificatesResponse, LxdCertificatePut, LxdCertificatesPost
699
+
700
+ // Operations
701
+ LxdOperationResponse, LxdOperationsResponse
702
+
703
+ // Cluster
704
+ LxdClusterResponse, LxdClusterPut, LxdClusterCertificatePut,
705
+ LxdClusterMembersResponse, LxdClusterMemberResponse,
706
+ LxdClusterMemberPut, LxdClusterMemberPost, LxdClusterMembersPost,
707
+ LxdClusterMemberStateResponse, LxdClusterMemberStatePost, LxdClusterMemberJoinToken,
708
+ LxdClusterGroupsResponse, LxdClusterGroupResponse,
709
+ LxdClusterGroupPut, LxdClusterGroupPost, LxdClusterGroupsPost
710
+
711
+ // Warnings
712
+ LxdWarningsResponse, LxdWarningResponse, LxdWarningPut
713
+
714
+ // Events
715
+ LxdEvent
716
+ ```
717
+
718
+ ---
719
+
720
+ ## Examples
721
+
722
+ ### List all instances with their state
723
+
724
+ ```typescript
725
+ const client = new Client({ url, cert, key });
726
+ const instances = await client.instances.list();
727
+
728
+ for (const name of instances.metadata) {
729
+ const instance = await client.instances.get(name);
730
+ console.log(`${name}: ${instance.status}`);
731
+ }
732
+ ```
733
+
734
+ ### Create and start a container
735
+
736
+ ```typescript
737
+ const instance = await client.instances.post({
738
+ name: 'my-container',
739
+ source: {
740
+ type: 'image',
741
+ alias: 'ubuntu/22.04'
742
+ }
743
+ });
744
+
745
+ // Start it
746
+ await instance.state.put({ action: 'start' });
747
+ ```
748
+
749
+ ### Upload a file via SFTP
750
+
751
+ ```typescript
752
+ const instance = await client.instances.get('my-container');
753
+
754
+ // Wait for it to be running
755
+ await instance.state.put({ action: 'start' });
756
+
757
+ // SFTP
758
+ const { sessionId } = await instance.sftp.connect();
759
+ const session = instance.sftp.session(sessionId);
760
+
761
+ const file = await session.create('/root/hello.txt');
762
+ await file.write(Buffer.from('Hello, LXD!'));
763
+ await file.close();
764
+ await session.disconnect();
765
+ ```
766
+
767
+ ---
768
+
769
+ ## Development
770
+
771
+ ```bash
772
+ # Install dependencies
773
+ bun install # or npm install
774
+
775
+ # Build TypeScript
776
+ npm run build
777
+
778
+ # Clean build output
779
+ npm run clean
780
+ ```