disk 0.8.9 → 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}", {
@@ -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
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}", {
@@ -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.
@@ -1120,10 +1343,34 @@ interface ArchilOptions {
1120
1343
  /** Override the control plane base URL (useful for testing). */
1121
1344
  baseUrl?: string;
1122
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
+ }
1123
1362
  declare class Archil {
1124
1363
  readonly disks: Disks;
1125
1364
  readonly tokens: Tokens;
1365
+ /** @internal */
1366
+ private readonly _client;
1126
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>;
1127
1374
  }
1128
1375
 
1129
1376
  declare class ArchilApiError extends Error {
@@ -1141,5 +1388,11 @@ declare function createApiKey(req: CreateApiTokenRequest): Promise<ApiTokenRespo
1141
1388
  token?: string;
1142
1389
  }>;
1143
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>;
1144
1397
 
1145
- 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.
@@ -1120,10 +1343,34 @@ interface ArchilOptions {
1120
1343
  /** Override the control plane base URL (useful for testing). */
1121
1344
  baseUrl?: string;
1122
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
+ }
1123
1362
  declare class Archil {
1124
1363
  readonly disks: Disks;
1125
1364
  readonly tokens: Tokens;
1365
+ /** @internal */
1366
+ private readonly _client;
1126
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>;
1127
1374
  }
1128
1375
 
1129
1376
  declare class ArchilApiError extends Error {
@@ -1141,5 +1388,11 @@ declare function createApiKey(req: CreateApiTokenRequest): Promise<ApiTokenRespo
1141
1388
  token?: string;
1142
1389
  }>;
1143
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>;
1144
1397
 
1145
- 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}", {
@@ -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.9",
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",