@decocms/sandbox-controller 0.6.0 → 0.7.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/dist/index.d.ts +2421 -5
- package/dist/index.js +191 -29
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -60,6 +60,15 @@ interface Sandbox {
|
|
|
60
60
|
warmPoolAdopted: boolean;
|
|
61
61
|
/** Where the sandbox runs. */
|
|
62
62
|
provider: SandboxProviderKind;
|
|
63
|
+
/** Why it runs there, when `SandboxProviderRouter` had a choice to make. */
|
|
64
|
+
placement?: SandboxPlacement;
|
|
65
|
+
}
|
|
66
|
+
declare const SANDBOX_PLACEMENT_REASONS: readonly ["existing", "requested", "split", "capacity", "fallback", "image", "warm-pool", "freestyle-unavailable"];
|
|
67
|
+
type SandboxPlacementReason = (typeof SANDBOX_PLACEMENT_REASONS)[number];
|
|
68
|
+
interface SandboxPlacement {
|
|
69
|
+
reason: SandboxPlacementReason;
|
|
70
|
+
/** The provider whose ensure failed, when `reason` is `fallback`. */
|
|
71
|
+
fallbackFrom?: SandboxProviderKind;
|
|
63
72
|
}
|
|
64
73
|
/** When omitted, no dev server is started; the default tool image is used. */
|
|
65
74
|
interface Workload {
|
|
@@ -490,6 +499,22 @@ declare const ensureOutputSchema: z.ZodObject<{
|
|
|
490
499
|
kubernetes: "kubernetes";
|
|
491
500
|
freestyle: "freestyle";
|
|
492
501
|
}>>;
|
|
502
|
+
placement: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
503
|
+
reason: z.ZodEnum<{
|
|
504
|
+
existing: "existing";
|
|
505
|
+
requested: "requested";
|
|
506
|
+
split: "split";
|
|
507
|
+
capacity: "capacity";
|
|
508
|
+
fallback: "fallback";
|
|
509
|
+
image: "image";
|
|
510
|
+
"warm-pool": "warm-pool";
|
|
511
|
+
"freestyle-unavailable": "freestyle-unavailable";
|
|
512
|
+
}>;
|
|
513
|
+
fallbackFrom: z.ZodOptional<z.ZodEnum<{
|
|
514
|
+
kubernetes: "kubernetes";
|
|
515
|
+
freestyle: "freestyle";
|
|
516
|
+
}>>;
|
|
517
|
+
}, z.core.$strip>>>;
|
|
493
518
|
daemon: z.ZodObject<{
|
|
494
519
|
url: z.ZodURL;
|
|
495
520
|
token: z.ZodString;
|
|
@@ -1310,6 +1335,2364 @@ interface TenantPoolReader {
|
|
|
1310
1335
|
/** A cached pool list for `AgentSandboxProvider`'s `tenantPools`; a failed load keeps the last one. */
|
|
1311
1336
|
declare function tenantPoolReader(store: Pick<TenantPoolStore, "list">, cluster: string, { refreshMs }?: TenantPoolReaderOptions): TenantPoolReader;
|
|
1312
1337
|
|
|
1338
|
+
/** Options every credential mode shares. */
|
|
1339
|
+
interface TransportOptions {
|
|
1340
|
+
baseUrl?: string;
|
|
1341
|
+
/**
|
|
1342
|
+
* Transport to use instead of the built-in one, which speaks HTTP/2 on Node
|
|
1343
|
+
* and defers to the runtime's `fetch` everywhere else. Pass one to route
|
|
1344
|
+
* through a proxy, size the connection pool yourself, or add retries.
|
|
1345
|
+
*/
|
|
1346
|
+
fetch?: typeof fetch;
|
|
1347
|
+
}
|
|
1348
|
+
type FreestyleOptions = ({
|
|
1349
|
+
apiKey?: string;
|
|
1350
|
+
identityAccessToken?: never;
|
|
1351
|
+
} & TransportOptions) | ({
|
|
1352
|
+
identityAccessToken: string;
|
|
1353
|
+
apiKey?: never;
|
|
1354
|
+
} & TransportOptions) | ({
|
|
1355
|
+
/** Stack access token used by the CLI; resolved to `teamId` at the gateway. */
|
|
1356
|
+
stackAccessToken: string;
|
|
1357
|
+
teamId: string;
|
|
1358
|
+
apiKey?: never;
|
|
1359
|
+
identityAccessToken?: never;
|
|
1360
|
+
} & TransportOptions);
|
|
1361
|
+
/** `undefined`/`null` query values are dropped; everything else is stringified. */
|
|
1362
|
+
type QueryValue = string | number | boolean | undefined | null;
|
|
1363
|
+
/**
|
|
1364
|
+
* Thin HTTP client for the Freestyle public API: attaches the configured
|
|
1365
|
+
* credential, resolves the base URL, and turns non-2xx responses into
|
|
1366
|
+
* {@link FreestyleApiError}.
|
|
1367
|
+
*
|
|
1368
|
+
* Every request runs as a background request: the API detaches the work from
|
|
1369
|
+
* the HTTP connection, so a call that outlives the connection (a long
|
|
1370
|
+
* snapshot materialization, a cold VM boot — including one triggered by a
|
|
1371
|
+
* read that wakes the VM, a client/proxy timeout) still runs to completion.
|
|
1372
|
+
* Calls that finish within a few seconds return inline exactly as usual;
|
|
1373
|
+
* longer ones answer `202 Accepted` and the client transparently polls
|
|
1374
|
+
* `GET /v5/background-requests/{id}` until the result — same value, same
|
|
1375
|
+
* errors — is ready. If a finished result was too large to store (the API
|
|
1376
|
+
* caps stored bodies), idempotent GETs are re-fetched once directly: by then
|
|
1377
|
+
* the slow part (e.g. waking the VM) has already happened. Identity-token
|
|
1378
|
+
* clients are the one exception: their scope cannot reach the poll route,
|
|
1379
|
+
* so none of their requests are backgrounded.
|
|
1380
|
+
*/
|
|
1381
|
+
declare class FreestyleClient {
|
|
1382
|
+
readonly baseUrl: string;
|
|
1383
|
+
private readonly authenticationHeaders;
|
|
1384
|
+
private readonly fetchImpl;
|
|
1385
|
+
/** Identity-token callers cannot poll `/v5/background-requests`, so their requests are never backgrounded. */
|
|
1386
|
+
private readonly backgroundEligible;
|
|
1387
|
+
constructor(options?: FreestyleOptions);
|
|
1388
|
+
/** Headers this client authenticates every request with, e.g. for a raw WebSocket handshake. */
|
|
1389
|
+
authHeaders(): Record<string, string>;
|
|
1390
|
+
/** Absolute URL for an API path, e.g. `/v5/vms/{id}` → `https://.../v5/vms/{id}`. */
|
|
1391
|
+
url(path: string, query?: Record<string, QueryValue>): string;
|
|
1392
|
+
/**
|
|
1393
|
+
* Low-level escape hatch: an authenticated `fetch` against the API, relative
|
|
1394
|
+
* to `baseUrl`. Pass the whole path, `/v5` included — the same paths the
|
|
1395
|
+
* methods above use, and the same ones the API declares. It is also how the
|
|
1396
|
+
* client follows a server-supplied URL (a 202's `resultUrl`), and the only
|
|
1397
|
+
* way to reach the unversioned `/status` and `/openapi.json`.
|
|
1398
|
+
*/
|
|
1399
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
1400
|
+
/** A path or URL as this client's `fetch` resolves it: absolute URLs are
|
|
1401
|
+
* followed as given, everything else hangs off `baseUrl` with no version
|
|
1402
|
+
* segment added — the caller already supplied whatever version it wants. */
|
|
1403
|
+
private absoluteUrl;
|
|
1404
|
+
private request;
|
|
1405
|
+
/**
|
|
1406
|
+
* Follow a 202 from a backgrounded request: poll its result URL until the
|
|
1407
|
+
* stored response — success or error, identical to what the inline call
|
|
1408
|
+
* would have produced — is ready.
|
|
1409
|
+
*/
|
|
1410
|
+
private pollBackgroundRequest;
|
|
1411
|
+
/** Turn a terminal API response into the call's value, or throw its error. */
|
|
1412
|
+
private finalize;
|
|
1413
|
+
get<T>(path: string, query?: Record<string, QueryValue>): Promise<T>;
|
|
1414
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
1415
|
+
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
1416
|
+
put<T>(path: string, body?: unknown): Promise<T>;
|
|
1417
|
+
putEmpty<T>(path: string): Promise<T>;
|
|
1418
|
+
/** PUT raw bytes as `application/octet-stream`: a whole file, or one chunk of one. */
|
|
1419
|
+
putBytes<T>(path: string, rawBody: Uint8Array | Blob, query?: Record<string, QueryValue>, signal?: AbortSignal): Promise<T>;
|
|
1420
|
+
/**
|
|
1421
|
+
* GET a response whose body is bytes rather than JSON, returned unread so the
|
|
1422
|
+
* caller can stream it. Errors still surface as {@link FreestyleApiError}.
|
|
1423
|
+
*/
|
|
1424
|
+
getBytes(path: string, query?: Record<string, QueryValue>, init?: {
|
|
1425
|
+
range?: string;
|
|
1426
|
+
signal?: AbortSignal;
|
|
1427
|
+
}): Promise<Response>;
|
|
1428
|
+
delete<T = void>(path: string, query?: Record<string, QueryValue>): Promise<T>;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
type VerificationState = "pending" | "verified";
|
|
1432
|
+
interface DomainVerification {
|
|
1433
|
+
id: string;
|
|
1434
|
+
domain: string;
|
|
1435
|
+
verificationCode: string;
|
|
1436
|
+
/** Place `verificationCode` in a TXT record at this name, then verify. */
|
|
1437
|
+
recordName: string;
|
|
1438
|
+
state: VerificationState;
|
|
1439
|
+
/** When this challenge proved control; null while pending. */
|
|
1440
|
+
verifiedAt: string | null;
|
|
1441
|
+
createdAt: string;
|
|
1442
|
+
}
|
|
1443
|
+
interface DomainOwnership {
|
|
1444
|
+
domain: string;
|
|
1445
|
+
createdAt: string;
|
|
1446
|
+
}
|
|
1447
|
+
/** A successful verification: ownership, plus which challenge proved it. */
|
|
1448
|
+
interface DomainVerified {
|
|
1449
|
+
domain: string;
|
|
1450
|
+
createdAt: string;
|
|
1451
|
+
verifiedBy: string;
|
|
1452
|
+
}
|
|
1453
|
+
interface CertificateInfo {
|
|
1454
|
+
domain: string;
|
|
1455
|
+
wildcard: boolean;
|
|
1456
|
+
active: boolean;
|
|
1457
|
+
notAfter: string;
|
|
1458
|
+
generation: number;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* What your placeholder pages wear. A placeholder page is the edge's 404 for
|
|
1462
|
+
* a hostname under one of your verified domains that none of your TLS rules
|
|
1463
|
+
* serve; it reloads itself until a route appears.
|
|
1464
|
+
*/
|
|
1465
|
+
interface PlaceholderBranding {
|
|
1466
|
+
/** An https URL of the logo. The visitor's browser loads it directly. */
|
|
1467
|
+
logoUrl: string;
|
|
1468
|
+
/** Named on the page in place of Freestyle; null = logo only. */
|
|
1469
|
+
productName: string | null;
|
|
1470
|
+
createdAt: string;
|
|
1471
|
+
updatedAt: string;
|
|
1472
|
+
}
|
|
1473
|
+
interface SetPlaceholderBrandingOptions {
|
|
1474
|
+
/** An https URL of the logo, at most 2048 characters. */
|
|
1475
|
+
logoUrl: string;
|
|
1476
|
+
/** Named on the page in place of Freestyle, at most 64 characters. Omit for logo only. */
|
|
1477
|
+
productName?: string;
|
|
1478
|
+
}
|
|
1479
|
+
interface ListVerificationsOptions {
|
|
1480
|
+
/** Narrow to one state. Omit for every challenge the account holds, verified records included. */
|
|
1481
|
+
state?: VerificationState;
|
|
1482
|
+
}
|
|
1483
|
+
/** The `freestyle.domains` namespace. */
|
|
1484
|
+
declare class DomainsNamespace {
|
|
1485
|
+
private readonly client;
|
|
1486
|
+
readonly verifications: DomainVerificationsNamespace;
|
|
1487
|
+
readonly certificates: DomainCertificatesNamespace;
|
|
1488
|
+
/** The branding on the 404 page for hostnames under your domains that nothing serves. */
|
|
1489
|
+
readonly placeholder: DomainPlaceholderNamespace;
|
|
1490
|
+
constructor(client: FreestyleClient);
|
|
1491
|
+
/**
|
|
1492
|
+
* List the domains you hold: those you verified, and any Freestyle
|
|
1493
|
+
* subdomain you have taken by publishing it.
|
|
1494
|
+
*/
|
|
1495
|
+
list(): Promise<DomainOwnership[]>;
|
|
1496
|
+
}
|
|
1497
|
+
declare class DomainVerificationsNamespace {
|
|
1498
|
+
private readonly client;
|
|
1499
|
+
constructor(client: FreestyleClient);
|
|
1500
|
+
/**
|
|
1501
|
+
* Issue a challenge. Publish the returned `verificationCode` as a TXT
|
|
1502
|
+
* record at `recordName`, then `complete()` it.
|
|
1503
|
+
*/
|
|
1504
|
+
create(domain: string): Promise<DomainVerification>;
|
|
1505
|
+
list(options?: ListVerificationsOptions): Promise<DomainVerification[]>;
|
|
1506
|
+
/** Fetch a verification challenge by its id or its domain. */
|
|
1507
|
+
get(domainOrId: string): Promise<DomainVerification>;
|
|
1508
|
+
/**
|
|
1509
|
+
* Check DNS and record ownership. Addressed by id, only that challenge's
|
|
1510
|
+
* own code counts; use this when several people are verifying the same
|
|
1511
|
+
* domain.
|
|
1512
|
+
*/
|
|
1513
|
+
complete(domainOrId: string): Promise<DomainVerified>;
|
|
1514
|
+
/**
|
|
1515
|
+
* Withdraw outstanding challenges; by domain this drops all of them. A
|
|
1516
|
+
* challenge that already verified is a permanent record and cannot be
|
|
1517
|
+
* deleted.
|
|
1518
|
+
*/
|
|
1519
|
+
delete(domainOrId: string): Promise<void>;
|
|
1520
|
+
}
|
|
1521
|
+
declare class DomainCertificatesNamespace {
|
|
1522
|
+
private readonly client;
|
|
1523
|
+
constructor(client: FreestyleClient);
|
|
1524
|
+
list(): Promise<CertificateInfo[]>;
|
|
1525
|
+
/**
|
|
1526
|
+
* Request an ongoing `*.domain` certificate. Requires
|
|
1527
|
+
* `_acme-challenge.<domain>` to be NS-delegated to Freestyle's
|
|
1528
|
+
* nameservers, since wildcards can only be proven over DNS.
|
|
1529
|
+
*/
|
|
1530
|
+
createWildcard(domain: string): Promise<CertificateInfo>;
|
|
1531
|
+
}
|
|
1532
|
+
/** The `freestyle.domains.placeholder` namespace. */
|
|
1533
|
+
declare class DomainPlaceholderNamespace {
|
|
1534
|
+
private readonly client;
|
|
1535
|
+
constructor(client: FreestyleClient);
|
|
1536
|
+
/** Your placeholder branding, or `null` when pages carry the Freestyle default. */
|
|
1537
|
+
get(): Promise<PlaceholderBranding | null>;
|
|
1538
|
+
/**
|
|
1539
|
+
* Set the branding for every placeholder page under your verified domains,
|
|
1540
|
+
* including domains you verify later. Replaces any earlier setting.
|
|
1541
|
+
*/
|
|
1542
|
+
set(options: SetPlaceholderBrandingOptions): Promise<PlaceholderBranding>;
|
|
1543
|
+
/** Remove your branding; pages go back to the Freestyle default. */
|
|
1544
|
+
delete(): Promise<void>;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/** The transport a matcher is restricted to. */
|
|
1548
|
+
type FirewallProtocol = "tcp" | "udp" | "icmp";
|
|
1549
|
+
/**
|
|
1550
|
+
* What a rule does with the traffic it matches.
|
|
1551
|
+
*
|
|
1552
|
+
* Only `allow` exists today, and every rule states it rather than defaulting:
|
|
1553
|
+
* a default would mean that the day `deny` arrives, every rule written before
|
|
1554
|
+
* it silently acquires an action it never stated — and "what does this rule
|
|
1555
|
+
* do" is the one question a firewall rule must never answer by omission.
|
|
1556
|
+
*/
|
|
1557
|
+
type FirewallAction = "allow";
|
|
1558
|
+
/**
|
|
1559
|
+
* One end of a rule: which traffic this side matches.
|
|
1560
|
+
*
|
|
1561
|
+
* Fields intersect. `{ vpcId: "vpc-db", port: 5432, protocol: "tcp" }` is
|
|
1562
|
+
* "traffic in that network, on 5432, over TCP" — not three alternatives.
|
|
1563
|
+
*
|
|
1564
|
+
* Matchers select two different ways. `vmId`/`vpcId`/`tunnelId` select by
|
|
1565
|
+
* **identity**: they match that resource however it is addressed. `cidr`/
|
|
1566
|
+
* `public` select by **address**: `public: true` is every publicly routable
|
|
1567
|
+
* address, whoever owns it — another Freestyle VM reached at its public address
|
|
1568
|
+
* is reached over the public Internet like any other host. The two overlap, and
|
|
1569
|
+
* allow rules union, so that is simply more traffic allowed.
|
|
1570
|
+
*
|
|
1571
|
+
* The public Internet is `public: true`, never an empty matcher. An empty
|
|
1572
|
+
* matcher is the absence of a statement, and that is what
|
|
1573
|
+
* {@link VmFirewallSpec} uses to mean "the VM being created"; if emptiness
|
|
1574
|
+
* also meant the Internet, the most dangerous rule you can write would be the
|
|
1575
|
+
* one you get by forgetting a field.
|
|
1576
|
+
*
|
|
1577
|
+
* New selectors arrive as new optional fields, so this type only ever grows.
|
|
1578
|
+
*/
|
|
1579
|
+
interface FirewallEndpoint {
|
|
1580
|
+
/** A single VM, by id or by your slug for it. Answers hold its id. */
|
|
1581
|
+
vmId?: string;
|
|
1582
|
+
/** Everything on one private network, by id or by your slug for it. Answers hold its id. */
|
|
1583
|
+
vpcId?: string;
|
|
1584
|
+
/**
|
|
1585
|
+
* Whatever is on the far side of one tunnel — the client that dials it, and
|
|
1586
|
+
* any remote range routed over it. Tunnels have no slug, so this is a tunnel
|
|
1587
|
+
* id: `tun-…`, or `wg-…` for one carried over from the per-VPC VPN records
|
|
1588
|
+
* tunnels replaced.
|
|
1589
|
+
*
|
|
1590
|
+
* The one selector that names traffic arriving from outside the platform by
|
|
1591
|
+
* identity rather than by address: the peer's addresses are the peer's
|
|
1592
|
+
* business, and can change without the rules admitting it changing too.
|
|
1593
|
+
*/
|
|
1594
|
+
tunnelId?: string;
|
|
1595
|
+
/** An address range, IPv4 or IPv6, in canonical `network/prefix` form. */
|
|
1596
|
+
cidr?: string;
|
|
1597
|
+
/**
|
|
1598
|
+
* Every publicly routable address, whoever owns it — including another
|
|
1599
|
+
* Freestyle VM reached at its public address. Only `true` is meaningful —
|
|
1600
|
+
* omit the field otherwise — and it is not combined with `vmId` or `vpcId`.
|
|
1601
|
+
*/
|
|
1602
|
+
public?: true;
|
|
1603
|
+
/** A single port, 1–65535. Requires {@link FirewallEndpoint.protocol}. */
|
|
1604
|
+
port?: number;
|
|
1605
|
+
protocol?: FirewallProtocol;
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* A resource a rule cannot outlive. Deleting the resource deletes the rule, so
|
|
1609
|
+
* no rule is ever left naming something that no longer exists.
|
|
1610
|
+
*/
|
|
1611
|
+
interface FirewallDependency {
|
|
1612
|
+
/** `"vm"`, `"vpc"`, or `"tunnel"` today; new selectors add new kinds. */
|
|
1613
|
+
kind: string;
|
|
1614
|
+
id: string;
|
|
1615
|
+
/**
|
|
1616
|
+
* Your handle for the named resource, when it has one — so a rule can say
|
|
1617
|
+
* what it points at without a lookup per id.
|
|
1618
|
+
*/
|
|
1619
|
+
slug?: string | null;
|
|
1620
|
+
/** Your label for the named resource, under the same rules as `slug`. */
|
|
1621
|
+
displayName?: string | null;
|
|
1622
|
+
}
|
|
1623
|
+
/** An allowed path through the network: traffic matching `source` may reach `destination`. */
|
|
1624
|
+
interface FirewallRuleData {
|
|
1625
|
+
id: string;
|
|
1626
|
+
action: FirewallAction;
|
|
1627
|
+
source: FirewallEndpoint;
|
|
1628
|
+
destination: FirewallEndpoint;
|
|
1629
|
+
description?: string | null;
|
|
1630
|
+
/** What this rule dies with. See {@link FirewallDependency}. */
|
|
1631
|
+
dependencies: FirewallDependency[];
|
|
1632
|
+
createdAt: string;
|
|
1633
|
+
updatedAt: string;
|
|
1634
|
+
}
|
|
1635
|
+
interface CreateFirewallRuleOptions {
|
|
1636
|
+
/** Required. `"allow"` is the only accepted value. */
|
|
1637
|
+
action: FirewallAction;
|
|
1638
|
+
source: FirewallEndpoint;
|
|
1639
|
+
destination: FirewallEndpoint;
|
|
1640
|
+
/** A free-form note, up to 1024 characters. */
|
|
1641
|
+
description?: string;
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* A rule declared inside a create, where the resource being created has no id
|
|
1645
|
+
* yet.
|
|
1646
|
+
*
|
|
1647
|
+
* Exactly one endpoint may be left without an identity; that one *is* the new
|
|
1648
|
+
* VM. So `{ source: { public: true }, destination: { port: 443, protocol:
|
|
1649
|
+
* "tcp" } }` is inbound HTTPS to it, and `{ source: {}, destination: { public:
|
|
1650
|
+
* true, port: 443, protocol: "tcp" } }` is outbound HTTPS from it.
|
|
1651
|
+
*/
|
|
1652
|
+
type InlineFirewallRule = CreateFirewallRuleOptions;
|
|
1653
|
+
/** The `firewall` block of a create. */
|
|
1654
|
+
interface FirewallSpec {
|
|
1655
|
+
rules: InlineFirewallRule[];
|
|
1656
|
+
}
|
|
1657
|
+
interface ListFirewallRulesOptions {
|
|
1658
|
+
/**
|
|
1659
|
+
* Rules that apply to this VM: those naming it, plus those naming a private
|
|
1660
|
+
* network it is attached to.
|
|
1661
|
+
*/
|
|
1662
|
+
vmId?: string;
|
|
1663
|
+
/** Rules naming this private network. */
|
|
1664
|
+
vpcId?: string;
|
|
1665
|
+
/** Rules naming this tunnel, by its id. */
|
|
1666
|
+
tunnelId?: string;
|
|
1667
|
+
limit?: number;
|
|
1668
|
+
offset?: number;
|
|
1669
|
+
}
|
|
1670
|
+
interface ListFirewallRulesResult {
|
|
1671
|
+
/** Newest first. */
|
|
1672
|
+
rules: FirewallRuleData[];
|
|
1673
|
+
totalCount: number;
|
|
1674
|
+
}
|
|
1675
|
+
/** One end of a connection to ask about: everything true about the party. */
|
|
1676
|
+
interface TrafficParty {
|
|
1677
|
+
/**
|
|
1678
|
+
* Freestyle's own ingress — the web proxy serving a mapped domain, or the
|
|
1679
|
+
* SSH proxy. Always allowed, whatever your rules say.
|
|
1680
|
+
*/
|
|
1681
|
+
platform?: boolean;
|
|
1682
|
+
vmId?: string;
|
|
1683
|
+
/** Every private network this party is on; a rule naming one of them matches. */
|
|
1684
|
+
vpcIds?: string[];
|
|
1685
|
+
tunnelId?: string;
|
|
1686
|
+
/** The address in use, when there is one — what `cidr` rules are tested against. */
|
|
1687
|
+
address?: string;
|
|
1688
|
+
/** Whether that address is publicly routable — what `public: true` matches. */
|
|
1689
|
+
public?: boolean;
|
|
1690
|
+
port?: number;
|
|
1691
|
+
}
|
|
1692
|
+
/** A connection to decide about. */
|
|
1693
|
+
interface TrafficDescription {
|
|
1694
|
+
source: TrafficParty;
|
|
1695
|
+
destination: TrafficParty;
|
|
1696
|
+
protocol?: FirewallProtocol;
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Why a connection would be allowed, or that it would not.
|
|
1700
|
+
*
|
|
1701
|
+
* `allowedByPlatform` means Freestyle itself delivered it — a mapped domain,
|
|
1702
|
+
* an SSH session — which no rule can block. `allowedByRule` names the rule.
|
|
1703
|
+
* `deniedByPlatform` runs the other way: platform policy closed the connection
|
|
1704
|
+
* and no rule opens it — today, outbound mail, which goes through an `smtp`
|
|
1705
|
+
* TLS rule instead.
|
|
1706
|
+
*/
|
|
1707
|
+
type FirewallDecision = {
|
|
1708
|
+
outcome: "allowedByPlatform";
|
|
1709
|
+
side: "source" | "destination" | "both";
|
|
1710
|
+
} | {
|
|
1711
|
+
outcome: "allowedByRule";
|
|
1712
|
+
ruleId: string;
|
|
1713
|
+
} | {
|
|
1714
|
+
outcome: "deniedByPlatform";
|
|
1715
|
+
reason: "outboundMail";
|
|
1716
|
+
} | {
|
|
1717
|
+
outcome: "denied";
|
|
1718
|
+
};
|
|
1719
|
+
/** Firewall rules: `freestyle.firewall.rules`. */
|
|
1720
|
+
declare class FirewallRulesNamespace {
|
|
1721
|
+
private readonly client;
|
|
1722
|
+
constructor(client: FreestyleClient);
|
|
1723
|
+
/**
|
|
1724
|
+
* Allow traffic from one place to another.
|
|
1725
|
+
*
|
|
1726
|
+
* @example
|
|
1727
|
+
* // Public Internet -> a VM, on HTTPS.
|
|
1728
|
+
* await freestyle.firewall.rules.create({
|
|
1729
|
+
* action: "allow",
|
|
1730
|
+
* source: { public: true },
|
|
1731
|
+
* destination: { vmId: "vm-123456", port: 443, protocol: "tcp" },
|
|
1732
|
+
* });
|
|
1733
|
+
*
|
|
1734
|
+
* @example
|
|
1735
|
+
* // One private network -> another, on Postgres.
|
|
1736
|
+
* await freestyle.firewall.rules.create({
|
|
1737
|
+
* action: "allow",
|
|
1738
|
+
* source: { vpcId: "vpc-frontend" },
|
|
1739
|
+
* destination: { vpcId: "vpc-backend", port: 5432, protocol: "tcp" },
|
|
1740
|
+
* });
|
|
1741
|
+
*
|
|
1742
|
+
* @throws {FirewallRuleValidationError} before any request, when the rule
|
|
1743
|
+
* cannot be well formed.
|
|
1744
|
+
*/
|
|
1745
|
+
create(options: CreateFirewallRuleOptions): Promise<FirewallRuleData>;
|
|
1746
|
+
/**
|
|
1747
|
+
* List your rules, newest first. Pass `vmId` for the rules that apply to one
|
|
1748
|
+
* VM — those naming it, plus those naming a private network it is on.
|
|
1749
|
+
*/
|
|
1750
|
+
list(options?: ListFirewallRulesOptions): Promise<ListFirewallRulesResult>;
|
|
1751
|
+
get(ruleId: string): Promise<FirewallRuleData>;
|
|
1752
|
+
/** Delete a rule. The resources it named are untouched. */
|
|
1753
|
+
delete(ruleId: string): Promise<void>;
|
|
1754
|
+
}
|
|
1755
|
+
/** The `freestyle.firewall` namespace. */
|
|
1756
|
+
declare class FirewallNamespace {
|
|
1757
|
+
private readonly client;
|
|
1758
|
+
readonly rules: FirewallRulesNamespace;
|
|
1759
|
+
constructor(client: FreestyleClient);
|
|
1760
|
+
/**
|
|
1761
|
+
* Ask whether a connection would be allowed, without changing anything.
|
|
1762
|
+
*
|
|
1763
|
+
* Useful for checking a rule set before you rely on it — and for confirming
|
|
1764
|
+
* what you already believe, e.g. that a mapped domain reaches a VM with no
|
|
1765
|
+
* rules at all.
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* // A mapped domain: allowed whatever your rules say.
|
|
1769
|
+
* await freestyle.firewall.evaluate({
|
|
1770
|
+
* source: { platform: true },
|
|
1771
|
+
* destination: { vmId: "vm-123456", port: 3000 },
|
|
1772
|
+
* protocol: "tcp",
|
|
1773
|
+
* });
|
|
1774
|
+
* // => { outcome: "allowedByPlatform", side: "source" }
|
|
1775
|
+
*/
|
|
1776
|
+
evaluate(traffic: TrafficDescription): Promise<FirewallDecision>;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
interface IdentityData {
|
|
1780
|
+
id: string;
|
|
1781
|
+
managed: boolean;
|
|
1782
|
+
}
|
|
1783
|
+
interface IdentityInfo {
|
|
1784
|
+
id: string;
|
|
1785
|
+
accountId: string;
|
|
1786
|
+
managed: boolean;
|
|
1787
|
+
}
|
|
1788
|
+
interface CreatedToken {
|
|
1789
|
+
id: string;
|
|
1790
|
+
/** Returned once, in full. */
|
|
1791
|
+
token: string;
|
|
1792
|
+
}
|
|
1793
|
+
interface AccessTokenInfo {
|
|
1794
|
+
id: string;
|
|
1795
|
+
}
|
|
1796
|
+
interface ListIdentitiesResult {
|
|
1797
|
+
identities: IdentityData[];
|
|
1798
|
+
total: number;
|
|
1799
|
+
}
|
|
1800
|
+
interface ListIdentitiesOptions {
|
|
1801
|
+
limit?: number;
|
|
1802
|
+
offset?: number;
|
|
1803
|
+
includeManaged?: boolean;
|
|
1804
|
+
}
|
|
1805
|
+
interface VmPermission {
|
|
1806
|
+
id: string;
|
|
1807
|
+
identityId: string;
|
|
1808
|
+
vmId: string;
|
|
1809
|
+
allowedLinuxUsers: string[] | null;
|
|
1810
|
+
grantedAt: string;
|
|
1811
|
+
grantedBy: string;
|
|
1812
|
+
}
|
|
1813
|
+
interface GrantVmPermissionOptions {
|
|
1814
|
+
vmId: string;
|
|
1815
|
+
/** Restrict the grant to these Linux users; omit (or null) for unrestricted access. */
|
|
1816
|
+
allowedLinuxUsers?: string[] | null;
|
|
1817
|
+
}
|
|
1818
|
+
/** An identity's access tokens: `identity.tokens`. */
|
|
1819
|
+
declare class IdentityTokensNamespace {
|
|
1820
|
+
private readonly client;
|
|
1821
|
+
private readonly basePath;
|
|
1822
|
+
constructor(client: FreestyleClient, basePath: string);
|
|
1823
|
+
/** Mint an access token for the identity. */
|
|
1824
|
+
create(): Promise<CreatedToken>;
|
|
1825
|
+
list(): Promise<AccessTokenInfo[]>;
|
|
1826
|
+
revoke(tokenId: string): Promise<void>;
|
|
1827
|
+
}
|
|
1828
|
+
/** An identity's per-VM permission grants: `identity.permissions.vm`. */
|
|
1829
|
+
declare class IdentityVmPermissionsNamespace {
|
|
1830
|
+
private readonly client;
|
|
1831
|
+
private readonly basePath;
|
|
1832
|
+
constructor(client: FreestyleClient, basePath: string);
|
|
1833
|
+
/** List the VMs this identity has been granted access to. */
|
|
1834
|
+
list(): Promise<VmPermission[]>;
|
|
1835
|
+
/** Grant this identity access to a VM. */
|
|
1836
|
+
grant(options: GrantVmPermissionOptions): Promise<VmPermission>;
|
|
1837
|
+
/** Fetch this identity's grant on a VM. */
|
|
1838
|
+
get(vmId: string): Promise<VmPermission>;
|
|
1839
|
+
/** Change which Linux users this identity may act as on a VM. */
|
|
1840
|
+
update(vmId: string, allowedLinuxUsers: string[] | null): Promise<VmPermission>;
|
|
1841
|
+
/** Revoke this identity's access to a VM. */
|
|
1842
|
+
revoke(vmId: string): Promise<void>;
|
|
1843
|
+
}
|
|
1844
|
+
/** A handle to one identity. */
|
|
1845
|
+
declare class Identity {
|
|
1846
|
+
readonly id: string;
|
|
1847
|
+
readonly tokens: IdentityTokensNamespace;
|
|
1848
|
+
readonly permissions: {
|
|
1849
|
+
vm: IdentityVmPermissionsNamespace;
|
|
1850
|
+
};
|
|
1851
|
+
private readonly client;
|
|
1852
|
+
private readonly basePath;
|
|
1853
|
+
constructor(client: FreestyleClient, id: string);
|
|
1854
|
+
data(): Promise<IdentityInfo>;
|
|
1855
|
+
/** Delete the identity, along with its tokens and grants. Managed identities cannot be deleted. */
|
|
1856
|
+
delete(): Promise<void>;
|
|
1857
|
+
}
|
|
1858
|
+
/** The `freestyle.identities` namespace: scoped access for your end users. */
|
|
1859
|
+
declare class IdentitiesNamespace {
|
|
1860
|
+
private readonly client;
|
|
1861
|
+
constructor(client: FreestyleClient);
|
|
1862
|
+
/** Create an identity. */
|
|
1863
|
+
create(options?: {
|
|
1864
|
+
managed?: boolean;
|
|
1865
|
+
}): Promise<{
|
|
1866
|
+
identity: Identity;
|
|
1867
|
+
identityId: string;
|
|
1868
|
+
data: IdentityData;
|
|
1869
|
+
}>;
|
|
1870
|
+
list(options?: ListIdentitiesOptions): Promise<ListIdentitiesResult>;
|
|
1871
|
+
get(identityId: string): Promise<IdentityInfo>;
|
|
1872
|
+
/** A handle to an identity, without a network call. */
|
|
1873
|
+
ref(identityId: string): Identity;
|
|
1874
|
+
delete(identityId: string): Promise<void>;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* How the edge treats the session's bytes.
|
|
1879
|
+
*
|
|
1880
|
+
* **`http` is the default.** A rule without a protocol is HTTP; the edge
|
|
1881
|
+
* terminates the session so a {@link TlsTransform} (header injection) can act on
|
|
1882
|
+
* it.
|
|
1883
|
+
*
|
|
1884
|
+
* `postgres` is served on **5432**. Two shapes use it. **Ingress** publishes a
|
|
1885
|
+
* database by name: the edge terminates the TLS your certificate covers and
|
|
1886
|
+
* splices the session to the VM's Postgres port, so your own server still
|
|
1887
|
+
* authenticates the client end to end. **Egress** is the interesting one — with a
|
|
1888
|
+
* {@link PostgresTransform} the guest connects to a vendor's database with *no
|
|
1889
|
+
* password at all*, and the edge completes the real handshake upstream
|
|
1890
|
+
* (SCRAM-SHA-256, MD5, or cleartext) with credentials the guest has never held.
|
|
1891
|
+
* Connect with `sslmode=require` or stricter: an unencrypted session offers no
|
|
1892
|
+
* SNI, and the name is what selects a rule.
|
|
1893
|
+
*
|
|
1894
|
+
* `minecraft` is served on **25565** for Minecraft: Java Edition. The edge reads
|
|
1895
|
+
* the server address out of the client's handshake — the routing key, exactly as
|
|
1896
|
+
* `Host` is over HTTP — and then splices the session through to the VM. Point the
|
|
1897
|
+
* domain's A/AAAA records at the edge as you would for a web domain and players
|
|
1898
|
+
* dial it directly; no SRV record is needed while they use the default port. It
|
|
1899
|
+
* terminates nothing, so it carries no transform and needs no certificate. A
|
|
1900
|
+
* **status ping** (the multiplayer list) never starts a stopped VM — it is
|
|
1901
|
+
* answered with a "sleeping" line instead; a player **joining** is what wakes it.
|
|
1902
|
+
*
|
|
1903
|
+
* `tcp` is opaque passthrough, and it shares **443** with `http` — the client
|
|
1904
|
+
* dials HTTPS either way, and the edge tells them apart by matching the SNI in
|
|
1905
|
+
* the ClientHello against your rules. Where an `http` rule has the edge
|
|
1906
|
+
* terminate the session, a `tcp` rule has it splice: the bytes go to your VM
|
|
1907
|
+
* untouched and **your own certificate** answers the handshake. Ask for it when
|
|
1908
|
+
* the session is yours to end — mutual TLS you verify yourself, a protocol over
|
|
1909
|
+
* TLS that is not HTTP, a certificate you would rather issue than delegate. The
|
|
1910
|
+
* cost is everything the edge could otherwise have done: no transform, no
|
|
1911
|
+
* certificate issued for the name, and no HTTP/3 (there is no TCP to splice).
|
|
1912
|
+
* It is **public ingress only** — `{ public: true } -> { vmId, port }`.
|
|
1913
|
+
*
|
|
1914
|
+
* `imap` and `imaps` are served on **993**, and share it the way `http` and
|
|
1915
|
+
* `tcp` share 443 — one door, one route table, and the rule decides whether the
|
|
1916
|
+
* edge is in the session. `imap` terminates: the edge answers the handshake
|
|
1917
|
+
* with a certificate issued for the name and forwards *plain* IMAP to your
|
|
1918
|
+
* server, so the rule lands on 143 by default and you run an unadorned mail
|
|
1919
|
+
* server. `imaps` splices: your server terminates its own TLS on its own
|
|
1920
|
+
* certificate and the platform holds no key to a byte of the mail, at the price
|
|
1921
|
+
* of your obtaining that certificate and renewing it. Neither carries a
|
|
1922
|
+
* transform — the credential in an IMAP session is your user's own. Both are
|
|
1923
|
+
* public ingress only.
|
|
1924
|
+
*
|
|
1925
|
+
* `smtp` is served on **25** (MX delivery), **587** (submission) and **465**
|
|
1926
|
+
* (implicit TLS). It is the one protocol whose `domain` is not a hostname: mail
|
|
1927
|
+
* is routed by the **envelope recipient**, so a rule's name is a mail route and
|
|
1928
|
+
* may carry a mailbox. `bot@acme.com` serves that one address, `acme.com` serves
|
|
1929
|
+
* every mailbox at the name, `bot@*.acme.com` serves that mailbox at any
|
|
1930
|
+
* subdomain and `*.acme.com` serves every mailbox at any subdomain — most
|
|
1931
|
+
* specific wins. That is what lets a single wildcard MX record at `*.acme.com`
|
|
1932
|
+
* give every subdomain its own VM, and what lets `agent-7@acme.com` land
|
|
1933
|
+
* somewhere different from `sales@acme.com`; no SNI could express either, which
|
|
1934
|
+
* is why the envelope and not the handshake is the routing key. Point an **MX**
|
|
1935
|
+
* record at the edge rather than an A/AAAA one. The edge speaks SMTP as far as
|
|
1936
|
+
* the first `RCPT TO`, then replays the envelope to your VM and splices — your
|
|
1937
|
+
* own server still accepts or refuses the mail itself. **Egress** works like
|
|
1938
|
+
* `postgres`: with an {@link SmtpTransform} the guest submits mail through a
|
|
1939
|
+
* provider with *no password at all*, and the edge completes `AUTH` upstream
|
|
1940
|
+
* with credentials the guest has never held. Your VM sees the edge as the
|
|
1941
|
+
* connecting peer, so a guest that scores senders by IP will score the edge.
|
|
1942
|
+
*
|
|
1943
|
+
* `socks5` (1080) is the mirror image of all of them and the only **egress-only**
|
|
1944
|
+
* protocol. Your VM opens SOCKS5 to the platform edge offering *no
|
|
1945
|
+
* authentication*, names its destination inside the request, and the edge opens
|
|
1946
|
+
* the session onward through your own proxy provider using the credentials a
|
|
1947
|
+
* {@link Socks5Transform} carries — so the workload can use the subscription
|
|
1948
|
+
* without ever being able to read it. Nothing is terminated: after the proxy
|
|
1949
|
+
* answers, the edge copies bytes it cannot decrypt, and your TLS runs end to end
|
|
1950
|
+
* to the real origin. Because the destination rides the SOCKS request rather
|
|
1951
|
+
* than DNS, a `socks5` rule's `domain` is an **allow-list** and not a name
|
|
1952
|
+
* anything resolves — `*` ("everything this VM opens goes through my proxy") is
|
|
1953
|
+
* the shape most people want and is accepted here alone. The destination names
|
|
1954
|
+
* the proxy: `{ vmId } -> { host: "gate.provider.com", port: 1080 }`.
|
|
1955
|
+
*
|
|
1956
|
+
* A **VM-to-VM internal service** (`{ vmId | vpcId } -> { vmId }`) is
|
|
1957
|
+
* edge-brokered like every other shape (the source opens the name at the edge's
|
|
1958
|
+
* front door for its protocol, and the edge forwards to the target VM's port) —
|
|
1959
|
+
* and it cannot carry a transform yet.
|
|
1960
|
+
*/
|
|
1961
|
+
type TlsProtocol = "tcp" | "http" | "postgres" | "minecraft" | "smtp" | "imap" | "imaps" | "socks5";
|
|
1962
|
+
/**
|
|
1963
|
+
* What a rule does with the session it matches.
|
|
1964
|
+
*
|
|
1965
|
+
* Only `allow` exists today, and every rule states it rather than defaulting —
|
|
1966
|
+
* the same discipline the firewall keeps, and for the same reason. A default
|
|
1967
|
+
* would mean that the day `deny` arrives (here it plausibly does: "allow every
|
|
1968
|
+
* domain except this one" is a real thing to want, given the `*` wildcard), every
|
|
1969
|
+
* rule written before it silently acquires an action it never stated.
|
|
1970
|
+
*/
|
|
1971
|
+
type TlsAction = "allow";
|
|
1972
|
+
/**
|
|
1973
|
+
* One end of a rule: which session this side matches.
|
|
1974
|
+
*
|
|
1975
|
+
* The vocabulary is the firewall's — identity (`vmId`/`vpcId`) or address
|
|
1976
|
+
* (`public`) — because a reader who knows the firewall should read this by
|
|
1977
|
+
* reflex. What differs is that the two ends are *not* symmetric, and the
|
|
1978
|
+
* asymmetries fall out of what each side means:
|
|
1979
|
+
*
|
|
1980
|
+
* - **A source never carries a landing coordinate** (`host`/`port`). A source is
|
|
1981
|
+
* *who opens* the session, not where it goes. The edge authenticates a source
|
|
1982
|
+
* by identity — its switch port, its anti-spoofed address — never by the name
|
|
1983
|
+
* it dials, because the name a client offers proves nothing about the client.
|
|
1984
|
+
* - **A destination is never a `vpcId`.** "Which member of a network does this
|
|
1985
|
+
* land on" has no answer; a landing is one place. A whole network may be a
|
|
1986
|
+
* *source* — every VM on it may open the session — but not a destination.
|
|
1987
|
+
*
|
|
1988
|
+
* `public` also means different things by side. On a source it is the firewall's
|
|
1989
|
+
* meaning exactly: every publicly routable client. On a destination it is *the
|
|
1990
|
+
* domain's own public origin* — wherever the world's DNS says the name lives — so
|
|
1991
|
+
* egress to a vendor is `{ public: true }`, never a blank matcher. The blank
|
|
1992
|
+
* matcher never means "the Internet" here, for the same reason it never does in
|
|
1993
|
+
* the firewall: the most dangerous statement you can write must not be the one
|
|
1994
|
+
* you get by forgetting a field. Every rule states both ends.
|
|
1995
|
+
*
|
|
1996
|
+
* New selectors arrive as new optional fields, so this type only ever grows.
|
|
1997
|
+
*/
|
|
1998
|
+
interface TlsEndpoint {
|
|
1999
|
+
/**
|
|
2000
|
+
* A single VM, by id or by your slug for it. On a source, the VM that may open
|
|
2001
|
+
* the session; on a destination, the VM the session lands on. Answers hold its
|
|
2002
|
+
* id.
|
|
2003
|
+
*/
|
|
2004
|
+
vmId?: string;
|
|
2005
|
+
/**
|
|
2006
|
+
* Everything on one private network, by id or by your slug for it. **Source
|
|
2007
|
+
* only** — every member may open the session. A landing is one place, so a
|
|
2008
|
+
* network cannot be a destination. Answers hold its id.
|
|
2009
|
+
*/
|
|
2010
|
+
vpcId?: string;
|
|
2011
|
+
/**
|
|
2012
|
+
* On a source, every publicly routable client — the open Internet dials the
|
|
2013
|
+
* name. On a destination, the domain's own public origin: wherever the world's
|
|
2014
|
+
* DNS resolves the name. Only `true` is meaningful — omit the field otherwise.
|
|
2015
|
+
*/
|
|
2016
|
+
public?: true;
|
|
2017
|
+
/**
|
|
2018
|
+
* **Destination only.** Pin the session to this host instead of resolving the
|
|
2019
|
+
* domain — an alias, or an address reachable over a tunnel — while still
|
|
2020
|
+
* presenting `domain` as the SNI.
|
|
2021
|
+
*/
|
|
2022
|
+
host?: string;
|
|
2023
|
+
/**
|
|
2024
|
+
* **Destination only.** The port the session lands on. Required when the
|
|
2025
|
+
* destination is a `vmId` or a `host`; defaults to 443 for a `public` origin.
|
|
2026
|
+
*/
|
|
2027
|
+
port?: number;
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Credentials injected into a Postgres startup handshake. The guest connects with
|
|
2031
|
+
* no password (or any); the edge completes the real handshake upstream — so a
|
|
2032
|
+
* compromised guest never holds a credential it could exfiltrate.
|
|
2033
|
+
*/
|
|
2034
|
+
interface PostgresTransform {
|
|
2035
|
+
/** The upstream role to authenticate as. */
|
|
2036
|
+
username: string;
|
|
2037
|
+
/** The upstream password. Write-only: sealed at rest, read back as `"***"`. */
|
|
2038
|
+
password: string;
|
|
2039
|
+
/** The database to select. Optional: omitted leaves the guest's choice. */
|
|
2040
|
+
database?: string;
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Credentials injected into an SMTP submission handshake. The guest submits mail
|
|
2044
|
+
* with no password; the edge completes `AUTH` upstream on its behalf — so a
|
|
2045
|
+
* compromised guest never holds a submission credential it could exfiltrate, and
|
|
2046
|
+
* cannot be turned into a spam relay by whoever took it.
|
|
2047
|
+
*/
|
|
2048
|
+
interface SmtpTransform {
|
|
2049
|
+
/** The submission username to authenticate as. */
|
|
2050
|
+
username: string;
|
|
2051
|
+
/** The submission password. Write-only: sealed at rest, read back as `"***"`. */
|
|
2052
|
+
password: string;
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Credentials presented to your upstream SOCKS5 proxy. Your VM connects to the
|
|
2056
|
+
* platform's SOCKS5 front offering *no authentication at all* and the edge
|
|
2057
|
+
* authenticates to the provider on its behalf — so the subscription is never
|
|
2058
|
+
* inside the VM to be read out of its environment, its disk or a snapshot of it.
|
|
2059
|
+
* A guest that is fully compromised can use the proxy; it cannot take it.
|
|
2060
|
+
*
|
|
2061
|
+
* Optional. A provider that allow-lists its customers by source address needs no
|
|
2062
|
+
* pair, and a `socks5` rule with no transform is an ordinary shape.
|
|
2063
|
+
*/
|
|
2064
|
+
interface Socks5Transform {
|
|
2065
|
+
/** The username to present to the upstream proxy. */
|
|
2066
|
+
username: string;
|
|
2067
|
+
/** The password to present to the upstream proxy. Write-only: sealed at rest, read back as `"***"`. */
|
|
2068
|
+
password: string;
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* One rewrite applied to a session in flight — something only the platform can
|
|
2072
|
+
* do, because the whole point is to do what the guest could not do itself.
|
|
2073
|
+
*
|
|
2074
|
+
* Externally tagged: the wire is `{ headers: { … } }`, `{ postgres: { … } }`,
|
|
2075
|
+
* `{ smtp: { … } }`, `{ socks5: { … } }` or `{ s3: { … } }`, exactly one key — `headers` injects
|
|
2076
|
+
* request headers into an `http` session, `postgres` completes a `postgres`
|
|
2077
|
+
* handshake, `smtp` completes a submission `AUTH`, and `socks5` authenticates the
|
|
2078
|
+
* hop to your proxy provider. Each names the protocol it needs, so a rule's
|
|
2079
|
+
* `protocol` is inferred from its transform when you omit it. Credential transforms are
|
|
2080
|
+
* **egress** acts: the platform spends a credential the guest never holds, so a
|
|
2081
|
+
* `postgres` or `smtp` transform on a rule landing on your own VM is refused —
|
|
2082
|
+
* pointed inward the first hands every client a logged-in session and the second
|
|
2083
|
+
* turns your mail server into an open relay. (`socks5` has no inward form at all
|
|
2084
|
+
* to refuse.) Secrets here are
|
|
2085
|
+
* **write-only**: sealed at rest and
|
|
2086
|
+
* read back as `"***"`, with the record marked {@link TlsRuleData.redacted}.
|
|
2087
|
+
* Header *names* survive a read — they are not secret, and seeing which headers a
|
|
2088
|
+
* rule sets is how you audit it.
|
|
2089
|
+
*/
|
|
2090
|
+
type TlsTransform = {
|
|
2091
|
+
headers: Record<string, string>;
|
|
2092
|
+
} | {
|
|
2093
|
+
jsonPatch: JsonPatchOperation[];
|
|
2094
|
+
} | {
|
|
2095
|
+
postgres: PostgresTransform;
|
|
2096
|
+
} | {
|
|
2097
|
+
smtp: SmtpTransform;
|
|
2098
|
+
} | {
|
|
2099
|
+
socks5: Socks5Transform;
|
|
2100
|
+
} | {
|
|
2101
|
+
s3: S3Transform;
|
|
2102
|
+
};
|
|
2103
|
+
/** An S3 operation understood by the edge. Other operations fail closed. */
|
|
2104
|
+
type S3Operation = "GetObject" | "HeadObject" | "PutObject" | "DeleteObject" | "ListObjectsV2" | "CreateMultipartUpload" | "UploadPart" | "ListParts" | "CompleteMultipartUpload" | "AbortMultipartUpload" | "ListMultipartUploads";
|
|
2105
|
+
/** Credentials are sealed at rest, redacted on reads, and never sent to the VM. */
|
|
2106
|
+
interface S3Credentials {
|
|
2107
|
+
accessKeyId: string;
|
|
2108
|
+
secretAccessKey: string;
|
|
2109
|
+
/** Optional temporary session token. Rotate with tls.rules.update before expiry. */
|
|
2110
|
+
sessionToken?: string;
|
|
2111
|
+
}
|
|
2112
|
+
interface S3Scope {
|
|
2113
|
+
bucket: string;
|
|
2114
|
+
/** Literal key prefix. Use a trailing / for a directory boundary; "" permits all keys. */
|
|
2115
|
+
prefix: string;
|
|
2116
|
+
operations: S3Operation[];
|
|
2117
|
+
}
|
|
2118
|
+
/** Authorize then SigV4-sign HTTP egress. Must be the sole transform, without match.
|
|
2119
|
+
* Supports virtual-hosted and path-style bucket addressing at a concrete origin.
|
|
2120
|
+
* Listing requires an explicit prefix within scope. Copy, versioned
|
|
2121
|
+
* requests, ACLs, and aws-chunked uploads are rejected. Bodies stream over HTTPS
|
|
2122
|
+
* using UNSIGNED-PAYLOAD; policies requiring signed payload hashes are unsupported.
|
|
2123
|
+
*/
|
|
2124
|
+
interface S3Transform {
|
|
2125
|
+
region: string;
|
|
2126
|
+
credentials: S3Credentials;
|
|
2127
|
+
scope: S3Scope;
|
|
2128
|
+
}
|
|
2129
|
+
/** A JSON value. Patch values are write-only and read back as `"***"`. */
|
|
2130
|
+
type JsonPatchValue = null | boolean | number | string | JsonPatchValue[] | {
|
|
2131
|
+
[key: string]: JsonPatchValue;
|
|
2132
|
+
};
|
|
2133
|
+
/** RFC 6902 operation. Paths use RFC 6901 JSON Pointer, including /~0 and /~1
|
|
2134
|
+
* escaping. `add` overwrites object members but inserts array elements; /-
|
|
2135
|
+
* appends to an existing array. Parent containers must already exist.
|
|
2136
|
+
* A rule accepts one patch of up to 128 operations / 64 KiB. All operations must
|
|
2137
|
+
* succeed before forwarding. Applies to every JSON HTTP egress request on the
|
|
2138
|
+
* rule; there is no endpoint filtering, provider translation, or wildcard path.
|
|
2139
|
+
*/
|
|
2140
|
+
type JsonPatchOperation = {
|
|
2141
|
+
op: "add" | "replace" | "test";
|
|
2142
|
+
path: string;
|
|
2143
|
+
value: JsonPatchValue;
|
|
2144
|
+
} | {
|
|
2145
|
+
op: "remove";
|
|
2146
|
+
path: string;
|
|
2147
|
+
} | {
|
|
2148
|
+
op: "move" | "copy";
|
|
2149
|
+
from: string;
|
|
2150
|
+
path: string;
|
|
2151
|
+
};
|
|
2152
|
+
/** A reusable forward-auth configuration attached to one public HTTP ingress rule. */
|
|
2153
|
+
interface TlsForwardAuthReference {
|
|
2154
|
+
/** The forward-auth configuration id returned by {@link TlsForwardAuthNamespace.create}. */
|
|
2155
|
+
id: string;
|
|
2156
|
+
}
|
|
2157
|
+
/** The customer-owned service Freestyle asks before forwarding a request to a VM. */
|
|
2158
|
+
interface CreateTlsForwardAuthOptions {
|
|
2159
|
+
/** Absolute HTTPS endpoint that receives the authorization subrequest. */
|
|
2160
|
+
url: string;
|
|
2161
|
+
/** Static headers added to authorization subrequests. Values are write-only. */
|
|
2162
|
+
headers?: Record<string, string>;
|
|
2163
|
+
/** Authorization deadline in milliseconds. Defaults server-side; accepted range is 100-5000. */
|
|
2164
|
+
timeoutMs?: number;
|
|
2165
|
+
/** Cookies withheld from the destination VM and protected from being overwritten by it. */
|
|
2166
|
+
protectedCookies?: string[];
|
|
2167
|
+
/** Headers copied from an allowed authorization response onto the request sent to the VM. */
|
|
2168
|
+
authResponseHeaders?: string[];
|
|
2169
|
+
}
|
|
2170
|
+
/** A stored forward-auth configuration. Static header values are returned as `"***"`. */
|
|
2171
|
+
interface TlsForwardAuthData {
|
|
2172
|
+
id: string;
|
|
2173
|
+
/** The account that owns the configuration. Taken from your credential, never a request body. */
|
|
2174
|
+
accountId: string;
|
|
2175
|
+
url: string;
|
|
2176
|
+
/** Static authorization-request headers, with every value replaced by `"***"`. */
|
|
2177
|
+
headers?: Record<string, string>;
|
|
2178
|
+
timeoutMs: number;
|
|
2179
|
+
protectedCookies?: string[];
|
|
2180
|
+
authResponseHeaders?: string[];
|
|
2181
|
+
/** Whether any static header value was redacted out of `headers`. */
|
|
2182
|
+
redacted: boolean;
|
|
2183
|
+
createdAt: string;
|
|
2184
|
+
updatedAt: string;
|
|
2185
|
+
}
|
|
2186
|
+
interface ListTlsForwardAuthResult {
|
|
2187
|
+
/** Newest first. */
|
|
2188
|
+
configs: TlsForwardAuthData[];
|
|
2189
|
+
totalCount: number;
|
|
2190
|
+
}
|
|
2191
|
+
/**
|
|
2192
|
+
* A resource a rule cannot outlive. Deleting the resource deletes the rule, so no
|
|
2193
|
+
* rule is ever left naming something that no longer exists — the same cascade the
|
|
2194
|
+
* firewall uses.
|
|
2195
|
+
*/
|
|
2196
|
+
interface TlsDependency {
|
|
2197
|
+
/** `"vm"` or `"vpc"` today; new selectors add new kinds. */
|
|
2198
|
+
kind: string;
|
|
2199
|
+
id: string;
|
|
2200
|
+
/**
|
|
2201
|
+
* Your handle for the named resource, when it has one — so a rule can say
|
|
2202
|
+
* what it points at without a lookup per id.
|
|
2203
|
+
*/
|
|
2204
|
+
slug?: string | null;
|
|
2205
|
+
/** Your label for the named resource, under the same rules as `slug`. */
|
|
2206
|
+
displayName?: string | null;
|
|
2207
|
+
}
|
|
2208
|
+
/**
|
|
2209
|
+
* A stored TLS rule: the name a session is addressed to, who may open it, where
|
|
2210
|
+
* it lands, and what the edge does to it in flight.
|
|
2211
|
+
*
|
|
2212
|
+
* `transform` is always redacted here — the sealed secrets live only in storage
|
|
2213
|
+
* and reach only the dataplane. When any secret was redacted out, `redacted` is
|
|
2214
|
+
* `true`, so a caller knows the shape it sees is not the whole story.
|
|
2215
|
+
*/
|
|
2216
|
+
/**
|
|
2217
|
+
* Method/exact-path subset of Vercel Sandbox matchers.
|
|
2218
|
+
* https://vercel.com/docs/sandbox/concepts/firewall#matchers
|
|
2219
|
+
* Dimensions are ANDed; methods are ORed. Non-matches skip all transforms.
|
|
2220
|
+
*/
|
|
2221
|
+
interface HttpRequestMatch {
|
|
2222
|
+
/** Case-sensitive HTTP method tokens. Omitted matches any; [] matches none. */
|
|
2223
|
+
method?: string[];
|
|
2224
|
+
/** Exact path, excluding query. No decoding or trailing-slash normalization. */
|
|
2225
|
+
path?: {
|
|
2226
|
+
exact: string;
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
interface TlsRuleData {
|
|
2230
|
+
id: string;
|
|
2231
|
+
/** The account that owns the rule. Taken from your credential, never a request body. */
|
|
2232
|
+
accountId: string;
|
|
2233
|
+
action: TlsAction;
|
|
2234
|
+
/** The name the session is addressed to: exact, `*.suffix`, or `*`. */
|
|
2235
|
+
domain: string;
|
|
2236
|
+
protocol: TlsProtocol;
|
|
2237
|
+
source: TlsEndpoint;
|
|
2238
|
+
destination: TlsEndpoint;
|
|
2239
|
+
/** The rewrites this rule applies, with every secret replaced by `"***"`. */
|
|
2240
|
+
transform?: TlsTransform[];
|
|
2241
|
+
/** Select which HTTP egress requests receive all of this rule's transforms. */
|
|
2242
|
+
match?: HttpRequestMatch;
|
|
2243
|
+
/** Authorization Freestyle performs before forwarding this public HTTP ingress. */
|
|
2244
|
+
forwardAuth?: TlsForwardAuthReference;
|
|
2245
|
+
/** Whether any transform secret was redacted out of `transform`. */
|
|
2246
|
+
redacted?: boolean;
|
|
2247
|
+
/**
|
|
2248
|
+
* Whether the platform owns this rule. Managed rules are Freestyle's own (the
|
|
2249
|
+
* SSH edge's standing grant, say), invisible to and undeletable by the account.
|
|
2250
|
+
*/
|
|
2251
|
+
managed?: boolean;
|
|
2252
|
+
/** What this rule dies with. See {@link TlsDependency}. */
|
|
2253
|
+
dependencies: TlsDependency[];
|
|
2254
|
+
createdAt: string;
|
|
2255
|
+
updatedAt: string;
|
|
2256
|
+
}
|
|
2257
|
+
interface CreateTlsRuleOptions {
|
|
2258
|
+
/** Required. `"allow"` is the only accepted value. */
|
|
2259
|
+
action: TlsAction;
|
|
2260
|
+
/**
|
|
2261
|
+
* The name the session is addressed to: exact (`app.acme.com`), `*.suffix`, or
|
|
2262
|
+
* `*`. For `smtp` this is a mail route instead, and may name a mailbox —
|
|
2263
|
+
* `bot@acme.com`, `acme.com`, `bot@*.acme.com` or `*.acme.com`.
|
|
2264
|
+
*/
|
|
2265
|
+
domain: string;
|
|
2266
|
+
/**
|
|
2267
|
+
* How the edge treats the bytes. Optional and usually omitted: it is inferred
|
|
2268
|
+
* from `transform` when one is present, and defaults to `http` otherwise. Set
|
|
2269
|
+
* it to `"imap"` to publish a mailbox on 993 (the edge terminates and forwards
|
|
2270
|
+
* plain IMAP to your server; `"imaps"` splices to a server holding its own
|
|
2271
|
+
* certificate), `"postgres"` to publish a database on 5432, `"minecraft"` to publish a
|
|
2272
|
+
* Minecraft server on 25565, `"smtp"` to receive mail on 25/587/465, or
|
|
2273
|
+
* `"tcp"` to have the edge splice the session on 443 without terminating it,
|
|
2274
|
+
* so your VM's own certificate answers the handshake. See
|
|
2275
|
+
* {@link TlsProtocol}.
|
|
2276
|
+
*/
|
|
2277
|
+
protocol?: TlsProtocol;
|
|
2278
|
+
/** Who may open the session. */
|
|
2279
|
+
source: TlsEndpoint;
|
|
2280
|
+
/** Where the session lands. */
|
|
2281
|
+
destination: TlsEndpoint;
|
|
2282
|
+
/** Rewrites applied in flight, in order. Secrets here are write-only. */
|
|
2283
|
+
transform?: TlsTransform[];
|
|
2284
|
+
/** Select which HTTP egress requests receive all of this rule's transforms. */
|
|
2285
|
+
match?: HttpRequestMatch;
|
|
2286
|
+
/** Ask this reusable configuration before forwarding a public HTTP request to the VM. */
|
|
2287
|
+
forwardAuth?: TlsForwardAuthReference;
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* A rule declared inside a create, where the resource being created has no id
|
|
2291
|
+
* yet.
|
|
2292
|
+
*
|
|
2293
|
+
* Exactly one endpoint may be left without an identity; that one *is* the new VM.
|
|
2294
|
+
* So `{ source: { public: true }, destination: { port: 8000 } }` is inbound to
|
|
2295
|
+
* it, and `{ source: {}, destination: { public: true } }` is egress from it. The
|
|
2296
|
+
* identity-less destination still owes its `port`: a landing without a port is
|
|
2297
|
+
* not a landing.
|
|
2298
|
+
*/
|
|
2299
|
+
type InlineTlsRule = CreateTlsRuleOptions;
|
|
2300
|
+
/** The `tls` block of a create. */
|
|
2301
|
+
interface TlsSpec {
|
|
2302
|
+
rules: InlineTlsRule[];
|
|
2303
|
+
}
|
|
2304
|
+
interface ListTlsRulesOptions {
|
|
2305
|
+
/** Rules that apply to this VM: those naming it, plus those naming a private network it is on. */
|
|
2306
|
+
vmId?: string;
|
|
2307
|
+
/** Rules naming this private network. */
|
|
2308
|
+
vpcId?: string;
|
|
2309
|
+
limit?: number;
|
|
2310
|
+
offset?: number;
|
|
2311
|
+
}
|
|
2312
|
+
interface ListTlsRulesResult {
|
|
2313
|
+
/** Newest first. */
|
|
2314
|
+
rules: TlsRuleData[];
|
|
2315
|
+
totalCount: number;
|
|
2316
|
+
}
|
|
2317
|
+
/** Reusable authorization checks for public HTTP ingress: `freestyle.tls.forwardAuth`. */
|
|
2318
|
+
declare class TlsForwardAuthNamespace {
|
|
2319
|
+
private readonly client;
|
|
2320
|
+
constructor(client: FreestyleClient);
|
|
2321
|
+
/** List your forward-auth configurations, newest first. */
|
|
2322
|
+
list(): Promise<ListTlsForwardAuthResult>;
|
|
2323
|
+
/** Create a forward-auth configuration. Static request-header values are write-only. */
|
|
2324
|
+
create(options: CreateTlsForwardAuthOptions): Promise<TlsForwardAuthData>;
|
|
2325
|
+
/** Fetch one forward-auth configuration by id. */
|
|
2326
|
+
get(id: string): Promise<TlsForwardAuthData>;
|
|
2327
|
+
/** Replace a configuration in place, including rotating its static headers. */
|
|
2328
|
+
update(id: string, options: CreateTlsForwardAuthOptions): Promise<TlsForwardAuthData>;
|
|
2329
|
+
/** Delete an unused forward-auth configuration. */
|
|
2330
|
+
delete(id: string): Promise<void>;
|
|
2331
|
+
}
|
|
2332
|
+
/** TLS rules: `freestyle.tls.rules`. */
|
|
2333
|
+
declare class TlsRulesNamespace {
|
|
2334
|
+
private readonly client;
|
|
2335
|
+
constructor(client: FreestyleClient);
|
|
2336
|
+
/**
|
|
2337
|
+
* Allow a named TLS session: `domain`, from `source`, to `destination`.
|
|
2338
|
+
*
|
|
2339
|
+
* An ingress `domain` is a name you have verified, or any unused subdomain
|
|
2340
|
+
* of `style.dev`, which is free and needs no verification or DNS records.
|
|
2341
|
+
*
|
|
2342
|
+
* @example
|
|
2343
|
+
* // Public HTTPS ingress: anyone dialing app.acme.com lands on a VM's port.
|
|
2344
|
+
* await freestyle.tls.rules.create({
|
|
2345
|
+
* action: "allow",
|
|
2346
|
+
* domain: "app.acme.com",
|
|
2347
|
+
* source: { public: true },
|
|
2348
|
+
* destination: { vmId: "vm-123456", port: 8000 },
|
|
2349
|
+
* });
|
|
2350
|
+
*
|
|
2351
|
+
* @example
|
|
2352
|
+
* // Egress with a secret injected: a VM reaches OpenAI at its real origin,
|
|
2353
|
+
* // and the edge adds the Authorization header the guest never held.
|
|
2354
|
+
* await freestyle.tls.rules.create({
|
|
2355
|
+
* action: "allow",
|
|
2356
|
+
* domain: "api.openai.com",
|
|
2357
|
+
* source: { vmId: "vm-123456" },
|
|
2358
|
+
* destination: { public: true },
|
|
2359
|
+
* transform: [{ headers: { authorization: "Bearer sk-…" } }],
|
|
2360
|
+
* });
|
|
2361
|
+
*
|
|
2362
|
+
* @example
|
|
2363
|
+
* // Internal service: one VM reaches another by a name. The source opens
|
|
2364
|
+
* // https://api.internal; the edge terminates it and forwards to the target
|
|
2365
|
+
* // VM's port. Steered at the edge, HTTP-only, no transform.
|
|
2366
|
+
* await freestyle.tls.rules.create({
|
|
2367
|
+
* action: "allow",
|
|
2368
|
+
* domain: "api.internal",
|
|
2369
|
+
* source: { vmId: "vm-123456" },
|
|
2370
|
+
* destination: { vmId: "vm-654321", port: 8000 },
|
|
2371
|
+
* });
|
|
2372
|
+
*
|
|
2373
|
+
* @throws {TlsRuleValidationError} before any request, when the rule cannot be
|
|
2374
|
+
* well formed.
|
|
2375
|
+
*/
|
|
2376
|
+
create(options: CreateTlsRuleOptions): Promise<TlsRuleData>;
|
|
2377
|
+
/**
|
|
2378
|
+
* List your rules, newest first. Pass `vmId` for the rules that apply to one
|
|
2379
|
+
* VM — those naming it, plus those naming a private network it is on.
|
|
2380
|
+
*/
|
|
2381
|
+
list(options?: ListTlsRulesOptions): Promise<ListTlsRulesResult>;
|
|
2382
|
+
get(ruleId: string): Promise<TlsRuleData>;
|
|
2383
|
+
/**
|
|
2384
|
+
* Replace a rule in place. The one mutation the firewall does not allow: a
|
|
2385
|
+
* transform carries a rotating secret, and delete-then-create would drop
|
|
2386
|
+
* traffic or trip the account limit mid-swap. The rule keeps its id and
|
|
2387
|
+
* creation time; everything in `options` is replaced.
|
|
2388
|
+
*
|
|
2389
|
+
* @throws {TlsRuleValidationError} before any request, when the replacement
|
|
2390
|
+
* cannot be well formed.
|
|
2391
|
+
*/
|
|
2392
|
+
update(ruleId: string, options: CreateTlsRuleOptions): Promise<TlsRuleData>;
|
|
2393
|
+
/** Delete a rule. The resources it named are untouched. */
|
|
2394
|
+
delete(ruleId: string): Promise<void>;
|
|
2395
|
+
}
|
|
2396
|
+
/** The `freestyle.tls` namespace. */
|
|
2397
|
+
declare class TlsNamespace {
|
|
2398
|
+
private readonly client;
|
|
2399
|
+
readonly rules: TlsRulesNamespace;
|
|
2400
|
+
readonly forwardAuth: TlsForwardAuthNamespace;
|
|
2401
|
+
constructor(client: FreestyleClient);
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
/**
|
|
2405
|
+
* One private network on the far side of a tunnel: the network, your address
|
|
2406
|
+
* inside it, and the `[Peer]` key that carries it.
|
|
2407
|
+
*/
|
|
2408
|
+
interface TunnelAttachment {
|
|
2409
|
+
vpcId: string;
|
|
2410
|
+
/**
|
|
2411
|
+
* Your IPv4 address inside the private network, absent on a v6-only
|
|
2412
|
+
* attachment. It belongs to this tunnel for as long as the network stays
|
|
2413
|
+
* attached, so you can write it into configs and rules.
|
|
2414
|
+
*/
|
|
2415
|
+
ipv4?: string | null;
|
|
2416
|
+
/** Your IPv6 address inside the private network, under the same rules. */
|
|
2417
|
+
ipv6?: string | null;
|
|
2418
|
+
/** @deprecated The primary address, whichever family. Read `ipv4`/`ipv6`. */
|
|
2419
|
+
address: string;
|
|
2420
|
+
/** @deprecated The second-family address. Read `ipv4`/`ipv6`. */
|
|
2421
|
+
secondaryAddress?: string | null;
|
|
2422
|
+
vpcCidr: string;
|
|
2423
|
+
/** The CIDRs the gateway routes to this network. */
|
|
2424
|
+
allowedIps: string[];
|
|
2425
|
+
/**
|
|
2426
|
+
* Ranges behind your client that this network routes through the tunnel (a
|
|
2427
|
+
* site-to-site grant). VMs reach them with an ordinary route via `address`.
|
|
2428
|
+
* Absent when the attachment has none.
|
|
2429
|
+
*/
|
|
2430
|
+
remoteCidrs?: string[];
|
|
2431
|
+
/**
|
|
2432
|
+
* Whether this attachment is a network exit: your client forwards for
|
|
2433
|
+
* destinations that are not its own address, and members route through it
|
|
2434
|
+
* with an ordinary route via the attachment's address. Absent when false.
|
|
2435
|
+
*/
|
|
2436
|
+
exit?: boolean;
|
|
2437
|
+
createdAt: string;
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* A tunnel: your WireGuard identity on the platform. It exists until you
|
|
2441
|
+
* delete it: connect, disconnect and reconnect as often as you like — a
|
|
2442
|
+
* session is set up on the handshake that needs it, so an idle tunnel costs
|
|
2443
|
+
* nothing and never expires. A tunnel routes nowhere until you attach a
|
|
2444
|
+
* private network to it; attach several and one WireGuard interface reaches
|
|
2445
|
+
* them all.
|
|
2446
|
+
*/
|
|
2447
|
+
interface TunnelData {
|
|
2448
|
+
/** @deprecated The tunnel's id under its old name. Read `tunnelId`. */
|
|
2449
|
+
id: string;
|
|
2450
|
+
/**
|
|
2451
|
+
* The tunnel's id, under the name the rest of the API uses for it — a
|
|
2452
|
+
* firewall rule says `tunnelId`, so the tunnel does too.
|
|
2453
|
+
*/
|
|
2454
|
+
tunnelId: string;
|
|
2455
|
+
/**
|
|
2456
|
+
* Your handle for this tunnel, if you gave it one. Usable anywhere the id
|
|
2457
|
+
* is, in place of it.
|
|
2458
|
+
*/
|
|
2459
|
+
slug?: string | null;
|
|
2460
|
+
/** Your label for this tunnel, if you gave it one. Shown in place of the slug. */
|
|
2461
|
+
displayName?: string | null;
|
|
2462
|
+
/**
|
|
2463
|
+
* A complete WireGuard config file, fixed for the life of the tunnel:
|
|
2464
|
+
* attaching and detaching networks never changes it, so bring it up once
|
|
2465
|
+
* and manage what it reaches through the API. `PrivateKey` is blank except
|
|
2466
|
+
* on {@link TunnelsNamespace.create} and {@link TunnelsNamespace.rotateKey},
|
|
2467
|
+
* the only two calls that mint a keypair.
|
|
2468
|
+
*/
|
|
2469
|
+
clientConfig: string;
|
|
2470
|
+
/** Host to dial. */
|
|
2471
|
+
endpointHost?: string | null;
|
|
2472
|
+
endpointPort: number;
|
|
2473
|
+
clientPublicKey: string;
|
|
2474
|
+
/** The one `[Peer]` public key in your config. */
|
|
2475
|
+
serverPublicKey: string;
|
|
2476
|
+
/**
|
|
2477
|
+
* Your fixed addresses inside the tunnel. Only the gateway ever sees them:
|
|
2478
|
+
* each attached network sees an address inside its own subnet instead
|
|
2479
|
+
* (`attachments[].address`).
|
|
2480
|
+
*/
|
|
2481
|
+
clientAddressV4: string;
|
|
2482
|
+
clientAddressV6: string;
|
|
2483
|
+
/**
|
|
2484
|
+
* The ranges your client routes through the tunnel — its `AllowedIPs`,
|
|
2485
|
+
* fixed at create. A network can only be attached if its CIDRs fall inside
|
|
2486
|
+
* these.
|
|
2487
|
+
*/
|
|
2488
|
+
routes: string[];
|
|
2489
|
+
/** The private networks on the far side, oldest attachment first. */
|
|
2490
|
+
attachments: TunnelAttachment[];
|
|
2491
|
+
createdAt: string;
|
|
2492
|
+
updatedAt: string;
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* A tunnel plus a client private key — returned only by the two calls that
|
|
2496
|
+
* mint a keypair. The key is never stored and cannot be read back. Empty when
|
|
2497
|
+
* you supplied your own public key.
|
|
2498
|
+
*/
|
|
2499
|
+
interface CreatedTunnel extends TunnelData {
|
|
2500
|
+
/** Returned once. Already embedded in `clientConfig`. */
|
|
2501
|
+
clientPrivateKey: string;
|
|
2502
|
+
}
|
|
2503
|
+
interface CreateTunnelOptions {
|
|
2504
|
+
/**
|
|
2505
|
+
* A URL-safe handle, unique within your account: lowercase letters, digits
|
|
2506
|
+
* and single hyphens, up to 63 characters. Address the tunnel by it instead
|
|
2507
|
+
* of by id.
|
|
2508
|
+
*/
|
|
2509
|
+
slug?: string;
|
|
2510
|
+
/**
|
|
2511
|
+
* A label for a slug that does not read as a name — a tunnel per device id,
|
|
2512
|
+
* per session. Shown in place of the slug.
|
|
2513
|
+
*/
|
|
2514
|
+
displayName?: string;
|
|
2515
|
+
/**
|
|
2516
|
+
* The public key of a keypair you already hold. Omit to have one minted for
|
|
2517
|
+
* you; supply it and the platform never sees a private key at all.
|
|
2518
|
+
*/
|
|
2519
|
+
clientPublicKey?: string;
|
|
2520
|
+
/**
|
|
2521
|
+
* The ranges your client will route through the tunnel — its `AllowedIPs`,
|
|
2522
|
+
* fixed for the tunnel's life so attaching and detaching networks never
|
|
2523
|
+
* changes your config. Omit for the default (`10.0.0.0/8` and `fd00::/8`),
|
|
2524
|
+
* which covers every network with default addressing. Only networks whose
|
|
2525
|
+
* CIDRs fall inside the routes can be attached.
|
|
2526
|
+
*/
|
|
2527
|
+
routes?: string[];
|
|
2528
|
+
/**
|
|
2529
|
+
* Networks to attach in the same call, so one request yields a tunnel that
|
|
2530
|
+
* already routes somewhere. All-or-nothing: if any attachment is refused,
|
|
2531
|
+
* nothing is created.
|
|
2532
|
+
*/
|
|
2533
|
+
vpcs?: CreateTunnelVpc[];
|
|
2534
|
+
}
|
|
2535
|
+
/** One network named at create, with the same address choices as an attach. */
|
|
2536
|
+
interface CreateTunnelVpc {
|
|
2537
|
+
/** The network to attach: a VPC id, or your slug for it. */
|
|
2538
|
+
vpcId?: string;
|
|
2539
|
+
/** @deprecated The network to attach, under its old name. Write `vpcId`. */
|
|
2540
|
+
vpc?: string;
|
|
2541
|
+
/** The address you want inside it; omit to be assigned one. */
|
|
2542
|
+
ipv4?: string;
|
|
2543
|
+
/** An explicit IPv6 address instead; only one may be set. */
|
|
2544
|
+
ipv6?: string;
|
|
2545
|
+
/** Ranges behind your client this network may route to; see {@link AttachVpcOptions.remoteCidrs}. */
|
|
2546
|
+
remoteCidrs?: string[];
|
|
2547
|
+
/** See {@link AttachVpcOptions.exit}. */
|
|
2548
|
+
exit?: boolean;
|
|
2549
|
+
}
|
|
2550
|
+
interface AttachVpcOptions {
|
|
2551
|
+
/**
|
|
2552
|
+
* The address you want inside the private network. Omit to be assigned one.
|
|
2553
|
+
* It must be free: a VM already holding it makes this a conflict, and once
|
|
2554
|
+
* yours, no VM can be given it.
|
|
2555
|
+
*/
|
|
2556
|
+
ipv4?: string;
|
|
2557
|
+
/**
|
|
2558
|
+
* An explicit IPv6 address, equivalent to passing one in `ipv4`; only one
|
|
2559
|
+
* may be set.
|
|
2560
|
+
*/
|
|
2561
|
+
ipv6?: string;
|
|
2562
|
+
/**
|
|
2563
|
+
* Ranges behind your client that this network routes through the tunnel — a
|
|
2564
|
+
* site-to-site setup where your client machine forwards for other hosts on
|
|
2565
|
+
* its side. Hosts inside these ranges keep their real addresses inside the
|
|
2566
|
+
* network. A range carved out of the network's own CIDR just works: VMs
|
|
2567
|
+
* reach it with no configuration, and the range is excluded from VM address
|
|
2568
|
+
* allocation (it must be free when granted). A range outside the network's
|
|
2569
|
+
* CIDRs is reached by giving VMs a route via the attachment's `address`. A
|
|
2570
|
+
* range may not straddle the network's CIDR boundary, nor overlap a range
|
|
2571
|
+
* another tunnel already grants this network.
|
|
2572
|
+
*/
|
|
2573
|
+
remoteCidrs?: string[];
|
|
2574
|
+
/**
|
|
2575
|
+
* Make your client this network's exit — a router members can name as a
|
|
2576
|
+
* next hop. A VM routes through it with an ordinary NIC route via the
|
|
2577
|
+
* attachment's address, and everything it sends the exit — whatever the
|
|
2578
|
+
* destination — is forwarded to your client, which is expected to NAT it
|
|
2579
|
+
* onward. Several attachments per network may be exits: members choose per
|
|
2580
|
+
* route, so two exits and two same-metric routes are a redundant pair, and
|
|
2581
|
+
* a dead exit stops being ARP/NDP-answered so the others take over by
|
|
2582
|
+
* themselves.
|
|
2583
|
+
*/
|
|
2584
|
+
exit?: boolean;
|
|
2585
|
+
}
|
|
2586
|
+
interface UpdateTunnelOptions {
|
|
2587
|
+
/** An empty string clears it. */
|
|
2588
|
+
slug?: string;
|
|
2589
|
+
/** An empty string clears it. See {@link CreateTunnelOptions.displayName}. */
|
|
2590
|
+
displayName?: string;
|
|
2591
|
+
}
|
|
2592
|
+
interface ListTunnelsResult {
|
|
2593
|
+
tunnels: TunnelData[];
|
|
2594
|
+
totalCount: number;
|
|
2595
|
+
}
|
|
2596
|
+
declare class TunnelsNamespace {
|
|
2597
|
+
private readonly client;
|
|
2598
|
+
constructor(client: FreestyleClient);
|
|
2599
|
+
/** List your tunnels, newest first. Listings never include a private key. */
|
|
2600
|
+
list(): Promise<ListTunnelsResult>;
|
|
2601
|
+
/**
|
|
2602
|
+
* Create a tunnel, optionally attaching networks in the same call
|
|
2603
|
+
* (`vpcs`, all-or-nothing). This is a one-time setup: the tunnel lives
|
|
2604
|
+
* until you delete it and its config never changes — save it, bring it up
|
|
2605
|
+
* once, and manage which networks it reaches by attaching and detaching
|
|
2606
|
+
* them.
|
|
2607
|
+
*/
|
|
2608
|
+
create(options?: CreateTunnelOptions): Promise<CreatedTunnel>;
|
|
2609
|
+
/** Read one tunnel by its id or its slug. Its config comes back with a blank `PrivateKey`. */
|
|
2610
|
+
get(tunnelIdOrSlug: string): Promise<TunnelData>;
|
|
2611
|
+
/**
|
|
2612
|
+
* Rename a tunnel or change its slug. Only the labels move: the keys,
|
|
2613
|
+
* addresses, routes and attached networks are untouched, so a connected
|
|
2614
|
+
* client sees nothing change and the config you saved keeps working.
|
|
2615
|
+
*/
|
|
2616
|
+
update(tunnelIdOrSlug: string, options: UpdateTunnelOptions): Promise<TunnelData>;
|
|
2617
|
+
/**
|
|
2618
|
+
* Replace a tunnel's keys, keeping its id and every attached network's
|
|
2619
|
+
* address. Use this if you lost the config or need to revoke one that
|
|
2620
|
+
* leaked.
|
|
2621
|
+
*/
|
|
2622
|
+
rotateKey(tunnelIdOrSlug: string, options?: {
|
|
2623
|
+
clientPublicKey?: string;
|
|
2624
|
+
}): Promise<CreatedTunnel>;
|
|
2625
|
+
/** Delete a tunnel, detaching every network as it goes. */
|
|
2626
|
+
delete(tunnelIdOrSlug: string): Promise<void>;
|
|
2627
|
+
/**
|
|
2628
|
+
* Attach a private network to a tunnel. The tunnel gains an address inside
|
|
2629
|
+
* the network — what that network's VMs see as your address — and a
|
|
2630
|
+
* connected client can reach it immediately, with no config change. The
|
|
2631
|
+
* network's CIDRs must fall inside the tunnel's routes and must not
|
|
2632
|
+
* overlap another attached network's.
|
|
2633
|
+
*/
|
|
2634
|
+
attachVpc(tunnelIdOrSlug: string, vpcIdOrSlug: string, options?: AttachVpcOptions): Promise<TunnelData>;
|
|
2635
|
+
/**
|
|
2636
|
+
* Detach a private network from a tunnel: it stops being reachable and the
|
|
2637
|
+
* tunnel's address inside it is released. The tunnel — and any connected
|
|
2638
|
+
* client — carries on untouched.
|
|
2639
|
+
*/
|
|
2640
|
+
detachVpc(tunnelIdOrSlug: string, vpcIdOrSlug: string): Promise<TunnelData>;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
type VmState = "starting" | "running" | "pausing" | "paused" | "stopped";
|
|
2644
|
+
/**
|
|
2645
|
+
* The base snapshots the platform publishes — the shared starting points every
|
|
2646
|
+
* account can boot from, qualified `{owner}/{slug}` because they belong to the
|
|
2647
|
+
* `freestyle` account rather than yours. Mirrors `catalog/snapshots.json`.
|
|
2648
|
+
*
|
|
2649
|
+
* A slug is a name, not a pointer: these are reassigned to a freshly built
|
|
2650
|
+
* snapshot on every catalog rebuild, which is the point — booting
|
|
2651
|
+
* `freestyle/ubuntu` gets you the current build. Pin a `sh-…` id instead when
|
|
2652
|
+
* you need the exact image to stay put.
|
|
2653
|
+
*/
|
|
2654
|
+
type BaseSnapshotId =
|
|
2655
|
+
/** Ubuntu 24.04 LTS · 2 vCPU · 4 GiB · 16 GB. */
|
|
2656
|
+
"freestyle/ubuntu-sm"
|
|
2657
|
+
/** Ubuntu 24.04 LTS · 4 vCPU · 8 GiB · 32 GB. The platform default. */
|
|
2658
|
+
| "freestyle/ubuntu"
|
|
2659
|
+
/** Ubuntu 24.04 LTS · 8 vCPU · 16 GiB · 64 GB. */
|
|
2660
|
+
| "freestyle/ubuntu-lg"
|
|
2661
|
+
/** Ubuntu 24.04 LTS · 16 vCPU · 32 GiB · 128 GB. */
|
|
2662
|
+
| "freestyle/ubuntu-xl"
|
|
2663
|
+
/** Ubuntu 24.04 LTS · 32 vCPU · 64 GiB · 128 GB. The largest size any plan can actually create. */
|
|
2664
|
+
| "freestyle/ubuntu-2xl"
|
|
2665
|
+
/** Ubuntu 24.04 LTS · 64 vCPU · 128 GiB · 256 GB. Above every plan's per-VM cap. */
|
|
2666
|
+
| "freestyle/ubuntu-3xl"
|
|
2667
|
+
/** BusyBox · 1 vCPU · 128 MiB · 1 GB. For when Ubuntu is too big to fit. */
|
|
2668
|
+
| "freestyle/busybox";
|
|
2669
|
+
/**
|
|
2670
|
+
* Anywhere a snapshot is named: a `sh-…` id, your own slug for one, or a
|
|
2671
|
+
* public `{owner}/{slug}`. The {@link BaseSnapshotId} arm exists purely so
|
|
2672
|
+
* editors suggest the platform's catalog; `(string & {})` keeps every other
|
|
2673
|
+
* snapshot just as valid, and stops TypeScript from collapsing the union back
|
|
2674
|
+
* to a bare `string` and losing those suggestions.
|
|
2675
|
+
*/
|
|
2676
|
+
type SnapshotIdOrSlug = BaseSnapshotId | (string & {});
|
|
2677
|
+
interface VmResources {
|
|
2678
|
+
/** vCPU count. */
|
|
2679
|
+
cpu: number;
|
|
2680
|
+
/** Memory, MiB. */
|
|
2681
|
+
memory: number;
|
|
2682
|
+
/** Disk, MiB. */
|
|
2683
|
+
storage: number;
|
|
2684
|
+
}
|
|
2685
|
+
/** An in-guest static route: reach `cidr` via the gateway `via`, inside the network's CIDR. */
|
|
2686
|
+
interface NetworkRoute {
|
|
2687
|
+
cidr: string;
|
|
2688
|
+
via: string;
|
|
2689
|
+
/**
|
|
2690
|
+
* Route priority, as `ip route`'s `metric`. Omit for the platform default.
|
|
2691
|
+
* Several routes to the same `cidr` at the same metric are installed as one
|
|
2692
|
+
* multipath route — the guest spreads flows across their gateways (turn on
|
|
2693
|
+
* `net.ipv4.fib_multipath_use_neigh` in the guest so a dead gateway drops
|
|
2694
|
+
* out) — while different metrics make a primary and a backup.
|
|
2695
|
+
*/
|
|
2696
|
+
metric?: number;
|
|
2697
|
+
}
|
|
2698
|
+
/** A VM's place on a private network. */
|
|
2699
|
+
interface VmNetwork {
|
|
2700
|
+
/** @deprecated The network's id under its old name. Read `vpcId`. */
|
|
2701
|
+
vpc: string;
|
|
2702
|
+
/** The network's id, under the name the rest of the API uses. */
|
|
2703
|
+
vpcId?: string;
|
|
2704
|
+
/** The VM's IPv4 address inside the network; absent unless it asked for one. */
|
|
2705
|
+
ipv4?: string | null;
|
|
2706
|
+
/** The network's CIDR, alongside `ipv4`. */
|
|
2707
|
+
cidr?: string | null;
|
|
2708
|
+
/** The VM's IPv6 address, when the network has an IPv6 CIDR. */
|
|
2709
|
+
ipv6?: string | null;
|
|
2710
|
+
/** The network's IPv6 CIDR, alongside `ipv6`. */
|
|
2711
|
+
cidrV6?: string | null;
|
|
2712
|
+
routes: NetworkRoute[];
|
|
2713
|
+
}
|
|
2714
|
+
/** A VM as its owning account sees it. */
|
|
2715
|
+
interface VmData {
|
|
2716
|
+
id: string;
|
|
2717
|
+
state: VmState;
|
|
2718
|
+
/** URL-safe identifier, unique within the account; usable anywhere an id is. */
|
|
2719
|
+
slug?: string | null;
|
|
2720
|
+
/** The VM's label, if it has one. Shown in place of the slug. */
|
|
2721
|
+
displayName?: string | null;
|
|
2722
|
+
resources: VmResources;
|
|
2723
|
+
/** The snapshot the VM booted from, when it booted from one. */
|
|
2724
|
+
snapshotId?: string | null;
|
|
2725
|
+
/**
|
|
2726
|
+
* What that snapshot was called when the VM was created, qualified
|
|
2727
|
+
* `{owner}/{slug}` when it belongs to another account. Recorded once, so it
|
|
2728
|
+
* still names the origin after the snapshot is renamed or deleted.
|
|
2729
|
+
*
|
|
2730
|
+
* A name, not a pointer. Slugs can be reassigned, so boot from `snapshotId`.
|
|
2731
|
+
*/
|
|
2732
|
+
sourceSnapshotSlugAtCreate?: string | null;
|
|
2733
|
+
/**
|
|
2734
|
+
* What that snapshot is called *now*, resolved when the VM is read. Absent
|
|
2735
|
+
* when it has no slug, and when it has been deleted — so against
|
|
2736
|
+
* `sourceSnapshotSlugAtCreate` it is what tells a renamed origin from a gone
|
|
2737
|
+
* one, without a second request.
|
|
2738
|
+
*/
|
|
2739
|
+
sourceSnapshotSlug?: string | null;
|
|
2740
|
+
/** The snapshot's display name now, under the same rules. */
|
|
2741
|
+
sourceSnapshotDisplayName?: string | null;
|
|
2742
|
+
idleTimeoutSeconds?: number | null;
|
|
2743
|
+
/**
|
|
2744
|
+
* Delete the VM once it has gone this long without running. Absent means
|
|
2745
|
+
* never; 0 means the VM is ephemeral — deleted the moment it stops. Note
|
|
2746
|
+
* this can be set for you: some plans cap how long an unused VM is kept,
|
|
2747
|
+
* and the cap shows up here.
|
|
2748
|
+
*/
|
|
2749
|
+
autoDeleteSeconds?: number | null;
|
|
2750
|
+
/**
|
|
2751
|
+
* Whether {@link Vm.autoDeleteSeconds} is your plan's doing rather than
|
|
2752
|
+
* yours. A window your plan set goes away by itself if you move to a plan
|
|
2753
|
+
* that does not reclaim unused VMs; one you set is yours and stays.
|
|
2754
|
+
*/
|
|
2755
|
+
autoDeleteFromPlan?: boolean;
|
|
2756
|
+
/** Delete the VM this long after it was created, whatever it is doing. */
|
|
2757
|
+
ttlSeconds?: number | null;
|
|
2758
|
+
/** Pause the VM once one run has lasted this long, however busy it is. */
|
|
2759
|
+
maxRunSeconds?: number | null;
|
|
2760
|
+
/**
|
|
2761
|
+
* The VM's lifetime runtime budget in seconds. Spending it pauses the VM and
|
|
2762
|
+
* makes every later start a 409 until you raise this.
|
|
2763
|
+
*/
|
|
2764
|
+
maxRunTotalSeconds?: number | null;
|
|
2765
|
+
/**
|
|
2766
|
+
* Seconds this VM has spent running, across every run it has ever had —
|
|
2767
|
+
* what {@link Vm.maxRunTotalSeconds} is measured against.
|
|
2768
|
+
*/
|
|
2769
|
+
totalRunSeconds?: number;
|
|
2770
|
+
/** Whether the VM is booted again when it stops without being asked to. */
|
|
2771
|
+
automaticRestart?: boolean;
|
|
2772
|
+
/**
|
|
2773
|
+
* The VM's stable public IPv6 address — assigned at create and kept for the
|
|
2774
|
+
* VM's life, across restarts and moves between machines.
|
|
2775
|
+
*/
|
|
2776
|
+
publicIpv6?: string | null;
|
|
2777
|
+
/**
|
|
2778
|
+
* The public IPv4 address the VM's outbound traffic is seen as coming from.
|
|
2779
|
+
* It belongs to your account rather than to this VM: every VM you own shares
|
|
2780
|
+
* it, and it survives restarts and moves between machines.
|
|
2781
|
+
*
|
|
2782
|
+
* Optional, and worth reading as optional even though a VM in service has
|
|
2783
|
+
* one: nothing promises an account keeps a single outbound address.
|
|
2784
|
+
*/
|
|
2785
|
+
egressIpv4?: string | null;
|
|
2786
|
+
/**
|
|
2787
|
+
* The public IPv6 address the VM's outbound traffic is seen as coming from.
|
|
2788
|
+
* Today it is the VM's own {@link Vm.publicIpv6} — it appears from the
|
|
2789
|
+
* address it answers on — but read it as its own field: where a VM answers
|
|
2790
|
+
* and where it appears from are two questions, and {@link Vm.egressIpv4}
|
|
2791
|
+
* already answers them differently.
|
|
2792
|
+
*/
|
|
2793
|
+
egressIpv6?: string | null;
|
|
2794
|
+
metadata: Record<string, string>;
|
|
2795
|
+
/** The placement constraints the VM was created with, when any. */
|
|
2796
|
+
placement?: VmPlacement | null;
|
|
2797
|
+
/** Total CPU time consumed, seconds. */
|
|
2798
|
+
cpuTimeSeconds?: number | null;
|
|
2799
|
+
lastNetworkActivity?: string | null;
|
|
2800
|
+
/** The private networks the VM is on (at most one today). */
|
|
2801
|
+
vpcs: VmNetwork[];
|
|
2802
|
+
/** @deprecated The same list as `vpcs`, kept for old readers. */
|
|
2803
|
+
networks: VmNetwork[];
|
|
2804
|
+
createdAt: string;
|
|
2805
|
+
updatedAt: string;
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Placement constraints for a VM, fixed at create time and honored again
|
|
2809
|
+
* whenever the platform relocates the VM.
|
|
2810
|
+
*/
|
|
2811
|
+
interface VmPlacement {
|
|
2812
|
+
/**
|
|
2813
|
+
* Hard rules: the VM will not land on a topology domain already hosting a
|
|
2814
|
+
* VM matched by any rule's selector. Creation fails with a 409 when no
|
|
2815
|
+
* domain can satisfy every rule. At most 8 rules.
|
|
2816
|
+
*/
|
|
2817
|
+
antiAffinity: VmAntiAffinityRule[];
|
|
2818
|
+
}
|
|
2819
|
+
/** One anti-affinity rule: "not on the same `topology` domain as any VM matching `selector`". */
|
|
2820
|
+
interface VmAntiAffinityRule {
|
|
2821
|
+
/** The domain the rule spreads across. `node` — the only domain today — means "not on the same host machine". */
|
|
2822
|
+
topology: "node";
|
|
2823
|
+
selector: VmLabelSelector;
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Selects VMs by their metadata: a VM matches when its metadata contains
|
|
2827
|
+
* every `matchLabels` entry. 1–8 entries; keys and values are limited to 63
|
|
2828
|
+
* characters, like the metadata they match.
|
|
2829
|
+
*/
|
|
2830
|
+
interface VmLabelSelector {
|
|
2831
|
+
matchLabels: Record<string, string>;
|
|
2832
|
+
}
|
|
2833
|
+
/** Attach a VM to one of your private networks. A VM may be on at most one. */
|
|
2834
|
+
interface AttachNetwork {
|
|
2835
|
+
/** The network to join, by id or by your slug for it. */
|
|
2836
|
+
vpcId?: string;
|
|
2837
|
+
/** @deprecated The network to join, under its old name. Write `vpcId`. */
|
|
2838
|
+
vpc?: string;
|
|
2839
|
+
/**
|
|
2840
|
+
* The IPv4 address to take inside the network: an explicit address, `true`
|
|
2841
|
+
* to auto-allocate, or `false` to opt out. Omitted = allocate one, since
|
|
2842
|
+
* networks are dual-stack.
|
|
2843
|
+
*/
|
|
2844
|
+
ipv4?: string | boolean;
|
|
2845
|
+
/**
|
|
2846
|
+
* The IPv6 address to take: an explicit address, `true` to auto-allocate
|
|
2847
|
+
* (the default), or `false` to opt out.
|
|
2848
|
+
*/
|
|
2849
|
+
ipv6?: string | boolean;
|
|
2850
|
+
/** Static routes to install in the guest. */
|
|
2851
|
+
routes?: NetworkRoute[];
|
|
2852
|
+
}
|
|
2853
|
+
interface CreateVmOptions {
|
|
2854
|
+
/** Boot from this snapshot: its id, your slug for it, or a public `{owner}/{slug}`. Omit for the platform default. */
|
|
2855
|
+
snapshotId?: SnapshotIdOrSlug | null;
|
|
2856
|
+
/**
|
|
2857
|
+
* URL-safe identifier, unique within your account: 1–63 chars of
|
|
2858
|
+
* `[a-z0-9-]`, no leading, trailing, or repeated hyphens.
|
|
2859
|
+
*
|
|
2860
|
+
* How you address the VM everywhere — the API, the CLI, the dashboard.
|
|
2861
|
+
*/
|
|
2862
|
+
slug?: string | null;
|
|
2863
|
+
/** Take `slug` from whichever VM currently holds it; the holder keeps running, slugless. Requires `slug`. */
|
|
2864
|
+
reassignSlug?: boolean;
|
|
2865
|
+
/**
|
|
2866
|
+
* A label for a slug that does not read as a name — one minted per run,
|
|
2867
|
+
* per commit or per tenant (`run-7f3a91c2`). Shown in place of the slug.
|
|
2868
|
+
*/
|
|
2869
|
+
displayName?: string | null;
|
|
2870
|
+
/**
|
|
2871
|
+
* Pause the VM after this many seconds without network activity. Traffic
|
|
2872
|
+
* over its public address counts, as does anything typed in a terminal
|
|
2873
|
+
* session; -1 (or omitting this) means it is never paused for being idle.
|
|
2874
|
+
*/
|
|
2875
|
+
idleTimeoutSeconds?: number | null;
|
|
2876
|
+
/**
|
|
2877
|
+
* Delete the VM once it has gone this many seconds without running. Every
|
|
2878
|
+
* start resets the clock, and a running VM is never deleted for this.
|
|
2879
|
+
* -1 (or omitting this) means never; 0 makes the VM ephemeral — deleted
|
|
2880
|
+
* the moment it stops.
|
|
2881
|
+
*
|
|
2882
|
+
* Some plans cap how long an unused VM is kept. On those, omitting this (or
|
|
2883
|
+
* sending -1) gets you the cap rather than "keep forever", and asking for
|
|
2884
|
+
* longer is a 400.
|
|
2885
|
+
*/
|
|
2886
|
+
autoDeleteSeconds?: number | null;
|
|
2887
|
+
/**
|
|
2888
|
+
* Delete the VM this many seconds after it is created, whatever it is doing
|
|
2889
|
+
* at the time. A deadline, not an idle window: nothing resets it. -1 (or
|
|
2890
|
+
* omitting this) means no deadline.
|
|
2891
|
+
*/
|
|
2892
|
+
ttlSeconds?: number | null;
|
|
2893
|
+
/**
|
|
2894
|
+
* Pause the VM once a single run has lasted this many seconds, however busy
|
|
2895
|
+
* it is. Starting it again gives it a fresh budget — unlike
|
|
2896
|
+
* {@link CreateVmOptions.maxRunTotalSeconds}, which never resets. -1 (or
|
|
2897
|
+
* omitting this) means no cap.
|
|
2898
|
+
*/
|
|
2899
|
+
maxRunSeconds?: number | null;
|
|
2900
|
+
/**
|
|
2901
|
+
* How many seconds this VM may run in total, ever. Spending the budget
|
|
2902
|
+
* pauses the VM, and every later start is a 409 until you raise it. -1 (or
|
|
2903
|
+
* omitting this) means no budget.
|
|
2904
|
+
*/
|
|
2905
|
+
maxRunTotalSeconds?: number | null;
|
|
2906
|
+
/**
|
|
2907
|
+
* Boot the VM again if it stops without you asking it to — the VMM crashes,
|
|
2908
|
+
* the guest kernel panics, the machine it runs on loses power. Defaults to
|
|
2909
|
+
* `true`.
|
|
2910
|
+
*
|
|
2911
|
+
* It never fights you: shutting the VM down, pausing it, running `poweroff`
|
|
2912
|
+
* inside it, or hitting an idle timeout all leave it off. This only decides
|
|
2913
|
+
* who gets the last word when a *failure* stops it.
|
|
2914
|
+
*/
|
|
2915
|
+
automaticRestart?: boolean;
|
|
2916
|
+
/** Put the VM on your private networks. Omit to leave it off them. At most one is accepted today. */
|
|
2917
|
+
vpcs?: AttachNetwork[];
|
|
2918
|
+
/** @deprecated The same list as `vpcs`; write `vpcs`. */
|
|
2919
|
+
networks?: AttachNetwork[];
|
|
2920
|
+
/** Up to 64 metadata entries; keys and values are limited to 63 characters. */
|
|
2921
|
+
metadata?: Record<string, string>;
|
|
2922
|
+
/**
|
|
2923
|
+
* Placement constraints (anti-affinity), fixed for the VM's life. Selectors
|
|
2924
|
+
* match the metadata of your other VMs.
|
|
2925
|
+
*/
|
|
2926
|
+
placement?: VmPlacement | null;
|
|
2927
|
+
/**
|
|
2928
|
+
* Firewall rules created with the VM and deleted with it.
|
|
2929
|
+
*
|
|
2930
|
+
* Inside this block an endpoint with no identity means the VM being created,
|
|
2931
|
+
* which is how a rule names a VM whose id does not exist yet. See
|
|
2932
|
+
* {@link FirewallSpec}.
|
|
2933
|
+
*
|
|
2934
|
+
* @example
|
|
2935
|
+
* firewall: {
|
|
2936
|
+
* rules: [
|
|
2937
|
+
* // Inbound HTTPS from anywhere.
|
|
2938
|
+
* { action: "allow", source: { public: true }, destination: { port: 443, protocol: "tcp" } },
|
|
2939
|
+
* // Outbound HTTPS to anywhere.
|
|
2940
|
+
* { action: "allow", source: {}, destination: { public: true, port: 443, protocol: "tcp" } },
|
|
2941
|
+
* ],
|
|
2942
|
+
* }
|
|
2943
|
+
*
|
|
2944
|
+
* Required. A VM gets nothing implicitly — no outbound Internet, no inbound —
|
|
2945
|
+
* so a VM that wants either says so here. `{ rules: [] }` is a legitimate
|
|
2946
|
+
* answer: a VM reachable only through a mapped domain or SSH.
|
|
2947
|
+
*/
|
|
2948
|
+
firewall: FirewallSpec;
|
|
2949
|
+
/**
|
|
2950
|
+
* TLS rules created with the VM and deleted with it — named sessions the VM
|
|
2951
|
+
* may serve or open. Optional, unlike `firewall`: a TLS rule is a grant
|
|
2952
|
+
* layered on top of the firewall's packet decision, never a baseline a VM
|
|
2953
|
+
* must state.
|
|
2954
|
+
*
|
|
2955
|
+
* Inside this block an endpoint with no identity means the VM being created,
|
|
2956
|
+
* exactly as in `firewall`. See {@link TlsSpec}.
|
|
2957
|
+
*
|
|
2958
|
+
* @example
|
|
2959
|
+
* tls: {
|
|
2960
|
+
* rules: [
|
|
2961
|
+
* // Publish app.acme.com to port 8000 on this VM.
|
|
2962
|
+
* { action: "allow", domain: "app.acme.com", source: { public: true }, destination: { port: 8000 } },
|
|
2963
|
+
* ],
|
|
2964
|
+
* }
|
|
2965
|
+
*/
|
|
2966
|
+
tls?: TlsSpec;
|
|
2967
|
+
}
|
|
2968
|
+
interface UpdateVmOptions {
|
|
2969
|
+
/** Change the slug; an empty string clears it. */
|
|
2970
|
+
slug?: string;
|
|
2971
|
+
/** Take `slug` from whichever VM currently holds it. */
|
|
2972
|
+
reassignSlug?: boolean;
|
|
2973
|
+
/**
|
|
2974
|
+
* Change the display label; an empty string clears it. See
|
|
2975
|
+
* {@link CreateVmOptions.displayName}.
|
|
2976
|
+
*/
|
|
2977
|
+
displayName?: string;
|
|
2978
|
+
/** Seconds of network idleness before the VM is paused; -1 removes the timeout. */
|
|
2979
|
+
idleTimeoutSeconds?: number;
|
|
2980
|
+
/**
|
|
2981
|
+
* Seconds of not running before the VM is deleted; -1 removes the window
|
|
2982
|
+
* (on a plan that caps it, -1 puts you back on the cap rather than off it),
|
|
2983
|
+
* and 0 makes the VM ephemeral — deleted the moment it stops, or right away
|
|
2984
|
+
* if it is already stopped.
|
|
2985
|
+
*/
|
|
2986
|
+
autoDeleteSeconds?: number;
|
|
2987
|
+
/**
|
|
2988
|
+
* Seconds from creation — not from now — before the VM is deleted; -1
|
|
2989
|
+
* removes the deadline. A value already in the past deletes it shortly.
|
|
2990
|
+
*/
|
|
2991
|
+
ttlSeconds?: number;
|
|
2992
|
+
/** Seconds one run may last before the VM is paused; -1 removes the cap. */
|
|
2993
|
+
maxRunSeconds?: number;
|
|
2994
|
+
/**
|
|
2995
|
+
* The lifetime runtime budget in seconds; -1 removes it. Raising this past
|
|
2996
|
+
* `totalRunSeconds` is how you start a VM that has spent its budget.
|
|
2997
|
+
*/
|
|
2998
|
+
maxRunTotalSeconds?: number;
|
|
2999
|
+
/**
|
|
3000
|
+
* Turn automatic restart on or off. Applies to the next failure: this neither
|
|
3001
|
+
* boots a stopped VM nor stops a running one.
|
|
3002
|
+
*/
|
|
3003
|
+
automaticRestart?: boolean;
|
|
3004
|
+
/** Metadata merged into the VM. Only the keys you send are touched; send an empty value to remove one. */
|
|
3005
|
+
metadata?: Record<string, string>;
|
|
3006
|
+
}
|
|
3007
|
+
interface ResizeVmOptions {
|
|
3008
|
+
/** vCPU count. Grow-only. Applies live to a running VM. */
|
|
3009
|
+
cpu?: number;
|
|
3010
|
+
/** Memory, MiB. Grow-only. Applies live to a running VM. */
|
|
3011
|
+
memory?: number;
|
|
3012
|
+
/** Disk, MiB. Grow-only, and only while the VM is running. */
|
|
3013
|
+
storage?: number;
|
|
3014
|
+
}
|
|
3015
|
+
interface ExecOptions {
|
|
3016
|
+
/** The command line, run through the guest's shell. */
|
|
3017
|
+
command: string;
|
|
3018
|
+
/**
|
|
3019
|
+
* Guest Linux user to run as. Omit for the VM's default user: the account
|
|
3020
|
+
* holding uid 1000, or `root` in an image that has no such account. Required
|
|
3021
|
+
* when an identity grant restricts users.
|
|
3022
|
+
*/
|
|
3023
|
+
linuxUser?: string;
|
|
3024
|
+
/** Wall-clock limit, milliseconds. 1–300000; defaults to the guest's own. */
|
|
3025
|
+
timeoutMs?: number;
|
|
3026
|
+
/** Extra environment variables. Keys must be valid POSIX names. */
|
|
3027
|
+
env?: Record<string, string>;
|
|
3028
|
+
/** Base64-encoded stdin, up to 1 MiB decoded. */
|
|
3029
|
+
stdin?: string;
|
|
3030
|
+
}
|
|
3031
|
+
interface ExecResult {
|
|
3032
|
+
stdout?: string | null;
|
|
3033
|
+
stderr?: string | null;
|
|
3034
|
+
/** The command's exit status; null if it was killed by the timeout. */
|
|
3035
|
+
statusCode?: number | null;
|
|
3036
|
+
}
|
|
3037
|
+
interface ListVmsOptions {
|
|
3038
|
+
/** Only VMs in this state. */
|
|
3039
|
+
state?: VmState;
|
|
3040
|
+
/** Exact slug match. */
|
|
3041
|
+
slug?: string;
|
|
3042
|
+
/** Only VMs booted from this snapshot. */
|
|
3043
|
+
snapshotId?: SnapshotIdOrSlug;
|
|
3044
|
+
/** Comma-separated `key:value` pairs, e.g. `env:prod,region:us-west`. */
|
|
3045
|
+
metadata?: string;
|
|
3046
|
+
limit?: number;
|
|
3047
|
+
offset?: number;
|
|
3048
|
+
}
|
|
3049
|
+
interface ListVmsResult {
|
|
3050
|
+
vms: VmData[];
|
|
3051
|
+
totalCount: number;
|
|
3052
|
+
runningCount: number;
|
|
3053
|
+
startingCount: number;
|
|
3054
|
+
pausingCount: number;
|
|
3055
|
+
pausedCount: number;
|
|
3056
|
+
stoppedCount: number;
|
|
3057
|
+
}
|
|
3058
|
+
interface DirEntry {
|
|
3059
|
+
name: string;
|
|
3060
|
+
/** `file`, `directory`, or `symlink`. */
|
|
3061
|
+
kind: string;
|
|
3062
|
+
}
|
|
3063
|
+
interface FileStat {
|
|
3064
|
+
/** Size in bytes. */
|
|
3065
|
+
size: number;
|
|
3066
|
+
isFile: boolean;
|
|
3067
|
+
isDirectory: boolean;
|
|
3068
|
+
isSymlink: boolean;
|
|
3069
|
+
/** Octal mode, e.g. `0644`. */
|
|
3070
|
+
permissions: string;
|
|
3071
|
+
owner: string;
|
|
3072
|
+
group: string;
|
|
3073
|
+
modified: string;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
interface ReadFileOptions {
|
|
3077
|
+
/** First byte to read. Default 0. */
|
|
3078
|
+
offset?: number;
|
|
3079
|
+
/** How many bytes to read. Default: to the end of the file. */
|
|
3080
|
+
length?: number;
|
|
3081
|
+
signal?: AbortSignal;
|
|
3082
|
+
}
|
|
3083
|
+
/**
|
|
3084
|
+
* How far a transfer has got. An object rather than positional arguments so it
|
|
3085
|
+
* can grow — a phase, a message — without breaking callers, and so progress
|
|
3086
|
+
* reads the same way wherever the SDK reports it.
|
|
3087
|
+
*/
|
|
3088
|
+
interface TransferProgress {
|
|
3089
|
+
/** Bytes confirmed stored so far. */
|
|
3090
|
+
completedBytes: number;
|
|
3091
|
+
/** Bytes in the whole transfer. */
|
|
3092
|
+
totalBytes: number;
|
|
3093
|
+
}
|
|
3094
|
+
interface WriteFileOptions {
|
|
3095
|
+
/** Final file mode bits, e.g. 0o755. Defaults to the target's existing mode, or 0o600. */
|
|
3096
|
+
mode?: number;
|
|
3097
|
+
/**
|
|
3098
|
+
* Called as bytes are confirmed stored. A chunked upload reports after each
|
|
3099
|
+
* chunk; a single-request write reports once, on completion.
|
|
3100
|
+
*/
|
|
3101
|
+
onProgress?: (progress: TransferProgress) => void;
|
|
3102
|
+
signal?: AbortSignal;
|
|
3103
|
+
/** Bytes per chunk for large files (default 16 MiB; the server caps a chunk at 256 MiB). */
|
|
3104
|
+
chunkSize?: number;
|
|
3105
|
+
/** Attempts per chunk before giving up (default 5). */
|
|
3106
|
+
maxAttempts?: number;
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* The guest filesystem of one VM: `GET/PUT /vms/{id}/fs/*`.
|
|
3110
|
+
*
|
|
3111
|
+
* Paths must be absolute, free of `..`, and at most 4096 characters. Files cross
|
|
3112
|
+
* the wire as raw bytes in both directions, so there is no size limit short of
|
|
3113
|
+
* the 16 GiB per-file ceiling and nothing to encode or decode.
|
|
3114
|
+
*
|
|
3115
|
+
* `writeFile` picks its own transport: small files go in one request, large ones
|
|
3116
|
+
* as a resumable chunked upload whose failed chunks are retried individually.
|
|
3117
|
+
* Either way the write is atomic — a reader in the guest sees the old file until
|
|
3118
|
+
* the new one has arrived intact — and verified by sha256 end to end.
|
|
3119
|
+
*/
|
|
3120
|
+
declare class VmFilesystem {
|
|
3121
|
+
private readonly client;
|
|
3122
|
+
private readonly basePath;
|
|
3123
|
+
constructor(client: FreestyleClient, basePath: string);
|
|
3124
|
+
/**
|
|
3125
|
+
* Read a whole file, or with `offset`/`length` just part of one, into memory.
|
|
3126
|
+
*
|
|
3127
|
+
* The result is a plain `Uint8Array` on every runtime — never a Node
|
|
3128
|
+
* `Buffer`, even on Node. Don't coerce it with `String(bytes)` or
|
|
3129
|
+
* `bytes.toString()`: those go through the typed-array `toString` and
|
|
3130
|
+
* produce the byte values joined by commas ("123,10,32,…"), not the file's
|
|
3131
|
+
* text. For text use {@link readTextFile}, or decode explicitly with
|
|
3132
|
+
* `new TextDecoder().decode(bytes)`.
|
|
3133
|
+
*/
|
|
3134
|
+
readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
|
|
3135
|
+
/** Read a file as UTF-8 text. Use this, not `String(await readFile(...))`. */
|
|
3136
|
+
readTextFile(path: string, options?: ReadFileOptions): Promise<string>;
|
|
3137
|
+
/**
|
|
3138
|
+
* Stream a file out of the VM without holding it in memory — pipe it to disk,
|
|
3139
|
+
* to an HTTP response, or through a parser.
|
|
3140
|
+
*
|
|
3141
|
+
* ```ts
|
|
3142
|
+
* const bytes = await vm.fs.readFileStream("/var/log/big.log");
|
|
3143
|
+
* await pipeline(Readable.fromWeb(bytes), createWriteStream("big.log"));
|
|
3144
|
+
* ```
|
|
3145
|
+
*/
|
|
3146
|
+
readFileStream(path: string, options?: ReadFileOptions): Promise<ReadableStream<Uint8Array>>;
|
|
3147
|
+
/**
|
|
3148
|
+
* Write a file into the VM, whatever its size.
|
|
3149
|
+
*
|
|
3150
|
+
* ```ts
|
|
3151
|
+
* await vm.fs.writeFile("/etc/app.conf", "debug = true");
|
|
3152
|
+
* await vm.fs.writeFile("/data/model.bin", await openAsBlob("model.bin"), {
|
|
3153
|
+
* onProgress: ({ completedBytes, totalBytes }) =>
|
|
3154
|
+
* console.log(`${completedBytes}/${totalBytes}`),
|
|
3155
|
+
* });
|
|
3156
|
+
* ```
|
|
3157
|
+
*
|
|
3158
|
+
* A `Blob` (`node:fs`'s `openAsBlob`, or a browser `File`) is read one chunk at
|
|
3159
|
+
* a time and never held in memory whole.
|
|
3160
|
+
*/
|
|
3161
|
+
writeFile(path: string, content: string | Uint8Array | Blob, options?: WriteFileOptions): Promise<void>;
|
|
3162
|
+
/** Write text. Identical to {@link writeFile} with a string. */
|
|
3163
|
+
writeTextFile(path: string, content: string, options?: WriteFileOptions): Promise<void>;
|
|
3164
|
+
readDir(path: string): Promise<DirEntry[]>;
|
|
3165
|
+
mkdir(path: string): Promise<void>;
|
|
3166
|
+
remove(path: string): Promise<void>;
|
|
3167
|
+
exists(path: string): Promise<boolean>;
|
|
3168
|
+
stat(path: string): Promise<FileStat>;
|
|
3169
|
+
private openRead;
|
|
3170
|
+
/**
|
|
3171
|
+
* Send a large file as an upload session: ordered chunks, each retried
|
|
3172
|
+
* independently with backoff, so one dropped connection costs one chunk rather
|
|
3173
|
+
* than the whole transfer. The file appears at `path` only on commit.
|
|
3174
|
+
*/
|
|
3175
|
+
private uploadInChunks;
|
|
3176
|
+
/**
|
|
3177
|
+
* PUT one chunk, retrying transient failures. After any failure the session
|
|
3178
|
+
* is re-fetched: the server only counts fully-stored chunks, so its
|
|
3179
|
+
* `receivedBytes` tells us whether this chunk actually landed (return) or
|
|
3180
|
+
* must be resent (retry).
|
|
3181
|
+
*/
|
|
3182
|
+
private putChunkWithRetry;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
interface PtySessionInfo {
|
|
3186
|
+
/** Per-VM session id, for attaching or closing this session. */
|
|
3187
|
+
sessionId: number;
|
|
3188
|
+
/** `"running"` while the shell is alive, `"exited"` once it has terminated. */
|
|
3189
|
+
state: string;
|
|
3190
|
+
/** Unix timestamp, in seconds, when the session was opened. */
|
|
3191
|
+
createdUnix: number;
|
|
3192
|
+
cols: number;
|
|
3193
|
+
rows: number;
|
|
3194
|
+
/** Exit code, present only once the session has exited. */
|
|
3195
|
+
exitCode?: number | null;
|
|
3196
|
+
/**
|
|
3197
|
+
* Linux user the session runs as, named even when the open left the choice to
|
|
3198
|
+
* the VM's default. Absent only for a session opened by a guest agent that
|
|
3199
|
+
* predates that default, which ran it as root.
|
|
3200
|
+
*/
|
|
3201
|
+
linuxUser?: string | null;
|
|
3202
|
+
/**
|
|
3203
|
+
* The name this session was opened with, while it still answers to it.
|
|
3204
|
+
* Released the moment the shell exits, so an exited session reports none and
|
|
3205
|
+
* is addressable only by `sessionId`.
|
|
3206
|
+
*/
|
|
3207
|
+
slug?: string | null;
|
|
3208
|
+
}
|
|
3209
|
+
interface ListPtySessionsResult {
|
|
3210
|
+
sessions: PtySessionInfo[];
|
|
3211
|
+
}
|
|
3212
|
+
interface ClosePtySessionResult {
|
|
3213
|
+
sessionId: number;
|
|
3214
|
+
/** Exit code if the session had already exited. Absent when it was still running. */
|
|
3215
|
+
exitCode?: number | null;
|
|
3216
|
+
}
|
|
3217
|
+
interface PtyOpenOptions {
|
|
3218
|
+
/** Command to run; omit for a login shell. */
|
|
3219
|
+
exec?: string;
|
|
3220
|
+
/** Initial width in columns (default 80). */
|
|
3221
|
+
cols?: number;
|
|
3222
|
+
/** Initial height in rows (default 24). */
|
|
3223
|
+
rows?: number;
|
|
3224
|
+
/**
|
|
3225
|
+
* Guest Linux user to run as. Omit for the VM's default user: the account
|
|
3226
|
+
* holding uid 1000, or `root` in an image that has no such account.
|
|
3227
|
+
*/
|
|
3228
|
+
linuxUser?: string;
|
|
3229
|
+
/**
|
|
3230
|
+
* Name this session, so you can reattach to it later without storing the id
|
|
3231
|
+
* we mint — `vm.pty.attach({ session: "main" })` from anywhere, any time.
|
|
3232
|
+
*
|
|
3233
|
+
* Get-or-create: if a session already has this name you get *that* one back —
|
|
3234
|
+
* alive, or exited with its final output and exit code still readable — and
|
|
3235
|
+
* `exec` is not run. `session.created` tells you which happened. Starting
|
|
3236
|
+
* over is `close()` then `open()` again, which is deliberate: it destroys the
|
|
3237
|
+
* dead session's output, and that output is usually why you came looking.
|
|
3238
|
+
*
|
|
3239
|
+
* Lowercase letters, digits and hyphens, and not all digits (session ids are
|
|
3240
|
+
* bare integers, so an all-digit name could not be told apart from one).
|
|
3241
|
+
*/
|
|
3242
|
+
slug?: string;
|
|
3243
|
+
/**
|
|
3244
|
+
* Respawn the shell in place when it exits, keeping this session's id, name
|
|
3245
|
+
* and scrollback — for a terminal that should outlive whatever runs in it.
|
|
3246
|
+
* A command that dies at startup is not respawned forever: after a few exits
|
|
3247
|
+
* in quick succession the session is left dead so the error stays readable.
|
|
3248
|
+
*
|
|
3249
|
+
* Leave this off if you are waiting on a command's exit code. A session that
|
|
3250
|
+
* comes back to life reports `running` again, and its `exitCode` becomes the
|
|
3251
|
+
* next shell's.
|
|
3252
|
+
*/
|
|
3253
|
+
replaceOnExit?: boolean;
|
|
3254
|
+
}
|
|
3255
|
+
/** A session's id, or the slug it was opened with. */
|
|
3256
|
+
type PtySessionSelector = number | string;
|
|
3257
|
+
interface PtyAttachOptions {
|
|
3258
|
+
/** The session to reattach to: its id, or the slug it was opened with. */
|
|
3259
|
+
session: PtySessionSelector;
|
|
3260
|
+
/** Guest Linux user that must own the session. */
|
|
3261
|
+
linuxUser?: string;
|
|
3262
|
+
}
|
|
3263
|
+
interface PtySessionEvents {
|
|
3264
|
+
onData?: (data: Uint8Array) => void;
|
|
3265
|
+
onExit?: (exitCode: number) => void;
|
|
3266
|
+
onClose?: (info: {
|
|
3267
|
+
wasClean: boolean;
|
|
3268
|
+
code: number;
|
|
3269
|
+
reason: string;
|
|
3270
|
+
}) => void;
|
|
3271
|
+
onError?: (err: unknown) => void;
|
|
3272
|
+
}
|
|
3273
|
+
/** Minimal surface both `ws` (Node) and the browser's native `WebSocket` provide. */
|
|
3274
|
+
interface Socket {
|
|
3275
|
+
readonly readyState: number;
|
|
3276
|
+
send(data: string | Uint8Array): void;
|
|
3277
|
+
close(code?: number, reason?: string): void;
|
|
3278
|
+
on?(event: "open" | "message" | "close" | "error", listener: (...args: unknown[]) => void): void;
|
|
3279
|
+
addEventListener?(event: string, listener: (event: unknown) => void): void;
|
|
3280
|
+
}
|
|
3281
|
+
/**
|
|
3282
|
+
* One interactive terminal session. Sessions are owned by the guest agent and
|
|
3283
|
+
* outlive the connection that opened them: closing this only detaches, so the
|
|
3284
|
+
* shell keeps running and a later `attach()` can pick it back up.
|
|
3285
|
+
*/
|
|
3286
|
+
/** Internal-only: lets {@link VmPty.open} learn the session id from the very
|
|
3287
|
+
* same listener that goes on to handle data/exit frames, so there's never a
|
|
3288
|
+
* gap between a throwaway listener and the real one where a fast command's
|
|
3289
|
+
* output (or even its exit) could arrive and be dropped. */
|
|
3290
|
+
interface InternalPtySessionEvents extends PtySessionEvents {
|
|
3291
|
+
onSessionInfo?: (sessionId: number) => void;
|
|
3292
|
+
}
|
|
3293
|
+
declare class PtySession {
|
|
3294
|
+
private _sessionId;
|
|
3295
|
+
private _slug;
|
|
3296
|
+
private _created;
|
|
3297
|
+
private readonly socket;
|
|
3298
|
+
constructor(socket: Socket, sessionId: number, events?: InternalPtySessionEvents);
|
|
3299
|
+
/** Send raw bytes (or UTF-8 text) to the guest's stdin. */
|
|
3300
|
+
write(data: Uint8Array | string): void;
|
|
3301
|
+
resize(options: {
|
|
3302
|
+
cols: number;
|
|
3303
|
+
rows: number;
|
|
3304
|
+
}): void;
|
|
3305
|
+
signal(sig: "sigint" | "sigkill"): void;
|
|
3306
|
+
private sendControl;
|
|
3307
|
+
/** Close the connection. The shell keeps running; a later `attach()` can reconnect. */
|
|
3308
|
+
detach(): void;
|
|
3309
|
+
get sessionId(): number;
|
|
3310
|
+
/**
|
|
3311
|
+
* The name this session answers to, from the server's `sessionInfo` frame.
|
|
3312
|
+
* `undefined` for a session opened without a slug — reattach to that one by
|
|
3313
|
+
* `sessionId` instead.
|
|
3314
|
+
*/
|
|
3315
|
+
get slug(): string | undefined;
|
|
3316
|
+
/**
|
|
3317
|
+
* Whether `open()` started this shell, or handed back one that was already
|
|
3318
|
+
* running under the same `slug`. `false` means your `exec` never ran — check
|
|
3319
|
+
* it before waiting on output from a command you thought you started.
|
|
3320
|
+
*/
|
|
3321
|
+
get created(): boolean;
|
|
3322
|
+
get readyState(): number;
|
|
3323
|
+
}
|
|
3324
|
+
/** The `vm.pty` surface: open/attach terminal WebSockets, and list/close sessions over REST. */
|
|
3325
|
+
declare class VmPty {
|
|
3326
|
+
private readonly client;
|
|
3327
|
+
private readonly basePath;
|
|
3328
|
+
constructor(client: FreestyleClient, basePath: string);
|
|
3329
|
+
/**
|
|
3330
|
+
* Open a new terminal on the VM. The returned session's data/exit
|
|
3331
|
+
* listeners are live from the moment the socket connects — installed
|
|
3332
|
+
* before we even know the session id — so a command that produces output
|
|
3333
|
+
* (or exits) immediately can never race past them.
|
|
3334
|
+
*/
|
|
3335
|
+
open(options?: PtyOpenOptions & PtySessionEvents): Promise<PtySession>;
|
|
3336
|
+
/** Reattach to an existing session; retained scrollback replays first. */
|
|
3337
|
+
attach(options: PtyAttachOptions & PtySessionEvents): Promise<PtySession>;
|
|
3338
|
+
/** List the VM's terminal sessions, running and recently exited. */
|
|
3339
|
+
list(options?: {
|
|
3340
|
+
linuxUser?: string;
|
|
3341
|
+
}): Promise<ListPtySessionsResult>;
|
|
3342
|
+
/** Kill a terminal session and remove it. */
|
|
3343
|
+
close(session: PtySessionSelector, options?: {
|
|
3344
|
+
linuxUser?: string;
|
|
3345
|
+
}): Promise<ClosePtySessionResult>;
|
|
3346
|
+
}
|
|
3347
|
+
/** PTY operations scoped to one existing Linux user by `vm.linuxUser(...)`. */
|
|
3348
|
+
declare class VmLinuxUserPty {
|
|
3349
|
+
private readonly pty;
|
|
3350
|
+
private readonly linuxUser;
|
|
3351
|
+
constructor(pty: VmPty, linuxUser: string);
|
|
3352
|
+
open(options?: Omit<PtyOpenOptions, "linuxUser"> & PtySessionEvents): Promise<PtySession>;
|
|
3353
|
+
attach(options: Omit<PtyAttachOptions, "linuxUser"> & PtySessionEvents): Promise<PtySession>;
|
|
3354
|
+
list(): Promise<ListPtySessionsResult>;
|
|
3355
|
+
close(session: PtySessionSelector): Promise<ClosePtySessionResult>;
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
/** A snapshot as its owner sees it. */
|
|
3359
|
+
interface SnapshotData {
|
|
3360
|
+
id: string;
|
|
3361
|
+
sourceVmId?: string | null;
|
|
3362
|
+
/** Your handle for this snapshot; boot from it anywhere an id is taken. */
|
|
3363
|
+
slug?: string | null;
|
|
3364
|
+
/** The snapshot's label, if it has one. Shown in place of the slug. */
|
|
3365
|
+
displayName?: string | null;
|
|
3366
|
+
/** Owning account; public snapshots from other accounts are read-only. */
|
|
3367
|
+
accountId?: string | null;
|
|
3368
|
+
/** Public snapshots are bootable by any account. */
|
|
3369
|
+
public: boolean;
|
|
3370
|
+
/**
|
|
3371
|
+
* Delete the snapshot once this long has passed without a VM being created
|
|
3372
|
+
* from it. Absent means never; some plans cap it and the cap shows up here.
|
|
3373
|
+
*/
|
|
3374
|
+
autoDeleteSeconds?: number | null;
|
|
3375
|
+
/**
|
|
3376
|
+
* Whether {@link SnapshotData.autoDeleteSeconds} is your plan's doing rather
|
|
3377
|
+
* than yours; one your plan set goes away by itself if you move to a plan
|
|
3378
|
+
* that does not reclaim unused snapshots.
|
|
3379
|
+
*/
|
|
3380
|
+
autoDeleteFromPlan?: boolean;
|
|
3381
|
+
/**
|
|
3382
|
+
* Delete the snapshot this long after it was taken, however recently
|
|
3383
|
+
* anything booted it.
|
|
3384
|
+
*/
|
|
3385
|
+
ttlSeconds?: number | null;
|
|
3386
|
+
/**
|
|
3387
|
+
* When a VM was last created from this snapshot. Absent if none ever has
|
|
3388
|
+
* been — which is not the same as untouched, since renaming a snapshot is
|
|
3389
|
+
* not using it.
|
|
3390
|
+
*/
|
|
3391
|
+
lastUsedAt?: string | null;
|
|
3392
|
+
createdAt: string;
|
|
3393
|
+
updatedAt: string;
|
|
3394
|
+
}
|
|
3395
|
+
interface CreateSnapshotOptions {
|
|
3396
|
+
/**
|
|
3397
|
+
* A URL-safe handle you can boot from directly, in place of the id.
|
|
3398
|
+
*/
|
|
3399
|
+
slug?: string;
|
|
3400
|
+
/**
|
|
3401
|
+
* A label for a slug that does not read as a name — a build id, a commit
|
|
3402
|
+
* sha. Shown in place of the slug.
|
|
3403
|
+
*/
|
|
3404
|
+
displayName?: string;
|
|
3405
|
+
/**
|
|
3406
|
+
* Delete the snapshot once this many seconds pass without a VM being created
|
|
3407
|
+
* from it. Every create from it resets the clock. -1 (or omitting this)
|
|
3408
|
+
* means never; some plans cap this, and on those omitting it (or sending
|
|
3409
|
+
* -1) gets you the cap.
|
|
3410
|
+
*/
|
|
3411
|
+
autoDeleteSeconds?: number;
|
|
3412
|
+
/**
|
|
3413
|
+
* Delete the snapshot this many seconds after it is taken, whatever has
|
|
3414
|
+
* booted it since.
|
|
3415
|
+
*/
|
|
3416
|
+
ttlSeconds?: number;
|
|
3417
|
+
}
|
|
3418
|
+
interface UpdateSnapshotOptions {
|
|
3419
|
+
/** An empty string clears it. */
|
|
3420
|
+
slug?: string;
|
|
3421
|
+
/** An empty string clears it. See {@link CreateSnapshotOptions.displayName}. */
|
|
3422
|
+
displayName?: string;
|
|
3423
|
+
/**
|
|
3424
|
+
* Seconds of nothing being created from it before the snapshot is deleted;
|
|
3425
|
+
* -1 removes the window. On a plan that caps it, -1 puts you back on the
|
|
3426
|
+
* cap.
|
|
3427
|
+
*/
|
|
3428
|
+
autoDeleteSeconds?: number;
|
|
3429
|
+
/**
|
|
3430
|
+
* Seconds from when the snapshot was taken — not from now — before it is
|
|
3431
|
+
* deleted; -1 removes the deadline.
|
|
3432
|
+
*/
|
|
3433
|
+
ttlSeconds?: number;
|
|
3434
|
+
}
|
|
3435
|
+
interface SnapshotCreated {
|
|
3436
|
+
snapshotId: string;
|
|
3437
|
+
sourceVmId: string | null;
|
|
3438
|
+
snapshot: SnapshotData;
|
|
3439
|
+
}
|
|
3440
|
+
interface ListSnapshotsOptions {
|
|
3441
|
+
/** Only snapshots taken from this VM. */
|
|
3442
|
+
sourceVmId?: string;
|
|
3443
|
+
limit?: number;
|
|
3444
|
+
offset?: number;
|
|
3445
|
+
}
|
|
3446
|
+
interface ListSnapshotsResult {
|
|
3447
|
+
snapshots: SnapshotData[];
|
|
3448
|
+
totalCount: number;
|
|
3449
|
+
}
|
|
3450
|
+
/** The `freestyle.vms.snapshots` namespace: `GET /v5/snapshots`, `/v5/snapshots/{id}`. */
|
|
3451
|
+
declare class VmSnapshotsNamespace {
|
|
3452
|
+
private readonly client;
|
|
3453
|
+
constructor(client: FreestyleClient);
|
|
3454
|
+
list(options?: ListSnapshotsOptions): Promise<ListSnapshotsResult>;
|
|
3455
|
+
get(snapshotId: string): Promise<SnapshotData>;
|
|
3456
|
+
update(snapshotId: string, options: UpdateSnapshotOptions): Promise<SnapshotData>;
|
|
3457
|
+
delete(snapshotId: string): Promise<void>;
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
/**
|
|
3461
|
+
* A handle to one VM, keyed by its id or slug. Every method hits the API
|
|
3462
|
+
* fresh (nothing is cached); `data()` re-fetches and returns the current
|
|
3463
|
+
* record.
|
|
3464
|
+
*/
|
|
3465
|
+
declare class Vm {
|
|
3466
|
+
readonly id: string;
|
|
3467
|
+
readonly fs: VmFilesystem;
|
|
3468
|
+
readonly pty: VmPty;
|
|
3469
|
+
private readonly client;
|
|
3470
|
+
private readonly basePath;
|
|
3471
|
+
constructor(client: FreestyleClient, id: string);
|
|
3472
|
+
/** Fetch the current VM record. */
|
|
3473
|
+
data(): Promise<VmData>;
|
|
3474
|
+
/**
|
|
3475
|
+
* The firewall rules that apply to this VM: those naming it, plus those
|
|
3476
|
+
* naming a private network it is attached to.
|
|
3477
|
+
*
|
|
3478
|
+
* Deleting the VM deletes the rules that name it, so this never reports a
|
|
3479
|
+
* rule over a machine that no longer exists.
|
|
3480
|
+
*/
|
|
3481
|
+
firewallRules(): Promise<ListFirewallRulesResult>;
|
|
3482
|
+
/** Rename the VM, change its slug or idle timeout, or merge in metadata. */
|
|
3483
|
+
update(options: UpdateVmOptions): Promise<VmData>;
|
|
3484
|
+
/** Boot a stopped VM, or resume a paused one. */
|
|
3485
|
+
start(): Promise<VmData>;
|
|
3486
|
+
/** Freeze a running VM, keeping its memory so a later start resumes it exactly. */
|
|
3487
|
+
pause(): Promise<VmData>;
|
|
3488
|
+
/**
|
|
3489
|
+
* Change vCPU, memory, or disk. Every axis is grow-only. vCPU and memory
|
|
3490
|
+
* apply live to a running VM, on resume for a paused one, and at the next
|
|
3491
|
+
* boot for a stopped one. Growing the disk needs a running VM.
|
|
3492
|
+
*/
|
|
3493
|
+
resize(options: ResizeVmOptions): Promise<VmData>;
|
|
3494
|
+
/** Permanently destroy the VM */
|
|
3495
|
+
delete(): Promise<void>;
|
|
3496
|
+
/**
|
|
3497
|
+
* Run a command in the guest and wait for it to finish. A non-zero exit
|
|
3498
|
+
* status is still a successful call — check `statusCode`, which is null if
|
|
3499
|
+
* the command was killed by its timeout.
|
|
3500
|
+
*/
|
|
3501
|
+
exec(options: string | ExecOptions): Promise<ExecResult>;
|
|
3502
|
+
/** Scope process and terminal operations to one existing in-guest Linux user. */
|
|
3503
|
+
linuxUser(linuxUser: string): VmLinuxUser;
|
|
3504
|
+
/**
|
|
3505
|
+
* Capture the VM's exact state, memory and disk. The VM must be running or
|
|
3506
|
+
* paused. The new snapshot is private and fully materialized when this
|
|
3507
|
+
* resolves.
|
|
3508
|
+
*/
|
|
3509
|
+
snapshot(options?: CreateSnapshotOptions): Promise<SnapshotCreated>;
|
|
3510
|
+
}
|
|
3511
|
+
/** A VM handle whose process-spawning operations run as one Linux user. */
|
|
3512
|
+
declare class VmLinuxUser {
|
|
3513
|
+
private readonly vm;
|
|
3514
|
+
private readonly linuxUser;
|
|
3515
|
+
readonly pty: VmLinuxUserPty;
|
|
3516
|
+
constructor(vm: Vm, linuxUser: string);
|
|
3517
|
+
exec(options: string | Omit<ExecOptions, "linuxUser">): Promise<ExecResult>;
|
|
3518
|
+
}
|
|
3519
|
+
/** The `freestyle.vms` namespace: create, list, and manage VMs. */
|
|
3520
|
+
declare class VmsNamespace {
|
|
3521
|
+
private readonly client;
|
|
3522
|
+
readonly snapshots: VmSnapshotsNamespace;
|
|
3523
|
+
constructor(client: FreestyleClient);
|
|
3524
|
+
/**
|
|
3525
|
+
* Boot a new VM.
|
|
3526
|
+
*
|
|
3527
|
+
* `firewall` is required: a VM gets nothing implicitly, so state what it may
|
|
3528
|
+
* reach. `{ rules: [] }` is a legitimate answer — a VM reachable only through
|
|
3529
|
+
* a mapped domain or SSH.
|
|
3530
|
+
*
|
|
3531
|
+
* @example
|
|
3532
|
+
* const { vm, vmId } = await freestyle.vms.create({
|
|
3533
|
+
* firewall: {
|
|
3534
|
+
* rules: [
|
|
3535
|
+
* // This VM can reach the Internet. Without it, it cannot.
|
|
3536
|
+
* { action: "allow", source: {}, destination: { public: true } },
|
|
3537
|
+
* ],
|
|
3538
|
+
* },
|
|
3539
|
+
* });
|
|
3540
|
+
* await vm.exec("echo hello");
|
|
3541
|
+
*/
|
|
3542
|
+
create(options: CreateVmOptions): Promise<{
|
|
3543
|
+
vm: Vm;
|
|
3544
|
+
vmId: string;
|
|
3545
|
+
data: VmData;
|
|
3546
|
+
firewallRules: FirewallRuleData[];
|
|
3547
|
+
/** The rules the create's `tls` block produced; empty without one. */
|
|
3548
|
+
tlsRules: TlsRuleData[];
|
|
3549
|
+
}>;
|
|
3550
|
+
/**
|
|
3551
|
+
* List your VMs.
|
|
3552
|
+
*
|
|
3553
|
+
* @example
|
|
3554
|
+
* const { vms } = await freestyle.vms.list();
|
|
3555
|
+
*/
|
|
3556
|
+
list(options?: ListVmsOptions): Promise<ListVmsResult>;
|
|
3557
|
+
/**
|
|
3558
|
+
* Fetch a VM by its id or its slug.
|
|
3559
|
+
*
|
|
3560
|
+
* @example
|
|
3561
|
+
* const vm = await freestyle.vms.get("vmId");
|
|
3562
|
+
*/
|
|
3563
|
+
get(vmIdOrSlug: string): Promise<VmData>;
|
|
3564
|
+
/** A handle to a VM, without a network call. */
|
|
3565
|
+
ref(vmIdOrSlug: string): Vm;
|
|
3566
|
+
delete(vmIdOrSlug: string): Promise<void>;
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
/** A private network. */
|
|
3570
|
+
interface VpcData {
|
|
3571
|
+
id: string;
|
|
3572
|
+
/** The network's IPv4 CIDR block, when it has one. */
|
|
3573
|
+
cidr?: string | null;
|
|
3574
|
+
/** The network's IPv6 CIDR block — always present. */
|
|
3575
|
+
cidrV6: string;
|
|
3576
|
+
/** Your handle for this network; usable anywhere its id is. */
|
|
3577
|
+
slug?: string | null;
|
|
3578
|
+
/** The network's label, if it has one. Shown in place of the slug. */
|
|
3579
|
+
displayName?: string | null;
|
|
3580
|
+
createdAt: string;
|
|
3581
|
+
}
|
|
3582
|
+
interface CreateVpcOptions {
|
|
3583
|
+
/**
|
|
3584
|
+
* Firewall rules created with the network and deleted with it.
|
|
3585
|
+
*
|
|
3586
|
+
* A bare endpoint means the network being created, so `{ source: {},
|
|
3587
|
+
* destination: {} }` is the most common thing anyone wants to say about a
|
|
3588
|
+
* network: its members can reach each other.
|
|
3589
|
+
*
|
|
3590
|
+
* @example
|
|
3591
|
+
* firewall: {
|
|
3592
|
+
* rules: [
|
|
3593
|
+
* // Members reach each other.
|
|
3594
|
+
* { action: "allow", source: {}, destination: {} },
|
|
3595
|
+
* // Members reach the Internet.
|
|
3596
|
+
* { action: "allow", source: {}, destination: { public: true } },
|
|
3597
|
+
* ],
|
|
3598
|
+
* }
|
|
3599
|
+
*/
|
|
3600
|
+
firewall?: FirewallSpec;
|
|
3601
|
+
/**
|
|
3602
|
+
* The network's IPv4 CIDR. Networks are dual-stack; a `/24` out of
|
|
3603
|
+
* `10.0.0.0/8` is derived for you when omitted. Name one to pick the range
|
|
3604
|
+
* yourself — worth doing when the network must not overlap an estate you
|
|
3605
|
+
* reach over a tunnel, or needs more than 254 IPv4 members.
|
|
3606
|
+
*/
|
|
3607
|
+
cidr?: string;
|
|
3608
|
+
/** The network's IPv6 CIDR; a unique-local /64 is derived when omitted. */
|
|
3609
|
+
cidrV6?: string;
|
|
3610
|
+
/** A URL-safe handle, unique within your account, usable anywhere its id is. */
|
|
3611
|
+
slug?: string;
|
|
3612
|
+
/**
|
|
3613
|
+
* A label for a slug that does not read as a name — one network per tenant
|
|
3614
|
+
* or per environment id. Shown in place of the slug.
|
|
3615
|
+
*/
|
|
3616
|
+
displayName?: string;
|
|
3617
|
+
}
|
|
3618
|
+
interface UpdateVpcOptions {
|
|
3619
|
+
/** An empty string clears it. */
|
|
3620
|
+
slug?: string;
|
|
3621
|
+
/** An empty string clears it. See {@link CreateVpcOptions.displayName}. */
|
|
3622
|
+
displayName?: string;
|
|
3623
|
+
}
|
|
3624
|
+
interface ListVpcsResult {
|
|
3625
|
+
vpcs: VpcData[];
|
|
3626
|
+
totalCount: number;
|
|
3627
|
+
}
|
|
3628
|
+
/** Tunnels attached to one private network: `vpc.tunnels`. */
|
|
3629
|
+
declare class VpcTunnelNamespace {
|
|
3630
|
+
private readonly client;
|
|
3631
|
+
private readonly basePath;
|
|
3632
|
+
constructor(client: FreestyleClient, basePath: string);
|
|
3633
|
+
/**
|
|
3634
|
+
* List the tunnels attached to this network, newest first. Listings never
|
|
3635
|
+
* include a private key: one is returned only by
|
|
3636
|
+
* `freestyle.tunnels.create` and `freestyle.tunnels.rotateKey`.
|
|
3637
|
+
*/
|
|
3638
|
+
list(): Promise<ListTunnelsResult>;
|
|
3639
|
+
}
|
|
3640
|
+
/** A handle to one private network. */
|
|
3641
|
+
declare class Vpc {
|
|
3642
|
+
readonly id: string;
|
|
3643
|
+
readonly tunnels: VpcTunnelNamespace;
|
|
3644
|
+
private readonly client;
|
|
3645
|
+
private readonly basePath;
|
|
3646
|
+
constructor(client: FreestyleClient, id: string);
|
|
3647
|
+
data(): Promise<VpcData>;
|
|
3648
|
+
update(options: UpdateVpcOptions): Promise<VpcData>;
|
|
3649
|
+
delete(): Promise<void>;
|
|
3650
|
+
}
|
|
3651
|
+
/** The `freestyle.vpc` namespace: create and manage private networks. */
|
|
3652
|
+
declare class VpcNamespace {
|
|
3653
|
+
private readonly client;
|
|
3654
|
+
constructor(client: FreestyleClient);
|
|
3655
|
+
/** List your private networks. */
|
|
3656
|
+
list(): Promise<ListVpcsResult>;
|
|
3657
|
+
/** Create a private network, optionally with the rules that say what it is for. */
|
|
3658
|
+
create(options?: CreateVpcOptions): Promise<{
|
|
3659
|
+
vpc: Vpc;
|
|
3660
|
+
vpcId: string;
|
|
3661
|
+
data: VpcData;
|
|
3662
|
+
firewallRules: FirewallRuleData[];
|
|
3663
|
+
}>;
|
|
3664
|
+
/** Fetch a private network by its id or its slug. */
|
|
3665
|
+
get(vpcIdOrSlug: string): Promise<VpcData>;
|
|
3666
|
+
/** A handle to a private network, without a network call. */
|
|
3667
|
+
ref(vpcIdOrSlug: string): Vpc;
|
|
3668
|
+
delete(vpcIdOrSlug: string): Promise<void>;
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3671
|
+
/**
|
|
3672
|
+
* The Freestyle API client. See https://freestyle.sh/docs and
|
|
3673
|
+
* https://api.freestyle.sh/openapi.json for the underlying HTTP contract.
|
|
3674
|
+
*
|
|
3675
|
+
* @example
|
|
3676
|
+
* const freestyle = new Freestyle({ apiKey: process.env.FREESTYLE_API_KEY });
|
|
3677
|
+
* const { vm } = await freestyle.vms.create();
|
|
3678
|
+
* const { stdout } = await vm.exec("echo hello");
|
|
3679
|
+
*/
|
|
3680
|
+
declare class Freestyle {
|
|
3681
|
+
private readonly client;
|
|
3682
|
+
readonly vms: VmsNamespace;
|
|
3683
|
+
readonly vpc: VpcNamespace;
|
|
3684
|
+
readonly tunnels: TunnelsNamespace;
|
|
3685
|
+
/** Allowed network communication, stated as `source -> destination` intent. */
|
|
3686
|
+
readonly firewall: FirewallNamespace;
|
|
3687
|
+
/** Named TLS sessions, stated as `domain, source -> destination` intent. */
|
|
3688
|
+
readonly tls: TlsNamespace;
|
|
3689
|
+
readonly domains: DomainsNamespace;
|
|
3690
|
+
readonly identities: IdentitiesNamespace;
|
|
3691
|
+
constructor(options?: FreestyleOptions);
|
|
3692
|
+
/** Low-level escape hatch: an authenticated `fetch` against the API. Pass the whole path, `/v5` included. */
|
|
3693
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
3694
|
+
}
|
|
3695
|
+
|
|
1313
3696
|
interface DaemonAddress {
|
|
1314
3697
|
url: string;
|
|
1315
3698
|
token: string;
|
|
@@ -1322,24 +3705,37 @@ interface DaemonAddress {
|
|
|
1322
3705
|
*
|
|
1323
3706
|
* The VM is the whole state: its slug is the handle and its metadata holds the
|
|
1324
3707
|
* daemon bearer, so any Studio replica can find a sandbox without a database.
|
|
1325
|
-
* An idle VM is paused,
|
|
3708
|
+
* An idle VM is paused, and traffic to its domain resumes it; one paused for
|
|
3709
|
+
* `autoDeleteSeconds` is deleted.
|
|
1326
3710
|
*/
|
|
1327
3711
|
|
|
1328
3712
|
interface FreestyleSandboxProviderOptions {
|
|
1329
3713
|
apiKey: string;
|
|
1330
3714
|
/** The sandbox image. Default: the release built from this package's version. */
|
|
1331
3715
|
image?: string;
|
|
3716
|
+
/** The org-fs sidecar image. Default: the chart's `orgFs.image`. */
|
|
3717
|
+
orgFsSidecarImage?: string;
|
|
1332
3718
|
/** Pause a VM after this long without network activity. Default 15 min. */
|
|
1333
3719
|
idleTimeoutSeconds?: number;
|
|
3720
|
+
/**
|
|
3721
|
+
* Delete a VM after this long without running. Default 3 days. A plan
|
|
3722
|
+
* that caps it lower gets its cap.
|
|
3723
|
+
*/
|
|
3724
|
+
autoDeleteSeconds?: number;
|
|
1334
3725
|
/** Where daemon ports are published. Default `style.dev`, free on every account. */
|
|
1335
3726
|
domainSuffix?: string;
|
|
1336
3727
|
/** Records daemon request latency; see `daemonProxyTimer`. */
|
|
1337
3728
|
meter?: Meter;
|
|
3729
|
+
/** Replaces the API client, for tests. */
|
|
3730
|
+
client?: Freestyle;
|
|
1338
3731
|
}
|
|
1339
3732
|
declare class FreestyleSandboxProvider implements SandboxProvider {
|
|
1340
3733
|
private readonly freestyle;
|
|
3734
|
+
private readonly lookupClient;
|
|
1341
3735
|
private readonly image;
|
|
3736
|
+
private readonly sidecarImage;
|
|
1342
3737
|
private readonly idleTimeoutSeconds;
|
|
3738
|
+
private readonly autoDeleteSeconds;
|
|
1343
3739
|
private readonly domainSuffix;
|
|
1344
3740
|
private readonly timeDaemonRequest;
|
|
1345
3741
|
private readonly inflight;
|
|
@@ -1353,8 +3749,8 @@ declare class FreestyleSandboxProvider implements SandboxProvider {
|
|
|
1353
3749
|
private find;
|
|
1354
3750
|
private remember;
|
|
1355
3751
|
/**
|
|
1356
|
-
* A snapshot of a VM with
|
|
1357
|
-
* seconds instead of pulling ~2GB. One per image; built on first use.
|
|
3752
|
+
* A snapshot of a VM with both images already pulled, so a sandbox boots in
|
|
3753
|
+
* seconds instead of pulling ~2GB. One per image pair; built on first use.
|
|
1358
3754
|
*/
|
|
1359
3755
|
private baseSnapshot;
|
|
1360
3756
|
private snapshotExists;
|
|
@@ -1362,6 +3758,8 @@ declare class FreestyleSandboxProvider implements SandboxProvider {
|
|
|
1362
3758
|
ensure(id: SandboxId, opts?: EnsureOptions): Promise<Sandbox>;
|
|
1363
3759
|
private resume;
|
|
1364
3760
|
private create;
|
|
3761
|
+
/** A plan that caps autoDelete lower answers 400; omitting it gets the cap. */
|
|
3762
|
+
private createVm;
|
|
1365
3763
|
private configPayload;
|
|
1366
3764
|
private waitForDaemon;
|
|
1367
3765
|
delete(handle: string): Promise<void>;
|
|
@@ -1398,6 +3796,7 @@ declare class FreestyleSandboxProvider implements SandboxProvider {
|
|
|
1398
3796
|
* where `EnsureOptions.provider` asks, when that provider is configured and
|
|
1399
3797
|
* can run it; else `freestyleShare` of new sandboxes go to Freestyle, and the
|
|
1400
3798
|
* rest to Kubernetes while it has room. An existing one stays where it is.
|
|
3799
|
+
* When the chosen provider's ensure fails, the other one gets a try.
|
|
1401
3800
|
* Handle-only calls go to the provider that owns the handle, because a handle
|
|
1402
3801
|
* carries no provider tag.
|
|
1403
3802
|
*/
|
|
@@ -1420,9 +3819,22 @@ interface SandboxProviderRouterOptions {
|
|
|
1420
3819
|
*/
|
|
1421
3820
|
freestyleShare?: number;
|
|
1422
3821
|
}
|
|
3822
|
+
interface Placement {
|
|
3823
|
+
kind: SandboxProviderKind;
|
|
3824
|
+
/** Absent when only one provider could take it. */
|
|
3825
|
+
reason?: SandboxPlacementReason;
|
|
3826
|
+
}
|
|
1423
3827
|
declare class SandboxProviderRouter implements SandboxProvider {
|
|
1424
3828
|
readonly providers: SandboxProviders;
|
|
1425
3829
|
private readonly picked;
|
|
3830
|
+
/**
|
|
3831
|
+
* Handles whose sandbox on this provider outlived a fallback because its
|
|
3832
|
+
* delete failed. Retried when `place` next sees the handle.
|
|
3833
|
+
*
|
|
3834
|
+
* ponytail: in-process, lost on restart; Freestyle's autoDeleteSeconds
|
|
3835
|
+
* reclaims a stray paused VM anyway, only later.
|
|
3836
|
+
*/
|
|
3837
|
+
private readonly strays;
|
|
1426
3838
|
private readonly freestyleShare;
|
|
1427
3839
|
constructor(providers: SandboxProviders, opts?: SandboxProviderRouterOptions);
|
|
1428
3840
|
private get;
|
|
@@ -1430,9 +3842,13 @@ declare class SandboxProviderRouter implements SandboxProvider {
|
|
|
1430
3842
|
/** Who serves an existing handle. Kubernetes is the fallback: it has no cheap ownership check. */
|
|
1431
3843
|
providerOf(handle: string): Promise<SandboxProviderKind>;
|
|
1432
3844
|
private owner;
|
|
1433
|
-
/** Where `ensure` puts this sandbox. */
|
|
1434
|
-
place(handle: string, opts: EnsureOptions): Promise<
|
|
3845
|
+
/** Where `ensure` puts this sandbox, and why. */
|
|
3846
|
+
place(handle: string, opts: EnsureOptions): Promise<Placement>;
|
|
3847
|
+
/** The org has pods already cloned and running for this image. */
|
|
3848
|
+
private hasWarmPool;
|
|
1435
3849
|
ensure(id: SandboxId, opts?: EnsureOptions): Promise<Sandbox>;
|
|
3850
|
+
/** Delete the sandbox a fallback left behind on `kind`, in the background. */
|
|
3851
|
+
private retire;
|
|
1436
3852
|
delete(handle: string): Promise<void>;
|
|
1437
3853
|
lastTermination(handle: string): Promise<PodTermination | null>;
|
|
1438
3854
|
alive(handle: string): Promise<boolean>;
|