disk 0.8.8 → 0.8.10

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/dist/cli.js CHANGED
@@ -99,6 +99,7 @@ var Disk = class {
99
99
  metrics;
100
100
  connectedClients;
101
101
  authorizedUsers;
102
+ allowedIps;
102
103
  /** @internal */
103
104
  _client;
104
105
  /** @internal */
@@ -120,6 +121,7 @@ var Disk = class {
120
121
  this.metrics = data.metrics;
121
122
  this.connectedClients = data.connectedClients;
122
123
  this.authorizedUsers = data.authorizedUsers;
124
+ this.allowedIps = data.allowedIps;
123
125
  this._client = client;
124
126
  this._archilRegion = archilRegion;
125
127
  }
@@ -139,7 +141,8 @@ var Disk = class {
139
141
  mounts: this.mounts,
140
142
  metrics: this.metrics,
141
143
  connectedClients: this.connectedClients,
142
- authorizedUsers: this.authorizedUsers
144
+ authorizedUsers: this.authorizedUsers,
145
+ allowedIps: this.allowedIps
143
146
  };
144
147
  }
145
148
  async addUser(user) {
@@ -175,6 +178,32 @@ var Disk = class {
175
178
  async removeTokenUser(identifier) {
176
179
  await this.removeUser("token", identifier);
177
180
  }
181
+ async getAllowedIPs() {
182
+ const data = await unwrap(
183
+ this._client.GET("/api/disks/{id}/allowed-ips", {
184
+ params: { path: { id: this.id } }
185
+ })
186
+ );
187
+ return data.allowedIps;
188
+ }
189
+ async setAllowedIPs(allowedIps) {
190
+ const data = await unwrap(
191
+ this._client.PUT("/api/disks/{id}/allowed-ips", {
192
+ params: { path: { id: this.id } },
193
+ body: { allowedIps }
194
+ })
195
+ );
196
+ return data.allowedIps;
197
+ }
198
+ async addAllowedIP(ip) {
199
+ const current = await this.getAllowedIPs();
200
+ if (current.includes(ip)) return current;
201
+ return this.setAllowedIPs([...current, ip]);
202
+ }
203
+ async removeAllowedIP(ip) {
204
+ const current = await this.getAllowedIPs();
205
+ return this.setAllowedIPs(current.filter((i) => i !== ip));
206
+ }
178
207
  async delete() {
179
208
  await unwrapEmpty(
180
209
  this._client.DELETE("/api/disks/{id}", {
@@ -236,7 +265,7 @@ var Disks = class {
236
265
  async list(opts) {
237
266
  const data = await unwrap(
238
267
  this._client.GET("/api/disks", {
239
- params: { query: { limit: opts?.limit, cursor: opts?.cursor } }
268
+ params: { query: { limit: opts?.limit, cursor: opts?.cursor, name: opts?.name } }
240
269
  })
241
270
  );
242
271
  return data.map(
@@ -312,6 +341,8 @@ var Tokens = class {
312
341
  var Archil = class {
313
342
  disks;
314
343
  tokens;
344
+ /** @internal */
345
+ _client;
315
346
  constructor(opts = {}) {
316
347
  const apiKey = opts.apiKey ?? process.env.ARCHIL_API_KEY;
317
348
  const region = opts.region ?? process.env.ARCHIL_REGION;
@@ -326,9 +357,26 @@ var Archil = class {
326
357
  region,
327
358
  baseUrl: opts.baseUrl
328
359
  });
360
+ this._client = client;
329
361
  this.disks = new Disks(client, region);
330
362
  this.tokens = new Tokens(client);
331
363
  }
364
+ /**
365
+ * Run a command in a container with multiple disks mounted simultaneously,
366
+ * each at its own relative path under `/mnt/archil`. Blocks until the
367
+ * command completes and returns its stdout, stderr, exit code, and timing.
368
+ */
369
+ async exec(opts) {
370
+ const disks = {};
371
+ for (const [relPath, mount] of Object.entries(opts.disks)) {
372
+ disks[relPath] = typeof mount === "string" ? mount : mount.id;
373
+ }
374
+ return unwrap(
375
+ this._client.POST("/api/exec", {
376
+ body: { disks, command: opts.command }
377
+ })
378
+ );
379
+ }
332
380
  };
333
381
 
334
382
  // src/index.ts
@@ -382,6 +430,25 @@ function fail(err) {
382
430
  }
383
431
  process.exit(1);
384
432
  }
433
+ function isDiskId(s) {
434
+ return /^(dsk-|fs-)[0-9a-fA-F]+$/.test(s);
435
+ }
436
+ async function resolveDisk(idOrName) {
437
+ if (isDiskId(idOrName)) {
438
+ return getDisk(idOrName);
439
+ }
440
+ const matches = await listDisks({ name: idOrName });
441
+ if (matches.length === 0) {
442
+ throw new ArchilApiError(`No disk found with name '${idOrName}'`, 404);
443
+ }
444
+ if (matches.length > 1) {
445
+ throw new ArchilApiError(
446
+ `Multiple disks match name '${idOrName}' \u2014 pass a disk id (dsk-...) instead`,
447
+ 400
448
+ );
449
+ }
450
+ return matches[0];
451
+ }
385
452
  function formatBytes(n) {
386
453
  if (n === void 0 || n === null) return void 0;
387
454
  if (n < 1024) return `${n} B`;
@@ -475,9 +542,9 @@ program.command("list").description("List disks in the current region").option("
475
542
  fail(err);
476
543
  }
477
544
  });
478
- program.command("get <id>").description("Show details for a disk").option("-o, --output <format>", "Output format: table | json", "table").action(async (id, opts) => {
545
+ program.command("get <id|name>").description("Show details for a disk (accepts a disk id or name)").option("-o, --output <format>", "Output format: table | json", "table").action(async (idOrName, opts) => {
479
546
  try {
480
- const d = await getDisk(id);
547
+ const d = await resolveDisk(idOrName);
481
548
  if (opts.output === "json") {
482
549
  console.log(JSON.stringify(d, null, 2));
483
550
  } else {
@@ -505,18 +572,18 @@ program.command("create <name>").description("Create a new disk").action(async (
505
572
  fail(err);
506
573
  }
507
574
  });
508
- program.command("delete <id>").description("Delete a disk").action(async (id) => {
575
+ program.command("delete <id|name>").description("Delete a disk (accepts a disk id or name)").action(async (idOrName) => {
509
576
  try {
510
- const d = await getDisk(id);
577
+ const d = await resolveDisk(idOrName);
511
578
  await d.delete();
512
579
  console.log(`Deleted ${d.organization}/${d.name} (${d.id})`);
513
580
  } catch (err) {
514
581
  fail(err);
515
582
  }
516
583
  });
517
- program.command("exec <id> <command...>").description("Run a command in a container with the disk mounted, return stdout/stderr/exit code").action(async (id, cmd) => {
584
+ program.command("exec <id|name> <command...>").description("Run a command in a container with the disk mounted, return stdout/stderr/exit code (accepts a disk id or name)").action(async (idOrName, cmd) => {
518
585
  try {
519
- const d = await getDisk(id);
586
+ const d = await resolveDisk(idOrName);
520
587
  const result = await d.exec(cmd.join(" "));
521
588
  if (result.stdout) process.stdout.write(result.stdout);
522
589
  if (result.stderr) process.stderr.write(result.stderr);
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  createApiKey: () => createApiKey,
40
40
  createDisk: () => createDisk,
41
41
  deleteApiKey: () => deleteApiKey,
42
+ exec: () => exec,
42
43
  getDisk: () => getDisk,
43
44
  listApiKeys: () => listApiKeys,
44
45
  listDisks: () => listDisks
@@ -141,6 +142,7 @@ var Disk = class {
141
142
  metrics;
142
143
  connectedClients;
143
144
  authorizedUsers;
145
+ allowedIps;
144
146
  /** @internal */
145
147
  _client;
146
148
  /** @internal */
@@ -162,6 +164,7 @@ var Disk = class {
162
164
  this.metrics = data.metrics;
163
165
  this.connectedClients = data.connectedClients;
164
166
  this.authorizedUsers = data.authorizedUsers;
167
+ this.allowedIps = data.allowedIps;
165
168
  this._client = client;
166
169
  this._archilRegion = archilRegion;
167
170
  }
@@ -181,7 +184,8 @@ var Disk = class {
181
184
  mounts: this.mounts,
182
185
  metrics: this.metrics,
183
186
  connectedClients: this.connectedClients,
184
- authorizedUsers: this.authorizedUsers
187
+ authorizedUsers: this.authorizedUsers,
188
+ allowedIps: this.allowedIps
185
189
  };
186
190
  }
187
191
  async addUser(user) {
@@ -217,6 +221,32 @@ var Disk = class {
217
221
  async removeTokenUser(identifier) {
218
222
  await this.removeUser("token", identifier);
219
223
  }
224
+ async getAllowedIPs() {
225
+ const data = await unwrap(
226
+ this._client.GET("/api/disks/{id}/allowed-ips", {
227
+ params: { path: { id: this.id } }
228
+ })
229
+ );
230
+ return data.allowedIps;
231
+ }
232
+ async setAllowedIPs(allowedIps) {
233
+ const data = await unwrap(
234
+ this._client.PUT("/api/disks/{id}/allowed-ips", {
235
+ params: { path: { id: this.id } },
236
+ body: { allowedIps }
237
+ })
238
+ );
239
+ return data.allowedIps;
240
+ }
241
+ async addAllowedIP(ip) {
242
+ const current = await this.getAllowedIPs();
243
+ if (current.includes(ip)) return current;
244
+ return this.setAllowedIPs([...current, ip]);
245
+ }
246
+ async removeAllowedIP(ip) {
247
+ const current = await this.getAllowedIPs();
248
+ return this.setAllowedIPs(current.filter((i) => i !== ip));
249
+ }
220
250
  async delete() {
221
251
  await unwrapEmpty(
222
252
  this._client.DELETE("/api/disks/{id}", {
@@ -278,7 +308,7 @@ var Disks = class {
278
308
  async list(opts) {
279
309
  const data = await unwrap(
280
310
  this._client.GET("/api/disks", {
281
- params: { query: { limit: opts?.limit, cursor: opts?.cursor } }
311
+ params: { query: { limit: opts?.limit, cursor: opts?.cursor, name: opts?.name } }
282
312
  })
283
313
  );
284
314
  return data.map(
@@ -354,6 +384,8 @@ var Tokens = class {
354
384
  var Archil = class {
355
385
  disks;
356
386
  tokens;
387
+ /** @internal */
388
+ _client;
357
389
  constructor(opts = {}) {
358
390
  const apiKey = opts.apiKey ?? process.env.ARCHIL_API_KEY;
359
391
  const region = opts.region ?? process.env.ARCHIL_REGION;
@@ -368,9 +400,26 @@ var Archil = class {
368
400
  region,
369
401
  baseUrl: opts.baseUrl
370
402
  });
403
+ this._client = client;
371
404
  this.disks = new Disks(client, region);
372
405
  this.tokens = new Tokens(client);
373
406
  }
407
+ /**
408
+ * Run a command in a container with multiple disks mounted simultaneously,
409
+ * each at its own relative path under `/mnt/archil`. Blocks until the
410
+ * command completes and returns its stdout, stderr, exit code, and timing.
411
+ */
412
+ async exec(opts) {
413
+ const disks = {};
414
+ for (const [relPath, mount] of Object.entries(opts.disks)) {
415
+ disks[relPath] = typeof mount === "string" ? mount : mount.id;
416
+ }
417
+ return unwrap(
418
+ this._client.POST("/api/exec", {
419
+ body: { disks, command: opts.command }
420
+ })
421
+ );
422
+ }
374
423
  };
375
424
 
376
425
  // src/index.ts
@@ -404,6 +453,9 @@ function createApiKey(req) {
404
453
  function deleteApiKey(id) {
405
454
  return archil().tokens.delete(id);
406
455
  }
456
+ function exec(opts) {
457
+ return archil().exec(opts);
458
+ }
407
459
  // Annotate the CommonJS export names for ESM import in node:
408
460
  0 && (module.exports = {
409
461
  Archil,
@@ -415,6 +467,7 @@ function deleteApiKey(id) {
415
467
  createApiKey,
416
468
  createDisk,
417
469
  deleteApiKey,
470
+ exec,
418
471
  getDisk,
419
472
  listApiKeys,
420
473
  listDisks
package/dist/index.d.cts CHANGED
@@ -102,6 +102,30 @@ interface paths {
102
102
  patch?: never;
103
103
  trace?: never;
104
104
  };
105
+ "/api/disks/{id}/allowed-ips": {
106
+ parameters: {
107
+ query?: never;
108
+ header?: never;
109
+ path?: never;
110
+ cookie?: never;
111
+ };
112
+ /**
113
+ * Get IP allowlist
114
+ * @description Returns the current IP allowlist for a disk.
115
+ */
116
+ get: operations["getAllowedIPs"];
117
+ /**
118
+ * Set IP allowlist
119
+ * @description Replaces the IP allowlist for a disk. When non-empty, only clients connecting from listed IPs or CIDR ranges can mount the disk. Pass an empty array to remove all restrictions.
120
+ */
121
+ put: operations["setAllowedIPs"];
122
+ post?: never;
123
+ delete?: never;
124
+ options?: never;
125
+ head?: never;
126
+ patch?: never;
127
+ trace?: never;
128
+ };
105
129
  "/api/disks/{id}/exec": {
106
130
  parameters: {
107
131
  query?: never;
@@ -124,6 +148,33 @@ interface paths {
124
148
  patch?: never;
125
149
  trace?: never;
126
150
  };
151
+ "/api/exec": {
152
+ parameters: {
153
+ query?: never;
154
+ header?: never;
155
+ path?: never;
156
+ cookie?: never;
157
+ };
158
+ get?: never;
159
+ put?: never;
160
+ /**
161
+ * Execute a command with multiple disks mounted
162
+ * @description Launches a container with the supplied set of disks each mounted at its
163
+ * own relative path under `/mnt/archil`, runs the command to completion,
164
+ * and shuts down the container. Activation is atomic: every disk mounts
165
+ * or none of them do.
166
+ *
167
+ * Relative paths must be non-empty, non-absolute, and contain no `.` /
168
+ * `..` segments. Mounting two disks at the same relative path is an
169
+ * error.
170
+ */
171
+ post: operations["exec"];
172
+ delete?: never;
173
+ options?: never;
174
+ head?: never;
175
+ patch?: never;
176
+ trace?: never;
177
+ };
127
178
  "/api/tokens": {
128
179
  parameters: {
129
180
  query?: never;
@@ -197,6 +248,14 @@ interface components {
197
248
  name: string;
198
249
  /** @description Storage mount to attach. Omit for archil-managed storage. */
199
250
  mounts?: components["schemas"]["MountConfig"][];
251
+ /**
252
+ * @description IP allowlist for mount access. When non-empty, only clients connecting from these IPs or CIDR ranges can mount the disk. An empty list (default) allows all IPs.
253
+ * @example [
254
+ * "203.0.113.0/24",
255
+ * "198.51.100.42"
256
+ * ]
257
+ */
258
+ allowedIps?: string[];
200
259
  /**
201
260
  * @deprecated
202
261
  * @description Deprecated. Use AddDiskUser after creation instead. When provided, suppresses the default auto-generated token user.
@@ -438,6 +497,24 @@ interface components {
438
497
  metrics?: components["schemas"]["DiskMetrics"];
439
498
  connectedClients?: components["schemas"]["ConnectedClient"][];
440
499
  authorizedUsers?: components["schemas"]["AuthorizedUser"][];
500
+ /**
501
+ * @description IP allowlist for mount access. Empty means all IPs are allowed.
502
+ * @example [
503
+ * "203.0.113.0/24"
504
+ * ]
505
+ */
506
+ allowedIps?: string[];
507
+ /**
508
+ * @description Capabilities supported by this disk. Determined at creation
509
+ * time and immutable thereafter. Defined values:
510
+ *
511
+ * * `checkpoints` — checkpoints can be created on this disk. Not
512
+ * available for disks with bring-your-own buckets.
513
+ * @example [
514
+ * "checkpoints"
515
+ * ]
516
+ */
517
+ capabilities?: "checkpoints"[];
441
518
  };
442
519
  MountResponse: {
443
520
  /** @description Mount identifier */
@@ -538,6 +615,19 @@ interface components {
538
615
  success: boolean;
539
616
  data: components["schemas"]["AuthorizedUser"];
540
617
  };
618
+ ApiResponse_AllowedIPs: {
619
+ /** @example true */
620
+ success: boolean;
621
+ data: {
622
+ /**
623
+ * @example [
624
+ * "203.0.113.0/24",
625
+ * "198.51.100.42"
626
+ * ]
627
+ */
628
+ allowedIps: string[];
629
+ };
630
+ };
541
631
  CreateApiTokenRequest: {
542
632
  /** @description Token name */
543
633
  name: string;
@@ -616,6 +706,30 @@ interface components {
616
706
  success: boolean;
617
707
  data: components["schemas"]["ExecDiskResult"];
618
708
  };
709
+ ExecRequest: {
710
+ /**
711
+ * @description Map of relative path under `/mnt/archil` to the disk to mount
712
+ * there. At least one entry is required. Relative paths must be
713
+ * non-empty, non-absolute, and contain no `.` or `..` segments.
714
+ * @example {
715
+ * "data": "dsk-abc123",
716
+ * "logs": "dsk-def456"
717
+ * }
718
+ */
719
+ disks: {
720
+ [key: string]: string;
721
+ };
722
+ /**
723
+ * @description Shell command to execute inside the container
724
+ * @example ls -la /mnt/archil/data /mnt/archil/logs
725
+ */
726
+ command: string;
727
+ };
728
+ ApiResponse_Exec: {
729
+ /** @example true */
730
+ success: boolean;
731
+ data: components["schemas"]["ExecDiskResult"];
732
+ };
619
733
  };
620
734
  responses: {
621
735
  /** @description Invalid or missing authentication credentials */
@@ -871,6 +985,72 @@ interface operations {
871
985
  500: components["responses"]["InternalError"];
872
986
  };
873
987
  };
988
+ getAllowedIPs: {
989
+ parameters: {
990
+ query?: never;
991
+ header?: never;
992
+ path: {
993
+ /** @description Disk ID (format `dsk-{16 hex chars}`) */
994
+ id: components["parameters"]["DiskId"];
995
+ };
996
+ cookie?: never;
997
+ };
998
+ requestBody?: never;
999
+ responses: {
1000
+ /** @description Current IP allowlist */
1001
+ 200: {
1002
+ headers: {
1003
+ [name: string]: unknown;
1004
+ };
1005
+ content: {
1006
+ "application/json": components["schemas"]["ApiResponse_AllowedIPs"];
1007
+ };
1008
+ };
1009
+ 401: components["responses"]["Unauthorized"];
1010
+ 404: components["responses"]["NotFound"];
1011
+ 500: components["responses"]["InternalError"];
1012
+ };
1013
+ };
1014
+ setAllowedIPs: {
1015
+ parameters: {
1016
+ query?: never;
1017
+ header?: never;
1018
+ path: {
1019
+ /** @description Disk ID (format `dsk-{16 hex chars}`) */
1020
+ id: components["parameters"]["DiskId"];
1021
+ };
1022
+ cookie?: never;
1023
+ };
1024
+ requestBody: {
1025
+ content: {
1026
+ "application/json": {
1027
+ /**
1028
+ * @description List of IPs or CIDR ranges. Empty array removes restrictions.
1029
+ * @example [
1030
+ * "203.0.113.0/24",
1031
+ * "198.51.100.42"
1032
+ * ]
1033
+ */
1034
+ allowedIps: string[];
1035
+ };
1036
+ };
1037
+ };
1038
+ responses: {
1039
+ /** @description Allowlist updated */
1040
+ 200: {
1041
+ headers: {
1042
+ [name: string]: unknown;
1043
+ };
1044
+ content: {
1045
+ "application/json": components["schemas"]["ApiResponse_AllowedIPs"];
1046
+ };
1047
+ };
1048
+ 400: components["responses"]["ValidationError"];
1049
+ 401: components["responses"]["Unauthorized"];
1050
+ 404: components["responses"]["NotFound"];
1051
+ 500: components["responses"]["InternalError"];
1052
+ };
1053
+ };
874
1054
  execDisk: {
875
1055
  parameters: {
876
1056
  query?: never;
@@ -911,6 +1091,43 @@ interface operations {
911
1091
  };
912
1092
  };
913
1093
  };
1094
+ exec: {
1095
+ parameters: {
1096
+ query?: never;
1097
+ header?: never;
1098
+ path?: never;
1099
+ cookie?: never;
1100
+ };
1101
+ requestBody: {
1102
+ content: {
1103
+ "application/json": components["schemas"]["ExecRequest"];
1104
+ };
1105
+ };
1106
+ responses: {
1107
+ /** @description Command completed */
1108
+ 200: {
1109
+ headers: {
1110
+ [name: string]: unknown;
1111
+ };
1112
+ content: {
1113
+ "application/json": components["schemas"]["ApiResponse_Exec"];
1114
+ };
1115
+ };
1116
+ 400: components["responses"]["ValidationError"];
1117
+ 401: components["responses"]["Unauthorized"];
1118
+ 404: components["responses"]["NotFound"];
1119
+ 500: components["responses"]["InternalError"];
1120
+ /** @description Command timed out */
1121
+ 504: {
1122
+ headers: {
1123
+ [name: string]: unknown;
1124
+ };
1125
+ content: {
1126
+ "application/json": components["schemas"]["ErrorResponse"];
1127
+ };
1128
+ };
1129
+ };
1130
+ };
914
1131
  listApiTokens: {
915
1132
  parameters: {
916
1133
  query?: {
@@ -1014,6 +1231,7 @@ type AwsStsUser = components["schemas"]["AwsStsUser"];
1014
1231
  type CreateApiTokenRequest = components["schemas"]["CreateApiTokenRequest"];
1015
1232
  type ApiTokenResponse = components["schemas"]["ApiTokenResponse"];
1016
1233
  type ExecDiskResult = components["schemas"]["ExecDiskResult"];
1234
+ type ExecRequest = components["schemas"]["ExecRequest"];
1017
1235
  type DiskStatus = DiskResponse["status"];
1018
1236
 
1019
1237
  interface MountOptions {
@@ -1039,6 +1257,7 @@ declare class Disk {
1039
1257
  readonly metrics?: DiskMetrics;
1040
1258
  readonly connectedClients?: ConnectedClient[];
1041
1259
  readonly authorizedUsers?: AuthorizedUser[];
1260
+ readonly allowedIps?: string[];
1042
1261
  /** @internal */
1043
1262
  private readonly _client;
1044
1263
  /** @internal */
@@ -1053,6 +1272,10 @@ declare class Disk {
1053
1272
  identifier: string;
1054
1273
  }>;
1055
1274
  removeTokenUser(identifier: string): Promise<void>;
1275
+ getAllowedIPs(): Promise<string[]>;
1276
+ setAllowedIPs(allowedIps: string[]): Promise<string[]>;
1277
+ addAllowedIP(ip: string): Promise<string[]>;
1278
+ removeAllowedIP(ip: string): Promise<string[]>;
1056
1279
  delete(): Promise<void>;
1057
1280
  /**
1058
1281
  * Execute a command in a container with this disk mounted.
@@ -1070,6 +1293,7 @@ declare class Disk {
1070
1293
  interface ListDisksOptions {
1071
1294
  limit?: number;
1072
1295
  cursor?: string;
1296
+ name?: string;
1073
1297
  }
1074
1298
  interface CreateDiskResult {
1075
1299
  disk: Disk;
@@ -1119,10 +1343,34 @@ interface ArchilOptions {
1119
1343
  /** Override the control plane base URL (useful for testing). */
1120
1344
  baseUrl?: string;
1121
1345
  }
1346
+ /**
1347
+ * One disk to mount, identified by a Disk instance or a raw disk id string.
1348
+ * Used by Archil#exec — the map key is the relative path under /mnt/archil
1349
+ * at which to mount the disk.
1350
+ */
1351
+ type ExecMount = Disk | string;
1352
+ interface ExecOptions {
1353
+ /**
1354
+ * Disks to mount, keyed by the relative path under `/mnt/archil` at which
1355
+ * to mount each one. At least one entry is required. Paths must be
1356
+ * non-empty, non-absolute, and contain no `.` or `..` segments.
1357
+ */
1358
+ disks: Record<string, ExecMount>;
1359
+ /** Shell command to run inside the container. */
1360
+ command: string;
1361
+ }
1122
1362
  declare class Archil {
1123
1363
  readonly disks: Disks;
1124
1364
  readonly tokens: Tokens;
1365
+ /** @internal */
1366
+ private readonly _client;
1125
1367
  constructor(opts?: ArchilOptions);
1368
+ /**
1369
+ * Run a command in a container with multiple disks mounted simultaneously,
1370
+ * each at its own relative path under `/mnt/archil`. Blocks until the
1371
+ * command completes and returns its stdout, stderr, exit code, and timing.
1372
+ */
1373
+ exec(opts: ExecOptions): Promise<ExecDiskResult>;
1126
1374
  }
1127
1375
 
1128
1376
  declare class ArchilApiError extends Error {
@@ -1140,5 +1388,11 @@ declare function createApiKey(req: CreateApiTokenRequest): Promise<ApiTokenRespo
1140
1388
  token?: string;
1141
1389
  }>;
1142
1390
  declare function deleteApiKey(id: string): Promise<void>;
1391
+ /**
1392
+ * Run a command in a container with multiple disks mounted simultaneously,
1393
+ * each at its own relative path under `/mnt/archil`. Blocks until the
1394
+ * command completes and returns its stdout, stderr, exit code, and timing.
1395
+ */
1396
+ declare function exec(opts: ExecOptions): Promise<ExecDiskResult>;
1143
1397
 
1144
- export { type ApiTokenResponse, Archil, ArchilApiError, type ArchilOptions, type AuthorizedUser, type AwsStsUser, type AzureBlobMount, type ConnectedClient, type CreateApiTokenRequest, type CreateDiskRequest, type CreateDiskResult, Disk, type DiskMetrics, type DiskResponse, type DiskStatus, type DiskUser, Disks, type ExecResult, type GCSMount, type ListDisksOptions, type ListTokensOptions, type MountConfig, type MountConfigResponse, type MountOptions, type MountResponse, type R2Mount, type S3CompatibleMount, type S3Mount, type TokenUser, Tokens, configure, createApiKey, createDisk, deleteApiKey, getDisk, listApiKeys, listDisks };
1398
+ export { type ApiTokenResponse, Archil, ArchilApiError, type ArchilOptions, type AuthorizedUser, type AwsStsUser, type AzureBlobMount, type ConnectedClient, type CreateApiTokenRequest, type CreateDiskRequest, type CreateDiskResult, Disk, type DiskMetrics, type DiskResponse, type DiskStatus, type DiskUser, Disks, type ExecMount, type ExecOptions, type ExecRequest, type ExecResult, type GCSMount, type ListDisksOptions, type ListTokensOptions, type MountConfig, type MountConfigResponse, type MountOptions, type MountResponse, type R2Mount, type S3CompatibleMount, type S3Mount, type TokenUser, Tokens, configure, createApiKey, createDisk, deleteApiKey, exec, getDisk, listApiKeys, listDisks };
package/dist/index.d.ts CHANGED
@@ -102,6 +102,30 @@ interface paths {
102
102
  patch?: never;
103
103
  trace?: never;
104
104
  };
105
+ "/api/disks/{id}/allowed-ips": {
106
+ parameters: {
107
+ query?: never;
108
+ header?: never;
109
+ path?: never;
110
+ cookie?: never;
111
+ };
112
+ /**
113
+ * Get IP allowlist
114
+ * @description Returns the current IP allowlist for a disk.
115
+ */
116
+ get: operations["getAllowedIPs"];
117
+ /**
118
+ * Set IP allowlist
119
+ * @description Replaces the IP allowlist for a disk. When non-empty, only clients connecting from listed IPs or CIDR ranges can mount the disk. Pass an empty array to remove all restrictions.
120
+ */
121
+ put: operations["setAllowedIPs"];
122
+ post?: never;
123
+ delete?: never;
124
+ options?: never;
125
+ head?: never;
126
+ patch?: never;
127
+ trace?: never;
128
+ };
105
129
  "/api/disks/{id}/exec": {
106
130
  parameters: {
107
131
  query?: never;
@@ -124,6 +148,33 @@ interface paths {
124
148
  patch?: never;
125
149
  trace?: never;
126
150
  };
151
+ "/api/exec": {
152
+ parameters: {
153
+ query?: never;
154
+ header?: never;
155
+ path?: never;
156
+ cookie?: never;
157
+ };
158
+ get?: never;
159
+ put?: never;
160
+ /**
161
+ * Execute a command with multiple disks mounted
162
+ * @description Launches a container with the supplied set of disks each mounted at its
163
+ * own relative path under `/mnt/archil`, runs the command to completion,
164
+ * and shuts down the container. Activation is atomic: every disk mounts
165
+ * or none of them do.
166
+ *
167
+ * Relative paths must be non-empty, non-absolute, and contain no `.` /
168
+ * `..` segments. Mounting two disks at the same relative path is an
169
+ * error.
170
+ */
171
+ post: operations["exec"];
172
+ delete?: never;
173
+ options?: never;
174
+ head?: never;
175
+ patch?: never;
176
+ trace?: never;
177
+ };
127
178
  "/api/tokens": {
128
179
  parameters: {
129
180
  query?: never;
@@ -197,6 +248,14 @@ interface components {
197
248
  name: string;
198
249
  /** @description Storage mount to attach. Omit for archil-managed storage. */
199
250
  mounts?: components["schemas"]["MountConfig"][];
251
+ /**
252
+ * @description IP allowlist for mount access. When non-empty, only clients connecting from these IPs or CIDR ranges can mount the disk. An empty list (default) allows all IPs.
253
+ * @example [
254
+ * "203.0.113.0/24",
255
+ * "198.51.100.42"
256
+ * ]
257
+ */
258
+ allowedIps?: string[];
200
259
  /**
201
260
  * @deprecated
202
261
  * @description Deprecated. Use AddDiskUser after creation instead. When provided, suppresses the default auto-generated token user.
@@ -438,6 +497,24 @@ interface components {
438
497
  metrics?: components["schemas"]["DiskMetrics"];
439
498
  connectedClients?: components["schemas"]["ConnectedClient"][];
440
499
  authorizedUsers?: components["schemas"]["AuthorizedUser"][];
500
+ /**
501
+ * @description IP allowlist for mount access. Empty means all IPs are allowed.
502
+ * @example [
503
+ * "203.0.113.0/24"
504
+ * ]
505
+ */
506
+ allowedIps?: string[];
507
+ /**
508
+ * @description Capabilities supported by this disk. Determined at creation
509
+ * time and immutable thereafter. Defined values:
510
+ *
511
+ * * `checkpoints` — checkpoints can be created on this disk. Not
512
+ * available for disks with bring-your-own buckets.
513
+ * @example [
514
+ * "checkpoints"
515
+ * ]
516
+ */
517
+ capabilities?: "checkpoints"[];
441
518
  };
442
519
  MountResponse: {
443
520
  /** @description Mount identifier */
@@ -538,6 +615,19 @@ interface components {
538
615
  success: boolean;
539
616
  data: components["schemas"]["AuthorizedUser"];
540
617
  };
618
+ ApiResponse_AllowedIPs: {
619
+ /** @example true */
620
+ success: boolean;
621
+ data: {
622
+ /**
623
+ * @example [
624
+ * "203.0.113.0/24",
625
+ * "198.51.100.42"
626
+ * ]
627
+ */
628
+ allowedIps: string[];
629
+ };
630
+ };
541
631
  CreateApiTokenRequest: {
542
632
  /** @description Token name */
543
633
  name: string;
@@ -616,6 +706,30 @@ interface components {
616
706
  success: boolean;
617
707
  data: components["schemas"]["ExecDiskResult"];
618
708
  };
709
+ ExecRequest: {
710
+ /**
711
+ * @description Map of relative path under `/mnt/archil` to the disk to mount
712
+ * there. At least one entry is required. Relative paths must be
713
+ * non-empty, non-absolute, and contain no `.` or `..` segments.
714
+ * @example {
715
+ * "data": "dsk-abc123",
716
+ * "logs": "dsk-def456"
717
+ * }
718
+ */
719
+ disks: {
720
+ [key: string]: string;
721
+ };
722
+ /**
723
+ * @description Shell command to execute inside the container
724
+ * @example ls -la /mnt/archil/data /mnt/archil/logs
725
+ */
726
+ command: string;
727
+ };
728
+ ApiResponse_Exec: {
729
+ /** @example true */
730
+ success: boolean;
731
+ data: components["schemas"]["ExecDiskResult"];
732
+ };
619
733
  };
620
734
  responses: {
621
735
  /** @description Invalid or missing authentication credentials */
@@ -871,6 +985,72 @@ interface operations {
871
985
  500: components["responses"]["InternalError"];
872
986
  };
873
987
  };
988
+ getAllowedIPs: {
989
+ parameters: {
990
+ query?: never;
991
+ header?: never;
992
+ path: {
993
+ /** @description Disk ID (format `dsk-{16 hex chars}`) */
994
+ id: components["parameters"]["DiskId"];
995
+ };
996
+ cookie?: never;
997
+ };
998
+ requestBody?: never;
999
+ responses: {
1000
+ /** @description Current IP allowlist */
1001
+ 200: {
1002
+ headers: {
1003
+ [name: string]: unknown;
1004
+ };
1005
+ content: {
1006
+ "application/json": components["schemas"]["ApiResponse_AllowedIPs"];
1007
+ };
1008
+ };
1009
+ 401: components["responses"]["Unauthorized"];
1010
+ 404: components["responses"]["NotFound"];
1011
+ 500: components["responses"]["InternalError"];
1012
+ };
1013
+ };
1014
+ setAllowedIPs: {
1015
+ parameters: {
1016
+ query?: never;
1017
+ header?: never;
1018
+ path: {
1019
+ /** @description Disk ID (format `dsk-{16 hex chars}`) */
1020
+ id: components["parameters"]["DiskId"];
1021
+ };
1022
+ cookie?: never;
1023
+ };
1024
+ requestBody: {
1025
+ content: {
1026
+ "application/json": {
1027
+ /**
1028
+ * @description List of IPs or CIDR ranges. Empty array removes restrictions.
1029
+ * @example [
1030
+ * "203.0.113.0/24",
1031
+ * "198.51.100.42"
1032
+ * ]
1033
+ */
1034
+ allowedIps: string[];
1035
+ };
1036
+ };
1037
+ };
1038
+ responses: {
1039
+ /** @description Allowlist updated */
1040
+ 200: {
1041
+ headers: {
1042
+ [name: string]: unknown;
1043
+ };
1044
+ content: {
1045
+ "application/json": components["schemas"]["ApiResponse_AllowedIPs"];
1046
+ };
1047
+ };
1048
+ 400: components["responses"]["ValidationError"];
1049
+ 401: components["responses"]["Unauthorized"];
1050
+ 404: components["responses"]["NotFound"];
1051
+ 500: components["responses"]["InternalError"];
1052
+ };
1053
+ };
874
1054
  execDisk: {
875
1055
  parameters: {
876
1056
  query?: never;
@@ -911,6 +1091,43 @@ interface operations {
911
1091
  };
912
1092
  };
913
1093
  };
1094
+ exec: {
1095
+ parameters: {
1096
+ query?: never;
1097
+ header?: never;
1098
+ path?: never;
1099
+ cookie?: never;
1100
+ };
1101
+ requestBody: {
1102
+ content: {
1103
+ "application/json": components["schemas"]["ExecRequest"];
1104
+ };
1105
+ };
1106
+ responses: {
1107
+ /** @description Command completed */
1108
+ 200: {
1109
+ headers: {
1110
+ [name: string]: unknown;
1111
+ };
1112
+ content: {
1113
+ "application/json": components["schemas"]["ApiResponse_Exec"];
1114
+ };
1115
+ };
1116
+ 400: components["responses"]["ValidationError"];
1117
+ 401: components["responses"]["Unauthorized"];
1118
+ 404: components["responses"]["NotFound"];
1119
+ 500: components["responses"]["InternalError"];
1120
+ /** @description Command timed out */
1121
+ 504: {
1122
+ headers: {
1123
+ [name: string]: unknown;
1124
+ };
1125
+ content: {
1126
+ "application/json": components["schemas"]["ErrorResponse"];
1127
+ };
1128
+ };
1129
+ };
1130
+ };
914
1131
  listApiTokens: {
915
1132
  parameters: {
916
1133
  query?: {
@@ -1014,6 +1231,7 @@ type AwsStsUser = components["schemas"]["AwsStsUser"];
1014
1231
  type CreateApiTokenRequest = components["schemas"]["CreateApiTokenRequest"];
1015
1232
  type ApiTokenResponse = components["schemas"]["ApiTokenResponse"];
1016
1233
  type ExecDiskResult = components["schemas"]["ExecDiskResult"];
1234
+ type ExecRequest = components["schemas"]["ExecRequest"];
1017
1235
  type DiskStatus = DiskResponse["status"];
1018
1236
 
1019
1237
  interface MountOptions {
@@ -1039,6 +1257,7 @@ declare class Disk {
1039
1257
  readonly metrics?: DiskMetrics;
1040
1258
  readonly connectedClients?: ConnectedClient[];
1041
1259
  readonly authorizedUsers?: AuthorizedUser[];
1260
+ readonly allowedIps?: string[];
1042
1261
  /** @internal */
1043
1262
  private readonly _client;
1044
1263
  /** @internal */
@@ -1053,6 +1272,10 @@ declare class Disk {
1053
1272
  identifier: string;
1054
1273
  }>;
1055
1274
  removeTokenUser(identifier: string): Promise<void>;
1275
+ getAllowedIPs(): Promise<string[]>;
1276
+ setAllowedIPs(allowedIps: string[]): Promise<string[]>;
1277
+ addAllowedIP(ip: string): Promise<string[]>;
1278
+ removeAllowedIP(ip: string): Promise<string[]>;
1056
1279
  delete(): Promise<void>;
1057
1280
  /**
1058
1281
  * Execute a command in a container with this disk mounted.
@@ -1070,6 +1293,7 @@ declare class Disk {
1070
1293
  interface ListDisksOptions {
1071
1294
  limit?: number;
1072
1295
  cursor?: string;
1296
+ name?: string;
1073
1297
  }
1074
1298
  interface CreateDiskResult {
1075
1299
  disk: Disk;
@@ -1119,10 +1343,34 @@ interface ArchilOptions {
1119
1343
  /** Override the control plane base URL (useful for testing). */
1120
1344
  baseUrl?: string;
1121
1345
  }
1346
+ /**
1347
+ * One disk to mount, identified by a Disk instance or a raw disk id string.
1348
+ * Used by Archil#exec — the map key is the relative path under /mnt/archil
1349
+ * at which to mount the disk.
1350
+ */
1351
+ type ExecMount = Disk | string;
1352
+ interface ExecOptions {
1353
+ /**
1354
+ * Disks to mount, keyed by the relative path under `/mnt/archil` at which
1355
+ * to mount each one. At least one entry is required. Paths must be
1356
+ * non-empty, non-absolute, and contain no `.` or `..` segments.
1357
+ */
1358
+ disks: Record<string, ExecMount>;
1359
+ /** Shell command to run inside the container. */
1360
+ command: string;
1361
+ }
1122
1362
  declare class Archil {
1123
1363
  readonly disks: Disks;
1124
1364
  readonly tokens: Tokens;
1365
+ /** @internal */
1366
+ private readonly _client;
1125
1367
  constructor(opts?: ArchilOptions);
1368
+ /**
1369
+ * Run a command in a container with multiple disks mounted simultaneously,
1370
+ * each at its own relative path under `/mnt/archil`. Blocks until the
1371
+ * command completes and returns its stdout, stderr, exit code, and timing.
1372
+ */
1373
+ exec(opts: ExecOptions): Promise<ExecDiskResult>;
1126
1374
  }
1127
1375
 
1128
1376
  declare class ArchilApiError extends Error {
@@ -1140,5 +1388,11 @@ declare function createApiKey(req: CreateApiTokenRequest): Promise<ApiTokenRespo
1140
1388
  token?: string;
1141
1389
  }>;
1142
1390
  declare function deleteApiKey(id: string): Promise<void>;
1391
+ /**
1392
+ * Run a command in a container with multiple disks mounted simultaneously,
1393
+ * each at its own relative path under `/mnt/archil`. Blocks until the
1394
+ * command completes and returns its stdout, stderr, exit code, and timing.
1395
+ */
1396
+ declare function exec(opts: ExecOptions): Promise<ExecDiskResult>;
1143
1397
 
1144
- export { type ApiTokenResponse, Archil, ArchilApiError, type ArchilOptions, type AuthorizedUser, type AwsStsUser, type AzureBlobMount, type ConnectedClient, type CreateApiTokenRequest, type CreateDiskRequest, type CreateDiskResult, Disk, type DiskMetrics, type DiskResponse, type DiskStatus, type DiskUser, Disks, type ExecResult, type GCSMount, type ListDisksOptions, type ListTokensOptions, type MountConfig, type MountConfigResponse, type MountOptions, type MountResponse, type R2Mount, type S3CompatibleMount, type S3Mount, type TokenUser, Tokens, configure, createApiKey, createDisk, deleteApiKey, getDisk, listApiKeys, listDisks };
1398
+ export { type ApiTokenResponse, Archil, ArchilApiError, type ArchilOptions, type AuthorizedUser, type AwsStsUser, type AzureBlobMount, type ConnectedClient, type CreateApiTokenRequest, type CreateDiskRequest, type CreateDiskResult, Disk, type DiskMetrics, type DiskResponse, type DiskStatus, type DiskUser, Disks, type ExecMount, type ExecOptions, type ExecRequest, type ExecResult, type GCSMount, type ListDisksOptions, type ListTokensOptions, type MountConfig, type MountConfigResponse, type MountOptions, type MountResponse, type R2Mount, type S3CompatibleMount, type S3Mount, type TokenUser, Tokens, configure, createApiKey, createDisk, deleteApiKey, exec, getDisk, listApiKeys, listDisks };
package/dist/index.js CHANGED
@@ -93,6 +93,7 @@ var Disk = class {
93
93
  metrics;
94
94
  connectedClients;
95
95
  authorizedUsers;
96
+ allowedIps;
96
97
  /** @internal */
97
98
  _client;
98
99
  /** @internal */
@@ -114,6 +115,7 @@ var Disk = class {
114
115
  this.metrics = data.metrics;
115
116
  this.connectedClients = data.connectedClients;
116
117
  this.authorizedUsers = data.authorizedUsers;
118
+ this.allowedIps = data.allowedIps;
117
119
  this._client = client;
118
120
  this._archilRegion = archilRegion;
119
121
  }
@@ -133,7 +135,8 @@ var Disk = class {
133
135
  mounts: this.mounts,
134
136
  metrics: this.metrics,
135
137
  connectedClients: this.connectedClients,
136
- authorizedUsers: this.authorizedUsers
138
+ authorizedUsers: this.authorizedUsers,
139
+ allowedIps: this.allowedIps
137
140
  };
138
141
  }
139
142
  async addUser(user) {
@@ -169,6 +172,32 @@ var Disk = class {
169
172
  async removeTokenUser(identifier) {
170
173
  await this.removeUser("token", identifier);
171
174
  }
175
+ async getAllowedIPs() {
176
+ const data = await unwrap(
177
+ this._client.GET("/api/disks/{id}/allowed-ips", {
178
+ params: { path: { id: this.id } }
179
+ })
180
+ );
181
+ return data.allowedIps;
182
+ }
183
+ async setAllowedIPs(allowedIps) {
184
+ const data = await unwrap(
185
+ this._client.PUT("/api/disks/{id}/allowed-ips", {
186
+ params: { path: { id: this.id } },
187
+ body: { allowedIps }
188
+ })
189
+ );
190
+ return data.allowedIps;
191
+ }
192
+ async addAllowedIP(ip) {
193
+ const current = await this.getAllowedIPs();
194
+ if (current.includes(ip)) return current;
195
+ return this.setAllowedIPs([...current, ip]);
196
+ }
197
+ async removeAllowedIP(ip) {
198
+ const current = await this.getAllowedIPs();
199
+ return this.setAllowedIPs(current.filter((i) => i !== ip));
200
+ }
172
201
  async delete() {
173
202
  await unwrapEmpty(
174
203
  this._client.DELETE("/api/disks/{id}", {
@@ -230,7 +259,7 @@ var Disks = class {
230
259
  async list(opts) {
231
260
  const data = await unwrap(
232
261
  this._client.GET("/api/disks", {
233
- params: { query: { limit: opts?.limit, cursor: opts?.cursor } }
262
+ params: { query: { limit: opts?.limit, cursor: opts?.cursor, name: opts?.name } }
234
263
  })
235
264
  );
236
265
  return data.map(
@@ -306,6 +335,8 @@ var Tokens = class {
306
335
  var Archil = class {
307
336
  disks;
308
337
  tokens;
338
+ /** @internal */
339
+ _client;
309
340
  constructor(opts = {}) {
310
341
  const apiKey = opts.apiKey ?? process.env.ARCHIL_API_KEY;
311
342
  const region = opts.region ?? process.env.ARCHIL_REGION;
@@ -320,9 +351,26 @@ var Archil = class {
320
351
  region,
321
352
  baseUrl: opts.baseUrl
322
353
  });
354
+ this._client = client;
323
355
  this.disks = new Disks(client, region);
324
356
  this.tokens = new Tokens(client);
325
357
  }
358
+ /**
359
+ * Run a command in a container with multiple disks mounted simultaneously,
360
+ * each at its own relative path under `/mnt/archil`. Blocks until the
361
+ * command completes and returns its stdout, stderr, exit code, and timing.
362
+ */
363
+ async exec(opts) {
364
+ const disks = {};
365
+ for (const [relPath, mount] of Object.entries(opts.disks)) {
366
+ disks[relPath] = typeof mount === "string" ? mount : mount.id;
367
+ }
368
+ return unwrap(
369
+ this._client.POST("/api/exec", {
370
+ body: { disks, command: opts.command }
371
+ })
372
+ );
373
+ }
326
374
  };
327
375
 
328
376
  // src/index.ts
@@ -356,6 +404,9 @@ function createApiKey(req) {
356
404
  function deleteApiKey(id) {
357
405
  return archil().tokens.delete(id);
358
406
  }
407
+ function exec(opts) {
408
+ return archil().exec(opts);
409
+ }
359
410
  export {
360
411
  Archil,
361
412
  ArchilApiError,
@@ -366,6 +417,7 @@ export {
366
417
  createApiKey,
367
418
  createDisk,
368
419
  deleteApiKey,
420
+ exec,
369
421
  getDisk,
370
422
  listApiKeys,
371
423
  listDisks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "disk",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "Pure-JS client and CLI for Archil disks",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",