@stacksjs/ts-cloud 0.2.22 → 0.2.24
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/aws/s3.d.ts +17 -0
- package/dist/bin/cli.js +583 -567
- package/dist/deploy/site-target.d.ts +3 -3
- package/dist/drivers/hetzner/driver.d.ts +5 -4
- package/dist/drivers/index.d.ts +0 -1
- package/dist/drivers/shared/compute-deploy.d.ts +1 -1
- package/dist/drivers/shared/deploy-script.d.ts +3 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +226 -213
- package/dist/object-storage/index.d.ts +1 -0
- package/dist/object-storage/migrate.d.ts +125 -0
- package/package.json +3 -3
- package/dist/drivers/shared/caddyfile.d.ts +0 -46
|
@@ -2,10 +2,10 @@ import type { CloudConfig, SiteConfig, SiteDeployTarget } from '@ts-cloud/core';
|
|
|
2
2
|
/**
|
|
3
3
|
* The three resolved deployment kinds for a site:
|
|
4
4
|
* - `'bucket'` — upload built `root` to object storage + CDN.
|
|
5
|
-
* - `'server-app'` — `server` + `start`: dynamic app as a systemd service
|
|
6
|
-
* behind the Caddy reverse proxy.
|
|
5
|
+
* - `'server-app'` — `server` + `start`: dynamic app as a systemd service.
|
|
7
6
|
* - `'server-static'` — `server` + no `start` (has static `root`): a static
|
|
8
|
-
* site built and
|
|
7
|
+
* site built and shipped to `/var/www/<site>` on the box
|
|
8
|
+
* (served by the operator's own proxy, e.g. rpx + tlsx).
|
|
9
9
|
*/
|
|
10
10
|
export type SiteDeployKind = 'bucket' | 'server-app' | 'server-static';
|
|
11
11
|
/**
|
|
@@ -67,8 +67,9 @@ export declare class HetznerDriver implements CloudDriver {
|
|
|
67
67
|
*/
|
|
68
68
|
private ensureFirewall;
|
|
69
69
|
/**
|
|
70
|
-
* Collect the upstream app ports that must be reachable
|
|
71
|
-
*
|
|
70
|
+
* Collect the upstream app ports that must be reachable on the box. ts-cloud
|
|
71
|
+
* does not front traffic with its own proxy, so each site's app port is
|
|
72
|
+
* opened directly. Drops 80/443 (always handled by the firewall base rules).
|
|
72
73
|
*/
|
|
73
74
|
private collectUpstreamPorts;
|
|
74
75
|
private sleep;
|
|
@@ -81,8 +82,8 @@ export declare class HetznerDriver implements CloudDriver {
|
|
|
81
82
|
private waitForSshReady;
|
|
82
83
|
/**
|
|
83
84
|
* Block until cloud-init finishes (`cloud-init status --wait`). cloud-init is
|
|
84
|
-
* what installs the runtime
|
|
85
|
-
* release pointing at a half-provisioned box (missing `bun
|
|
85
|
+
* what installs the runtime; deploying before it completes leaves the
|
|
86
|
+
* release pointing at a half-provisioned box (missing `bun`).
|
|
86
87
|
*/
|
|
87
88
|
private waitForCloudInit;
|
|
88
89
|
private outputsFromState;
|
package/dist/drivers/index.d.ts
CHANGED
|
@@ -3,6 +3,5 @@ export { AwsDriver } from './aws/driver';
|
|
|
3
3
|
export { HetznerDriver } from './hetzner/driver';
|
|
4
4
|
export { HetznerClient, resolveHetznerApiToken } from './hetzner/client';
|
|
5
5
|
export { generateUbuntuAppCloudInit, wrapCloudInitUserData } from './hetzner/cloud-init';
|
|
6
|
-
export { buildCaddyfile, buildCaddyfileFromProxy, isOnDemandDomain, proxyConfigFromSites, resolveCaddyfile, staticSiteServerRoot, } from './shared/caddyfile';
|
|
7
6
|
export { buildAwsArtifactFetch, buildLocalArtifactFetch, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, } from './shared/deploy-script';
|
|
8
7
|
export { deployAllComputeSites, deploySiteRelease } from './shared/compute-deploy';
|
|
@@ -22,6 +22,6 @@ export interface DeployAllSitesOptions {
|
|
|
22
22
|
/**
|
|
23
23
|
* Deploy every site that targets the compute server — both dynamic apps
|
|
24
24
|
* (`server` + `start`, run as systemd services) and static sites (`server`
|
|
25
|
-
* without `start`,
|
|
25
|
+
* without `start`, shipped to `/var/www/<site>`). Bucket sites are skipped.
|
|
26
26
|
*/
|
|
27
27
|
export declare function deployAllComputeSites(options: DeployAllSitesOptions): Promise<boolean>;
|
|
@@ -31,7 +31,7 @@ export interface BuildStaticSiteDeployScriptOptions {
|
|
|
31
31
|
siteName: string;
|
|
32
32
|
/** How the remote host obtains the release tarball */
|
|
33
33
|
artifactFetch: string[];
|
|
34
|
-
/** Target directory
|
|
34
|
+
/** Target directory the static site is shipped to. Default `/var/www/<site>`. */
|
|
35
35
|
appDir?: string;
|
|
36
36
|
/**
|
|
37
37
|
* Commands run inside `appDir` after extraction — e.g. build the docs/blog on
|
|
@@ -43,8 +43,8 @@ export interface BuildStaticSiteDeployScriptOptions {
|
|
|
43
43
|
/**
|
|
44
44
|
* Build the remote shell commands that install/refresh a STATIC site on a
|
|
45
45
|
* compute target. Unlike {@link buildSiteDeployScript}, there is no systemd
|
|
46
|
-
* service — the extracted files are
|
|
47
|
-
* (
|
|
46
|
+
* service — the extracted files are shipped to `/var/www/<site>` and served by
|
|
47
|
+
* the operator's own proxy (e.g. rpx + tlsx), which ts-cloud does not manage.
|
|
48
48
|
*/
|
|
49
49
|
export declare function buildStaticSiteDeployScript(options: BuildStaticSiteDeployScriptOptions): string[];
|
|
50
50
|
export declare function buildAwsArtifactFetch(bucket: string, key: string, region: string, siteName: string): string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,12 @@ export { AWSClient, CloudFormationClient, CloudFormationClient as AWSCloudFormat
|
|
|
6
6
|
export type { AWSRequestOptions, AWSClientConfig, AWSError, AWSCredentials as AWSClientCredentials, StackParameter, StackTag, CreateStackOptions, UpdateStackOptions, DescribeStacksOptions, StackEvent, Stack, InvalidationOptions, Distribution, S3SyncOptions, S3CopyOptions, S3ListOptions, S3Object, CertificateDetail, Certificate as ELBv2Certificate, RekognitionS3Object, RekognitionBoundingBox, TextractS3Object, TextractBoundingBox, CountryCode, ContactType, ContactDetail, KendraCreateDataSourceCommandInput, KendraCreateDataSourceCommandOutput, KendraListDataSourcesCommandInput, KendraListDataSourcesCommandOutput, InvokeModelCommandInput, InvokeModelCommandOutput, InvokeModelWithResponseStreamCommandInput, InvokeModelWithResponseStreamCommandOutput, CreateModelCustomizationJobCommandInput, CreateModelCustomizationJobCommandOutput, GetModelCustomizationJobCommandInput, GetModelCustomizationJobCommandOutput, ListFoundationModelsCommandInput, ListFoundationModelsCommandOutput, AttributeValue as DynamoDBAttributeValue, KeySchemaElement, AttributeDefinition as DynamoDBAttributeDefinition, } from './aws';
|
|
7
7
|
export { createObjectStorageClient, providerEndpoint, resolveObjectStorage, } from './object-storage';
|
|
8
8
|
export type { ObjectStorageConfig, ObjectStorageCredentials, ObjectStorageProvider, ResolvedObjectStorage, } from './object-storage';
|
|
9
|
+
export { keyMatchesFilters, migrateObjectStorage, remapKey, } from './object-storage/migrate';
|
|
10
|
+
export type { MigrateEndpoint, MigrateError, MigrateOptions, MigratePlanItem, MigrateProgress, MigrateResult, MigrateVerification, } from './object-storage/migrate';
|
|
9
11
|
export * from './ssl';
|
|
10
12
|
export { deployStaticSite, deployStaticSiteFull, uploadStaticFiles, invalidateCache, deleteStaticSite, generateStaticSiteTemplate, deployStaticSiteWithExternalDns, deployStaticSiteWithExternalDnsFull, generateExternalDnsStaticSiteTemplate, deploySite, resolveSiteDeployTarget, resolveSiteKind, validateDeploymentConfig, } from './deploy';
|
|
11
13
|
export type { StaticSiteConfig, DeployResult, UploadOptions, ExternalDnsStaticSiteConfig, ExternalDnsDeployResult, DeploySiteConfig, DeploySiteResult, StaticSiteDnsProvider, SiteDeployKind, DeploymentValidationResult, } from './deploy';
|
|
12
|
-
export { createCloudDriver, CloudDriverFactory, cloudDrivers, AwsDriver, HetznerDriver, HetznerClient, resolveHetznerApiToken, generateUbuntuAppCloudInit, wrapCloudInitUserData,
|
|
14
|
+
export { createCloudDriver, CloudDriverFactory, cloudDrivers, AwsDriver, HetznerDriver, HetznerClient, resolveHetznerApiToken, generateUbuntuAppCloudInit, wrapCloudInitUserData, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, deployAllComputeSites, deploySiteRelease, } from './drivers';
|
|
13
15
|
export type { CreateCloudDriverOptions } from './drivers/factory';
|
|
14
16
|
export { createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, PorkbunProvider, GoDaddyProvider, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, } from './dns';
|
|
15
17
|
export type { DnsProvider, DnsProviderConfig, DnsRecord, DnsRecordType, DnsRecordResult, CreateRecordResult, DeleteRecordResult, ListRecordsResult, } from './dns';
|
package/dist/index.js
CHANGED
|
@@ -2123,6 +2123,67 @@ class S3Client2 {
|
|
|
2123
2123
|
});
|
|
2124
2124
|
return result;
|
|
2125
2125
|
}
|
|
2126
|
+
async getObjectBytes(bucket, key) {
|
|
2127
|
+
const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
|
|
2128
|
+
const host = this.s3VirtualHost(bucket);
|
|
2129
|
+
const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
|
|
2130
|
+
const canonicalUri = this.forcePathStyle ? `/${bucket}/${encodedKey}` : `/${encodedKey}`;
|
|
2131
|
+
const url = `https://${host}${canonicalUri}`;
|
|
2132
|
+
const now = new Date;
|
|
2133
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
2134
|
+
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
2135
|
+
const payloadHash = crypto3.createHash("sha256").update("").digest("hex");
|
|
2136
|
+
const requestHeaders = {
|
|
2137
|
+
host,
|
|
2138
|
+
"x-amz-date": amzDate,
|
|
2139
|
+
"x-amz-content-sha256": payloadHash
|
|
2140
|
+
};
|
|
2141
|
+
if (sessionToken) {
|
|
2142
|
+
requestHeaders["x-amz-security-token"] = sessionToken;
|
|
2143
|
+
}
|
|
2144
|
+
const canonicalHeaders = Object.keys(requestHeaders).sort().map((k) => `${k.toLowerCase()}:${requestHeaders[k].trim()}
|
|
2145
|
+
`).join("");
|
|
2146
|
+
const signedHeaders = Object.keys(requestHeaders).sort().map((k) => k.toLowerCase()).join(";");
|
|
2147
|
+
const canonicalRequest = [
|
|
2148
|
+
"GET",
|
|
2149
|
+
canonicalUri,
|
|
2150
|
+
"",
|
|
2151
|
+
canonicalHeaders,
|
|
2152
|
+
signedHeaders,
|
|
2153
|
+
payloadHash
|
|
2154
|
+
].join(`
|
|
2155
|
+
`);
|
|
2156
|
+
const algorithm = "AWS4-HMAC-SHA256";
|
|
2157
|
+
const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
|
|
2158
|
+
const stringToSign = [
|
|
2159
|
+
algorithm,
|
|
2160
|
+
amzDate,
|
|
2161
|
+
credentialScope,
|
|
2162
|
+
crypto3.createHash("sha256").update(canonicalRequest).digest("hex")
|
|
2163
|
+
].join(`
|
|
2164
|
+
`);
|
|
2165
|
+
const kDate = crypto3.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
|
|
2166
|
+
const kRegion = crypto3.createHmac("sha256", kDate).update(this.region).digest();
|
|
2167
|
+
const kService = crypto3.createHmac("sha256", kRegion).update("s3").digest();
|
|
2168
|
+
const kSigning = crypto3.createHmac("sha256", kService).update("aws4_request").digest();
|
|
2169
|
+
const signature = crypto3.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
|
|
2170
|
+
const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
2171
|
+
const response = await fetch(url, {
|
|
2172
|
+
method: "GET",
|
|
2173
|
+
headers: { ...requestHeaders, Authorization: authorizationHeader }
|
|
2174
|
+
});
|
|
2175
|
+
if (!response.ok) {
|
|
2176
|
+
const errorText = await response.text();
|
|
2177
|
+
throw new Error(`S3 GET failed: ${response.status} ${errorText}`);
|
|
2178
|
+
}
|
|
2179
|
+
const buffer = await response.arrayBuffer();
|
|
2180
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
2181
|
+
return {
|
|
2182
|
+
body: new Uint8Array(buffer),
|
|
2183
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
2184
|
+
contentLength: contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : undefined
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2126
2187
|
async copyObject(options) {
|
|
2127
2188
|
const headers = {
|
|
2128
2189
|
"x-amz-copy-source": `/${options.sourceBucket}/${options.sourceKey}`
|
|
@@ -65570,6 +65631,165 @@ init_client();
|
|
|
65570
65631
|
|
|
65571
65632
|
// src/object-storage/index.ts
|
|
65572
65633
|
init_s3();
|
|
65634
|
+
|
|
65635
|
+
// src/object-storage/migrate.ts
|
|
65636
|
+
function stripPrefix(key, prefix) {
|
|
65637
|
+
if (!prefix)
|
|
65638
|
+
return key;
|
|
65639
|
+
return key.startsWith(prefix) ? key.slice(prefix.length) : key;
|
|
65640
|
+
}
|
|
65641
|
+
function remapKey(sourceKey, fromPrefix, toPrefix) {
|
|
65642
|
+
const stripped = stripPrefix(sourceKey, fromPrefix);
|
|
65643
|
+
return `${toPrefix ?? ""}${stripped}`;
|
|
65644
|
+
}
|
|
65645
|
+
function keyMatchesFilters(key, include, exclude) {
|
|
65646
|
+
if (exclude && exclude.some((p) => key.startsWith(p)))
|
|
65647
|
+
return false;
|
|
65648
|
+
if (include && include.length > 0)
|
|
65649
|
+
return include.some((p) => key.startsWith(p));
|
|
65650
|
+
return true;
|
|
65651
|
+
}
|
|
65652
|
+
async function mapWithConcurrency(items, limit, worker) {
|
|
65653
|
+
let cursor = 0;
|
|
65654
|
+
const runners = [];
|
|
65655
|
+
const size = Math.max(1, Math.min(limit, items.length || 1));
|
|
65656
|
+
for (let i = 0;i < size; i++) {
|
|
65657
|
+
runners.push((async () => {
|
|
65658
|
+
while (true) {
|
|
65659
|
+
const index = cursor++;
|
|
65660
|
+
if (index >= items.length)
|
|
65661
|
+
return;
|
|
65662
|
+
await worker(items[index], index);
|
|
65663
|
+
}
|
|
65664
|
+
})());
|
|
65665
|
+
}
|
|
65666
|
+
await Promise.all(runners);
|
|
65667
|
+
}
|
|
65668
|
+
function clientFor(endpoint) {
|
|
65669
|
+
if (endpoint.client)
|
|
65670
|
+
return endpoint.client;
|
|
65671
|
+
return createObjectStorageClient({
|
|
65672
|
+
provider: endpoint.provider,
|
|
65673
|
+
region: endpoint.region,
|
|
65674
|
+
endpoint: endpoint.endpoint,
|
|
65675
|
+
forcePathStyle: endpoint.forcePathStyle,
|
|
65676
|
+
credentials: endpoint.credentials
|
|
65677
|
+
});
|
|
65678
|
+
}
|
|
65679
|
+
async function migrateObjectStorage(options) {
|
|
65680
|
+
const concurrency = options.concurrency ?? 8;
|
|
65681
|
+
const fromClient = clientFor(options.from);
|
|
65682
|
+
const toClient = clientFor(options.to);
|
|
65683
|
+
const sourceObjects = await fromClient.listAllObjects({
|
|
65684
|
+
bucket: options.from.bucket,
|
|
65685
|
+
prefix: options.from.prefix
|
|
65686
|
+
});
|
|
65687
|
+
const result = {
|
|
65688
|
+
copied: 0,
|
|
65689
|
+
skipped: 0,
|
|
65690
|
+
excluded: 0,
|
|
65691
|
+
bytesCopied: 0,
|
|
65692
|
+
errors: [],
|
|
65693
|
+
excludedKeys: [],
|
|
65694
|
+
deleted: []
|
|
65695
|
+
};
|
|
65696
|
+
const toCopy = [];
|
|
65697
|
+
for (const obj of sourceObjects) {
|
|
65698
|
+
if (!keyMatchesFilters(obj.Key, options.include, options.exclude)) {
|
|
65699
|
+
result.excluded++;
|
|
65700
|
+
result.excludedKeys.push(obj.Key);
|
|
65701
|
+
continue;
|
|
65702
|
+
}
|
|
65703
|
+
toCopy.push({ source: obj, destKey: remapKey(obj.Key, options.from.prefix, options.to.prefix) });
|
|
65704
|
+
}
|
|
65705
|
+
if (options.dryRun) {
|
|
65706
|
+
result.plan = toCopy.map(({ source, destKey }) => ({ key: source.Key, destKey, size: source.Size }));
|
|
65707
|
+
const total2 = sourceObjects.length;
|
|
65708
|
+
let index = 0;
|
|
65709
|
+
for (const obj of sourceObjects) {
|
|
65710
|
+
index++;
|
|
65711
|
+
const excluded = !keyMatchesFilters(obj.Key, options.include, options.exclude);
|
|
65712
|
+
options.onProgress?.({
|
|
65713
|
+
key: obj.Key,
|
|
65714
|
+
destKey: excluded ? "" : remapKey(obj.Key, options.from.prefix, options.to.prefix),
|
|
65715
|
+
size: obj.Size,
|
|
65716
|
+
action: excluded ? "excluded" : "planned",
|
|
65717
|
+
index,
|
|
65718
|
+
total: total2
|
|
65719
|
+
});
|
|
65720
|
+
}
|
|
65721
|
+
return result;
|
|
65722
|
+
}
|
|
65723
|
+
const total = toCopy.length;
|
|
65724
|
+
let processed = 0;
|
|
65725
|
+
await mapWithConcurrency(toCopy, concurrency, async ({ source, destKey }) => {
|
|
65726
|
+
const myIndex = ++processed;
|
|
65727
|
+
try {
|
|
65728
|
+
if (!options.force) {
|
|
65729
|
+
const head = await toClient.headObject(options.to.bucket, destKey);
|
|
65730
|
+
if (head && head.ContentLength === source.Size) {
|
|
65731
|
+
result.skipped++;
|
|
65732
|
+
options.onProgress?.({ key: source.Key, destKey, size: source.Size, action: "skipped", index: myIndex, total });
|
|
65733
|
+
return;
|
|
65734
|
+
}
|
|
65735
|
+
}
|
|
65736
|
+
const { body, contentType } = await fromClient.getObjectBytes(options.from.bucket, source.Key);
|
|
65737
|
+
await toClient.putObject({
|
|
65738
|
+
bucket: options.to.bucket,
|
|
65739
|
+
key: destKey,
|
|
65740
|
+
body,
|
|
65741
|
+
contentType
|
|
65742
|
+
});
|
|
65743
|
+
result.copied++;
|
|
65744
|
+
result.bytesCopied += body.byteLength;
|
|
65745
|
+
options.onProgress?.({ key: source.Key, destKey, size: source.Size, action: "copied", index: myIndex, total });
|
|
65746
|
+
} catch (err) {
|
|
65747
|
+
result.errors.push({ key: source.Key, message: err?.message ?? String(err) });
|
|
65748
|
+
options.onProgress?.({ key: source.Key, destKey, size: source.Size, action: "error", index: myIndex, total });
|
|
65749
|
+
}
|
|
65750
|
+
});
|
|
65751
|
+
const copiedDestKeys = new Set(toCopy.map((c) => c.destKey));
|
|
65752
|
+
if (options.deleteExtraneous) {
|
|
65753
|
+
const destObjects = await toClient.listAllObjects({ bucket: options.to.bucket, prefix: options.to.prefix });
|
|
65754
|
+
const extraneous = destObjects.filter((o) => !copiedDestKeys.has(o.Key)).map((o) => o.Key);
|
|
65755
|
+
for (const key of extraneous) {
|
|
65756
|
+
try {
|
|
65757
|
+
await toClient.deleteObject(options.to.bucket, key);
|
|
65758
|
+
result.deleted.push(key);
|
|
65759
|
+
} catch (err) {
|
|
65760
|
+
result.errors.push({ key, message: `delete failed: ${err?.message ?? String(err)}` });
|
|
65761
|
+
}
|
|
65762
|
+
}
|
|
65763
|
+
}
|
|
65764
|
+
if (options.verify) {
|
|
65765
|
+
const destObjects = await toClient.listAllObjects({ bucket: options.to.bucket, prefix: options.to.prefix });
|
|
65766
|
+
const destBySizeKey = new Map(destObjects.map((o) => [o.Key, o.Size]));
|
|
65767
|
+
const missing = [];
|
|
65768
|
+
const sizeMismatches = [];
|
|
65769
|
+
let matched = 0;
|
|
65770
|
+
for (const { source, destKey } of toCopy) {
|
|
65771
|
+
if (!destBySizeKey.has(destKey)) {
|
|
65772
|
+
missing.push(destKey);
|
|
65773
|
+
continue;
|
|
65774
|
+
}
|
|
65775
|
+
const actual = destBySizeKey.get(destKey);
|
|
65776
|
+
if (actual !== source.Size) {
|
|
65777
|
+
sizeMismatches.push({ key: destKey, expected: source.Size, actual });
|
|
65778
|
+
continue;
|
|
65779
|
+
}
|
|
65780
|
+
matched++;
|
|
65781
|
+
}
|
|
65782
|
+
result.verification = {
|
|
65783
|
+
ok: missing.length === 0 && sizeMismatches.length === 0,
|
|
65784
|
+
matched,
|
|
65785
|
+
missing,
|
|
65786
|
+
sizeMismatches
|
|
65787
|
+
};
|
|
65788
|
+
}
|
|
65789
|
+
return result;
|
|
65790
|
+
}
|
|
65791
|
+
|
|
65792
|
+
// src/object-storage/index.ts
|
|
65573
65793
|
var DEFAULT_REGION = {
|
|
65574
65794
|
aws: "us-east-1",
|
|
65575
65795
|
backblaze: "us-west-004",
|
|
@@ -81229,204 +81449,6 @@ import { homedir as homedir7 } from "node:os";
|
|
|
81229
81449
|
import { join as join13 } from "node:path";
|
|
81230
81450
|
import { execSync } from "node:child_process";
|
|
81231
81451
|
|
|
81232
|
-
// src/drivers/shared/caddyfile.ts
|
|
81233
|
-
function isOnDemandDomain(domain) {
|
|
81234
|
-
return domain === "*" || domain.includes("*");
|
|
81235
|
-
}
|
|
81236
|
-
function isStaticApp(app) {
|
|
81237
|
-
return typeof app.root === "string" && app.root.length > 0;
|
|
81238
|
-
}
|
|
81239
|
-
function isProxyApp(app) {
|
|
81240
|
-
return typeof app.port === "number";
|
|
81241
|
-
}
|
|
81242
|
-
function staticSiteServerRoot(name) {
|
|
81243
|
-
return `/var/www/${name}`;
|
|
81244
|
-
}
|
|
81245
|
-
function normalizeOnDemandTls(onDemandTls) {
|
|
81246
|
-
if (!onDemandTls)
|
|
81247
|
-
return;
|
|
81248
|
-
if (onDemandTls === true)
|
|
81249
|
-
return {};
|
|
81250
|
-
return onDemandTls;
|
|
81251
|
-
}
|
|
81252
|
-
function wrapInHandle(body, path, indent) {
|
|
81253
|
-
const inner = body.split(`
|
|
81254
|
-
`).map((line) => ` ${line}`).join(`
|
|
81255
|
-
`);
|
|
81256
|
-
return `${indent}handle ${path} {
|
|
81257
|
-
${inner}
|
|
81258
|
-
${indent}}`;
|
|
81259
|
-
}
|
|
81260
|
-
function renderUpstreamBlock(app, indent) {
|
|
81261
|
-
const host = app.upstreamHost || "localhost";
|
|
81262
|
-
const upstream = `${host}:${app.port}`;
|
|
81263
|
-
const directives = app.reverseProxyDirectives ?? [];
|
|
81264
|
-
const proxyLine = directives.length === 0 ? `${indent}reverse_proxy ${upstream}` : [
|
|
81265
|
-
`${indent}reverse_proxy ${upstream} {`,
|
|
81266
|
-
...directives.map((d) => `${indent} ${d}`),
|
|
81267
|
-
`${indent}}`
|
|
81268
|
-
].join(`
|
|
81269
|
-
`);
|
|
81270
|
-
const isCatchAll = !app.path || app.path === "/";
|
|
81271
|
-
if (isCatchAll)
|
|
81272
|
-
return proxyLine;
|
|
81273
|
-
return wrapInHandle(proxyLine, app.path, indent);
|
|
81274
|
-
}
|
|
81275
|
-
function renderStaticBlock(app, indent) {
|
|
81276
|
-
const root = app.root;
|
|
81277
|
-
const lines = [`${indent}root * ${root}`];
|
|
81278
|
-
if (app.cache?.enabled) {
|
|
81279
|
-
const maxAge = app.cache.maxAge ?? 3600;
|
|
81280
|
-
lines.push(`${indent}header Cache-Control "public, max-age=${maxAge}"`);
|
|
81281
|
-
}
|
|
81282
|
-
if (app.spa) {
|
|
81283
|
-
lines.push(`${indent}try_files {path} /index.html`);
|
|
81284
|
-
} else if (app.pathRewriteStyle === "flat") {
|
|
81285
|
-
lines.push(`${indent}try_files {path} {path}.html {path}/index.html`);
|
|
81286
|
-
} else {
|
|
81287
|
-
lines.push(`${indent}try_files {path} {path}/index.html {path}.html`);
|
|
81288
|
-
}
|
|
81289
|
-
lines.push(`${indent}file_server`);
|
|
81290
|
-
const body = lines.join(`
|
|
81291
|
-
`);
|
|
81292
|
-
const isCatchAll = !app.path || app.path === "/";
|
|
81293
|
-
if (isCatchAll)
|
|
81294
|
-
return body;
|
|
81295
|
-
return wrapInHandle(body, app.path, indent);
|
|
81296
|
-
}
|
|
81297
|
-
function renderAppBlock(app, indent) {
|
|
81298
|
-
return isStaticApp(app) ? renderStaticBlock(app, indent) : renderUpstreamBlock(app, indent);
|
|
81299
|
-
}
|
|
81300
|
-
function groupAppsByDomains(apps) {
|
|
81301
|
-
const groups = new Map;
|
|
81302
|
-
for (const app of apps) {
|
|
81303
|
-
const domains = [...app.domains].filter(Boolean);
|
|
81304
|
-
if (domains.length === 0)
|
|
81305
|
-
continue;
|
|
81306
|
-
const key = [...domains].sort().join(" ");
|
|
81307
|
-
const group = groups.get(key) ?? { domains, apps: [] };
|
|
81308
|
-
group.apps.push(app);
|
|
81309
|
-
groups.set(key, group);
|
|
81310
|
-
}
|
|
81311
|
-
return [...groups.values()];
|
|
81312
|
-
}
|
|
81313
|
-
function sortAppsByPath(apps) {
|
|
81314
|
-
return [...apps].sort((a, b) => {
|
|
81315
|
-
const aCatchAll = !a.path || a.path === "/";
|
|
81316
|
-
const bCatchAll = !b.path || b.path === "/";
|
|
81317
|
-
if (aCatchAll && !bCatchAll)
|
|
81318
|
-
return 1;
|
|
81319
|
-
if (!aCatchAll && bCatchAll)
|
|
81320
|
-
return -1;
|
|
81321
|
-
return (b.path?.length ?? 0) - (a.path?.length ?? 0);
|
|
81322
|
-
});
|
|
81323
|
-
}
|
|
81324
|
-
function buildCaddyfileFromProxy(proxy) {
|
|
81325
|
-
if (proxy.raw && proxy.raw.trim())
|
|
81326
|
-
return proxy.raw.trim();
|
|
81327
|
-
const apps = (proxy.apps ?? []).filter((app) => app.domains.length > 0 && (isProxyApp(app) || isStaticApp(app)));
|
|
81328
|
-
if (apps.length === 0)
|
|
81329
|
-
return;
|
|
81330
|
-
const onDemand = normalizeOnDemandTls(proxy.onDemandTls);
|
|
81331
|
-
const hasOnDemandDomain = apps.some((app) => app.domains.some(isOnDemandDomain));
|
|
81332
|
-
const globalLines = [];
|
|
81333
|
-
if (proxy.email)
|
|
81334
|
-
globalLines.push(`email ${proxy.email}`);
|
|
81335
|
-
if (proxy.staging)
|
|
81336
|
-
globalLines.push("acme_ca https://acme-staging-v02.api.letsencrypt.org/directory");
|
|
81337
|
-
if (onDemand) {
|
|
81338
|
-
if (onDemand.ask || onDemand.interval || onDemand.burst != null) {
|
|
81339
|
-
const inner = [];
|
|
81340
|
-
if (onDemand.ask)
|
|
81341
|
-
inner.push(` ask ${onDemand.ask}`);
|
|
81342
|
-
if (onDemand.interval || onDemand.burst != null) {
|
|
81343
|
-
const rl = [];
|
|
81344
|
-
if (onDemand.interval)
|
|
81345
|
-
rl.push(` interval ${onDemand.interval}`);
|
|
81346
|
-
if (onDemand.burst != null)
|
|
81347
|
-
rl.push(` burst ${onDemand.burst}`);
|
|
81348
|
-
inner.push(` rate_limit {
|
|
81349
|
-
${rl.join(`
|
|
81350
|
-
`)}
|
|
81351
|
-
}`);
|
|
81352
|
-
}
|
|
81353
|
-
globalLines.push(`on_demand_tls {
|
|
81354
|
-
${inner.join(`
|
|
81355
|
-
`)}
|
|
81356
|
-
}`);
|
|
81357
|
-
} else {
|
|
81358
|
-
globalLines.push(`on_demand_tls {
|
|
81359
|
-
}`);
|
|
81360
|
-
}
|
|
81361
|
-
}
|
|
81362
|
-
for (const directive of proxy.globalDirectives ?? [])
|
|
81363
|
-
globalLines.push(directive);
|
|
81364
|
-
const blocks = [];
|
|
81365
|
-
if (globalLines.length > 0)
|
|
81366
|
-
blocks.push(`{
|
|
81367
|
-
${globalLines.map((line) => ` ${line}`).join(`
|
|
81368
|
-
`)}
|
|
81369
|
-
}`);
|
|
81370
|
-
for (const group of groupAppsByDomains(apps)) {
|
|
81371
|
-
const sorted = sortAppsByPath(group.apps);
|
|
81372
|
-
const body = sorted.map((app) => renderAppBlock(app, " ")).join(`
|
|
81373
|
-
`);
|
|
81374
|
-
const needsOnDemand = onDemand && group.domains.some(isOnDemandDomain);
|
|
81375
|
-
const tlsBlock = needsOnDemand ? `
|
|
81376
|
-
tls {
|
|
81377
|
-
on_demand
|
|
81378
|
-
}` : "";
|
|
81379
|
-
blocks.push(`${group.domains.join(", ")} {
|
|
81380
|
-
${body}${tlsBlock}
|
|
81381
|
-
}`);
|
|
81382
|
-
}
|
|
81383
|
-
if (hasOnDemandDomain && !onDemand) {
|
|
81384
|
-
blocks.unshift(`# WARNING: wildcard/catch-all domain present but on_demand_tls is not enabled.
|
|
81385
|
-
` + "# Caddy cannot provision TLS for these hosts. Set compute.proxy.onDemandTls.");
|
|
81386
|
-
}
|
|
81387
|
-
return blocks.join(`
|
|
81388
|
-
|
|
81389
|
-
`);
|
|
81390
|
-
}
|
|
81391
|
-
function proxyConfigFromSites(sites) {
|
|
81392
|
-
const apps = [];
|
|
81393
|
-
for (const [name, site] of Object.entries(sites)) {
|
|
81394
|
-
if (typeof site.domain !== "string" || !site.domain)
|
|
81395
|
-
continue;
|
|
81396
|
-
if (typeof site.port === "number" && site.deploy !== "bucket") {
|
|
81397
|
-
apps.push({
|
|
81398
|
-
name,
|
|
81399
|
-
domains: [site.domain],
|
|
81400
|
-
port: site.port,
|
|
81401
|
-
path: site.path
|
|
81402
|
-
});
|
|
81403
|
-
} else if (resolveSiteKind(site) === "server-static") {
|
|
81404
|
-
apps.push({
|
|
81405
|
-
name,
|
|
81406
|
-
domains: [site.domain],
|
|
81407
|
-
root: staticSiteServerRoot(name),
|
|
81408
|
-
path: site.path,
|
|
81409
|
-
spa: site.spa,
|
|
81410
|
-
pathRewriteStyle: site.pathRewriteStyle,
|
|
81411
|
-
cache: site.cache
|
|
81412
|
-
});
|
|
81413
|
-
}
|
|
81414
|
-
}
|
|
81415
|
-
return { apps };
|
|
81416
|
-
}
|
|
81417
|
-
function resolveCaddyfile(sites, proxy) {
|
|
81418
|
-
if (proxy) {
|
|
81419
|
-
if (proxy.raw && proxy.raw.trim())
|
|
81420
|
-
return proxy.raw.trim();
|
|
81421
|
-
const resolved = proxy.apps && proxy.apps.length > 0 ? proxy : { ...proxy, apps: proxyConfigFromSites(sites).apps };
|
|
81422
|
-
return buildCaddyfileFromProxy(resolved);
|
|
81423
|
-
}
|
|
81424
|
-
return buildCaddyfileFromProxy(proxyConfigFromSites(sites));
|
|
81425
|
-
}
|
|
81426
|
-
function buildCaddyfile(sites) {
|
|
81427
|
-
return buildCaddyfileFromProxy(proxyConfigFromSites(sites));
|
|
81428
|
-
}
|
|
81429
|
-
|
|
81430
81452
|
// src/drivers/hetzner/client.ts
|
|
81431
81453
|
var DEFAULT_API_URL = "https://api.hetzner.cloud/v1";
|
|
81432
81454
|
|
|
@@ -81829,14 +81851,12 @@ class HetznerDriver {
|
|
|
81829
81851
|
return this.outputsFromState(rehydrated, alreadyRunning);
|
|
81830
81852
|
}
|
|
81831
81853
|
const sites = config6.sites || {};
|
|
81832
|
-
const
|
|
81833
|
-
const sitePorts = caddyfile ? [] : this.collectUpstreamPorts(sites, compute.proxy);
|
|
81854
|
+
const sitePorts = this.collectUpstreamPorts(sites);
|
|
81834
81855
|
const bootstrap = generateUbuntuAppCloudInit({
|
|
81835
81856
|
runtime: compute.runtime || "bun",
|
|
81836
81857
|
runtimeVersion: compute.runtimeVersion || "latest",
|
|
81837
81858
|
systemPackages: compute.systemPackages,
|
|
81838
|
-
database: config6.infrastructure?.database
|
|
81839
|
-
caddyfile
|
|
81859
|
+
database: config6.infrastructure?.database
|
|
81840
81860
|
});
|
|
81841
81861
|
const userData = wrapCloudInitUserData(bootstrap);
|
|
81842
81862
|
const serverType = resolveHetznerServerType(compute.size);
|
|
@@ -82002,12 +82022,8 @@ class HetznerDriver {
|
|
|
82002
82022
|
const { firewall } = await this.client.createFirewall({ name, labels, rules });
|
|
82003
82023
|
return { firewall };
|
|
82004
82024
|
}
|
|
82005
|
-
collectUpstreamPorts(sites
|
|
82025
|
+
collectUpstreamPorts(sites) {
|
|
82006
82026
|
const ports = new Set;
|
|
82007
|
-
for (const app of proxy?.apps ?? []) {
|
|
82008
|
-
if (typeof app.port === "number")
|
|
82009
|
-
ports.add(app.port);
|
|
82010
|
-
}
|
|
82011
82027
|
for (const site of Object.values(sites)) {
|
|
82012
82028
|
if (typeof site.port === "number")
|
|
82013
82029
|
ports.add(site.port);
|
|
@@ -82361,7 +82377,6 @@ export {
|
|
|
82361
82377
|
suggestFlags,
|
|
82362
82378
|
suggestCommand,
|
|
82363
82379
|
storageAdvancedManager,
|
|
82364
|
-
staticSiteServerRoot,
|
|
82365
82380
|
staticSiteManager,
|
|
82366
82381
|
stackDependencyManager,
|
|
82367
82382
|
signRequestAsync,
|
|
@@ -82393,13 +82408,12 @@ export {
|
|
|
82393
82408
|
resolveDeployBucketName,
|
|
82394
82409
|
resolveCredentials,
|
|
82395
82410
|
resolveCloudProvider,
|
|
82396
|
-
resolveCaddyfile,
|
|
82397
82411
|
requiresReplacement,
|
|
82398
82412
|
replicaManager,
|
|
82413
|
+
remapKey,
|
|
82399
82414
|
regionPairManager,
|
|
82400
82415
|
quickHash,
|
|
82401
82416
|
queueManagementManager,
|
|
82402
|
-
proxyConfigFromSites,
|
|
82403
82417
|
providerEndpoint,
|
|
82404
82418
|
progressiveDeploymentManager,
|
|
82405
82419
|
processInChunks,
|
|
@@ -82417,6 +82431,7 @@ export {
|
|
|
82417
82431
|
multiRegionManager,
|
|
82418
82432
|
multiAccountManager,
|
|
82419
82433
|
migrationManager,
|
|
82434
|
+
migrateObjectStorage,
|
|
82420
82435
|
metricsManager,
|
|
82421
82436
|
mergeInfrastructure,
|
|
82422
82437
|
makeAWSRequestOnce,
|
|
@@ -82430,9 +82445,9 @@ export {
|
|
|
82430
82445
|
lambdaDestinationsManager,
|
|
82431
82446
|
lambdaDLQManager,
|
|
82432
82447
|
lambdaConcurrencyManager,
|
|
82448
|
+
keyMatchesFilters,
|
|
82433
82449
|
isWebCryptoAvailable,
|
|
82434
82450
|
isValidRegion,
|
|
82435
|
-
isOnDemandDomain,
|
|
82436
82451
|
isNodeCryptoAvailable,
|
|
82437
82452
|
isLocalDevelopment,
|
|
82438
82453
|
isLikelyTypo,
|
|
@@ -82585,8 +82600,6 @@ export {
|
|
|
82585
82600
|
buildSiteDeployScript,
|
|
82586
82601
|
buildOptimizationManager,
|
|
82587
82602
|
buildCloudFormationTemplate,
|
|
82588
|
-
buildCaddyfileFromProxy,
|
|
82589
|
-
buildCaddyfile,
|
|
82590
82603
|
bounceComplaintHandler,
|
|
82591
82604
|
blueGreenManager,
|
|
82592
82605
|
batchProcessingManager,
|