@stacksjs/ts-cloud 0.7.6 → 0.7.9
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/bin/cli.js +232 -232
- package/dist/{chunk-p6309384.js → chunk-x07dz3sd.js} +1 -1
- package/dist/{chunk-dpkk640m.js → chunk-xzn0ntr0.js} +225 -1
- package/dist/deploy/index.js +2 -2
- package/dist/drivers/index.d.ts +2 -0
- package/dist/drivers/index.js +12 -2
- package/dist/drivers/shared/box-provision.d.ts +102 -0
- package/dist/index.js +2 -2
- package/dist/ui/index.html +3 -3
- package/dist/ui/server/actions.html +3 -3
- package/dist/ui/server/backups.html +3 -3
- package/dist/ui/server/database.html +3 -3
- package/dist/ui/server/deployments.html +3 -3
- package/dist/ui/server/firewall.html +3 -3
- package/dist/ui/server/logs.html +3 -3
- package/dist/ui/server/services.html +3 -3
- package/dist/ui/server/sites.html +3 -3
- package/dist/ui/server/ssh-keys.html +3 -3
- package/dist/ui/server/terminal.html +3 -3
- package/dist/ui/server/workers.html +3 -3
- package/dist/ui/serverless/alarms.html +3 -3
- package/dist/ui/serverless/assets.html +3 -3
- package/dist/ui/serverless/data.html +3 -3
- package/dist/ui/serverless/deployments.html +3 -3
- package/dist/ui/serverless/functions.html +3 -3
- package/dist/ui/serverless/logs.html +3 -3
- package/dist/ui/serverless/queues.html +3 -3
- package/dist/ui/serverless/scheduler.html +3 -3
- package/dist/ui/serverless/secrets.html +3 -3
- package/dist/ui/serverless/traces.html +3 -3
- package/dist/ui/serverless.html +3 -3
- package/package.json +3 -3
|
@@ -3204,6 +3204,230 @@ async function waitForCloudInit(host, options = {}) {
|
|
|
3204
3204
|
}
|
|
3205
3205
|
throw new Error(`cloud-init did not finish on ${host} after ${timeoutMs}ms`);
|
|
3206
3206
|
}
|
|
3207
|
+
// src/drivers/shared/box-provision.ts
|
|
3208
|
+
var UBUNTU_2404_AMI_PARAM = "/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id";
|
|
3209
|
+
function buildBoxUserData(spec) {
|
|
3210
|
+
const key = spec.sshPublicKey.trim();
|
|
3211
|
+
const lines = [
|
|
3212
|
+
"#cloud-config",
|
|
3213
|
+
"disable_root: false",
|
|
3214
|
+
"ssh_authorized_keys:",
|
|
3215
|
+
` - ${key}`
|
|
3216
|
+
];
|
|
3217
|
+
if (spec.bootstrapScript) {
|
|
3218
|
+
const path = "/var/lib/cloud/box-bootstrap.sh";
|
|
3219
|
+
const indented = spec.bootstrapScript.split(`
|
|
3220
|
+
`).map((l) => ` ${l}`).join(`
|
|
3221
|
+
`);
|
|
3222
|
+
lines.push("write_files:", ` - path: ${path}`, ` permissions: '0755'`, " owner: root:root", " content: |", indented);
|
|
3223
|
+
}
|
|
3224
|
+
lines.push("runcmd:", ` - mkdir -p /root/.ssh && grep -qF "${key}" /root/.ssh/authorized_keys 2>/dev/null || echo "${key}" >> /root/.ssh/authorized_keys`);
|
|
3225
|
+
if (spec.bootstrapScript)
|
|
3226
|
+
lines.push(" - [ bash, /var/lib/cloud/box-bootstrap.sh ]");
|
|
3227
|
+
return `${lines.join(`
|
|
3228
|
+
`)}
|
|
3229
|
+
`;
|
|
3230
|
+
}
|
|
3231
|
+
function toHetznerRules(ports = []) {
|
|
3232
|
+
const rules = [
|
|
3233
|
+
{ direction: "in", protocol: "tcp", port: "22", source_ips: ["0.0.0.0/0", "::/0"], description: "SSH" }
|
|
3234
|
+
];
|
|
3235
|
+
for (const p of ports) {
|
|
3236
|
+
rules.push({
|
|
3237
|
+
direction: "in",
|
|
3238
|
+
protocol: p.protocol,
|
|
3239
|
+
...p.protocol === "icmp" ? {} : { port: String(p.port) },
|
|
3240
|
+
source_ips: ["0.0.0.0/0", "::/0"]
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
return rules;
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
class HetznerBoxProvisioner {
|
|
3247
|
+
client;
|
|
3248
|
+
provider = "hetzner";
|
|
3249
|
+
constructor(client) {
|
|
3250
|
+
this.client = client;
|
|
3251
|
+
}
|
|
3252
|
+
async ensureBox(spec) {
|
|
3253
|
+
const key = await ensureSshKey(this.client, { name: `${spec.name}-key`, publicKey: spec.sshPublicKey });
|
|
3254
|
+
const firewall = await ensureFirewall(this.client, { name: `${spec.name}-fw`, rules: toHetznerRules(spec.ports) });
|
|
3255
|
+
const { server, created } = await ensureServer(this.client, {
|
|
3256
|
+
name: spec.name,
|
|
3257
|
+
serverType: spec.size,
|
|
3258
|
+
image: spec.image ?? "ubuntu-24.04",
|
|
3259
|
+
location: spec.location,
|
|
3260
|
+
sshKeys: [key.id],
|
|
3261
|
+
firewalls: [{ firewall: firewall.id }],
|
|
3262
|
+
userData: buildBoxUserData(spec),
|
|
3263
|
+
labels: spec.labels
|
|
3264
|
+
});
|
|
3265
|
+
return { provider: "hetzner", id: String(server.id), name: server.name, publicIp: serverPublicIpv4(server), created };
|
|
3266
|
+
}
|
|
3267
|
+
async destroyBox(name) {
|
|
3268
|
+
const destroyed = [];
|
|
3269
|
+
const server = (await this.client.listServers()).find((s) => s.name === name);
|
|
3270
|
+
if (server) {
|
|
3271
|
+
const action = await this.client.deleteServer(server.id);
|
|
3272
|
+
await this.client.waitForAction(action.id).catch(() => {});
|
|
3273
|
+
destroyed.push(`server ${name} (#${server.id})`);
|
|
3274
|
+
}
|
|
3275
|
+
const firewall = (await this.client.listFirewalls()).find((f) => f.name === `${name}-fw`);
|
|
3276
|
+
if (firewall) {
|
|
3277
|
+
for (let i = 0;i < 10; i++) {
|
|
3278
|
+
try {
|
|
3279
|
+
await this.client.deleteFirewall(firewall.id);
|
|
3280
|
+
destroyed.push(`firewall ${name}-fw (#${firewall.id})`);
|
|
3281
|
+
break;
|
|
3282
|
+
} catch {
|
|
3283
|
+
if (i === 9)
|
|
3284
|
+
break;
|
|
3285
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
return { destroyed };
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
function toIpPermissions(ports = []) {
|
|
3293
|
+
const permissions = [
|
|
3294
|
+
{ IpProtocol: "tcp", FromPort: 22, ToPort: 22, IpRanges: [{ CidrIp: "0.0.0.0/0", Description: "SSH" }] }
|
|
3295
|
+
];
|
|
3296
|
+
for (const p of ports) {
|
|
3297
|
+
permissions.push(p.protocol === "icmp" ? { IpProtocol: "icmp", FromPort: -1, ToPort: -1, IpRanges: [{ CidrIp: "0.0.0.0/0" }] } : { IpProtocol: p.protocol, FromPort: p.port, ToPort: p.port, IpRanges: [{ CidrIp: "0.0.0.0/0" }] });
|
|
3298
|
+
}
|
|
3299
|
+
return permissions;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
class AwsBoxProvisioner {
|
|
3303
|
+
provider = "aws";
|
|
3304
|
+
ec2;
|
|
3305
|
+
ssm;
|
|
3306
|
+
constructor(options) {
|
|
3307
|
+
this.ec2 = options.ec2;
|
|
3308
|
+
this.ssm = options.ssm;
|
|
3309
|
+
}
|
|
3310
|
+
async findInstance(name, states) {
|
|
3311
|
+
const result = await this.ec2.describeInstances({
|
|
3312
|
+
Filters: [
|
|
3313
|
+
{ Name: "tag:Name", Values: [name] },
|
|
3314
|
+
{ Name: "instance-state-name", Values: states }
|
|
3315
|
+
]
|
|
3316
|
+
});
|
|
3317
|
+
return result.Reservations?.flatMap((r) => r.Instances ?? [])[0];
|
|
3318
|
+
}
|
|
3319
|
+
async waitForPublicIp(instanceId, timeoutMs = 300000) {
|
|
3320
|
+
const start = Date.now();
|
|
3321
|
+
while (Date.now() - start < timeoutMs) {
|
|
3322
|
+
const instance = await this.ec2.getInstance(instanceId);
|
|
3323
|
+
if (instance?.State?.Name === "running" && instance.PublicIpAddress)
|
|
3324
|
+
return instance;
|
|
3325
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
3326
|
+
}
|
|
3327
|
+
throw new Error(`EC2 instance ${instanceId} did not reach running with a public IP within ${timeoutMs}ms`);
|
|
3328
|
+
}
|
|
3329
|
+
async ensureSecurityGroup(spec) {
|
|
3330
|
+
const groupName = `${spec.name}-sg`;
|
|
3331
|
+
const existing = await this.ec2.describeSecurityGroups({
|
|
3332
|
+
Filters: [{ Name: "group-name", Values: [groupName] }]
|
|
3333
|
+
});
|
|
3334
|
+
const found = existing.SecurityGroups?.[0]?.GroupId;
|
|
3335
|
+
if (found)
|
|
3336
|
+
return found;
|
|
3337
|
+
const created = await this.ec2.createSecurityGroup({
|
|
3338
|
+
GroupName: groupName,
|
|
3339
|
+
Description: `box ${spec.name} (managed by ts-cloud box-provision)`
|
|
3340
|
+
});
|
|
3341
|
+
if (!created.GroupId)
|
|
3342
|
+
throw new Error(`could not create security group ${groupName}`);
|
|
3343
|
+
await this.ec2.authorizeSecurityGroupIngress({
|
|
3344
|
+
GroupId: created.GroupId,
|
|
3345
|
+
IpPermissions: toIpPermissions(spec.ports)
|
|
3346
|
+
});
|
|
3347
|
+
return created.GroupId;
|
|
3348
|
+
}
|
|
3349
|
+
async resolveImage(spec) {
|
|
3350
|
+
if (spec.image?.startsWith("ami-"))
|
|
3351
|
+
return spec.image;
|
|
3352
|
+
const result = await this.ssm.getParameter({ Name: UBUNTU_2404_AMI_PARAM });
|
|
3353
|
+
const ami = result.Parameter?.Value;
|
|
3354
|
+
if (!ami)
|
|
3355
|
+
throw new Error(`could not resolve the Ubuntu 24.04 AMI via SSM (${UBUNTU_2404_AMI_PARAM})`);
|
|
3356
|
+
return ami;
|
|
3357
|
+
}
|
|
3358
|
+
async ensureBox(spec) {
|
|
3359
|
+
const existing = await this.findInstance(spec.name, ["pending", "running"]);
|
|
3360
|
+
if (existing?.InstanceId) {
|
|
3361
|
+
const instance2 = await this.waitForPublicIp(existing.InstanceId);
|
|
3362
|
+
return { provider: "aws", id: instance2.InstanceId, name: spec.name, publicIp: instance2.PublicIpAddress, created: false };
|
|
3363
|
+
}
|
|
3364
|
+
const [groupId, imageId] = await Promise.all([
|
|
3365
|
+
this.ensureSecurityGroup(spec),
|
|
3366
|
+
this.resolveImage(spec)
|
|
3367
|
+
]);
|
|
3368
|
+
const tags = [
|
|
3369
|
+
{ Key: "Name", Value: spec.name },
|
|
3370
|
+
...Object.entries(spec.labels ?? {}).map(([Key, Value]) => ({ Key, Value }))
|
|
3371
|
+
];
|
|
3372
|
+
const result = await this.ec2.runInstances({
|
|
3373
|
+
ImageId: imageId,
|
|
3374
|
+
InstanceType: spec.size,
|
|
3375
|
+
MinCount: 1,
|
|
3376
|
+
MaxCount: 1,
|
|
3377
|
+
SecurityGroupIds: [groupId],
|
|
3378
|
+
UserData: btoa(buildBoxUserData(spec)),
|
|
3379
|
+
TagSpecifications: [{ ResourceType: "instance", Tags: tags }]
|
|
3380
|
+
});
|
|
3381
|
+
const instanceId = result.Instances?.[0]?.InstanceId;
|
|
3382
|
+
if (!instanceId)
|
|
3383
|
+
throw new Error(`RunInstances returned no instance for box ${spec.name}`);
|
|
3384
|
+
const instance = await this.waitForPublicIp(instanceId);
|
|
3385
|
+
return { provider: "aws", id: instanceId, name: spec.name, publicIp: instance.PublicIpAddress, created: true };
|
|
3386
|
+
}
|
|
3387
|
+
async destroyBox(name) {
|
|
3388
|
+
const destroyed = [];
|
|
3389
|
+
const instance = await this.findInstance(name, ["pending", "running", "stopping", "stopped"]);
|
|
3390
|
+
if (instance?.InstanceId) {
|
|
3391
|
+
await this.ec2.terminateInstances([instance.InstanceId]);
|
|
3392
|
+
destroyed.push(`instance ${name} (${instance.InstanceId})`);
|
|
3393
|
+
const start = Date.now();
|
|
3394
|
+
while (Date.now() - start < 300000) {
|
|
3395
|
+
const current = await this.ec2.getInstance(instance.InstanceId);
|
|
3396
|
+
if (!current || current.State?.Name === "terminated")
|
|
3397
|
+
break;
|
|
3398
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
const groups = await this.ec2.describeSecurityGroups({
|
|
3402
|
+
Filters: [{ Name: "group-name", Values: [`${name}-sg`] }]
|
|
3403
|
+
});
|
|
3404
|
+
const groupId = groups.SecurityGroups?.[0]?.GroupId;
|
|
3405
|
+
if (groupId) {
|
|
3406
|
+
for (let i = 0;i < 10; i++) {
|
|
3407
|
+
try {
|
|
3408
|
+
await this.ec2.deleteSecurityGroup(groupId);
|
|
3409
|
+
destroyed.push(`security group ${name}-sg (${groupId})`);
|
|
3410
|
+
break;
|
|
3411
|
+
} catch {
|
|
3412
|
+
if (i === 9)
|
|
3413
|
+
break;
|
|
3414
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return { destroyed };
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
function createBoxProvisioner(options) {
|
|
3422
|
+
if (options.provider === "hetzner") {
|
|
3423
|
+
if (!options.hetzner)
|
|
3424
|
+
throw new Error('createBoxProvisioner: options.hetzner (a HetznerClient) is required for provider "hetzner"');
|
|
3425
|
+
return new HetznerBoxProvisioner(options.hetzner);
|
|
3426
|
+
}
|
|
3427
|
+
if (!options.aws)
|
|
3428
|
+
throw new Error('createBoxProvisioner: options.aws ({ ec2, ssm }) is required for provider "aws"');
|
|
3429
|
+
return new AwsBoxProvisioner(options.aws);
|
|
3430
|
+
}
|
|
3207
3431
|
// src/drivers/shared/env-file.ts
|
|
3208
3432
|
function formatEnvFile(env) {
|
|
3209
3433
|
return Object.entries(env).map(([k, v]) => `${k}=${quoteEnvValue(String(v))}`).join(`
|
|
@@ -4389,4 +4613,4 @@ function buildCloudFrontOriginConfig(options) {
|
|
|
4389
4613
|
CustomErrorResponses: { Quantity: 0 }
|
|
4390
4614
|
};
|
|
4391
4615
|
}
|
|
4392
|
-
export { isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, PANTRY_PROJECT_DIR, BACKUP_RUNNER_PATH, buildBackupRestoreScript, buildUbuntuBootstrapScript, AwsDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, buildAwsArtifactFetch, buildLocalArtifactFetch, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
|
|
4616
|
+
export { isPhpSite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, PANTRY_PROJECT_DIR, BACKUP_RUNNER_PATH, buildBackupRestoreScript, buildUbuntuBootstrapScript, AwsDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, wrapCloudInitUserData, DEFAULT_RPX_CERTS_DIR, normalizeRoutePath, deriveRouteId, buildRpxConfig, buildRpxLbConfig, renderRpxLauncher, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, buildRpxProvisionScript, HetznerDriver, LocalBoxDriver, isBoxMode, createCloudDriver, CloudDriverFactory, cloudDrivers, ensureSshKey, ensureFirewall, ensureServer, serverPublicIpv4, buildSshArgs, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, UBUNTU_2404_AMI_PARAM, buildBoxUserData, HetznerBoxProvisioner, AwsBoxProvisioner, createBoxProvisioner, releasePaths, buildRollbackScript, resolveExecStart, buildSiteDeployScript, buildStaticSiteDeployScript, buildAwsArtifactFetch, buildLocalArtifactFetch, MANAGEMENT_DASHBOARD_SITE, DASHBOARD_CREDENTIALS_FILE, resolveDashboardAuth, resolveUiSource, ensureManagementDashboard, buildManagementDashboardArtifact, resolveSiteFramework, deploySiteRelease, deployAllComputeSites, reloadRpxGateway, MANAGED_CACHE_POLICY_OPTIMIZED, MANAGED_CACHE_POLICY_DISABLED, MANAGED_ORIGIN_REQUEST_POLICY_ALL_VIEWER, buildCloudFrontOriginConfig };
|
package/dist/deploy/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
sanitizeCloudConfig,
|
|
13
13
|
setMaintenance,
|
|
14
14
|
startLocalDashboardServer
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-x07dz3sd.js";
|
|
16
16
|
import {
|
|
17
17
|
buildAndPushServerlessImage
|
|
18
18
|
} from "../chunk-0wxyppza.js";
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveSiteKind,
|
|
30
30
|
resolveUiSource,
|
|
31
31
|
validateDeploymentConfig
|
|
32
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-xzn0ntr0.js";
|
|
33
33
|
import"../chunk-c6rgvg1j.js";
|
|
34
34
|
import"../chunk-93hjhs78.js";
|
|
35
35
|
import {
|
package/dist/drivers/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { ensureFirewall, ensureServer, ensureSshKey, serverPublicIpv4 } from './
|
|
|
8
8
|
export type { EnsuredResource, EnsuredServer, EnsureFirewallOptions, EnsureServerOptions, EnsureSshKeyOptions, } from './hetzner/provision';
|
|
9
9
|
export { buildSshArgs, scpUpload, sshExec, sshExecOrThrow, waitForCloudInit, waitForSsh } from './shared/remote-exec';
|
|
10
10
|
export type { RemoteExecOptions, RemoteExecResult, WaitOptions } from './shared/remote-exec';
|
|
11
|
+
export { AwsBoxProvisioner, buildBoxUserData, createBoxProvisioner, HetznerBoxProvisioner, UBUNTU_2404_AMI_PARAM, } from './shared/box-provision';
|
|
12
|
+
export type { AwsBoxProvisionerOptions, BoxPort, BoxProviderName, BoxProvisioner, BoxSpec, CreateBoxProvisionerOptions, ProvisionedBox, } from './shared/box-provision';
|
|
11
13
|
export { buildAwsArtifactFetch, buildLocalArtifactFetch, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, } from './shared/deploy-script';
|
|
12
14
|
export { deployAllComputeSites, deploySiteRelease, reloadRpxGateway } from './shared/compute-deploy';
|
|
13
15
|
export { buildRpxConfig, buildRpxLbConfig, buildRpxProvisionScript, deriveRouteId, normalizeRoutePath, renderRpxLauncher, DEFAULT_RPX_CERTS_DIR, RPX_DIR, RPX_LAUNCHER_PATH, RPX_SERVICE_NAME, } from './shared/rpx-gateway';
|
package/dist/drivers/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AwsBoxProvisioner,
|
|
2
3
|
AwsDriver,
|
|
3
4
|
CloudDriverFactory,
|
|
4
5
|
DEFAULT_RPX_CERTS_DIR,
|
|
6
|
+
HetznerBoxProvisioner,
|
|
5
7
|
HetznerClient,
|
|
6
8
|
HetznerDriver,
|
|
7
9
|
LocalBoxDriver,
|
|
@@ -11,7 +13,9 @@ import {
|
|
|
11
13
|
RPX_DIR,
|
|
12
14
|
RPX_LAUNCHER_PATH,
|
|
13
15
|
RPX_SERVICE_NAME,
|
|
16
|
+
UBUNTU_2404_AMI_PARAM,
|
|
14
17
|
buildAwsArtifactFetch,
|
|
18
|
+
buildBoxUserData,
|
|
15
19
|
buildCloudFrontOriginConfig,
|
|
16
20
|
buildLocalArtifactFetch,
|
|
17
21
|
buildRpxConfig,
|
|
@@ -22,6 +26,7 @@ import {
|
|
|
22
26
|
buildStaticSiteDeployScript,
|
|
23
27
|
buildUbuntuBootstrapScript,
|
|
24
28
|
cloudDrivers,
|
|
29
|
+
createBoxProvisioner,
|
|
25
30
|
createCloudDriver,
|
|
26
31
|
deployAllComputeSites,
|
|
27
32
|
deploySiteRelease,
|
|
@@ -43,7 +48,7 @@ import {
|
|
|
43
48
|
waitForCloudInit,
|
|
44
49
|
waitForSsh,
|
|
45
50
|
wrapCloudInitUserData
|
|
46
|
-
} from "../chunk-
|
|
51
|
+
} from "../chunk-xzn0ntr0.js";
|
|
47
52
|
import"../chunk-c6rgvg1j.js";
|
|
48
53
|
import"../chunk-93hjhs78.js";
|
|
49
54
|
import"../chunk-qpj3edwz.js";
|
|
@@ -73,6 +78,7 @@ export {
|
|
|
73
78
|
deploySiteRelease,
|
|
74
79
|
deployAllComputeSites,
|
|
75
80
|
createCloudDriver,
|
|
81
|
+
createBoxProvisioner,
|
|
76
82
|
cloudDrivers,
|
|
77
83
|
buildStaticSiteDeployScript,
|
|
78
84
|
buildSshArgs,
|
|
@@ -82,7 +88,9 @@ export {
|
|
|
82
88
|
buildRpxConfig,
|
|
83
89
|
buildLocalArtifactFetch,
|
|
84
90
|
buildCloudFrontOriginConfig,
|
|
91
|
+
buildBoxUserData,
|
|
85
92
|
buildAwsArtifactFetch,
|
|
93
|
+
UBUNTU_2404_AMI_PARAM,
|
|
86
94
|
RPX_SERVICE_NAME,
|
|
87
95
|
RPX_LAUNCHER_PATH,
|
|
88
96
|
RPX_DIR,
|
|
@@ -92,7 +100,9 @@ export {
|
|
|
92
100
|
LocalBoxDriver,
|
|
93
101
|
HetznerDriver,
|
|
94
102
|
HetznerClient,
|
|
103
|
+
HetznerBoxProvisioner,
|
|
95
104
|
DEFAULT_RPX_CERTS_DIR,
|
|
96
105
|
CloudDriverFactory,
|
|
97
|
-
AwsDriver
|
|
106
|
+
AwsDriver,
|
|
107
|
+
AwsBoxProvisioner
|
|
98
108
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic single-box provisioning.
|
|
3
|
+
*
|
|
4
|
+
* `createBoxProvisioner` gives deploy scripts one interface — ensure a named
|
|
5
|
+
* box exists (running, reachable, firewall open, SSH key authorized, bootstrap
|
|
6
|
+
* script run) and tear it down again — regardless of whether the box lives on
|
|
7
|
+
* Hetzner Cloud or AWS EC2. Idempotent like the underlying `ensure*` helpers:
|
|
8
|
+
* re-running converges instead of duplicating resources.
|
|
9
|
+
*
|
|
10
|
+
* SSH access is normalized to `root@<publicIp>`: the public key is injected
|
|
11
|
+
* via cloud-init (`disable_root: false` plus an authorized_keys append) so the
|
|
12
|
+
* same `sshExec(host, cmd)` calls work on both providers, even though AWS
|
|
13
|
+
* Ubuntu images default to the `ubuntu` user.
|
|
14
|
+
*/
|
|
15
|
+
import type { EC2Client } from '../../aws/ec2';
|
|
16
|
+
import type { SSMClient } from '../../aws/ssm';
|
|
17
|
+
import type { HetznerClient } from '../hetzner/client';
|
|
18
|
+
export type BoxProviderName = 'hetzner' | 'aws';
|
|
19
|
+
export interface BoxPort {
|
|
20
|
+
protocol: 'tcp' | 'udp' | 'icmp';
|
|
21
|
+
/** Port number; omit for icmp. */
|
|
22
|
+
port?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface BoxSpec {
|
|
25
|
+
/** Resource name; also names the firewall/security group (`<name>-fw`/`<name>-sg`). */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Provider-native size (Hetzner server type like `cx23`, EC2 instance type like `t3.micro`). */
|
|
28
|
+
size: string;
|
|
29
|
+
/** Hetzner image name or an `ami-...` id. Defaults to Ubuntu 24.04 on both providers. */
|
|
30
|
+
image?: string;
|
|
31
|
+
/** Hetzner location (e.g. `fsn1`). AWS placement follows the provisioner's region. */
|
|
32
|
+
location?: string;
|
|
33
|
+
/** Ingress to open in addition to tcp/22. */
|
|
34
|
+
ports?: BoxPort[];
|
|
35
|
+
/** OpenSSH public key authorized for root. */
|
|
36
|
+
sshPublicKey: string;
|
|
37
|
+
/** Plain bash bootstrap script, run once at first boot via cloud-init. */
|
|
38
|
+
bootstrapScript?: string;
|
|
39
|
+
labels?: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
export interface ProvisionedBox {
|
|
42
|
+
provider: BoxProviderName;
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
publicIp: string;
|
|
46
|
+
created: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface BoxProvisioner {
|
|
49
|
+
readonly provider: BoxProviderName;
|
|
50
|
+
ensureBox(spec: BoxSpec): Promise<ProvisionedBox>;
|
|
51
|
+
/** Tear down the box and its firewall/security group. */
|
|
52
|
+
destroyBox(name: string): Promise<{
|
|
53
|
+
destroyed: string[];
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
/** SSM parameter for the current Ubuntu 24.04 amd64 gp3 AMI (Canonical-published). */
|
|
57
|
+
export declare const UBUNTU_2404_AMI_PARAM = "/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id";
|
|
58
|
+
/**
|
|
59
|
+
* Cloud-config shared by both providers: root SSH access with the given key
|
|
60
|
+
* (AWS Ubuntu images otherwise lock root and point at the `ubuntu` user) and
|
|
61
|
+
* the optional bootstrap script.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildBoxUserData(spec: Pick<BoxSpec, 'sshPublicKey' | 'bootstrapScript'>): string;
|
|
64
|
+
export declare class HetznerBoxProvisioner implements BoxProvisioner {
|
|
65
|
+
private client;
|
|
66
|
+
readonly provider: "hetzner";
|
|
67
|
+
constructor(client: HetznerClient);
|
|
68
|
+
ensureBox(spec: BoxSpec): Promise<ProvisionedBox>;
|
|
69
|
+
destroyBox(name: string): Promise<{
|
|
70
|
+
destroyed: string[];
|
|
71
|
+
}>;
|
|
72
|
+
}
|
|
73
|
+
/** The EC2 surface the AWS provisioner uses (injectable for tests). */
|
|
74
|
+
export type BoxEc2Client = Pick<EC2Client, 'describeInstances' | 'describeSecurityGroups' | 'createSecurityGroup' | 'authorizeSecurityGroupIngress' | 'runInstances' | 'terminateInstances' | 'deleteSecurityGroup' | 'getInstance'>;
|
|
75
|
+
/** The SSM surface the AWS provisioner uses (injectable for tests). */
|
|
76
|
+
export type BoxSsmClient = Pick<SSMClient, 'getParameter'>;
|
|
77
|
+
export interface AwsBoxProvisionerOptions {
|
|
78
|
+
ec2: BoxEc2Client;
|
|
79
|
+
ssm: BoxSsmClient;
|
|
80
|
+
}
|
|
81
|
+
export declare class AwsBoxProvisioner implements BoxProvisioner {
|
|
82
|
+
readonly provider: "aws";
|
|
83
|
+
private ec2;
|
|
84
|
+
private ssm;
|
|
85
|
+
constructor(options: AwsBoxProvisionerOptions);
|
|
86
|
+
private findInstance;
|
|
87
|
+
private waitForPublicIp;
|
|
88
|
+
private ensureSecurityGroup;
|
|
89
|
+
private resolveImage;
|
|
90
|
+
ensureBox(spec: BoxSpec): Promise<ProvisionedBox>;
|
|
91
|
+
destroyBox(name: string): Promise<{
|
|
92
|
+
destroyed: string[];
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
export interface CreateBoxProvisionerOptions {
|
|
96
|
+
provider: BoxProviderName;
|
|
97
|
+
/** Required for provider 'hetzner'. */
|
|
98
|
+
hetzner?: HetznerClient;
|
|
99
|
+
/** Required for provider 'aws'. */
|
|
100
|
+
aws?: AwsBoxProvisionerOptions;
|
|
101
|
+
}
|
|
102
|
+
export declare function createBoxProvisioner(options: CreateBoxProvisionerOptions): BoxProvisioner;
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
sanitizeCloudConfig,
|
|
56
56
|
setMaintenance,
|
|
57
57
|
startLocalDashboardServer
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-x07dz3sd.js";
|
|
59
59
|
import {
|
|
60
60
|
buildAndPushServerlessImage
|
|
61
61
|
} from "./chunk-0wxyppza.js";
|
|
@@ -105,7 +105,7 @@ import {
|
|
|
105
105
|
waitForCloudInit,
|
|
106
106
|
waitForSsh,
|
|
107
107
|
wrapCloudInitUserData
|
|
108
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-xzn0ntr0.js";
|
|
109
109
|
import {
|
|
110
110
|
ABTestManager,
|
|
111
111
|
AI,
|