@stacksjs/ts-cloud 0.2.27 → 0.3.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/aws/lambda.d.ts +10 -2
- package/dist/bin/cli.js +995 -914
- package/dist/deploy/serverless-app.d.ts +53 -0
- package/dist/deploy/serverless-app.test.d.ts +1 -0
- package/dist/deploy/serverless-image.d.ts +32 -0
- package/dist/drivers/shared/db-provision.d.ts +15 -11
- package/dist/drivers/shared/nginx-vhost.d.ts +12 -0
- package/dist/drivers/shared/package-manager.d.ts +84 -0
- package/dist/drivers/shared/php-provision.d.ts +23 -25
- package/dist/index.js +1778 -358
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1194,9 +1194,9 @@ __export(exports_client, {
|
|
|
1194
1194
|
AWSClient: () => AWSClient
|
|
1195
1195
|
});
|
|
1196
1196
|
import * as crypto2 from "node:crypto";
|
|
1197
|
-
import { existsSync as existsSync14, readFileSync as
|
|
1197
|
+
import { existsSync as existsSync14, readFileSync as readFileSync7 } from "node:fs";
|
|
1198
1198
|
import { homedir as homedir6 } from "node:os";
|
|
1199
|
-
import { join as
|
|
1199
|
+
import { join as join10 } from "node:path";
|
|
1200
1200
|
function resolveS3Endpoint(options) {
|
|
1201
1201
|
const base = options.endpoint || `s3.${options.region}.amazonaws.com`;
|
|
1202
1202
|
if (!options.bucket) {
|
|
@@ -1254,12 +1254,12 @@ class AWSClient {
|
|
|
1254
1254
|
}
|
|
1255
1255
|
loadCredentialsFromFile() {
|
|
1256
1256
|
const profile = process.env.AWS_PROFILE || "default";
|
|
1257
|
-
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE ||
|
|
1257
|
+
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join10(homedir6(), ".aws", "credentials");
|
|
1258
1258
|
if (!existsSync14(credentialsPath)) {
|
|
1259
1259
|
return null;
|
|
1260
1260
|
}
|
|
1261
1261
|
try {
|
|
1262
|
-
const content =
|
|
1262
|
+
const content = readFileSync7(credentialsPath, "utf-8");
|
|
1263
1263
|
const credentials = this.parseCredentialsFile(content, profile);
|
|
1264
1264
|
if (credentials.accessKeyId && credentials.secretAccessKey) {
|
|
1265
1265
|
return credentials;
|
|
@@ -1688,7 +1688,7 @@ class AWSClient {
|
|
|
1688
1688
|
return delay + jitter;
|
|
1689
1689
|
}
|
|
1690
1690
|
sleep(ms) {
|
|
1691
|
-
return new Promise((
|
|
1691
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
1692
1692
|
}
|
|
1693
1693
|
getFromCache(key) {
|
|
1694
1694
|
const entry = this.cache.get(key);
|
|
@@ -1753,7 +1753,7 @@ function detectCredentialSource() {
|
|
|
1753
1753
|
};
|
|
1754
1754
|
}
|
|
1755
1755
|
const profile = process.env.AWS_PROFILE || "default";
|
|
1756
|
-
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE ||
|
|
1756
|
+
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join10(homedir6(), ".aws", "credentials");
|
|
1757
1757
|
if (existsSync14(credentialsPath)) {
|
|
1758
1758
|
return {
|
|
1759
1759
|
source: "file",
|
|
@@ -1795,13 +1795,13 @@ function resolveCredentials2(profile) {
|
|
|
1795
1795
|
return loadProfileFromFile(process.env.AWS_PROFILE || "default") ?? { accessKeyId: "", secretAccessKey: "" };
|
|
1796
1796
|
}
|
|
1797
1797
|
function loadProfileFromFile(profile) {
|
|
1798
|
-
const { existsSync: existsSync15, readFileSync:
|
|
1798
|
+
const { existsSync: existsSync15, readFileSync: readFileSync8 } = __require("node:fs");
|
|
1799
1799
|
const { homedir: homedir5 } = __require("node:os");
|
|
1800
|
-
const { join:
|
|
1801
|
-
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE ||
|
|
1800
|
+
const { join: join11 } = __require("node:path");
|
|
1801
|
+
const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join11(homedir5(), ".aws", "credentials");
|
|
1802
1802
|
if (!existsSync15(credentialsPath))
|
|
1803
1803
|
return null;
|
|
1804
|
-
const content =
|
|
1804
|
+
const content = readFileSync8(credentialsPath, "utf-8");
|
|
1805
1805
|
let currentProfile = null;
|
|
1806
1806
|
let accessKeyId;
|
|
1807
1807
|
let secretAccessKey;
|
|
@@ -1847,8 +1847,8 @@ function loadProfileFromFile(profile) {
|
|
|
1847
1847
|
// src/aws/s3.ts
|
|
1848
1848
|
import * as crypto3 from "node:crypto";
|
|
1849
1849
|
import { readdir as readdir4 } from "node:fs/promises";
|
|
1850
|
-
import { join as
|
|
1851
|
-
import { readFileSync as
|
|
1850
|
+
import { join as join12 } from "node:path";
|
|
1851
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
1852
1852
|
function toFetchBody(data) {
|
|
1853
1853
|
const { buffer, byteOffset, byteLength } = data;
|
|
1854
1854
|
if (buffer instanceof ArrayBuffer) {
|
|
@@ -2250,7 +2250,7 @@ class S3Client2 {
|
|
|
2250
2250
|
}
|
|
2251
2251
|
}
|
|
2252
2252
|
async copy(options) {
|
|
2253
|
-
const fileContent =
|
|
2253
|
+
const fileContent = readFileSync9(options.source);
|
|
2254
2254
|
await this.putObject({
|
|
2255
2255
|
bucket: options.bucket,
|
|
2256
2256
|
key: options.key,
|
|
@@ -2273,7 +2273,7 @@ class S3Client2 {
|
|
|
2273
2273
|
const relativePath = file.substring(options.source.length + 1);
|
|
2274
2274
|
const s3Key = options.prefix ? `${options.prefix}/${relativePath}` : relativePath;
|
|
2275
2275
|
if (!options.dryRun) {
|
|
2276
|
-
const fileContent =
|
|
2276
|
+
const fileContent = readFileSync9(file);
|
|
2277
2277
|
await this.putObject({
|
|
2278
2278
|
bucket: options.bucket,
|
|
2279
2279
|
key: s3Key,
|
|
@@ -2304,7 +2304,7 @@ class S3Client2 {
|
|
|
2304
2304
|
const files = [];
|
|
2305
2305
|
const entries = await readdir4(dir, { withFileTypes: true });
|
|
2306
2306
|
for (const entry of entries) {
|
|
2307
|
-
const fullPath =
|
|
2307
|
+
const fullPath = join12(dir, entry.name);
|
|
2308
2308
|
if (entry.isDirectory()) {
|
|
2309
2309
|
const subFiles = await this.listFilesRecursive(fullPath);
|
|
2310
2310
|
files.push(...subFiles);
|
|
@@ -3722,7 +3722,7 @@ class CloudFormationClient {
|
|
|
3722
3722
|
if (attempts % 10 === 0) {
|
|
3723
3723
|
console.log(`[waitForStack] Attempt ${attempts}: Stack not visible yet`);
|
|
3724
3724
|
}
|
|
3725
|
-
await new Promise((
|
|
3725
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
3726
3726
|
attempts++;
|
|
3727
3727
|
continue;
|
|
3728
3728
|
}
|
|
@@ -3761,7 +3761,7 @@ class CloudFormationClient {
|
|
|
3761
3761
|
if (failureStatuses.includes(stack.StackStatus)) {
|
|
3762
3762
|
throw new Error(`Stack reached failure status: ${stack.StackStatus}`);
|
|
3763
3763
|
}
|
|
3764
|
-
await new Promise((
|
|
3764
|
+
await new Promise((resolve14) => setTimeout(resolve14, 5000));
|
|
3765
3765
|
attempts++;
|
|
3766
3766
|
} catch (error) {
|
|
3767
3767
|
if (waitType === "stack-delete-complete" && error.message?.includes("does not exist")) {
|
|
@@ -3771,7 +3771,7 @@ class CloudFormationClient {
|
|
|
3771
3771
|
if (attempts % 10 === 0) {
|
|
3772
3772
|
console.log(`[waitForStack] Attempt ${attempts}: Stack does not exist (error), retrying...`);
|
|
3773
3773
|
}
|
|
3774
|
-
await new Promise((
|
|
3774
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
3775
3775
|
attempts++;
|
|
3776
3776
|
continue;
|
|
3777
3777
|
}
|
|
@@ -4042,7 +4042,7 @@ class CloudFormationClient {
|
|
|
4042
4042
|
if (waitType === "stack-delete-complete") {
|
|
4043
4043
|
return;
|
|
4044
4044
|
}
|
|
4045
|
-
await new Promise((
|
|
4045
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
4046
4046
|
attempts++;
|
|
4047
4047
|
continue;
|
|
4048
4048
|
}
|
|
@@ -4060,14 +4060,14 @@ class CloudFormationClient {
|
|
|
4060
4060
|
if (failureStatuses.includes(stack.StackStatus)) {
|
|
4061
4061
|
throw new Error(`Stack reached failure status: ${stack.StackStatus}`);
|
|
4062
4062
|
}
|
|
4063
|
-
await new Promise((
|
|
4063
|
+
await new Promise((resolve14) => setTimeout(resolve14, 3000));
|
|
4064
4064
|
attempts++;
|
|
4065
4065
|
} catch (error) {
|
|
4066
4066
|
if (waitType === "stack-delete-complete" && error.message?.includes("does not exist")) {
|
|
4067
4067
|
return;
|
|
4068
4068
|
}
|
|
4069
4069
|
if (waitType === "stack-create-complete" && error.message?.includes("does not exist")) {
|
|
4070
|
-
await new Promise((
|
|
4070
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
4071
4071
|
attempts++;
|
|
4072
4072
|
continue;
|
|
4073
4073
|
}
|
|
@@ -4105,7 +4105,7 @@ class CloudFormationClient {
|
|
|
4105
4105
|
if (failureStatuses.includes(status)) {
|
|
4106
4106
|
return { success: false, status, reason: stack.StackStatusReason };
|
|
4107
4107
|
}
|
|
4108
|
-
await new Promise((
|
|
4108
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
4109
4109
|
} catch (error) {
|
|
4110
4110
|
if (error.message?.includes("does not exist")) {
|
|
4111
4111
|
return { success: true, status: "DELETE_COMPLETE" };
|
|
@@ -4194,7 +4194,7 @@ class CloudFrontClient {
|
|
|
4194
4194
|
if (invalidation.Status === "Completed") {
|
|
4195
4195
|
return;
|
|
4196
4196
|
}
|
|
4197
|
-
await new Promise((
|
|
4197
|
+
await new Promise((resolve14) => setTimeout(resolve14, 5000));
|
|
4198
4198
|
attempts++;
|
|
4199
4199
|
}
|
|
4200
4200
|
throw new Error(`Timeout waiting for invalidation ${invalidationId} to complete`);
|
|
@@ -5024,7 +5024,7 @@ ${Object.entries(config6).filter(([k]) => !k.startsWith("@_")).map(([key, val])
|
|
|
5024
5024
|
if (dist.Status === "Deployed") {
|
|
5025
5025
|
return true;
|
|
5026
5026
|
}
|
|
5027
|
-
await new Promise((
|
|
5027
|
+
await new Promise((resolve14) => setTimeout(resolve14, 30000));
|
|
5028
5028
|
}
|
|
5029
5029
|
return false;
|
|
5030
5030
|
}
|
|
@@ -5085,7 +5085,7 @@ ${Object.entries(config6).filter(([k]) => !k.startsWith("@_")).map(([key, val])
|
|
|
5085
5085
|
if (dist.Status === "Deployed" && !dist.Enabled) {
|
|
5086
5086
|
return true;
|
|
5087
5087
|
}
|
|
5088
|
-
await new Promise((
|
|
5088
|
+
await new Promise((resolve14) => setTimeout(resolve14, 30000));
|
|
5089
5089
|
}
|
|
5090
5090
|
return false;
|
|
5091
5091
|
}
|
|
@@ -5618,7 +5618,7 @@ var init_route53 = __esm(() => {
|
|
|
5618
5618
|
if (status === "INSYNC") {
|
|
5619
5619
|
return true;
|
|
5620
5620
|
}
|
|
5621
|
-
await new Promise((
|
|
5621
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
5622
5622
|
}
|
|
5623
5623
|
return false;
|
|
5624
5624
|
}
|
|
@@ -6976,7 +6976,7 @@ class UnifiedDnsValidator {
|
|
|
6976
6976
|
if (cert.DomainValidationOptions && cert.DomainValidationOptions.length > 0 && cert.DomainValidationOptions[0].ResourceRecord) {
|
|
6977
6977
|
return;
|
|
6978
6978
|
}
|
|
6979
|
-
await new Promise((
|
|
6979
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
6980
6980
|
}
|
|
6981
6981
|
throw new Error("Timeout waiting for DNS validation options");
|
|
6982
6982
|
}
|
|
@@ -7308,7 +7308,7 @@ class ACMClient {
|
|
|
7308
7308
|
if (cert.Status === "FAILED" || cert.Status === "VALIDATION_TIMED_OUT") {
|
|
7309
7309
|
return null;
|
|
7310
7310
|
}
|
|
7311
|
-
await new Promise((
|
|
7311
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
7312
7312
|
}
|
|
7313
7313
|
return null;
|
|
7314
7314
|
}
|
|
@@ -7456,7 +7456,7 @@ class ACMDnsValidator {
|
|
|
7456
7456
|
if (cert.DomainValidationOptions && cert.DomainValidationOptions.length > 0 && cert.DomainValidationOptions[0].ResourceRecord) {
|
|
7457
7457
|
return;
|
|
7458
7458
|
}
|
|
7459
|
-
await new Promise((
|
|
7459
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
7460
7460
|
}
|
|
7461
7461
|
throw new Error("Timeout waiting for DNS validation options");
|
|
7462
7462
|
}
|
|
@@ -7875,7 +7875,7 @@ class SESClient {
|
|
|
7875
7875
|
if (isVerified) {
|
|
7876
7876
|
return true;
|
|
7877
7877
|
}
|
|
7878
|
-
await new Promise((
|
|
7878
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
7879
7879
|
}
|
|
7880
7880
|
return false;
|
|
7881
7881
|
}
|
|
@@ -10891,13 +10891,13 @@ async function uploadStaticFiles(options) {
|
|
|
10891
10891
|
const { sourceDir, bucket, region, cacheControl = "max-age=31536000, public", onProgress } = options;
|
|
10892
10892
|
const s32 = new S3Client2(region);
|
|
10893
10893
|
const { readdir: readdir5 } = await import("node:fs/promises");
|
|
10894
|
-
const { join:
|
|
10895
|
-
const { createHash:
|
|
10894
|
+
const { join: join11, relative: relative5 } = await import("node:path");
|
|
10895
|
+
const { createHash: createHash7 } = await import("node:crypto");
|
|
10896
10896
|
async function listFiles(dir) {
|
|
10897
10897
|
const files2 = [];
|
|
10898
10898
|
const entries = await readdir5(dir, { withFileTypes: true });
|
|
10899
10899
|
for (const entry of entries) {
|
|
10900
|
-
const fullPath =
|
|
10900
|
+
const fullPath = join11(dir, entry.name);
|
|
10901
10901
|
if (entry.isDirectory()) {
|
|
10902
10902
|
files2.push(...await listFiles(fullPath));
|
|
10903
10903
|
} else {
|
|
@@ -10932,7 +10932,7 @@ async function uploadStaticFiles(options) {
|
|
|
10932
10932
|
return types2[ext || ""] || "application/octet-stream";
|
|
10933
10933
|
}
|
|
10934
10934
|
function computeMD5(content) {
|
|
10935
|
-
return
|
|
10935
|
+
return createHash7("md5").update(content).digest("hex");
|
|
10936
10936
|
}
|
|
10937
10937
|
async function getExistingETags() {
|
|
10938
10938
|
const etagMap = new Map;
|
|
@@ -19781,6 +19781,24 @@ import { join as join42 } from "node:path";
|
|
|
19781
19781
|
import { createHash as createHash3 } from "node:crypto";
|
|
19782
19782
|
import { createReadStream as createReadStream4, readdirSync as readdirSync42, statSync as statSync22 } from "node:fs";
|
|
19783
19783
|
import { join as join52, relative as relative4 } from "node:path";
|
|
19784
|
+
import { deflateRawSync } from "node:zlib";
|
|
19785
|
+
import { execSync } from "node:child_process";
|
|
19786
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
19787
|
+
import { cpSync, mkdtempSync, rmSync, writeFileSync as writeFileSync32 } from "node:fs";
|
|
19788
|
+
import { tmpdir } from "node:os";
|
|
19789
|
+
import { dirname as dirname22, isAbsolute as isAbsolute3, join as join62, resolve as resolve13 } from "node:path";
|
|
19790
|
+
import { fileURLToPath } from "node:url";
|
|
19791
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
19792
|
+
import { dirname as dirname32, join as join72 } from "node:path";
|
|
19793
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19794
|
+
import { execFileSync } from "node:child_process";
|
|
19795
|
+
import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readdirSync as readdirSync52, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync32, writeFileSync as writeFileSync42 } from "node:fs";
|
|
19796
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
19797
|
+
import { join as join8, relative as relative22 } from "node:path";
|
|
19798
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
19799
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
19800
|
+
import { readdirSync as readdirSync62, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
|
|
19801
|
+
import { join as join9, relative as relative32, resolve as resolve22 } from "node:path";
|
|
19784
19802
|
var __defProp3 = Object.defineProperty;
|
|
19785
19803
|
var __returnValue3 = (v) => v;
|
|
19786
19804
|
function __exportSetter3(name, newValue) {
|
|
@@ -20320,7 +20338,7 @@ function calculateBackoff(attempt, initialDelayMs, maxDelayMs) {
|
|
|
20320
20338
|
return Math.min(exponentialDelay + jitter, maxDelayMs);
|
|
20321
20339
|
}
|
|
20322
20340
|
function sleep(ms) {
|
|
20323
|
-
return new Promise((
|
|
20341
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
20324
20342
|
}
|
|
20325
20343
|
async function makeAWSRequest(options, retryOptions) {
|
|
20326
20344
|
const {
|
|
@@ -21040,6 +21058,15 @@ function resolveStorageBucketName(slug, environment, bucketKey, explicitBucket)
|
|
|
21040
21058
|
function resolveDeployBucketName(slug, environment) {
|
|
21041
21059
|
return `${slug}-${environment}-deploy`;
|
|
21042
21060
|
}
|
|
21061
|
+
function resolveServerlessAppStackName(config6, environment) {
|
|
21062
|
+
return `${config6.project.slug}-${environment}-app`;
|
|
21063
|
+
}
|
|
21064
|
+
function resolveServerlessArtifactBucketName(slug, environment) {
|
|
21065
|
+
return `${slug}-${environment}-deployments`;
|
|
21066
|
+
}
|
|
21067
|
+
function resolveServerlessAssetBucketName(slug, environment) {
|
|
21068
|
+
return `${slug}-${environment}-assets`;
|
|
21069
|
+
}
|
|
21043
21070
|
|
|
21044
21071
|
class DependencyGraph {
|
|
21045
21072
|
nodes = new Map;
|
|
@@ -39955,6 +39982,44 @@ function createNodeJsServerlessPreset(options) {
|
|
|
39955
39982
|
}
|
|
39956
39983
|
};
|
|
39957
39984
|
}
|
|
39985
|
+
function createServerlessNodePreset(options) {
|
|
39986
|
+
const {
|
|
39987
|
+
name,
|
|
39988
|
+
slug,
|
|
39989
|
+
entry,
|
|
39990
|
+
domain,
|
|
39991
|
+
runtime = "nodejs20.x",
|
|
39992
|
+
memory = 1024,
|
|
39993
|
+
build,
|
|
39994
|
+
deploy,
|
|
39995
|
+
assets,
|
|
39996
|
+
queues = true,
|
|
39997
|
+
scheduler = "on",
|
|
39998
|
+
region = "us-east-1"
|
|
39999
|
+
} = options;
|
|
40000
|
+
return {
|
|
40001
|
+
project: { name, slug, region },
|
|
40002
|
+
mode: "serverless",
|
|
40003
|
+
environments: {
|
|
40004
|
+
production: {
|
|
40005
|
+
type: "production",
|
|
40006
|
+
domain,
|
|
40007
|
+
app: {
|
|
40008
|
+
kind: "node",
|
|
40009
|
+
runtime,
|
|
40010
|
+
entry,
|
|
40011
|
+
memory,
|
|
40012
|
+
build,
|
|
40013
|
+
deploy,
|
|
40014
|
+
assets,
|
|
40015
|
+
queues,
|
|
40016
|
+
scheduler,
|
|
40017
|
+
domain
|
|
40018
|
+
}
|
|
40019
|
+
}
|
|
40020
|
+
}
|
|
40021
|
+
};
|
|
40022
|
+
}
|
|
39958
40023
|
function createFullStackAppPreset(options) {
|
|
39959
40024
|
const {
|
|
39960
40025
|
name,
|
|
@@ -41515,6 +41580,47 @@ function createLaravelPreset(options) {
|
|
|
41515
41580
|
}
|
|
41516
41581
|
};
|
|
41517
41582
|
}
|
|
41583
|
+
function createServerlessLaravelPreset(options) {
|
|
41584
|
+
const {
|
|
41585
|
+
name,
|
|
41586
|
+
slug,
|
|
41587
|
+
domain,
|
|
41588
|
+
layers,
|
|
41589
|
+
phpVersion = "8.3",
|
|
41590
|
+
architecture = "x86_64",
|
|
41591
|
+
memory = 1024,
|
|
41592
|
+
build,
|
|
41593
|
+
deploy = ["migrate --force"],
|
|
41594
|
+
cache: cache2 = "dynamodb",
|
|
41595
|
+
scheduler = "on",
|
|
41596
|
+
region = "us-east-1"
|
|
41597
|
+
} = options;
|
|
41598
|
+
return {
|
|
41599
|
+
project: { name, slug, region },
|
|
41600
|
+
mode: "serverless",
|
|
41601
|
+
environments: {
|
|
41602
|
+
production: {
|
|
41603
|
+
type: "production",
|
|
41604
|
+
domain,
|
|
41605
|
+
app: {
|
|
41606
|
+
kind: "php",
|
|
41607
|
+
runtime: "provided.al2023",
|
|
41608
|
+
phpVersion,
|
|
41609
|
+
architecture,
|
|
41610
|
+
layers,
|
|
41611
|
+
memory,
|
|
41612
|
+
build,
|
|
41613
|
+
deploy,
|
|
41614
|
+
assets: "public",
|
|
41615
|
+
queues: true,
|
|
41616
|
+
scheduler,
|
|
41617
|
+
cache: { driver: cache2 },
|
|
41618
|
+
domain
|
|
41619
|
+
}
|
|
41620
|
+
}
|
|
41621
|
+
}
|
|
41622
|
+
};
|
|
41623
|
+
}
|
|
41518
41624
|
function createDashboardSite(options) {
|
|
41519
41625
|
return {
|
|
41520
41626
|
root: options.root ?? "ui/dist",
|
|
@@ -43287,11 +43393,11 @@ var templateCache = new TemplateCache;
|
|
|
43287
43393
|
async function hashFile(filePath, options = {}) {
|
|
43288
43394
|
const algorithm = options.algorithm || "sha256";
|
|
43289
43395
|
const chunkSize = options.chunkSize || 65536;
|
|
43290
|
-
return new Promise((
|
|
43396
|
+
return new Promise((resolve14, reject) => {
|
|
43291
43397
|
const hash2 = createHash3(algorithm);
|
|
43292
43398
|
const stream = createReadStream4(filePath, { highWaterMark: chunkSize });
|
|
43293
43399
|
stream.on("data", (chunk) => hash2.update(chunk));
|
|
43294
|
-
stream.on("end", () =>
|
|
43400
|
+
stream.on("end", () => resolve14(hash2.digest("hex")));
|
|
43295
43401
|
stream.on("error", reject);
|
|
43296
43402
|
});
|
|
43297
43403
|
}
|
|
@@ -43493,7 +43599,7 @@ async function sequence(tasks) {
|
|
|
43493
43599
|
return results;
|
|
43494
43600
|
}
|
|
43495
43601
|
function sleep2(ms) {
|
|
43496
|
-
return new Promise((
|
|
43602
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
43497
43603
|
}
|
|
43498
43604
|
async function withTimeout(task, timeoutMs, timeoutMessage = "Operation timed out") {
|
|
43499
43605
|
return Promise.race([
|
|
@@ -43531,12 +43637,12 @@ class RateLimiter {
|
|
|
43531
43637
|
}
|
|
43532
43638
|
return sleep2(this.minInterval - timeSinceLastExecution);
|
|
43533
43639
|
}
|
|
43534
|
-
return new Promise((
|
|
43640
|
+
return new Promise((resolve14) => this.queue.push(resolve14));
|
|
43535
43641
|
}
|
|
43536
43642
|
processQueue() {
|
|
43537
43643
|
if (this.queue.length > 0 && this.running < this.maxConcurrent) {
|
|
43538
|
-
const
|
|
43539
|
-
|
|
43644
|
+
const resolve14 = this.queue.shift();
|
|
43645
|
+
resolve14();
|
|
43540
43646
|
}
|
|
43541
43647
|
}
|
|
43542
43648
|
stats() {
|
|
@@ -43847,6 +43953,27 @@ var cloud_config_schema_default = {
|
|
|
43847
43953
|
region: {
|
|
43848
43954
|
type: "string",
|
|
43849
43955
|
description: "AWS region override for this environment"
|
|
43956
|
+
},
|
|
43957
|
+
app: {
|
|
43958
|
+
type: "object",
|
|
43959
|
+
description: "Serverless application manifest (Laravel-Vapor-equivalent). Defining this opts the environment into the serverless app deploy pipeline (http/queue/cli Lambda functions, assets, build/deploy hooks).",
|
|
43960
|
+
properties: {
|
|
43961
|
+
runtime: { type: "string", description: "Lambda runtime (e.g. nodejs20.x, provided.al2023)" },
|
|
43962
|
+
kind: { type: "string", enum: ["node", "bun", "php"], description: "Application kind (drives packaging + runtime)" },
|
|
43963
|
+
entry: { type: "string", description: "Entry file exporting the request handler" },
|
|
43964
|
+
memory: { type: "number", description: "HTTP function memory in MB" },
|
|
43965
|
+
timeout: { type: "number", description: "HTTP request timeout in seconds" },
|
|
43966
|
+
gatewayVersion: { type: "number", enum: [1, 2], description: "API Gateway version (2 = HTTP API, 1 = REST)" },
|
|
43967
|
+
warm: { type: "number", description: "Keep-warm / provisioned concurrency count" },
|
|
43968
|
+
queues: { description: "Queue names (true = single default queue, false = disabled)" },
|
|
43969
|
+
scheduler: { type: "string", enum: ["off", "on", "sub-minute"], description: "Task scheduler mode" },
|
|
43970
|
+
build: { type: "array", items: { type: "string" }, description: "Commands run locally before packaging" },
|
|
43971
|
+
deploy: { type: "array", items: { type: "string" }, description: "Commands run remotely after activation (e.g. migrations)" },
|
|
43972
|
+
octane: { type: "boolean", description: "Persistent application mode (Laravel Octane)" },
|
|
43973
|
+
packaging: { type: "string", enum: ["zip", "image"], description: "Deployment package format (zip or container image)" },
|
|
43974
|
+
phpVersion: { type: "string", description: "PHP version for the runtime layer (kind: php)" },
|
|
43975
|
+
architecture: { type: "string", enum: ["x86_64", "arm64"], description: "CPU architecture" }
|
|
43976
|
+
}
|
|
43850
43977
|
}
|
|
43851
43978
|
}
|
|
43852
43979
|
},
|
|
@@ -45920,9 +46047,9 @@ class REPL {
|
|
|
45920
46047
|
this.running = false;
|
|
45921
46048
|
}
|
|
45922
46049
|
async readInput() {
|
|
45923
|
-
return new Promise((
|
|
46050
|
+
return new Promise((resolve14) => {
|
|
45924
46051
|
process.stdout.write(this.options.prompt || "> ");
|
|
45925
|
-
|
|
46052
|
+
resolve14("");
|
|
45926
46053
|
});
|
|
45927
46054
|
}
|
|
45928
46055
|
async executeCommand(input) {
|
|
@@ -50330,7 +50457,7 @@ class BlueGreenManager {
|
|
|
50330
50457
|
console.log(` Switching to: ${targetEnv}`);
|
|
50331
50458
|
console.log(`\\n1. Deploying to ${targetEnv} environment`);
|
|
50332
50459
|
if (!dryRun) {
|
|
50333
|
-
await new Promise((
|
|
50460
|
+
await new Promise((resolve14) => setTimeout(resolve14, 100));
|
|
50334
50461
|
}
|
|
50335
50462
|
console.log(`\\n2. Running health checks on ${targetEnv} environment`);
|
|
50336
50463
|
if (deployment2.healthCheckConfig) {
|
|
@@ -50356,7 +50483,7 @@ class BlueGreenManager {
|
|
|
50356
50483
|
}
|
|
50357
50484
|
console.log(`\\n4. Monitoring ${targetEnv} environment`);
|
|
50358
50485
|
if (!dryRun) {
|
|
50359
|
-
await new Promise((
|
|
50486
|
+
await new Promise((resolve14) => setTimeout(resolve14, 100));
|
|
50360
50487
|
}
|
|
50361
50488
|
result.success = true;
|
|
50362
50489
|
result.endTime = new Date;
|
|
@@ -50398,7 +50525,7 @@ class BlueGreenManager {
|
|
|
50398
50525
|
let consecutiveSuccesses = 0;
|
|
50399
50526
|
const maxAttempts = config22.healthyThreshold + 2;
|
|
50400
50527
|
for (let i = 0;i < maxAttempts; i++) {
|
|
50401
|
-
await new Promise((
|
|
50528
|
+
await new Promise((resolve14) => setTimeout(resolve14, 50));
|
|
50402
50529
|
const healthy = Math.random() > 0.1;
|
|
50403
50530
|
if (healthy) {
|
|
50404
50531
|
consecutiveSuccesses++;
|
|
@@ -50646,7 +50773,7 @@ class CanaryManager {
|
|
|
50646
50773
|
console.log(` [SKIPPED - DRY RUN]`);
|
|
50647
50774
|
return true;
|
|
50648
50775
|
}
|
|
50649
|
-
await new Promise((
|
|
50776
|
+
await new Promise((resolve14) => setTimeout(resolve14, 100));
|
|
50650
50777
|
const metrics = {
|
|
50651
50778
|
baselineErrorRate: Math.random() * 0.5,
|
|
50652
50779
|
canaryErrorRate: Math.random() * 0.8,
|
|
@@ -63219,7 +63346,7 @@ class DLQMonitoringManager {
|
|
|
63219
63346
|
job.status = "processing";
|
|
63220
63347
|
job.startedAt = new Date;
|
|
63221
63348
|
job.attempts++;
|
|
63222
|
-
await new Promise((
|
|
63349
|
+
await new Promise((resolve14) => setTimeout(resolve14, 100));
|
|
63223
63350
|
const success = Math.random() > 0.3;
|
|
63224
63351
|
job.status = success ? "success" : "failed";
|
|
63225
63352
|
job.completedAt = new Date;
|
|
@@ -63422,7 +63549,7 @@ class BatchProcessingManager {
|
|
|
63422
63549
|
async processMessage(message, config22) {
|
|
63423
63550
|
message.status = "processing";
|
|
63424
63551
|
const startTime = Date.now();
|
|
63425
|
-
await new Promise((
|
|
63552
|
+
await new Promise((resolve14) => setTimeout(resolve14, Math.random() * 100));
|
|
63426
63553
|
const processingTime = Date.now() - startTime;
|
|
63427
63554
|
message.processingTime = processingTime;
|
|
63428
63555
|
const success = Math.random() > 0.1;
|
|
@@ -63779,6 +63906,1115 @@ class QueueManagementManager {
|
|
|
63779
63906
|
}
|
|
63780
63907
|
}
|
|
63781
63908
|
var queueManagementManager = new QueueManagementManager;
|
|
63909
|
+
var CRC_TABLE = (() => {
|
|
63910
|
+
const table2 = [];
|
|
63911
|
+
for (let i = 0;i < 256; i++) {
|
|
63912
|
+
let c = i;
|
|
63913
|
+
for (let j = 0;j < 8; j++)
|
|
63914
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
63915
|
+
table2[i] = c >>> 0;
|
|
63916
|
+
}
|
|
63917
|
+
return table2;
|
|
63918
|
+
})();
|
|
63919
|
+
function crc32(data) {
|
|
63920
|
+
let crc = 4294967295;
|
|
63921
|
+
for (let i = 0;i < data.length; i++)
|
|
63922
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
63923
|
+
return (crc ^ 4294967295) >>> 0;
|
|
63924
|
+
}
|
|
63925
|
+
function toBuffer(data) {
|
|
63926
|
+
if (typeof data === "string")
|
|
63927
|
+
return Buffer.from(data, "utf-8");
|
|
63928
|
+
if (Buffer.isBuffer(data))
|
|
63929
|
+
return data;
|
|
63930
|
+
return Buffer.from(data);
|
|
63931
|
+
}
|
|
63932
|
+
function dosTimeDate(date) {
|
|
63933
|
+
const year = Math.max(1980, date.getFullYear());
|
|
63934
|
+
const time = (date.getHours() << 11 | date.getMinutes() << 5 | date.getSeconds() >> 1) & 65535;
|
|
63935
|
+
const d = (year - 1980 << 9 | date.getMonth() + 1 << 5 | date.getDate()) & 65535;
|
|
63936
|
+
return { time, date: d };
|
|
63937
|
+
}
|
|
63938
|
+
function createZip(entries) {
|
|
63939
|
+
const localParts = [];
|
|
63940
|
+
const centralParts = [];
|
|
63941
|
+
let offset = 0;
|
|
63942
|
+
for (const entry of entries) {
|
|
63943
|
+
const raw = toBuffer(entry.data);
|
|
63944
|
+
const compressed = deflateRawSync(raw);
|
|
63945
|
+
const crc = crc32(raw);
|
|
63946
|
+
const nameBuf = Buffer.from(entry.name.replace(/\\/g, "/"), "utf-8");
|
|
63947
|
+
const { time, date } = dosTimeDate(entry.date ?? new Date(0));
|
|
63948
|
+
const mode = entry.mode ?? 420;
|
|
63949
|
+
const local = Buffer.alloc(30 + nameBuf.length);
|
|
63950
|
+
local.writeUInt32LE(67324752, 0);
|
|
63951
|
+
local.writeUInt16LE(20, 4);
|
|
63952
|
+
local.writeUInt16LE(0, 6);
|
|
63953
|
+
local.writeUInt16LE(8, 8);
|
|
63954
|
+
local.writeUInt16LE(time, 10);
|
|
63955
|
+
local.writeUInt16LE(date, 12);
|
|
63956
|
+
local.writeUInt32LE(crc, 14);
|
|
63957
|
+
local.writeUInt32LE(compressed.length, 18);
|
|
63958
|
+
local.writeUInt32LE(raw.length, 22);
|
|
63959
|
+
local.writeUInt16LE(nameBuf.length, 26);
|
|
63960
|
+
local.writeUInt16LE(0, 28);
|
|
63961
|
+
nameBuf.copy(local, 30);
|
|
63962
|
+
const central = Buffer.alloc(46 + nameBuf.length);
|
|
63963
|
+
central.writeUInt32LE(33639248, 0);
|
|
63964
|
+
central.writeUInt16LE(798, 4);
|
|
63965
|
+
central.writeUInt16LE(20, 6);
|
|
63966
|
+
central.writeUInt16LE(0, 8);
|
|
63967
|
+
central.writeUInt16LE(8, 10);
|
|
63968
|
+
central.writeUInt16LE(time, 12);
|
|
63969
|
+
central.writeUInt16LE(date, 14);
|
|
63970
|
+
central.writeUInt32LE(crc, 16);
|
|
63971
|
+
central.writeUInt32LE(compressed.length, 20);
|
|
63972
|
+
central.writeUInt32LE(raw.length, 24);
|
|
63973
|
+
central.writeUInt16LE(nameBuf.length, 28);
|
|
63974
|
+
central.writeUInt16LE(0, 30);
|
|
63975
|
+
central.writeUInt16LE(0, 32);
|
|
63976
|
+
central.writeUInt16LE(0, 34);
|
|
63977
|
+
central.writeUInt16LE(0, 36);
|
|
63978
|
+
central.writeUInt32LE((mode & 65535) << 16, 38);
|
|
63979
|
+
central.writeUInt32LE(offset, 42);
|
|
63980
|
+
nameBuf.copy(central, 46);
|
|
63981
|
+
localParts.push(local, compressed);
|
|
63982
|
+
centralParts.push(central);
|
|
63983
|
+
offset += local.length + compressed.length;
|
|
63984
|
+
}
|
|
63985
|
+
const centralDir = Buffer.concat(centralParts);
|
|
63986
|
+
const end = Buffer.alloc(22);
|
|
63987
|
+
end.writeUInt32LE(101010256, 0);
|
|
63988
|
+
end.writeUInt16LE(0, 4);
|
|
63989
|
+
end.writeUInt16LE(0, 6);
|
|
63990
|
+
end.writeUInt16LE(entries.length, 8);
|
|
63991
|
+
end.writeUInt16LE(entries.length, 10);
|
|
63992
|
+
end.writeUInt32LE(centralDir.length, 12);
|
|
63993
|
+
end.writeUInt32LE(offset, 16);
|
|
63994
|
+
end.writeUInt16LE(0, 20);
|
|
63995
|
+
return Buffer.concat([...localParts, centralDir, end]);
|
|
63996
|
+
}
|
|
63997
|
+
function generateBootstrap(opts) {
|
|
63998
|
+
const adapter = opts.adapterImport ?? "./adapter";
|
|
63999
|
+
const entry = opts.entryImport.replace(/\\/g, "/");
|
|
64000
|
+
return `// Generated by ts-cloud — serverless app bootstrap. Do not edit.
|
|
64001
|
+
import * as __userModule from ${JSON.stringify(entry)}
|
|
64002
|
+
import { resolveApp, createHandlers } from ${JSON.stringify(adapter)}
|
|
64003
|
+
|
|
64004
|
+
const __app = resolveApp(__userModule)
|
|
64005
|
+
const __handlers = createHandlers(__app)
|
|
64006
|
+
|
|
64007
|
+
export const http = __handlers.http
|
|
64008
|
+
export const queue = __handlers.queue
|
|
64009
|
+
export const cli = __handlers.cli
|
|
64010
|
+
`;
|
|
64011
|
+
}
|
|
64012
|
+
function adapterSourcePath() {
|
|
64013
|
+
return join62(dirname22(fileURLToPath(import.meta.url)), "runtime", "adapter.ts");
|
|
64014
|
+
}
|
|
64015
|
+
function runBuildHooks(hooks, cwd, onStep) {
|
|
64016
|
+
for (const hook of hooks ?? []) {
|
|
64017
|
+
onStep?.(`build: ${hook}`);
|
|
64018
|
+
execSync(hook, { stdio: "inherit", cwd });
|
|
64019
|
+
}
|
|
64020
|
+
}
|
|
64021
|
+
function sha256(data) {
|
|
64022
|
+
return createHash4("sha256").update(data).digest("hex");
|
|
64023
|
+
}
|
|
64024
|
+
function artifactKey(slug, environment, hash2) {
|
|
64025
|
+
return `deployments/${slug}/${environment}/${hash2}.zip`;
|
|
64026
|
+
}
|
|
64027
|
+
async function packageServerlessApp(opts) {
|
|
64028
|
+
const projectRoot = resolve13(opts.projectRoot ?? process.cwd());
|
|
64029
|
+
const { app } = opts;
|
|
64030
|
+
if (!opts.skipBuild)
|
|
64031
|
+
runBuildHooks(app.build, projectRoot, opts.onStep);
|
|
64032
|
+
const entry = app.entry;
|
|
64033
|
+
if (!entry)
|
|
64034
|
+
throw new Error("serverless app: `entry` is required to package a Node/Bun application");
|
|
64035
|
+
const entryPath = isAbsolute3(entry) ? entry : join62(projectRoot, entry);
|
|
64036
|
+
const stage = mkdtempSync(join62(tmpdir(), "tscloud-pkg-"));
|
|
64037
|
+
try {
|
|
64038
|
+
cpSync(adapterSourcePath(), join62(stage, "adapter.ts"));
|
|
64039
|
+
const bootstrapPath = join62(stage, "bootstrap.ts");
|
|
64040
|
+
writeFileSync32(bootstrapPath, generateBootstrap({ entryImport: entryPath, adapterImport: "./adapter" }));
|
|
64041
|
+
opts.onStep?.("bundling application");
|
|
64042
|
+
const result = await Bun.build({
|
|
64043
|
+
entrypoints: [bootstrapPath],
|
|
64044
|
+
target: "node",
|
|
64045
|
+
format: "esm",
|
|
64046
|
+
minify: false,
|
|
64047
|
+
sourcemap: "none"
|
|
64048
|
+
});
|
|
64049
|
+
if (!result.success) {
|
|
64050
|
+
const logs = result.logs.map((l) => String(l)).join(`
|
|
64051
|
+
`);
|
|
64052
|
+
throw new Error(`serverless app bundle failed:
|
|
64053
|
+
${logs}`);
|
|
64054
|
+
}
|
|
64055
|
+
const output = result.outputs[0];
|
|
64056
|
+
const bundle = Buffer.from(await output.arrayBuffer());
|
|
64057
|
+
const handlerFile = "index";
|
|
64058
|
+
const zip = createZip([{ name: `${handlerFile}.mjs`, data: bundle }]);
|
|
64059
|
+
return {
|
|
64060
|
+
zip,
|
|
64061
|
+
bundle,
|
|
64062
|
+
sha256: sha256(zip),
|
|
64063
|
+
handlerFile,
|
|
64064
|
+
handlers: {
|
|
64065
|
+
http: app.handlers?.http ?? `${handlerFile}.http`,
|
|
64066
|
+
queue: app.handlers?.queue ?? `${handlerFile}.queue`,
|
|
64067
|
+
cli: app.handlers?.cli ?? `${handlerFile}.cli`
|
|
64068
|
+
},
|
|
64069
|
+
bundleBytes: bundle.length
|
|
64070
|
+
};
|
|
64071
|
+
} finally {
|
|
64072
|
+
rmSync(stage, { recursive: true, force: true });
|
|
64073
|
+
}
|
|
64074
|
+
}
|
|
64075
|
+
function resolveQueueNames(app, slug, env) {
|
|
64076
|
+
if (app.queues === false)
|
|
64077
|
+
return [];
|
|
64078
|
+
if (app.queues === undefined || app.queues === true)
|
|
64079
|
+
return [`${slug}-${env}-default`];
|
|
64080
|
+
return app.queues.map((q) => {
|
|
64081
|
+
const name = typeof q === "string" ? q : Object.keys(q)[0];
|
|
64082
|
+
return `${slug}-${env}-${name}`;
|
|
64083
|
+
});
|
|
64084
|
+
}
|
|
64085
|
+
function composeServerlessAppTemplate(opts) {
|
|
64086
|
+
const { app, environment, handlers } = opts;
|
|
64087
|
+
const slug = opts.config.project.slug;
|
|
64088
|
+
const runtime = app.runtime ?? "nodejs20.x";
|
|
64089
|
+
const architecture = app.architecture ?? "x86_64";
|
|
64090
|
+
const region = opts.config.project.region;
|
|
64091
|
+
const functionNames = {
|
|
64092
|
+
http: `${slug}-${environment}-http`,
|
|
64093
|
+
queue: `${slug}-${environment}-queue`,
|
|
64094
|
+
cli: `${slug}-${environment}-cli`
|
|
64095
|
+
};
|
|
64096
|
+
const queueNames = resolveQueueNames(app, slug, environment);
|
|
64097
|
+
const hasQueue = queueNames.length > 0;
|
|
64098
|
+
const imageMode = app.packaging === "image";
|
|
64099
|
+
const schedulerEnabled = (app.scheduler ?? "on") !== "off";
|
|
64100
|
+
const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
|
|
64101
|
+
const assetsEnabled = Boolean(app.assets);
|
|
64102
|
+
const assetsBucket = resolveServerlessAssetBucketName(slug, environment);
|
|
64103
|
+
const tmpStorage = app.tmpStorage ?? 512;
|
|
64104
|
+
const resources = {};
|
|
64105
|
+
const outputs = {};
|
|
64106
|
+
const inlinePolicies = [
|
|
64107
|
+
{
|
|
64108
|
+
PolicyName: "tscloud-serverless-app",
|
|
64109
|
+
PolicyDocument: {
|
|
64110
|
+
Version: "2012-10-17",
|
|
64111
|
+
Statement: [
|
|
64112
|
+
{
|
|
64113
|
+
Effect: "Allow",
|
|
64114
|
+
Action: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
|
|
64115
|
+
Resource: Fn2.sub("arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*")
|
|
64116
|
+
},
|
|
64117
|
+
{
|
|
64118
|
+
Effect: "Allow",
|
|
64119
|
+
Action: ["lambda:InvokeFunction"],
|
|
64120
|
+
Resource: Fn2.sub(`arn:aws:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${slug}-${environment}-*`)
|
|
64121
|
+
},
|
|
64122
|
+
{
|
|
64123
|
+
Effect: "Allow",
|
|
64124
|
+
Action: ["secretsmanager:GetSecretValue", "ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
|
|
64125
|
+
Resource: [
|
|
64126
|
+
Fn2.sub(`arn:aws:secretsmanager:\${AWS::Region}:\${AWS::AccountId}:secret:${slug}/${environment}/*`),
|
|
64127
|
+
Fn2.sub(`arn:aws:ssm:\${AWS::Region}:\${AWS::AccountId}:parameter/${slug}/${environment}/*`)
|
|
64128
|
+
]
|
|
64129
|
+
},
|
|
64130
|
+
{
|
|
64131
|
+
Effect: "Allow",
|
|
64132
|
+
Action: ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
|
|
64133
|
+
Resource: [
|
|
64134
|
+
Fn2.sub(`arn:aws:s3:::${assetsBucket}`),
|
|
64135
|
+
Fn2.sub(`arn:aws:s3:::${assetsBucket}/*`),
|
|
64136
|
+
...app.storage?.bucket ? [Fn2.sub(`arn:aws:s3:::${app.storage.bucket}`), Fn2.sub(`arn:aws:s3:::${app.storage.bucket}/*`)] : []
|
|
64137
|
+
]
|
|
64138
|
+
}
|
|
64139
|
+
]
|
|
64140
|
+
}
|
|
64141
|
+
}
|
|
64142
|
+
];
|
|
64143
|
+
if (hasQueue) {
|
|
64144
|
+
inlinePolicies[0].PolicyDocument.Statement.push({
|
|
64145
|
+
Effect: "Allow",
|
|
64146
|
+
Action: ["sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes", "sqs:GetQueueUrl"],
|
|
64147
|
+
Resource: Fn2.sub(`arn:aws:sqs:\${AWS::Region}:\${AWS::AccountId}:${slug}-${environment}-*`)
|
|
64148
|
+
});
|
|
64149
|
+
}
|
|
64150
|
+
if (cacheEnabled) {
|
|
64151
|
+
inlinePolicies[0].PolicyDocument.Statement.push({
|
|
64152
|
+
Effect: "Allow",
|
|
64153
|
+
Action: ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan", "dynamodb:BatchGetItem", "dynamodb:BatchWriteItem"],
|
|
64154
|
+
Resource: Fn2.sub(`arn:aws:dynamodb:\${AWS::Region}:\${AWS::AccountId}:table/${slug}-${environment}-cache*`)
|
|
64155
|
+
});
|
|
64156
|
+
}
|
|
64157
|
+
const managedPolicies = ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"];
|
|
64158
|
+
if (app.vpc?.subnets?.length)
|
|
64159
|
+
managedPolicies.push("arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole");
|
|
64160
|
+
resources.AppRole = {
|
|
64161
|
+
Type: "AWS::IAM::Role",
|
|
64162
|
+
Properties: {
|
|
64163
|
+
RoleName: `${slug}-${environment}-app-role`,
|
|
64164
|
+
AssumeRolePolicyDocument: {
|
|
64165
|
+
Version: "2012-10-17",
|
|
64166
|
+
Statement: [{ Effect: "Allow", Principal: { Service: "lambda.amazonaws.com" }, Action: "sts:AssumeRole" }]
|
|
64167
|
+
},
|
|
64168
|
+
ManagedPolicyArns: managedPolicies,
|
|
64169
|
+
Policies: inlinePolicies
|
|
64170
|
+
}
|
|
64171
|
+
};
|
|
64172
|
+
const baseEnv = (mode) => ({
|
|
64173
|
+
TSCLOUD_LAMBDA_MODE: mode,
|
|
64174
|
+
TSCLOUD_ENV: environment,
|
|
64175
|
+
...app.octane ? { TSCLOUD_OCTANE: "1" } : {},
|
|
64176
|
+
...cacheEnabled ? { TSCLOUD_CACHE_TABLE: `${slug}-${environment}-cache` } : {},
|
|
64177
|
+
...hasQueue ? { TSCLOUD_QUEUE: queueNames[0] } : {},
|
|
64178
|
+
...app.env ?? {}
|
|
64179
|
+
});
|
|
64180
|
+
const subnets = app.vpc?.subnets ?? [];
|
|
64181
|
+
const hasVpc = subnets.length > 0;
|
|
64182
|
+
const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy);
|
|
64183
|
+
if (needsDataVpc && !hasVpc) {
|
|
64184
|
+
throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy require app.vpc.subnets (private subnets) to be set.");
|
|
64185
|
+
}
|
|
64186
|
+
const vpcConfig = hasVpc ? {
|
|
64187
|
+
VpcConfig: {
|
|
64188
|
+
SubnetIds: subnets,
|
|
64189
|
+
SecurityGroupIds: [
|
|
64190
|
+
...app.vpc?.securityGroups ?? [],
|
|
64191
|
+
...needsDataVpc ? [Fn2.getAtt("DataSecurityGroup", "GroupId")] : []
|
|
64192
|
+
]
|
|
64193
|
+
}
|
|
64194
|
+
} : {};
|
|
64195
|
+
function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency) {
|
|
64196
|
+
resources[`${logicalId}LogGroup`] = {
|
|
64197
|
+
Type: "AWS::Logs::LogGroup",
|
|
64198
|
+
Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
|
|
64199
|
+
};
|
|
64200
|
+
const codeProps = imageMode ? {
|
|
64201
|
+
PackageType: "Image",
|
|
64202
|
+
Code: { ImageUri: Fn2.ref("ImageUri") },
|
|
64203
|
+
...app.kind === "php" ? {} : { ImageConfig: { Command: [handler8] } }
|
|
64204
|
+
} : {
|
|
64205
|
+
Runtime: runtime,
|
|
64206
|
+
Handler: handler8,
|
|
64207
|
+
Code: { S3Bucket: Fn2.ref("ArtifactBucket"), S3Key: Fn2.ref("ArtifactKey") },
|
|
64208
|
+
...opts.runtimeLayers?.length ? { Layers: opts.runtimeLayers } : {}
|
|
64209
|
+
};
|
|
64210
|
+
resources[logicalId] = {
|
|
64211
|
+
Type: "AWS::Lambda::Function",
|
|
64212
|
+
DependsOn: [`${logicalId}LogGroup`],
|
|
64213
|
+
Properties: {
|
|
64214
|
+
FunctionName: name,
|
|
64215
|
+
Architectures: [architecture],
|
|
64216
|
+
MemorySize: memory,
|
|
64217
|
+
Timeout: timeout,
|
|
64218
|
+
Role: Fn2.getAtt("AppRole", "Arn"),
|
|
64219
|
+
Environment: { Variables: baseEnv(mode) },
|
|
64220
|
+
EphemeralStorage: { Size: tmpStorage },
|
|
64221
|
+
...codeProps,
|
|
64222
|
+
...reservedConcurrency !== undefined ? { ReservedConcurrentExecutions: reservedConcurrency } : {},
|
|
64223
|
+
...vpcConfig
|
|
64224
|
+
}
|
|
64225
|
+
};
|
|
64226
|
+
}
|
|
64227
|
+
addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency);
|
|
64228
|
+
addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900);
|
|
64229
|
+
if (hasQueue)
|
|
64230
|
+
addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120);
|
|
64231
|
+
resources.HttpApi = {
|
|
64232
|
+
Type: "AWS::ApiGatewayV2::Api",
|
|
64233
|
+
Properties: {
|
|
64234
|
+
Name: `${slug}-${environment}`,
|
|
64235
|
+
ProtocolType: "HTTP"
|
|
64236
|
+
}
|
|
64237
|
+
};
|
|
64238
|
+
resources.HttpIntegration = {
|
|
64239
|
+
Type: "AWS::ApiGatewayV2::Integration",
|
|
64240
|
+
Properties: {
|
|
64241
|
+
ApiId: Fn2.ref("HttpApi"),
|
|
64242
|
+
IntegrationType: "AWS_PROXY",
|
|
64243
|
+
IntegrationUri: Fn2.getAtt("HttpFunction", "Arn"),
|
|
64244
|
+
PayloadFormatVersion: "2.0"
|
|
64245
|
+
}
|
|
64246
|
+
};
|
|
64247
|
+
resources.HttpRoute = {
|
|
64248
|
+
Type: "AWS::ApiGatewayV2::Route",
|
|
64249
|
+
Properties: {
|
|
64250
|
+
ApiId: Fn2.ref("HttpApi"),
|
|
64251
|
+
RouteKey: "$default",
|
|
64252
|
+
Target: Fn2.join("/", ["integrations", Fn2.ref("HttpIntegration")])
|
|
64253
|
+
}
|
|
64254
|
+
};
|
|
64255
|
+
resources.HttpStage = {
|
|
64256
|
+
Type: "AWS::ApiGatewayV2::Stage",
|
|
64257
|
+
Properties: {
|
|
64258
|
+
ApiId: Fn2.ref("HttpApi"),
|
|
64259
|
+
StageName: "$default",
|
|
64260
|
+
AutoDeploy: true
|
|
64261
|
+
}
|
|
64262
|
+
};
|
|
64263
|
+
resources.HttpPermission = {
|
|
64264
|
+
Type: "AWS::Lambda::Permission",
|
|
64265
|
+
Properties: {
|
|
64266
|
+
FunctionName: Fn2.ref("HttpFunction"),
|
|
64267
|
+
Action: "lambda:InvokeFunction",
|
|
64268
|
+
Principal: "apigateway.amazonaws.com",
|
|
64269
|
+
SourceArn: Fn2.sub("arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*")
|
|
64270
|
+
}
|
|
64271
|
+
};
|
|
64272
|
+
outputs.HttpApiEndpoint = {
|
|
64273
|
+
Description: "HTTP API endpoint",
|
|
64274
|
+
Value: Fn2.getAtt("HttpApi", "ApiEndpoint")
|
|
64275
|
+
};
|
|
64276
|
+
outputs.HttpApiId = { Description: "HTTP API id", Value: Fn2.ref("HttpApi") };
|
|
64277
|
+
if (hasQueue) {
|
|
64278
|
+
resources.AppQueueDlq = {
|
|
64279
|
+
Type: "AWS::SQS::Queue",
|
|
64280
|
+
Properties: {
|
|
64281
|
+
QueueName: `${slug}-${environment}-dlq`,
|
|
64282
|
+
MessageRetentionPeriod: 1209600
|
|
64283
|
+
}
|
|
64284
|
+
};
|
|
64285
|
+
queueNames.forEach((qName, i) => {
|
|
64286
|
+
const qId = `AppQueue${i}`;
|
|
64287
|
+
resources[qId] = {
|
|
64288
|
+
Type: "AWS::SQS::Queue",
|
|
64289
|
+
Properties: {
|
|
64290
|
+
QueueName: qName,
|
|
64291
|
+
VisibilityTimeout: Math.max(app.queueTimeout ?? 120, app.queueTimeout ?? 120),
|
|
64292
|
+
RedrivePolicy: {
|
|
64293
|
+
deadLetterTargetArn: Fn2.getAtt("AppQueueDlq", "Arn"),
|
|
64294
|
+
maxReceiveCount: app.queueTries ?? 3
|
|
64295
|
+
}
|
|
64296
|
+
}
|
|
64297
|
+
};
|
|
64298
|
+
resources[`${qId}Mapping`] = {
|
|
64299
|
+
Type: "AWS::Lambda::EventSourceMapping",
|
|
64300
|
+
Properties: {
|
|
64301
|
+
EventSourceArn: Fn2.getAtt(qId, "Arn"),
|
|
64302
|
+
FunctionName: Fn2.ref("QueueFunction"),
|
|
64303
|
+
BatchSize: 1,
|
|
64304
|
+
FunctionResponseTypes: ["ReportBatchItemFailures"],
|
|
64305
|
+
...app.queueConcurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, app.queueConcurrency) } } : {}
|
|
64306
|
+
}
|
|
64307
|
+
};
|
|
64308
|
+
outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${qName}`, Value: Fn2.ref(qId) };
|
|
64309
|
+
});
|
|
64310
|
+
}
|
|
64311
|
+
if (schedulerEnabled) {
|
|
64312
|
+
resources.SchedulerRule = {
|
|
64313
|
+
Type: "AWS::Events::Rule",
|
|
64314
|
+
Properties: {
|
|
64315
|
+
Name: `${slug}-${environment}-scheduler`,
|
|
64316
|
+
ScheduleExpression: "rate(1 minute)",
|
|
64317
|
+
State: "ENABLED",
|
|
64318
|
+
Targets: [{
|
|
64319
|
+
Id: "cli",
|
|
64320
|
+
Arn: Fn2.getAtt("CliFunction", "Arn"),
|
|
64321
|
+
Input: JSON.stringify({ command: "schedule:run" })
|
|
64322
|
+
}]
|
|
64323
|
+
}
|
|
64324
|
+
};
|
|
64325
|
+
resources.SchedulerPermission = {
|
|
64326
|
+
Type: "AWS::Lambda::Permission",
|
|
64327
|
+
Properties: {
|
|
64328
|
+
FunctionName: Fn2.ref("CliFunction"),
|
|
64329
|
+
Action: "lambda:InvokeFunction",
|
|
64330
|
+
Principal: "events.amazonaws.com",
|
|
64331
|
+
SourceArn: Fn2.getAtt("SchedulerRule", "Arn")
|
|
64332
|
+
}
|
|
64333
|
+
};
|
|
64334
|
+
}
|
|
64335
|
+
if (app.warm && app.warm > 0) {
|
|
64336
|
+
const TARGETS_PER_RULE = 5;
|
|
64337
|
+
const ruleCount = Math.ceil(app.warm / TARGETS_PER_RULE);
|
|
64338
|
+
let warmed = 0;
|
|
64339
|
+
for (let r = 0;r < ruleCount; r++) {
|
|
64340
|
+
const targets = Math.min(TARGETS_PER_RULE, app.warm - warmed);
|
|
64341
|
+
resources[`WarmerRule${r}`] = {
|
|
64342
|
+
Type: "AWS::Events::Rule",
|
|
64343
|
+
Properties: {
|
|
64344
|
+
Name: `${slug}-${environment}-warmer-${r}`,
|
|
64345
|
+
ScheduleExpression: "rate(5 minutes)",
|
|
64346
|
+
State: "ENABLED",
|
|
64347
|
+
Targets: Array.from({ length: targets }, (_, i) => ({
|
|
64348
|
+
Id: `warm-${r}-${i}`,
|
|
64349
|
+
Arn: Fn2.getAtt("HttpFunction", "Arn"),
|
|
64350
|
+
Input: JSON.stringify({ warmer: true })
|
|
64351
|
+
}))
|
|
64352
|
+
}
|
|
64353
|
+
};
|
|
64354
|
+
warmed += targets;
|
|
64355
|
+
}
|
|
64356
|
+
resources.WarmerPermission = {
|
|
64357
|
+
Type: "AWS::Lambda::Permission",
|
|
64358
|
+
Properties: {
|
|
64359
|
+
FunctionName: Fn2.ref("HttpFunction"),
|
|
64360
|
+
Action: "lambda:InvokeFunction",
|
|
64361
|
+
Principal: "events.amazonaws.com",
|
|
64362
|
+
SourceArn: Fn2.sub(`arn:aws:events:\${AWS::Region}:\${AWS::AccountId}:rule/${slug}-${environment}-warmer-*`)
|
|
64363
|
+
}
|
|
64364
|
+
};
|
|
64365
|
+
}
|
|
64366
|
+
if (cacheEnabled) {
|
|
64367
|
+
resources.CacheTable = {
|
|
64368
|
+
Type: "AWS::DynamoDB::Table",
|
|
64369
|
+
Properties: {
|
|
64370
|
+
TableName: `${slug}-${environment}-cache`,
|
|
64371
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
64372
|
+
AttributeDefinitions: [{ AttributeName: "key", AttributeType: "S" }],
|
|
64373
|
+
KeySchema: [{ AttributeName: "key", KeyType: "HASH" }],
|
|
64374
|
+
TimeToLiveSpecification: { AttributeName: "expires_at", Enabled: true }
|
|
64375
|
+
}
|
|
64376
|
+
};
|
|
64377
|
+
outputs.CacheTableName = { Description: "DynamoDB cache table", Value: Fn2.ref("CacheTable") };
|
|
64378
|
+
}
|
|
64379
|
+
if (assetsEnabled) {
|
|
64380
|
+
resources.AssetsBucket = {
|
|
64381
|
+
Type: "AWS::S3::Bucket",
|
|
64382
|
+
Properties: {
|
|
64383
|
+
BucketName: assetsBucket,
|
|
64384
|
+
PublicAccessBlockConfiguration: {
|
|
64385
|
+
BlockPublicAcls: true,
|
|
64386
|
+
BlockPublicPolicy: true,
|
|
64387
|
+
IgnorePublicAcls: true,
|
|
64388
|
+
RestrictPublicBuckets: true
|
|
64389
|
+
}
|
|
64390
|
+
}
|
|
64391
|
+
};
|
|
64392
|
+
resources.AssetsOAC = {
|
|
64393
|
+
Type: "AWS::CloudFront::OriginAccessControl",
|
|
64394
|
+
Properties: {
|
|
64395
|
+
OriginAccessControlConfig: {
|
|
64396
|
+
Name: `${slug}-${environment}-assets-oac`,
|
|
64397
|
+
OriginAccessControlOriginType: "s3",
|
|
64398
|
+
SigningBehavior: "always",
|
|
64399
|
+
SigningProtocol: "sigv4"
|
|
64400
|
+
}
|
|
64401
|
+
}
|
|
64402
|
+
};
|
|
64403
|
+
resources.AssetsDistribution = {
|
|
64404
|
+
Type: "AWS::CloudFront::Distribution",
|
|
64405
|
+
Properties: {
|
|
64406
|
+
DistributionConfig: {
|
|
64407
|
+
Enabled: true,
|
|
64408
|
+
DefaultCacheBehavior: {
|
|
64409
|
+
TargetOriginId: "assets",
|
|
64410
|
+
ViewerProtocolPolicy: "redirect-to-https",
|
|
64411
|
+
Compress: true,
|
|
64412
|
+
CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6"
|
|
64413
|
+
},
|
|
64414
|
+
Origins: [{
|
|
64415
|
+
Id: "assets",
|
|
64416
|
+
DomainName: Fn2.getAtt("AssetsBucket", "RegionalDomainName"),
|
|
64417
|
+
OriginAccessControlId: Fn2.ref("AssetsOAC"),
|
|
64418
|
+
S3OriginConfig: { OriginAccessIdentity: "" }
|
|
64419
|
+
}]
|
|
64420
|
+
}
|
|
64421
|
+
}
|
|
64422
|
+
};
|
|
64423
|
+
resources.AssetsBucketPolicy = {
|
|
64424
|
+
Type: "AWS::S3::BucketPolicy",
|
|
64425
|
+
Properties: {
|
|
64426
|
+
Bucket: Fn2.ref("AssetsBucket"),
|
|
64427
|
+
PolicyDocument: {
|
|
64428
|
+
Version: "2012-10-17",
|
|
64429
|
+
Statement: [{
|
|
64430
|
+
Effect: "Allow",
|
|
64431
|
+
Principal: { Service: "cloudfront.amazonaws.com" },
|
|
64432
|
+
Action: "s3:GetObject",
|
|
64433
|
+
Resource: Fn2.sub(`arn:aws:s3:::${assetsBucket}/*`),
|
|
64434
|
+
Condition: { StringEquals: { "AWS:SourceArn": Fn2.sub("arn:aws:cloudfront::${AWS::AccountId}:distribution/${AssetsDistribution}") } }
|
|
64435
|
+
}]
|
|
64436
|
+
}
|
|
64437
|
+
}
|
|
64438
|
+
};
|
|
64439
|
+
outputs.AssetsBucketName = { Description: "Assets bucket", Value: Fn2.ref("AssetsBucket") };
|
|
64440
|
+
outputs.AssetsCdnDomain = { Description: "Assets CloudFront domain", Value: Fn2.getAtt("AssetsDistribution", "DomainName") };
|
|
64441
|
+
}
|
|
64442
|
+
if (app.firewall?.enabled) {
|
|
64443
|
+
const wafRules = [];
|
|
64444
|
+
let priority = 0;
|
|
64445
|
+
if (app.firewall.rateLimit) {
|
|
64446
|
+
wafRules.push({
|
|
64447
|
+
Name: "rate-limit",
|
|
64448
|
+
Priority: priority++,
|
|
64449
|
+
Action: { Block: {} },
|
|
64450
|
+
Statement: { RateBasedStatement: { Limit: app.firewall.rateLimit, AggregateKeyType: "IP" } },
|
|
64451
|
+
VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-rate` }
|
|
64452
|
+
});
|
|
64453
|
+
}
|
|
64454
|
+
const managed = {
|
|
64455
|
+
sqlInjection: "AWSManagedRulesSQLiRuleSet",
|
|
64456
|
+
xss: "AWSManagedRulesCommonRuleSet",
|
|
64457
|
+
common: "AWSManagedRulesCommonRuleSet",
|
|
64458
|
+
botControl: "AWSManagedRulesBotControlRuleSet",
|
|
64459
|
+
ipReputation: "AWSManagedRulesAmazonIpReputationList"
|
|
64460
|
+
};
|
|
64461
|
+
for (const rule of app.firewall.rules ?? []) {
|
|
64462
|
+
const name = managed[rule];
|
|
64463
|
+
if (!name)
|
|
64464
|
+
continue;
|
|
64465
|
+
wafRules.push({
|
|
64466
|
+
Name: `managed-${rule}`,
|
|
64467
|
+
Priority: priority++,
|
|
64468
|
+
OverrideAction: { None: {} },
|
|
64469
|
+
Statement: { ManagedRuleGroupStatement: { VendorName: "AWS", Name: name } },
|
|
64470
|
+
VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-${rule}` }
|
|
64471
|
+
});
|
|
64472
|
+
}
|
|
64473
|
+
resources.WebAcl = {
|
|
64474
|
+
Type: "AWS::WAFv2::WebACL",
|
|
64475
|
+
Properties: {
|
|
64476
|
+
Name: `${slug}-${environment}-waf`,
|
|
64477
|
+
Scope: "REGIONAL",
|
|
64478
|
+
DefaultAction: { Allow: {} },
|
|
64479
|
+
Rules: wafRules,
|
|
64480
|
+
VisibilityConfig: { SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: `${slug}-${environment}-waf` }
|
|
64481
|
+
}
|
|
64482
|
+
};
|
|
64483
|
+
resources.WebAclAssociation = {
|
|
64484
|
+
Type: "AWS::WAFv2::WebACLAssociation",
|
|
64485
|
+
DependsOn: ["HttpStage"],
|
|
64486
|
+
Properties: {
|
|
64487
|
+
ResourceArn: Fn2.sub("arn:aws:apigateway:${AWS::Region}::/apis/${HttpApi}/stages/$default"),
|
|
64488
|
+
WebACLArn: Fn2.getAtt("WebAcl", "Arn")
|
|
64489
|
+
}
|
|
64490
|
+
};
|
|
64491
|
+
}
|
|
64492
|
+
if (hasVpc && needsDataVpc) {
|
|
64493
|
+
resources.DataSecurityGroup = {
|
|
64494
|
+
Type: "AWS::EC2::SecurityGroup",
|
|
64495
|
+
Properties: {
|
|
64496
|
+
GroupDescription: `${slug}-${environment} serverless data access`,
|
|
64497
|
+
SecurityGroupIngress: [{ IpProtocol: "-1", CidrIp: "10.0.0.0/8" }]
|
|
64498
|
+
}
|
|
64499
|
+
};
|
|
64500
|
+
}
|
|
64501
|
+
if (app.cache?.driver === "elasticache") {
|
|
64502
|
+
resources.CacheSubnetGroup = {
|
|
64503
|
+
Type: "AWS::ElastiCache::SubnetGroup",
|
|
64504
|
+
Properties: { Description: `${slug}-${environment} cache subnets`, SubnetIds: subnets }
|
|
64505
|
+
};
|
|
64506
|
+
resources.CacheCluster = {
|
|
64507
|
+
Type: "AWS::ElastiCache::ReplicationGroup",
|
|
64508
|
+
Properties: {
|
|
64509
|
+
ReplicationGroupId: `${slug}-${environment}-cache`,
|
|
64510
|
+
ReplicationGroupDescription: `${slug}-${environment} redis`,
|
|
64511
|
+
Engine: "redis",
|
|
64512
|
+
CacheNodeType: "cache.t4g.micro",
|
|
64513
|
+
NumCacheClusters: 1,
|
|
64514
|
+
AutomaticFailoverEnabled: false,
|
|
64515
|
+
CacheSubnetGroupName: Fn2.ref("CacheSubnetGroup"),
|
|
64516
|
+
SecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")],
|
|
64517
|
+
TransitEncryptionEnabled: false
|
|
64518
|
+
}
|
|
64519
|
+
};
|
|
64520
|
+
outputs.CacheEndpoint = { Description: "Redis primary endpoint", Value: Fn2.getAtt("CacheCluster", "PrimaryEndPoint.Address") };
|
|
64521
|
+
}
|
|
64522
|
+
if (app.database?.connection === "aurora-serverless") {
|
|
64523
|
+
resources.DbSubnetGroup = {
|
|
64524
|
+
Type: "AWS::RDS::DBSubnetGroup",
|
|
64525
|
+
Properties: { DBSubnetGroupDescription: `${slug}-${environment} db subnets`, SubnetIds: subnets }
|
|
64526
|
+
};
|
|
64527
|
+
resources.DbSecret = {
|
|
64528
|
+
Type: "AWS::SecretsManager::Secret",
|
|
64529
|
+
Properties: {
|
|
64530
|
+
Name: `${slug}/${environment}/db`,
|
|
64531
|
+
GenerateSecretString: {
|
|
64532
|
+
SecretStringTemplate: JSON.stringify({ username: "app" }),
|
|
64533
|
+
GenerateStringKey: "password",
|
|
64534
|
+
PasswordLength: 32,
|
|
64535
|
+
ExcludePunctuation: true
|
|
64536
|
+
}
|
|
64537
|
+
}
|
|
64538
|
+
};
|
|
64539
|
+
resources.DbCluster = {
|
|
64540
|
+
Type: "AWS::RDS::DBCluster",
|
|
64541
|
+
Properties: {
|
|
64542
|
+
Engine: "aurora-mysql",
|
|
64543
|
+
EngineMode: "provisioned",
|
|
64544
|
+
DBClusterIdentifier: `${slug}-${environment}-db`,
|
|
64545
|
+
MasterUsername: Fn2.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:username}}"),
|
|
64546
|
+
MasterUserPassword: Fn2.sub("{{resolve:secretsmanager:${DbSecret}:SecretString:password}}"),
|
|
64547
|
+
ServerlessV2ScalingConfiguration: { MinCapacity: 0.5, MaxCapacity: 4 },
|
|
64548
|
+
DBSubnetGroupName: Fn2.ref("DbSubnetGroup"),
|
|
64549
|
+
VpcSecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")]
|
|
64550
|
+
}
|
|
64551
|
+
};
|
|
64552
|
+
resources.DbInstance = {
|
|
64553
|
+
Type: "AWS::RDS::DBInstance",
|
|
64554
|
+
Properties: {
|
|
64555
|
+
Engine: "aurora-mysql",
|
|
64556
|
+
DBInstanceClass: "db.serverless",
|
|
64557
|
+
DBClusterIdentifier: Fn2.ref("DbCluster")
|
|
64558
|
+
}
|
|
64559
|
+
};
|
|
64560
|
+
outputs.DbEndpoint = { Description: "Aurora cluster endpoint", Value: Fn2.getAtt("DbCluster", "Endpoint.Address") };
|
|
64561
|
+
}
|
|
64562
|
+
if (app.rdsProxy && app.database?.connection === "aurora-serverless") {
|
|
64563
|
+
resources.DbProxyRole = {
|
|
64564
|
+
Type: "AWS::IAM::Role",
|
|
64565
|
+
Properties: {
|
|
64566
|
+
AssumeRolePolicyDocument: {
|
|
64567
|
+
Version: "2012-10-17",
|
|
64568
|
+
Statement: [{ Effect: "Allow", Principal: { Service: "rds.amazonaws.com" }, Action: "sts:AssumeRole" }]
|
|
64569
|
+
},
|
|
64570
|
+
Policies: [{
|
|
64571
|
+
PolicyName: "read-db-secret",
|
|
64572
|
+
PolicyDocument: {
|
|
64573
|
+
Version: "2012-10-17",
|
|
64574
|
+
Statement: [{ Effect: "Allow", Action: ["secretsmanager:GetSecretValue"], Resource: Fn2.ref("DbSecret") }]
|
|
64575
|
+
}
|
|
64576
|
+
}]
|
|
64577
|
+
}
|
|
64578
|
+
};
|
|
64579
|
+
resources.DbProxy = {
|
|
64580
|
+
Type: "AWS::RDS::DBProxy",
|
|
64581
|
+
Properties: {
|
|
64582
|
+
DBProxyName: typeof app.rdsProxy === "object" && app.rdsProxy.name ? app.rdsProxy.name : `${slug}-${environment}-proxy`,
|
|
64583
|
+
EngineFamily: "MYSQL",
|
|
64584
|
+
RoleArn: Fn2.getAtt("DbProxyRole", "Arn"),
|
|
64585
|
+
Auth: [{ AuthScheme: "SECRETS", SecretArn: Fn2.ref("DbSecret"), IAMAuth: "DISABLED" }],
|
|
64586
|
+
VpcSubnetIds: subnets,
|
|
64587
|
+
VpcSecurityGroupIds: [Fn2.getAtt("DataSecurityGroup", "GroupId")],
|
|
64588
|
+
RequireTLS: false
|
|
64589
|
+
}
|
|
64590
|
+
};
|
|
64591
|
+
outputs.DbProxyEndpoint = { Description: "RDS Proxy endpoint", Value: Fn2.getAtt("DbProxy", "Endpoint") };
|
|
64592
|
+
}
|
|
64593
|
+
outputs.HttpFunctionName = { Description: "HTTP function name", Value: Fn2.ref("HttpFunction") };
|
|
64594
|
+
outputs.CliFunctionName = { Description: "CLI function name", Value: Fn2.ref("CliFunction") };
|
|
64595
|
+
if (hasQueue)
|
|
64596
|
+
outputs.QueueFunctionName = { Description: "Queue function name", Value: Fn2.ref("QueueFunction") };
|
|
64597
|
+
const template = {
|
|
64598
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
64599
|
+
Description: `Serverless application for ${opts.config.project.name} (${slug}-${environment})`,
|
|
64600
|
+
Parameters: imageMode ? {
|
|
64601
|
+
ImageUri: { Type: "String", Description: "ECR image URI of the deployment artifact" }
|
|
64602
|
+
} : {
|
|
64603
|
+
ArtifactBucket: { Type: "String", Description: "S3 bucket holding the deployment artifact" },
|
|
64604
|
+
ArtifactKey: { Type: "String", Description: "S3 key of the deployment artifact (zip)" }
|
|
64605
|
+
},
|
|
64606
|
+
Resources: resources,
|
|
64607
|
+
Outputs: outputs
|
|
64608
|
+
};
|
|
64609
|
+
const resourceSummary = {};
|
|
64610
|
+
for (const r of Object.values(resources)) {
|
|
64611
|
+
resourceSummary[r.Type] = (resourceSummary[r.Type] ?? 0) + 1;
|
|
64612
|
+
}
|
|
64613
|
+
return { template, functionNames, queueNames, resourceSummary };
|
|
64614
|
+
}
|
|
64615
|
+
var PHP_LAYER_EXTENSIONS = [
|
|
64616
|
+
"cli",
|
|
64617
|
+
"fpm",
|
|
64618
|
+
"mbstring",
|
|
64619
|
+
"xml",
|
|
64620
|
+
"pdo",
|
|
64621
|
+
"mysqlnd",
|
|
64622
|
+
"gd",
|
|
64623
|
+
"bcmath",
|
|
64624
|
+
"intl",
|
|
64625
|
+
"opcache",
|
|
64626
|
+
"sodium",
|
|
64627
|
+
"process",
|
|
64628
|
+
"pecl-redis6",
|
|
64629
|
+
"pecl-apcu",
|
|
64630
|
+
"pgsql"
|
|
64631
|
+
];
|
|
64632
|
+
function phpLayerPackages(phpVersion) {
|
|
64633
|
+
const scl = `php${phpVersion.replace(".", "")}`;
|
|
64634
|
+
return PHP_LAYER_EXTENSIONS.map((ext) => `${scl}-php-${ext}`);
|
|
64635
|
+
}
|
|
64636
|
+
function phpLayerBuildStage(phpVersion, asName) {
|
|
64637
|
+
const scl = `php${phpVersion.replace(".", "")}`;
|
|
64638
|
+
const packages = phpLayerPackages(phpVersion).join(" \\\n ");
|
|
64639
|
+
const sclRoot = `/opt/remi/${scl}/root`;
|
|
64640
|
+
const from = asName ? `FROM amazonlinux:2023 AS ${asName}` : "FROM amazonlinux:2023";
|
|
64641
|
+
return `${from}
|
|
64642
|
+
|
|
64643
|
+
# Remi provides version-isolated PHP SCL packages for EL9 (AL2023 compatible).
|
|
64644
|
+
RUN dnf -y install dnf-plugins-core 'dnf-command(config-manager)' && \\
|
|
64645
|
+
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm && \\
|
|
64646
|
+
dnf -y update && \\
|
|
64647
|
+
dnf -y install \\
|
|
64648
|
+
${packages} \\
|
|
64649
|
+
findutils tar gzip && \\
|
|
64650
|
+
dnf clean all
|
|
64651
|
+
|
|
64652
|
+
# Relocate PHP + php-fpm + extensions and their shared libs under /opt.
|
|
64653
|
+
RUN set -eux; \\
|
|
64654
|
+
mkdir -p /opt/php/bin /opt/php/sbin /opt/php/lib /opt/php/lib/php/modules /opt/php/etc/php.d /opt/tscloud; \\
|
|
64655
|
+
cp ${sclRoot}/usr/bin/php /opt/php/bin/php; \\
|
|
64656
|
+
cp ${sclRoot}/usr/sbin/php-fpm /opt/php/sbin/php-fpm; \\
|
|
64657
|
+
EXT_DIR="$(${sclRoot}/usr/bin/php -r 'echo ini_get("extension_dir");')"; \\
|
|
64658
|
+
cp -a "$EXT_DIR"/*.so /opt/php/lib/php/modules/ || true; \\
|
|
64659
|
+
cp -a ${sclRoot}/etc/php.d/*.ini /opt/php/etc/php.d/ 2>/dev/null || true; \\
|
|
64660
|
+
# Copy shared-library dependencies of php, php-fpm, and the extension modules.
|
|
64661
|
+
for bin in /opt/php/bin/php /opt/php/sbin/php-fpm /opt/php/lib/php/modules/*.so; do \\
|
|
64662
|
+
ldd "$bin" 2>/dev/null | awk '/=>/{print $3}/ld-linux/{print $1}' | sort -u | while read -r lib; do \\
|
|
64663
|
+
[ -f "$lib" ] && cp -Ln "$lib" /opt/php/lib/ || true; \\
|
|
64664
|
+
done; \\
|
|
64665
|
+
done
|
|
64666
|
+
|
|
64667
|
+
# Point PHP at the relocated config + extension dir.
|
|
64668
|
+
RUN printf 'extension_dir=/opt/php/lib/php/modules\\n' > /opt/php/etc/php.ini && \\
|
|
64669
|
+
cat /opt/php/etc/php.d/*.ini >> /opt/php/etc/php.ini 2>/dev/null || true
|
|
64670
|
+
ENV PHP_INI_SCAN_DIR=/opt/php/etc/php.d
|
|
64671
|
+
|
|
64672
|
+
# Runtime assets (bootstrap, runtime loops, fpm config) are added by the build
|
|
64673
|
+
# orchestrator after the image is produced. Export /opt as the layer payload.
|
|
64674
|
+
CMD ["true"]
|
|
64675
|
+
`;
|
|
64676
|
+
}
|
|
64677
|
+
function generatePhpLayerDockerfile(options = {}) {
|
|
64678
|
+
const phpVersion = options.phpVersion ?? "8.3";
|
|
64679
|
+
return `# ts-cloud PHP ${phpVersion} runtime layer (generated) — AWS Lambda provided.al2023.
|
|
64680
|
+
${phpLayerBuildStage(phpVersion)}`;
|
|
64681
|
+
}
|
|
64682
|
+
function generateAppImageDockerfile(options) {
|
|
64683
|
+
if (options.kind === "php") {
|
|
64684
|
+
const phpVersion = options.phpVersion ?? "8.3";
|
|
64685
|
+
return `# ts-cloud serverless PHP app image (generated, multi-stage).
|
|
64686
|
+
${phpLayerBuildStage(phpVersion, "phpbuild")}
|
|
64687
|
+
|
|
64688
|
+
FROM public.ecr.aws/lambda/provided:al2023
|
|
64689
|
+
# Bake the relocated PHP runtime from the build stage.
|
|
64690
|
+
COPY --from=phpbuild /opt/ /opt/
|
|
64691
|
+
# ts-cloud runtime assets (bootstrap, runtime loops, fpm config).
|
|
64692
|
+
COPY runtime/ /opt/
|
|
64693
|
+
# The application source tree.
|
|
64694
|
+
COPY app/ /var/task/
|
|
64695
|
+
# /opt/bootstrap is the runtime entrypoint; mode comes from TSCLOUD_LAMBDA_MODE.
|
|
64696
|
+
ENTRYPOINT [ "/opt/bootstrap" ]
|
|
64697
|
+
`;
|
|
64698
|
+
}
|
|
64699
|
+
const nodeMajor = options.nodeMajor ?? "20";
|
|
64700
|
+
return `# ts-cloud serverless Node app image (generated).
|
|
64701
|
+
FROM public.ecr.aws/lambda/nodejs:${nodeMajor}
|
|
64702
|
+
|
|
64703
|
+
# The bundled handler artifact (index.mjs) at the Lambda task root.
|
|
64704
|
+
COPY app/ \${LAMBDA_TASK_ROOT}/
|
|
64705
|
+
|
|
64706
|
+
# Per-function CMD (e.g. index.http) is supplied via ImageConfig.Command.
|
|
64707
|
+
CMD [ "index.http" ]
|
|
64708
|
+
`;
|
|
64709
|
+
}
|
|
64710
|
+
function resolveApp(mod) {
|
|
64711
|
+
const m = mod;
|
|
64712
|
+
const def = m?.default ?? m;
|
|
64713
|
+
if (typeof def === "function")
|
|
64714
|
+
return { fetch: def };
|
|
64715
|
+
return {
|
|
64716
|
+
fetch: def?.fetch ?? m?.fetch,
|
|
64717
|
+
queue: def?.queue ?? m?.queue,
|
|
64718
|
+
cli: def?.cli ?? m?.cli
|
|
64719
|
+
};
|
|
64720
|
+
}
|
|
64721
|
+
var TEXT_CONTENT = /^(?:text\/|application\/(?:json|xml|javascript|graphql|x-www-form-urlencoded|.*\+json|.*\+xml)|image\/svg)/i;
|
|
64722
|
+
function isTextContentType(contentType) {
|
|
64723
|
+
if (!contentType)
|
|
64724
|
+
return true;
|
|
64725
|
+
return TEXT_CONTENT.test(contentType);
|
|
64726
|
+
}
|
|
64727
|
+
function readMaintenance(opts) {
|
|
64728
|
+
if (opts?.maintenance)
|
|
64729
|
+
return opts.maintenance;
|
|
64730
|
+
const env = globalThis.process?.env ?? {};
|
|
64731
|
+
return {
|
|
64732
|
+
enabled: env.MAINTENANCE_MODE === "1" || env.MAINTENANCE_MODE === "true",
|
|
64733
|
+
bypassSecret: env.MAINTENANCE_BYPASS_SECRET
|
|
64734
|
+
};
|
|
64735
|
+
}
|
|
64736
|
+
function eventToRequest(event) {
|
|
64737
|
+
const host = event.requestContext?.domainName ?? "localhost";
|
|
64738
|
+
const query = event.rawQueryString ? `?${event.rawQueryString}` : "";
|
|
64739
|
+
const url = `https://${host}${event.rawPath || "/"}${query}`;
|
|
64740
|
+
const headers = new Headers;
|
|
64741
|
+
for (const [key, value] of Object.entries(event.headers ?? {})) {
|
|
64742
|
+
if (value !== undefined)
|
|
64743
|
+
headers.set(key, value);
|
|
64744
|
+
}
|
|
64745
|
+
if (event.cookies?.length)
|
|
64746
|
+
headers.set("cookie", event.cookies.join("; "));
|
|
64747
|
+
const method = event.requestContext.http.method;
|
|
64748
|
+
let body;
|
|
64749
|
+
if (event.body !== undefined && method !== "GET" && method !== "HEAD") {
|
|
64750
|
+
body = event.isBase64Encoded ? new Uint8Array(Buffer.from(event.body, "base64")) : new TextEncoder().encode(event.body);
|
|
64751
|
+
}
|
|
64752
|
+
return new Request(url, { method, headers, body });
|
|
64753
|
+
}
|
|
64754
|
+
async function responseToResult(response) {
|
|
64755
|
+
const headers = {};
|
|
64756
|
+
const cookies = [];
|
|
64757
|
+
const setCookies = typeof response.headers.getSetCookie === "function" ? response.headers.getSetCookie() : [];
|
|
64758
|
+
for (const c of setCookies)
|
|
64759
|
+
cookies.push(c);
|
|
64760
|
+
response.headers.forEach((value, key) => {
|
|
64761
|
+
if (key.toLowerCase() === "set-cookie")
|
|
64762
|
+
return;
|
|
64763
|
+
headers[key] = value;
|
|
64764
|
+
});
|
|
64765
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
64766
|
+
const textual = isTextContentType(response.headers.get("content-type"));
|
|
64767
|
+
return {
|
|
64768
|
+
statusCode: response.status,
|
|
64769
|
+
headers,
|
|
64770
|
+
...cookies.length ? { cookies } : {},
|
|
64771
|
+
body: textual ? buffer.toString("utf-8") : buffer.toString("base64"),
|
|
64772
|
+
isBase64Encoded: !textual
|
|
64773
|
+
};
|
|
64774
|
+
}
|
|
64775
|
+
function createHttpHandler(handler8, opts) {
|
|
64776
|
+
return async (event) => {
|
|
64777
|
+
if (event.warmer) {
|
|
64778
|
+
return { statusCode: 200, headers: { "content-type": "text/plain" }, body: "warm", isBase64Encoded: false };
|
|
64779
|
+
}
|
|
64780
|
+
if (!handler8) {
|
|
64781
|
+
return { statusCode: 501, headers: { "content-type": "text/plain" }, body: "No HTTP handler configured", isBase64Encoded: false };
|
|
64782
|
+
}
|
|
64783
|
+
const maintenance = readMaintenance(opts);
|
|
64784
|
+
if (maintenance.enabled) {
|
|
64785
|
+
const bypass = event.headers?.["x-maintenance-bypass"] ?? event.cookies?.find((c) => c.startsWith("tscloud_bypass="))?.split("=")[1];
|
|
64786
|
+
if (!maintenance.bypassSecret || bypass !== maintenance.bypassSecret) {
|
|
64787
|
+
return {
|
|
64788
|
+
statusCode: 503,
|
|
64789
|
+
headers: { "content-type": "text/plain", "retry-after": "120" },
|
|
64790
|
+
body: "Service temporarily unavailable (maintenance mode)",
|
|
64791
|
+
isBase64Encoded: false
|
|
64792
|
+
};
|
|
64793
|
+
}
|
|
64794
|
+
}
|
|
64795
|
+
const request = eventToRequest(event);
|
|
64796
|
+
const response = await handler8(request);
|
|
64797
|
+
return responseToResult(response);
|
|
64798
|
+
};
|
|
64799
|
+
}
|
|
64800
|
+
function parseRecordBody(body) {
|
|
64801
|
+
try {
|
|
64802
|
+
return JSON.parse(body);
|
|
64803
|
+
} catch {
|
|
64804
|
+
return body;
|
|
64805
|
+
}
|
|
64806
|
+
}
|
|
64807
|
+
function createQueueHandler(handler8) {
|
|
64808
|
+
return async (event) => {
|
|
64809
|
+
const batchItemFailures = [];
|
|
64810
|
+
if (!handler8)
|
|
64811
|
+
return { batchItemFailures };
|
|
64812
|
+
for (const record of event.Records ?? []) {
|
|
64813
|
+
try {
|
|
64814
|
+
await handler8(parseRecordBody(record.body), record);
|
|
64815
|
+
} catch {
|
|
64816
|
+
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
64817
|
+
}
|
|
64818
|
+
}
|
|
64819
|
+
return { batchItemFailures };
|
|
64820
|
+
};
|
|
64821
|
+
}
|
|
64822
|
+
function createCliHandler(handler8) {
|
|
64823
|
+
return async (event) => {
|
|
64824
|
+
if (!handler8)
|
|
64825
|
+
return { statusCode: 501, output: "No CLI handler configured" };
|
|
64826
|
+
return handler8(event);
|
|
64827
|
+
};
|
|
64828
|
+
}
|
|
64829
|
+
function createHandlers(app, opts) {
|
|
64830
|
+
return {
|
|
64831
|
+
http: createHttpHandler(app.fetch, opts),
|
|
64832
|
+
queue: createQueueHandler(app.queue),
|
|
64833
|
+
cli: createCliHandler(app.cli)
|
|
64834
|
+
};
|
|
64835
|
+
}
|
|
64836
|
+
function generatePhpFpmConfig(options = {}) {
|
|
64837
|
+
const socket = options.socketPath ?? "/tmp/.tscloud-fpm.sock";
|
|
64838
|
+
const maxChildren = options.maxChildren ?? 1;
|
|
64839
|
+
const errorLog = options.errorLog ?? "/tmp/storage/logs/php-fpm.log";
|
|
64840
|
+
return `; ts-cloud php-fpm configuration (generated) — AWS Lambda custom runtime.
|
|
64841
|
+
[global]
|
|
64842
|
+
daemonize = no
|
|
64843
|
+
error_log = ${errorLog}
|
|
64844
|
+
log_level = warning
|
|
64845
|
+
|
|
64846
|
+
[www]
|
|
64847
|
+
listen = ${socket}
|
|
64848
|
+
listen.mode = 0666
|
|
64849
|
+
pm = static
|
|
64850
|
+
pm.max_children = ${maxChildren}
|
|
64851
|
+
catch_workers_output = yes
|
|
64852
|
+
decorate_workers_output = no
|
|
64853
|
+
; Surface fatal errors / fpm logs to the Lambda log stream.
|
|
64854
|
+
php_admin_value[error_log] = /dev/stderr
|
|
64855
|
+
php_admin_flag[log_errors] = on
|
|
64856
|
+
clear_env = no
|
|
64857
|
+
`;
|
|
64858
|
+
}
|
|
64859
|
+
function assetsDir() {
|
|
64860
|
+
return join72(dirname32(fileURLToPath2(import.meta.url)), "runtime-assets");
|
|
64861
|
+
}
|
|
64862
|
+
function phpRuntimeLayerAssets() {
|
|
64863
|
+
const dir = assetsDir();
|
|
64864
|
+
const read = (f) => readFileSync4(join72(dir, f), "utf-8");
|
|
64865
|
+
return [
|
|
64866
|
+
{ path: "bootstrap", contents: read("bootstrap"), mode: 493 },
|
|
64867
|
+
{ path: "tscloud/runtime.php", contents: read("runtime.php"), mode: 420 },
|
|
64868
|
+
{ path: "tscloud/octane-runtime.php", contents: read("octane-runtime.php"), mode: 420 },
|
|
64869
|
+
{ path: "tscloud/cli-runtime.php", contents: read("cli-runtime.php"), mode: 420 },
|
|
64870
|
+
{ path: "tscloud/fastcgi-client.php", contents: read("fastcgi-client.php"), mode: 420 },
|
|
64871
|
+
{ path: "tscloud/php-fpm.conf", contents: generatePhpFpmConfig(), mode: 420 }
|
|
64872
|
+
];
|
|
64873
|
+
}
|
|
64874
|
+
function laravelServerlessEnvDefaults(opts = {}) {
|
|
64875
|
+
const cache2 = opts.cacheDriver ?? "dynamodb";
|
|
64876
|
+
return {
|
|
64877
|
+
APP_ENV: "production",
|
|
64878
|
+
LOG_CHANNEL: "stderr",
|
|
64879
|
+
CACHE_STORE: cache2,
|
|
64880
|
+
CACHE_DRIVER: cache2,
|
|
64881
|
+
SESSION_DRIVER: cache2,
|
|
64882
|
+
QUEUE_CONNECTION: "sqs",
|
|
64883
|
+
FILESYSTEM_DISK: "s3",
|
|
64884
|
+
VIEW_COMPILED_PATH: "/tmp/storage/framework/views",
|
|
64885
|
+
APP_SERVICES_CACHE: "/tmp/bootstrap/cache/services.php",
|
|
64886
|
+
APP_PACKAGES_CACHE: "/tmp/bootstrap/cache/packages.php",
|
|
64887
|
+
APP_CONFIG_CACHE: "/tmp/bootstrap/cache/config.php",
|
|
64888
|
+
APP_ROUTES_CACHE: "/tmp/bootstrap/cache/routes.php",
|
|
64889
|
+
APP_EVENTS_CACHE: "/tmp/bootstrap/cache/events.php"
|
|
64890
|
+
};
|
|
64891
|
+
}
|
|
64892
|
+
var LARAVEL_SERVERLESS_BUILD_STEPS = [
|
|
64893
|
+
"composer install --no-dev --optimize-autoloader --no-interaction",
|
|
64894
|
+
"php artisan config:cache",
|
|
64895
|
+
"php artisan route:cache",
|
|
64896
|
+
"php artisan event:cache",
|
|
64897
|
+
"php artisan view:cache"
|
|
64898
|
+
];
|
|
64899
|
+
function* walk(dir) {
|
|
64900
|
+
for (const entry of readdirSync52(dir)) {
|
|
64901
|
+
const full = join8(dir, entry);
|
|
64902
|
+
if (statSync32(full).isDirectory())
|
|
64903
|
+
yield* walk(full);
|
|
64904
|
+
else
|
|
64905
|
+
yield full;
|
|
64906
|
+
}
|
|
64907
|
+
}
|
|
64908
|
+
function buildPhpRuntimeLayerZip(options = {}) {
|
|
64909
|
+
const architecture = options.architecture ?? "x86_64";
|
|
64910
|
+
const platform = options.platform ?? (architecture === "arm64" ? "linux/arm64" : "linux/amd64");
|
|
64911
|
+
const step = options.onStep ?? (() => {});
|
|
64912
|
+
const stage = mkdtempSync2(join8(tmpdir2(), "tscloud-php-layer-"));
|
|
64913
|
+
const imageTag = "tscloud-php-layer:build";
|
|
64914
|
+
try {
|
|
64915
|
+
writeFileSync42(join8(stage, "Dockerfile"), generatePhpLayerDockerfile(options));
|
|
64916
|
+
step("Building PHP runtime image (docker)");
|
|
64917
|
+
execFileSync("docker", ["build", "--platform", platform, "-t", imageTag, stage], { stdio: "inherit" });
|
|
64918
|
+
step("Extracting /opt from image");
|
|
64919
|
+
const cid = execFileSync("docker", ["create", "--platform", platform, imageTag], { encoding: "utf-8" }).trim();
|
|
64920
|
+
const optDir = join8(stage, "opt");
|
|
64921
|
+
try {
|
|
64922
|
+
execFileSync("docker", ["cp", `${cid}:/opt/.`, optDir], { stdio: "inherit" });
|
|
64923
|
+
} finally {
|
|
64924
|
+
execFileSync("docker", ["rm", cid], { stdio: "ignore" });
|
|
64925
|
+
}
|
|
64926
|
+
if (!existsSync52(optDir))
|
|
64927
|
+
throw new Error("layer build produced no /opt directory");
|
|
64928
|
+
const entries = [];
|
|
64929
|
+
for (const file of walk(optDir)) {
|
|
64930
|
+
const rel = relative22(optDir, file).replace(/\\/g, "/");
|
|
64931
|
+
const mode = statSync32(file).mode & 73 ? 493 : 420;
|
|
64932
|
+
entries.push({ name: rel, data: readFileSync5(file), mode });
|
|
64933
|
+
}
|
|
64934
|
+
step("Injecting runtime assets");
|
|
64935
|
+
const assetPaths = new Set(phpRuntimeLayerAssets().map((a) => a.path));
|
|
64936
|
+
const filtered = entries.filter((e) => !assetPaths.has(e.name));
|
|
64937
|
+
for (const asset of phpRuntimeLayerAssets()) {
|
|
64938
|
+
filtered.push({ name: asset.path, data: asset.contents, mode: asset.mode });
|
|
64939
|
+
}
|
|
64940
|
+
step("Packaging layer ZIP");
|
|
64941
|
+
const zip2 = createZip(filtered);
|
|
64942
|
+
return { zip: zip2, architecture, fileCount: filtered.length };
|
|
64943
|
+
} finally {
|
|
64944
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
64945
|
+
}
|
|
64946
|
+
}
|
|
64947
|
+
var PHP_DEFAULT_EXCLUDES = [
|
|
64948
|
+
".git",
|
|
64949
|
+
".github",
|
|
64950
|
+
"node_modules",
|
|
64951
|
+
"tests",
|
|
64952
|
+
"storage/logs",
|
|
64953
|
+
"storage/framework/cache",
|
|
64954
|
+
"storage/framework/sessions",
|
|
64955
|
+
"storage/framework/views",
|
|
64956
|
+
".env",
|
|
64957
|
+
".env.local",
|
|
64958
|
+
".vapor",
|
|
64959
|
+
".ts-cloud",
|
|
64960
|
+
"dist-lambda"
|
|
64961
|
+
];
|
|
64962
|
+
function isExcluded(rel, excludes) {
|
|
64963
|
+
return excludes.some((ex) => rel === ex || rel.startsWith(`${ex}/`));
|
|
64964
|
+
}
|
|
64965
|
+
function* walk2(dir, root, excludes) {
|
|
64966
|
+
for (const entry of readdirSync62(dir)) {
|
|
64967
|
+
const full = join9(dir, entry);
|
|
64968
|
+
const rel = relative32(root, full).replace(/\\/g, "/");
|
|
64969
|
+
if (isExcluded(rel, excludes))
|
|
64970
|
+
continue;
|
|
64971
|
+
if (statSync4(full).isDirectory())
|
|
64972
|
+
yield* walk2(full, root, excludes);
|
|
64973
|
+
else
|
|
64974
|
+
yield full;
|
|
64975
|
+
}
|
|
64976
|
+
}
|
|
64977
|
+
function runPhpBuildHooks(opts) {
|
|
64978
|
+
if (opts.skipBuild)
|
|
64979
|
+
return;
|
|
64980
|
+
const projectRoot = resolve22(opts.projectRoot ?? process.cwd());
|
|
64981
|
+
const steps = opts.app.build ?? LARAVEL_SERVERLESS_BUILD_STEPS;
|
|
64982
|
+
for (const step of steps) {
|
|
64983
|
+
opts.onStep?.(`build: ${step}`);
|
|
64984
|
+
execSync2(step, { stdio: "inherit", cwd: projectRoot });
|
|
64985
|
+
}
|
|
64986
|
+
}
|
|
64987
|
+
function collectPhpAppEntries(projectRoot, exclude = []) {
|
|
64988
|
+
const root = resolve22(projectRoot);
|
|
64989
|
+
const excludes = [...PHP_DEFAULT_EXCLUDES, ...exclude];
|
|
64990
|
+
const entries = [];
|
|
64991
|
+
for (const file of walk2(root, root, excludes)) {
|
|
64992
|
+
const rel = relative32(root, file).replace(/\\/g, "/");
|
|
64993
|
+
const executable = (statSync4(file).mode & 73) !== 0;
|
|
64994
|
+
entries.push({ name: rel, data: readFileSync6(file), mode: executable ? 493 : 420 });
|
|
64995
|
+
}
|
|
64996
|
+
return entries;
|
|
64997
|
+
}
|
|
64998
|
+
function packagePhpApp(opts) {
|
|
64999
|
+
const projectRoot = resolve22(opts.projectRoot ?? process.cwd());
|
|
65000
|
+
runPhpBuildHooks(opts);
|
|
65001
|
+
opts.onStep?.("packaging application tree");
|
|
65002
|
+
const entries = collectPhpAppEntries(projectRoot, opts.exclude);
|
|
65003
|
+
if (!entries.length)
|
|
65004
|
+
throw new Error(`No files to package under ${projectRoot}`);
|
|
65005
|
+
const zip2 = createZip(entries);
|
|
65006
|
+
const handler8 = opts.app.handlers?.http ?? "public/index.php";
|
|
65007
|
+
return {
|
|
65008
|
+
zip: zip2,
|
|
65009
|
+
sha256: createHash5("sha256").update(zip2).digest("hex"),
|
|
65010
|
+
handlers: {
|
|
65011
|
+
http: handler8,
|
|
65012
|
+
queue: opts.app.handlers?.queue ?? handler8,
|
|
65013
|
+
cli: opts.app.handlers?.cli ?? handler8
|
|
65014
|
+
},
|
|
65015
|
+
fileCount: entries.length
|
|
65016
|
+
};
|
|
65017
|
+
}
|
|
63782
65018
|
|
|
63783
65019
|
class StaticSiteManager {
|
|
63784
65020
|
optimizations = new Map;
|
|
@@ -68336,7 +69572,7 @@ class EC2Client {
|
|
|
68336
69572
|
if (instance?.State?.Name === targetState) {
|
|
68337
69573
|
return instance;
|
|
68338
69574
|
}
|
|
68339
|
-
await new Promise((
|
|
69575
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
68340
69576
|
}
|
|
68341
69577
|
return;
|
|
68342
69578
|
}
|
|
@@ -69796,7 +71032,7 @@ class ECSClient {
|
|
|
69796
71032
|
return true;
|
|
69797
71033
|
}
|
|
69798
71034
|
}
|
|
69799
|
-
await new Promise((
|
|
71035
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
69800
71036
|
}
|
|
69801
71037
|
return false;
|
|
69802
71038
|
}
|
|
@@ -70337,7 +71573,7 @@ class SSMClient {
|
|
|
70337
71573
|
const maxWait = options?.maxWaitMs || 300000;
|
|
70338
71574
|
const startTime = Date.now();
|
|
70339
71575
|
while (Date.now() - startTime < maxWait) {
|
|
70340
|
-
await new Promise((
|
|
71576
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
70341
71577
|
try {
|
|
70342
71578
|
const invocation = await this.getCommandInvocation({
|
|
70343
71579
|
CommandId: sendResult.CommandId,
|
|
@@ -70415,7 +71651,7 @@ class SSMClient {
|
|
|
70415
71651
|
const terminalStatuses = new Set(["Success", "Failed", "Cancelled", "TimedOut"]);
|
|
70416
71652
|
let lastInvocations = [];
|
|
70417
71653
|
while (Date.now() - startTime < maxWait) {
|
|
70418
|
-
await new Promise((
|
|
71654
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
70419
71655
|
try {
|
|
70420
71656
|
lastInvocations = await this.listCommandInvocations({
|
|
70421
71657
|
CommandId: sendResult.CommandId,
|
|
@@ -71696,11 +72932,11 @@ class SQSClient {
|
|
|
71696
72932
|
}
|
|
71697
72933
|
// src/aws/lambda.ts
|
|
71698
72934
|
init_client();
|
|
71699
|
-
import { deflateRawSync } from "zlib";
|
|
72935
|
+
import { deflateRawSync as deflateRawSync2 } from "zlib";
|
|
71700
72936
|
function createZipFile(filename, content) {
|
|
71701
72937
|
const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
71702
|
-
const compressedData =
|
|
71703
|
-
const
|
|
72938
|
+
const compressedData = deflateRawSync2(data);
|
|
72939
|
+
const crc322 = calculateCrc32(data);
|
|
71704
72940
|
const now = new Date;
|
|
71705
72941
|
const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
|
|
71706
72942
|
const dosDate = (now.getFullYear() - 1980 << 9 | now.getMonth() + 1 << 5 | now.getDate()) & 65535;
|
|
@@ -71712,7 +72948,7 @@ function createZipFile(filename, content) {
|
|
|
71712
72948
|
localHeader.writeUInt16LE(8, 8);
|
|
71713
72949
|
localHeader.writeUInt16LE(dosTime, 10);
|
|
71714
72950
|
localHeader.writeUInt16LE(dosDate, 12);
|
|
71715
|
-
localHeader.writeUInt32LE(
|
|
72951
|
+
localHeader.writeUInt32LE(crc322, 14);
|
|
71716
72952
|
localHeader.writeUInt32LE(compressedData.length, 18);
|
|
71717
72953
|
localHeader.writeUInt32LE(data.length, 22);
|
|
71718
72954
|
localHeader.writeUInt16LE(filenameBuffer.length, 26);
|
|
@@ -71726,7 +72962,7 @@ function createZipFile(filename, content) {
|
|
|
71726
72962
|
centralHeader.writeUInt16LE(8, 10);
|
|
71727
72963
|
centralHeader.writeUInt16LE(dosTime, 12);
|
|
71728
72964
|
centralHeader.writeUInt16LE(dosDate, 14);
|
|
71729
|
-
centralHeader.writeUInt32LE(
|
|
72965
|
+
centralHeader.writeUInt32LE(crc322, 16);
|
|
71730
72966
|
centralHeader.writeUInt32LE(compressedData.length, 20);
|
|
71731
72967
|
centralHeader.writeUInt32LE(data.length, 24);
|
|
71732
72968
|
centralHeader.writeUInt16LE(filenameBuffer.length, 28);
|
|
@@ -71957,10 +73193,10 @@ class LambdaClient {
|
|
|
71957
73193
|
if (state === "Failed") {
|
|
71958
73194
|
throw new Error(`Function ${functionName} failed: ${response.Configuration?.StateReason}`);
|
|
71959
73195
|
}
|
|
71960
|
-
await new Promise((
|
|
73196
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
71961
73197
|
} catch (error) {
|
|
71962
73198
|
if (error.code === "ResourceNotFoundException") {
|
|
71963
|
-
await new Promise((
|
|
73199
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
71964
73200
|
continue;
|
|
71965
73201
|
}
|
|
71966
73202
|
throw error;
|
|
@@ -73898,7 +75134,7 @@ class RDSClient {
|
|
|
73898
75134
|
if (["deleted", "failed", "incompatible-restore", "incompatible-parameters"].includes(instance?.DBInstanceStatus || "")) {
|
|
73899
75135
|
throw new Error(`DB instance ${dbInstanceIdentifier} is in terminal state: ${instance?.DBInstanceStatus}`);
|
|
73900
75136
|
}
|
|
73901
|
-
await new Promise((
|
|
75137
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
73902
75138
|
}
|
|
73903
75139
|
throw new Error(`Timeout waiting for DB instance ${dbInstanceIdentifier} to become available`);
|
|
73904
75140
|
}
|
|
@@ -73907,7 +75143,7 @@ class RDSClient {
|
|
|
73907
75143
|
try {
|
|
73908
75144
|
const instance = await this.describeDBInstance(dbInstanceIdentifier);
|
|
73909
75145
|
if (instance?.DBInstanceStatus === "deleting") {
|
|
73910
|
-
await new Promise((
|
|
75146
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
73911
75147
|
continue;
|
|
73912
75148
|
}
|
|
73913
75149
|
throw new Error(`DB instance ${dbInstanceIdentifier} is in state: ${instance?.DBInstanceStatus}`);
|
|
@@ -74678,7 +75914,7 @@ class BedrockClient {
|
|
|
74678
75914
|
if (job.status === "Completed" || job.status === "Failed" || job.status === "Stopped") {
|
|
74679
75915
|
return job;
|
|
74680
75916
|
}
|
|
74681
|
-
await new Promise((
|
|
75917
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
74682
75918
|
}
|
|
74683
75919
|
throw new Error(`Timeout waiting for model customization job ${jobIdentifier}`);
|
|
74684
75920
|
}
|
|
@@ -75442,7 +76678,7 @@ class TextractClient {
|
|
|
75442
76678
|
if (result.JobStatus === "FAILED") {
|
|
75443
76679
|
throw new Error(`Textract job ${jobId} failed`);
|
|
75444
76680
|
}
|
|
75445
|
-
await new Promise((
|
|
76681
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
75446
76682
|
}
|
|
75447
76683
|
throw new Error(`Timeout waiting for Textract job ${jobId}`);
|
|
75448
76684
|
}
|
|
@@ -75645,7 +76881,7 @@ class PollyClient {
|
|
|
75645
76881
|
if (task?.TaskStatus === "failed") {
|
|
75646
76882
|
throw new Error(`Polly task ${taskId} failed: ${task.TaskStatusReason}`);
|
|
75647
76883
|
}
|
|
75648
|
-
await new Promise((
|
|
76884
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
75649
76885
|
}
|
|
75650
76886
|
throw new Error(`Timeout waiting for Polly task ${taskId}`);
|
|
75651
76887
|
}
|
|
@@ -75755,7 +76991,7 @@ class TranslateClient {
|
|
|
75755
76991
|
if (job?.JobStatus === "FAILED" || job?.JobStatus === "STOPPED") {
|
|
75756
76992
|
throw new Error(`Translation job ${jobId} failed: ${job.Message}`);
|
|
75757
76993
|
}
|
|
75758
|
-
await new Promise((
|
|
76994
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
75759
76995
|
}
|
|
75760
76996
|
throw new Error(`Timeout waiting for translation job ${jobId}`);
|
|
75761
76997
|
}
|
|
@@ -75857,7 +77093,7 @@ class PersonalizeClient {
|
|
|
75857
77093
|
if (sv?.status === "CREATE FAILED") {
|
|
75858
77094
|
throw new Error(`Solution version failed: ${sv.failureReason}`);
|
|
75859
77095
|
}
|
|
75860
|
-
await new Promise((
|
|
77096
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
75861
77097
|
}
|
|
75862
77098
|
throw new Error(`Timeout waiting for solution version ${solutionVersionArn}`);
|
|
75863
77099
|
}
|
|
@@ -75874,7 +77110,7 @@ class PersonalizeClient {
|
|
|
75874
77110
|
if (campaign?.status === "CREATE FAILED") {
|
|
75875
77111
|
throw new Error(`Campaign failed: ${campaign.failureReason}`);
|
|
75876
77112
|
}
|
|
75877
|
-
await new Promise((
|
|
77113
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
75878
77114
|
}
|
|
75879
77115
|
throw new Error(`Timeout waiting for campaign ${campaignArn}`);
|
|
75880
77116
|
}
|
|
@@ -75891,7 +77127,7 @@ class PersonalizeClient {
|
|
|
75891
77127
|
if (job?.status === "CREATE FAILED") {
|
|
75892
77128
|
throw new Error(`Dataset import job failed: ${job.failureReason}`);
|
|
75893
77129
|
}
|
|
75894
|
-
await new Promise((
|
|
77130
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
75895
77131
|
}
|
|
75896
77132
|
throw new Error(`Timeout waiting for dataset import job ${jobArn}`);
|
|
75897
77133
|
}
|
|
@@ -76046,7 +77282,7 @@ class KendraClient {
|
|
|
76046
77282
|
if (result.Status === "FAILED") {
|
|
76047
77283
|
throw new Error(`Index creation failed: ${result.ErrorMessage}`);
|
|
76048
77284
|
}
|
|
76049
|
-
await new Promise((
|
|
77285
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
76050
77286
|
}
|
|
76051
77287
|
throw new Error(`Timeout waiting for index ${indexId}`);
|
|
76052
77288
|
}
|
|
@@ -76065,7 +77301,7 @@ class KendraClient {
|
|
|
76065
77301
|
if (result.Status === "FAILED") {
|
|
76066
77302
|
throw new Error(`Data source creation failed: ${result.ErrorMessage}`);
|
|
76067
77303
|
}
|
|
76068
|
-
await new Promise((
|
|
77304
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollIntervalMs));
|
|
76069
77305
|
}
|
|
76070
77306
|
throw new Error(`Timeout waiting for data source ${dataSourceId}`);
|
|
76071
77307
|
}
|
|
@@ -77099,7 +78335,7 @@ class ApplicationAutoScalingClient {
|
|
|
77099
78335
|
init_s3();
|
|
77100
78336
|
import * as net from "node:net";
|
|
77101
78337
|
import * as tls from "node:tls";
|
|
77102
|
-
import { readFileSync as
|
|
78338
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
77103
78339
|
import * as crypto4 from "node:crypto";
|
|
77104
78340
|
|
|
77105
78341
|
class ImapServer {
|
|
@@ -77134,8 +78370,8 @@ class ImapServer {
|
|
|
77134
78370
|
});
|
|
77135
78371
|
if (this.config.tls?.key && this.config.tls?.cert) {
|
|
77136
78372
|
const tlsOptions = {
|
|
77137
|
-
key:
|
|
77138
|
-
cert:
|
|
78373
|
+
key: readFileSync10(this.config.tls.key),
|
|
78374
|
+
cert: readFileSync10(this.config.tls.cert)
|
|
77139
78375
|
};
|
|
77140
78376
|
this.tlsServer = tls.createServer(tlsOptions, (socket) => {
|
|
77141
78377
|
this.handleConnection(socket);
|
|
@@ -77939,8 +79175,8 @@ class ImapServer {
|
|
|
77939
79175
|
}
|
|
77940
79176
|
this.send(session, `${tag} OK Begin TLS negotiation`);
|
|
77941
79177
|
const tlsOptions = {
|
|
77942
|
-
key:
|
|
77943
|
-
cert:
|
|
79178
|
+
key: readFileSync10(this.config.tls.key),
|
|
79179
|
+
cert: readFileSync10(this.config.tls.cert),
|
|
77944
79180
|
isServer: true
|
|
77945
79181
|
};
|
|
77946
79182
|
const tlsSocket = new tls.TLSSocket(session.socket, tlsOptions);
|
|
@@ -78533,7 +79769,7 @@ init_ses();
|
|
|
78533
79769
|
init_s3();
|
|
78534
79770
|
import * as net2 from "node:net";
|
|
78535
79771
|
import * as tls2 from "node:tls";
|
|
78536
|
-
import { readFileSync as
|
|
79772
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
78537
79773
|
import * as crypto5 from "node:crypto";
|
|
78538
79774
|
|
|
78539
79775
|
class SmtpServer {
|
|
@@ -78564,8 +79800,8 @@ class SmtpServer {
|
|
|
78564
79800
|
});
|
|
78565
79801
|
if (this.config.tls?.key && this.config.tls?.cert) {
|
|
78566
79802
|
const tlsOptions = {
|
|
78567
|
-
key:
|
|
78568
|
-
cert:
|
|
79803
|
+
key: readFileSync11(this.config.tls.key),
|
|
79804
|
+
cert: readFileSync11(this.config.tls.cert)
|
|
78569
79805
|
};
|
|
78570
79806
|
this.tlsServer = tls2.createServer(tlsOptions, (socket) => {
|
|
78571
79807
|
this.handleConnection(socket, true);
|
|
@@ -78699,8 +79935,8 @@ class SmtpServer {
|
|
|
78699
79935
|
}
|
|
78700
79936
|
this.send(session, "220 Ready to start TLS");
|
|
78701
79937
|
const tlsOptions = {
|
|
78702
|
-
key:
|
|
78703
|
-
cert:
|
|
79938
|
+
key: readFileSync11(this.config.tls.key),
|
|
79939
|
+
cert: readFileSync11(this.config.tls.cert),
|
|
78704
79940
|
isServer: true
|
|
78705
79941
|
};
|
|
78706
79942
|
const tlsSocket = new tls2.TLSSocket(session.socket, tlsOptions);
|
|
@@ -79469,7 +80705,7 @@ class SmsClient {
|
|
|
79469
80705
|
if (receipt && (receipt.status === "delivered" || receipt.status === "failed")) {
|
|
79470
80706
|
return receipt;
|
|
79471
80707
|
}
|
|
79472
|
-
await new Promise((
|
|
80708
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
79473
80709
|
}
|
|
79474
80710
|
return await this.getDeliveryReceipt(messageId);
|
|
79475
80711
|
}
|
|
@@ -81041,15 +82277,15 @@ async function cleanupDns01Challenge(options) {
|
|
|
81041
82277
|
}
|
|
81042
82278
|
function needsRenewal(certPath) {
|
|
81043
82279
|
try {
|
|
81044
|
-
const { execSync } = __require("node:child_process");
|
|
81045
|
-
const _result =
|
|
82280
|
+
const { execSync: execSync3 } = __require("node:child_process");
|
|
82281
|
+
const _result = execSync3(`openssl x509 -checkend 2592000 -noout -in ${certPath}/cert.pem`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
81046
82282
|
return false;
|
|
81047
82283
|
} catch {
|
|
81048
82284
|
return true;
|
|
81049
82285
|
}
|
|
81050
82286
|
}
|
|
81051
82287
|
// src/ssl/acme-client.ts
|
|
81052
|
-
import { createHash as
|
|
82288
|
+
import { createHash as createHash9, createSign, generateKeyPairSync } from "node:crypto";
|
|
81053
82289
|
var ACME_DIRECTORIES = {
|
|
81054
82290
|
production: "https://acme-v02.api.letsencrypt.org/directory",
|
|
81055
82291
|
staging: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
@@ -81119,7 +82355,7 @@ class AcmeClient {
|
|
|
81119
82355
|
x: jwk.x,
|
|
81120
82356
|
y: jwk.y
|
|
81121
82357
|
});
|
|
81122
|
-
const hash2 =
|
|
82358
|
+
const hash2 = createHash9("sha256").update(canonical).digest();
|
|
81123
82359
|
return this.base64UrlEncode(hash2);
|
|
81124
82360
|
}
|
|
81125
82361
|
base64UrlEncode(data) {
|
|
@@ -81232,7 +82468,7 @@ class AcmeClient {
|
|
|
81232
82468
|
identifier: `/.well-known/acme-challenge/${c.token}`
|
|
81233
82469
|
};
|
|
81234
82470
|
} else {
|
|
81235
|
-
const dnsValue = this.base64UrlEncode(
|
|
82471
|
+
const dnsValue = this.base64UrlEncode(createHash9("sha256").update(keyAuthorization).digest());
|
|
81236
82472
|
return {
|
|
81237
82473
|
type: "dns-01",
|
|
81238
82474
|
token: c.token,
|
|
@@ -81256,7 +82492,7 @@ class AcmeClient {
|
|
|
81256
82492
|
if (body.status === "invalid") {
|
|
81257
82493
|
throw new Error(`Authorization failed: ${JSON.stringify(body)}`);
|
|
81258
82494
|
}
|
|
81259
|
-
await new Promise((
|
|
82495
|
+
await new Promise((resolve14) => setTimeout(resolve14, 2000));
|
|
81260
82496
|
}
|
|
81261
82497
|
throw new Error("Authorization timed out");
|
|
81262
82498
|
}
|
|
@@ -81399,11 +82635,11 @@ init_static_site_external_dns();
|
|
|
81399
82635
|
init_static_site_external_dns();
|
|
81400
82636
|
import process19 from "node:process";
|
|
81401
82637
|
import { existsSync as existsSync16 } from "node:fs";
|
|
81402
|
-
import { join as
|
|
82638
|
+
import { join as join13 } from "node:path";
|
|
81403
82639
|
async function deploySite(config6) {
|
|
81404
82640
|
const start = Date.now();
|
|
81405
82641
|
const sourceDir = config6.sourceDir ?? "dist";
|
|
81406
|
-
if (!existsSync16(
|
|
82642
|
+
if (!existsSync16(join13(sourceDir, "index.html"))) {
|
|
81407
82643
|
return fail(start, `${sourceDir}/ has no index.html — build first.`);
|
|
81408
82644
|
}
|
|
81409
82645
|
if (!process19.env.AWS_ACCESS_KEY_ID || !process19.env.AWS_SECRET_ACCESS_KEY) {
|
|
@@ -81472,10 +82708,46 @@ function fail(start, message) {
|
|
|
81472
82708
|
return { success: false, message, durationMs: Date.now() - start };
|
|
81473
82709
|
}
|
|
81474
82710
|
// src/drivers/aws/driver.ts
|
|
81475
|
-
import { readFileSync as
|
|
82711
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
81476
82712
|
init_cloudformation();
|
|
81477
82713
|
init_s3();
|
|
81478
82714
|
|
|
82715
|
+
// src/drivers/shared/package-manager.ts
|
|
82716
|
+
var PANTRY_INSTALL_DIR = "/usr/local/bin";
|
|
82717
|
+
var PANTRY_PROJECT_DIR = "/opt/pantry";
|
|
82718
|
+
function sh(value) {
|
|
82719
|
+
return `'${value.split("'").join("'\\''")}'`;
|
|
82720
|
+
}
|
|
82721
|
+
function buildPantryBootstrapScript(options = {}) {
|
|
82722
|
+
const versionLine = options.version ? `export PANTRY_VERSION=${sh(options.version)}` : 'export PANTRY_VERSION="${PANTRY_VERSION:-latest}"';
|
|
82723
|
+
return [
|
|
82724
|
+
"export DEBIAN_FRONTEND=noninteractive",
|
|
82725
|
+
"export PANTRY_SERVICE_SCOPE=system",
|
|
82726
|
+
`export PANTRY_INSTALL_DIR=${PANTRY_INSTALL_DIR}`,
|
|
82727
|
+
versionLine,
|
|
82728
|
+
"command -v curl >/dev/null 2>&1 || (apt-get update -y && apt-get install -y curl ca-certificates)",
|
|
82729
|
+
"command -v unzip >/dev/null 2>&1 || (apt-get update -y && apt-get install -y unzip)",
|
|
82730
|
+
"command -v pantry >/dev/null 2>&1 || curl -fsSL https://pantry.dev | bash",
|
|
82731
|
+
`export PATH="${PANTRY_INSTALL_DIR}:$PATH"`,
|
|
82732
|
+
`mkdir -p ${PANTRY_PROJECT_DIR}`
|
|
82733
|
+
];
|
|
82734
|
+
}
|
|
82735
|
+
function pantryEnvActivation() {
|
|
82736
|
+
return `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)" || true`;
|
|
82737
|
+
}
|
|
82738
|
+
function buildPantryInstallScript(specs) {
|
|
82739
|
+
if (specs.length === 0)
|
|
82740
|
+
return [];
|
|
82741
|
+
const unique = [...new Set(specs)];
|
|
82742
|
+
return [`(cd ${PANTRY_PROJECT_DIR} && pantry install ${unique.map(sh).join(" ")})`];
|
|
82743
|
+
}
|
|
82744
|
+
function buildPantryServiceScript(services) {
|
|
82745
|
+
return [...new Set(services)].flatMap((name) => [
|
|
82746
|
+
`(cd ${PANTRY_PROJECT_DIR} && pantry start ${sh(name)})`,
|
|
82747
|
+
`(cd ${PANTRY_PROJECT_DIR} && pantry enable ${sh(name)})`
|
|
82748
|
+
]);
|
|
82749
|
+
}
|
|
82750
|
+
|
|
81479
82751
|
// src/drivers/shared/db-provision.ts
|
|
81480
82752
|
function enabled(spec) {
|
|
81481
82753
|
return spec === true || typeof spec === "object" && spec != null;
|
|
@@ -81484,50 +82756,42 @@ function sq(value) {
|
|
|
81484
82756
|
const escaped = value.split("'").join("'\\''");
|
|
81485
82757
|
return `'${escaped}'`;
|
|
81486
82758
|
}
|
|
81487
|
-
function
|
|
81488
|
-
const
|
|
81489
|
-
|
|
81490
|
-
|
|
81491
|
-
|
|
81492
|
-
|
|
81493
|
-
|
|
81494
|
-
|
|
81495
|
-
|
|
81496
|
-
const bind = options.bindPrivate === true;
|
|
81497
|
-
if (enabled(services.mysql) || enabled(services.mariadb)) {
|
|
81498
|
-
ensureUpdate();
|
|
81499
|
-
const pkg = enabled(services.mysql) ? "mysql-server" : "mariadb-server";
|
|
81500
|
-
const svc = enabled(services.mysql) ? "mysql" : "mariadb";
|
|
81501
|
-
out.push(`apt-get install -y ${pkg}`);
|
|
81502
|
-
if (bind) {
|
|
81503
|
-
out.push("find /etc/mysql -name '*.cnf' -exec sed -i 's/^[[:space:]]*bind-address.*/bind-address = 0.0.0.0/; s/^[[:space:]]*mysqlx-bind-address.*/mysqlx-bind-address = 0.0.0.0/' {} + 2>/dev/null || true", `for d in /etc/mysql/mysql.conf.d /etc/mysql/mariadb.conf.d; do [ -d "$d" ] && printf '[mysqld]\\nbind-address = 0.0.0.0\\n' > "$d/zz-ts-cloud-bind.cnf"; done`);
|
|
81504
|
-
}
|
|
81505
|
-
out.push(`systemctl enable ${svc}`, `systemctl restart ${svc}`);
|
|
82759
|
+
function planServices(services) {
|
|
82760
|
+
const packages = [];
|
|
82761
|
+
const names = [];
|
|
82762
|
+
if (enabled(services.mysql)) {
|
|
82763
|
+
packages.push("mysql.com");
|
|
82764
|
+
names.push("mysql");
|
|
82765
|
+
} else if (enabled(services.mariadb)) {
|
|
82766
|
+
packages.push("mariadb.org");
|
|
82767
|
+
names.push("mariadb");
|
|
81506
82768
|
}
|
|
81507
82769
|
if (enabled(services.postgres)) {
|
|
81508
|
-
|
|
81509
|
-
|
|
81510
|
-
if (bind) {
|
|
81511
|
-
out.push(`echo "listen_addresses = '*'" > /etc/postgresql/conf.d-ts-cloud.conf 2>/dev/null || true`, `CONF=$(ls /etc/postgresql/*/main/postgresql.conf 2>/dev/null | head -1); [ -n "$CONF" ] && sed -i "s/^#\\?listen_addresses.*/listen_addresses = '*'/" "$CONF" || true`, 'HBA=$(ls /etc/postgresql/*/main/pg_hba.conf 2>/dev/null | head -1); [ -n "$HBA" ] && echo "host all all 10.0.0.0/8 md5" >> "$HBA" || true');
|
|
81512
|
-
}
|
|
81513
|
-
out.push("systemctl enable postgresql", "systemctl restart postgresql");
|
|
82770
|
+
packages.push("postgresql.org");
|
|
82771
|
+
names.push("postgres");
|
|
81514
82772
|
}
|
|
81515
82773
|
if (enabled(services.redis)) {
|
|
81516
|
-
|
|
81517
|
-
|
|
81518
|
-
if (bind) {
|
|
81519
|
-
out.push("sed -i 's/^bind .*/bind 0.0.0.0/' /etc/redis/redis.conf || true", "sed -i 's/^protected-mode yes/protected-mode no/' /etc/redis/redis.conf || true");
|
|
81520
|
-
}
|
|
81521
|
-
out.push("systemctl enable redis-server", "systemctl restart redis-server");
|
|
82774
|
+
packages.push("redis.io");
|
|
82775
|
+
names.push("redis");
|
|
81522
82776
|
}
|
|
81523
82777
|
if (enabled(services.memcached)) {
|
|
81524
|
-
|
|
81525
|
-
|
|
82778
|
+
packages.push("memcached.org");
|
|
82779
|
+
names.push("memcached");
|
|
81526
82780
|
}
|
|
81527
82781
|
if (enabled(services.meilisearch)) {
|
|
81528
|
-
|
|
82782
|
+
packages.push("meilisearch.com");
|
|
82783
|
+
names.push("meilisearch");
|
|
81529
82784
|
}
|
|
81530
|
-
return
|
|
82785
|
+
return { packages, services: names };
|
|
82786
|
+
}
|
|
82787
|
+
function buildServicesProvisionScript(services = {}, _options = {}) {
|
|
82788
|
+
const plan = planServices(services);
|
|
82789
|
+
if (plan.packages.length === 0)
|
|
82790
|
+
return [];
|
|
82791
|
+
return [
|
|
82792
|
+
...buildPantryInstallScript(plan.packages),
|
|
82793
|
+
...buildPantryServiceScript(plan.services)
|
|
82794
|
+
];
|
|
81531
82795
|
}
|
|
81532
82796
|
function buildDatabaseSetupScript(database, services = {}) {
|
|
81533
82797
|
if (!database?.name)
|
|
@@ -81541,15 +82805,18 @@ function buildDatabaseSetupScript(database, services = {}) {
|
|
|
81541
82805
|
const useMariadb = enabled(services.mariadb) || database.engine === "mariadb";
|
|
81542
82806
|
const useMysql = enabled(services.mysql) || database.engine === "mysql";
|
|
81543
82807
|
if (usePostgres && !useMysql && !useMariadb) {
|
|
82808
|
+
const psql = "psql -h 127.0.0.1 -p 5432 -U postgres";
|
|
81544
82809
|
return [
|
|
81545
|
-
|
|
81546
|
-
|
|
82810
|
+
pantryEnvActivation(),
|
|
82811
|
+
`${psql} -tc "SELECT 1 FROM pg_roles WHERE rolname=${sq(user)}" | grep -q 1 ` + `|| ${psql} -c "CREATE ROLE ${sq(user)} LOGIN PASSWORD ${sq(pass)};"`,
|
|
82812
|
+
`${psql} -tc "SELECT 1 FROM pg_database WHERE datname=${sq(name)}" | grep -q 1 ` + `|| ${psql} -c "CREATE DATABASE ${sq(name)} OWNER ${sq(user)};"`
|
|
81547
82813
|
];
|
|
81548
82814
|
}
|
|
81549
82815
|
const ident = (v) => v.replace(/`/g, "``");
|
|
81550
82816
|
const lit = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
81551
82817
|
return [
|
|
81552
|
-
|
|
82818
|
+
pantryEnvActivation(),
|
|
82819
|
+
"mysql -h 127.0.0.1 -P 3306 -u root <<'TS_CLOUD_SQL_EOF'",
|
|
81553
82820
|
`CREATE DATABASE IF NOT EXISTS \`${ident(name)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
|
|
81554
82821
|
`CREATE USER IF NOT EXISTS '${lit(user)}'@'%' IDENTIFIED BY '${lit(pass)}';`,
|
|
81555
82822
|
`GRANT ALL PRIVILEGES ON \`${ident(name)}\`.* TO '${lit(user)}'@'%';`,
|
|
@@ -81576,60 +82843,225 @@ function buildManagedDbEnv(database) {
|
|
|
81576
82843
|
}
|
|
81577
82844
|
|
|
81578
82845
|
// src/drivers/shared/php-provision.ts
|
|
81579
|
-
var
|
|
81580
|
-
|
|
81581
|
-
|
|
81582
|
-
"common",
|
|
81583
|
-
"mbstring",
|
|
81584
|
-
"xml",
|
|
81585
|
-
"curl",
|
|
81586
|
-
"mysql",
|
|
81587
|
-
"pgsql",
|
|
81588
|
-
"sqlite3",
|
|
81589
|
-
"redis",
|
|
81590
|
-
"gd",
|
|
81591
|
-
"bcmath",
|
|
81592
|
-
"zip",
|
|
81593
|
-
"intl",
|
|
81594
|
-
"readline",
|
|
81595
|
-
"soap",
|
|
81596
|
-
"gmp",
|
|
81597
|
-
"opcache"
|
|
81598
|
-
];
|
|
81599
|
-
function phpPackagesForVersion(version2, extensions) {
|
|
81600
|
-
return extensions.map((suffix) => `php${version2}-${suffix}`);
|
|
82846
|
+
var PHP_FPM_LISTEN = "127.0.0.1:9074";
|
|
82847
|
+
function phpFpmSocketPath(_version) {
|
|
82848
|
+
return PHP_FPM_LISTEN;
|
|
81601
82849
|
}
|
|
81602
|
-
function
|
|
81603
|
-
|
|
82850
|
+
function resolveDefaultPhpVersion(options = {}) {
|
|
82851
|
+
const versions = options.versions?.length ? options.versions : ["8.3"];
|
|
82852
|
+
return options.default && versions.includes(options.default) ? options.default : versions[0];
|
|
81604
82853
|
}
|
|
81605
82854
|
function buildPhpProvisionScript(options = {}) {
|
|
81606
|
-
const
|
|
81607
|
-
const defaultVersion = options.default && versions.includes(options.default) ? options.default : versions[0];
|
|
82855
|
+
const defaultVersion = resolveDefaultPhpVersion(options);
|
|
81608
82856
|
const installNginx = options.installNginx !== false;
|
|
81609
82857
|
const installComposer = options.installComposer !== false;
|
|
81610
|
-
const
|
|
82858
|
+
const specs = [`php.net@${defaultVersion}`];
|
|
82859
|
+
if (installComposer)
|
|
82860
|
+
specs.push("getcomposer.org");
|
|
82861
|
+
if (installNginx)
|
|
82862
|
+
specs.push("nginx.org");
|
|
82863
|
+
return [
|
|
82864
|
+
...buildPantryInstallScript(specs),
|
|
82865
|
+
...buildPantryServiceScript(["php-fpm"])
|
|
82866
|
+
];
|
|
82867
|
+
}
|
|
82868
|
+
|
|
82869
|
+
// src/drivers/shared/nginx-vhost.ts
|
|
82870
|
+
function htpasswdPath(siteName) {
|
|
82871
|
+
return `/etc/nginx/.htpasswd-${siteName}`;
|
|
82872
|
+
}
|
|
82873
|
+
var PHP_TYPES = new Set(["laravel", "php", "statamic", "wordpress"]);
|
|
82874
|
+
function isPhpSiteType(type) {
|
|
82875
|
+
return PHP_TYPES.has(type);
|
|
82876
|
+
}
|
|
82877
|
+
function defaultWebDirectory(type) {
|
|
82878
|
+
return type === "laravel" || type === "statamic" || type === "wordpress" ? "public" : "";
|
|
82879
|
+
}
|
|
82880
|
+
function resolveRoot(appDir, webDirectory) {
|
|
82881
|
+
const base = appDir.replace(/\/+$/, "");
|
|
82882
|
+
const sub = webDirectory.replace(/^\/+|\/+$/g, "");
|
|
82883
|
+
return sub ? `${base}/${sub}` : base;
|
|
82884
|
+
}
|
|
82885
|
+
function vhostBody(options) {
|
|
82886
|
+
const type = options.type ?? "laravel";
|
|
82887
|
+
const webDirectory = options.webDirectory ?? defaultWebDirectory(type);
|
|
82888
|
+
const root = resolveRoot(options.appDir, webDirectory);
|
|
82889
|
+
const isPhp = isPhpSiteType(type);
|
|
82890
|
+
const phpVersion = options.phpVersion ?? "8.3";
|
|
81611
82891
|
const lines = [
|
|
81612
|
-
|
|
81613
|
-
|
|
81614
|
-
'
|
|
81615
|
-
|
|
81616
|
-
"
|
|
81617
|
-
"
|
|
81618
|
-
"
|
|
82892
|
+
` root ${root};`,
|
|
82893
|
+
"",
|
|
82894
|
+
' add_header X-Frame-Options "SAMEORIGIN";',
|
|
82895
|
+
' add_header X-Content-Type-Options "nosniff";',
|
|
82896
|
+
"",
|
|
82897
|
+
` index ${isPhp ? "index.php index.html" : "index.html index.htm"};`,
|
|
82898
|
+
"",
|
|
82899
|
+
" charset utf-8;",
|
|
82900
|
+
""
|
|
81619
82901
|
];
|
|
81620
|
-
if (
|
|
81621
|
-
lines.push("
|
|
82902
|
+
if (options.auth) {
|
|
82903
|
+
lines.push(` auth_basic "${options.auth.realm || "Restricted"}";`, ` auth_basic_user_file ${htpasswdPath(options.siteName)};`, "");
|
|
81622
82904
|
}
|
|
81623
|
-
for (const
|
|
81624
|
-
|
|
81625
|
-
lines.push(`apt-get install -y ${pkgs.join(" ")}`, `systemctl enable php${version2}-fpm`, `systemctl start php${version2}-fpm`);
|
|
82905
|
+
for (const [from, to] of Object.entries(options.redirects || {})) {
|
|
82906
|
+
lines.push(` location = ${from} { return 301 ${to}; }`);
|
|
81626
82907
|
}
|
|
81627
|
-
|
|
81628
|
-
|
|
81629
|
-
|
|
82908
|
+
if (Object.keys(options.redirects || {}).length > 0)
|
|
82909
|
+
lines.push("");
|
|
82910
|
+
if (isPhp) {
|
|
82911
|
+
lines.push(" location / {", " try_files $uri $uri/ /index.php?$query_string;", " }", "", " location = /favicon.ico { access_log off; log_not_found off; }", " location = /robots.txt { access_log off; log_not_found off; }", "", " error_page 404 /index.php;", "", " location ~ \\.php$ {", ` fastcgi_pass ${phpFpmSocketPath(phpVersion)};`, " fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;", " include fastcgi_params;", " }", "", " location ~ /\\.(?!well-known).* {", " deny all;", " }");
|
|
82912
|
+
} else if (type === "spa") {
|
|
82913
|
+
lines.push(" location / {", " try_files $uri $uri/ /index.html;", " }");
|
|
82914
|
+
} else {
|
|
82915
|
+
lines.push(" location / {", " try_files $uri $uri/ =404;", " }");
|
|
81630
82916
|
}
|
|
81631
82917
|
return lines;
|
|
81632
82918
|
}
|
|
82919
|
+
function buildNginxVhost(options) {
|
|
82920
|
+
const serverNames = [options.domain, ...options.aliases || []].filter(Boolean).join(" ");
|
|
82921
|
+
const body = vhostBody(options);
|
|
82922
|
+
if (options.ssl) {
|
|
82923
|
+
const redirect = [
|
|
82924
|
+
"server {",
|
|
82925
|
+
" listen 80;",
|
|
82926
|
+
" listen [::]:80;",
|
|
82927
|
+
` server_name ${serverNames};`,
|
|
82928
|
+
" return 301 https://$host$request_uri;",
|
|
82929
|
+
"}"
|
|
82930
|
+
];
|
|
82931
|
+
const tls3 = [
|
|
82932
|
+
"server {",
|
|
82933
|
+
" listen 443 ssl;",
|
|
82934
|
+
" listen [::]:443 ssl;",
|
|
82935
|
+
` server_name ${serverNames};`,
|
|
82936
|
+
` ssl_certificate ${options.ssl.certPath};`,
|
|
82937
|
+
` ssl_certificate_key ${options.ssl.keyPath};`,
|
|
82938
|
+
"",
|
|
82939
|
+
...body,
|
|
82940
|
+
"}"
|
|
82941
|
+
];
|
|
82942
|
+
return `${[...redirect, "", ...tls3].join(`
|
|
82943
|
+
`)}
|
|
82944
|
+
`;
|
|
82945
|
+
}
|
|
82946
|
+
const lines = [
|
|
82947
|
+
"server {",
|
|
82948
|
+
" listen 80;",
|
|
82949
|
+
" listen [::]:80;",
|
|
82950
|
+
` server_name ${serverNames};`,
|
|
82951
|
+
...body,
|
|
82952
|
+
"}"
|
|
82953
|
+
];
|
|
82954
|
+
return `${lines.join(`
|
|
82955
|
+
`)}
|
|
82956
|
+
`;
|
|
82957
|
+
}
|
|
82958
|
+
function buildNginxVhostScript(options) {
|
|
82959
|
+
const available = `/etc/nginx/sites-available/${options.siteName}`;
|
|
82960
|
+
const enabled2 = `/etc/nginx/sites-enabled/${options.siteName}`;
|
|
82961
|
+
const vhost = buildNginxVhost(options);
|
|
82962
|
+
const out = [];
|
|
82963
|
+
if (options.auth) {
|
|
82964
|
+
const file = htpasswdPath(options.siteName);
|
|
82965
|
+
const sq2 = (v) => v.split("'").join("'\\''");
|
|
82966
|
+
const pw = sq2(options.auth.password);
|
|
82967
|
+
const user = sq2(options.auth.username);
|
|
82968
|
+
out.push(`TS_CLOUD_HTPASS=$(openssl passwd -apr1 '${pw}')`, `printf '%s:%s\\n' '${user}' "$TS_CLOUD_HTPASS" > ${file}`, `chmod 640 ${file}`, `chown root:www-data ${file} 2>/dev/null || true`);
|
|
82969
|
+
}
|
|
82970
|
+
out.push(`cat > ${available} <<'TS_CLOUD_NGINX_EOF'`, vhost.replace(/\n$/, ""), "TS_CLOUD_NGINX_EOF", `ln -sf ${available} ${enabled2}`, "rm -f /etc/nginx/sites-enabled/default", `${NGINX_WRAPPER} -t`, "systemctl reload ts-cloud-nginx 2>/dev/null || systemctl restart ts-cloud-nginx 2>/dev/null || true");
|
|
82971
|
+
return out;
|
|
82972
|
+
}
|
|
82973
|
+
var NGINX_WRAPPER = "/usr/local/bin/ts-cloud-nginx";
|
|
82974
|
+
function buildNginxServiceScript(projectDir = "/opt/pantry") {
|
|
82975
|
+
return [
|
|
82976
|
+
"mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled /var/log/nginx /var/lib/nginx/body /var/lib/nginx/proxy /var/lib/nginx/fastcgi /var/lib/nginx/uwsgi /var/lib/nginx/scgi",
|
|
82977
|
+
"getent passwd www-data >/dev/null || useradd --system --no-create-home --shell /usr/sbin/nologin www-data",
|
|
82978
|
+
"chown -R www-data:www-data /var/lib/nginx /var/log/nginx",
|
|
82979
|
+
`cat > ${NGINX_WRAPPER} <<'TS_CLOUD_NGINXBIN_EOF'`,
|
|
82980
|
+
"#!/bin/sh",
|
|
82981
|
+
`eval "$(cd ${projectDir} && pantry env 2>/dev/null)"`,
|
|
82982
|
+
"NGINX_BIN=$(command -v nginx || echo /opt/pantry/pantry/.bin/nginx)",
|
|
82983
|
+
'exec "$NGINX_BIN" -c /etc/nginx/nginx.conf "$@"',
|
|
82984
|
+
"TS_CLOUD_NGINXBIN_EOF",
|
|
82985
|
+
`chmod +x ${NGINX_WRAPPER}`,
|
|
82986
|
+
"cat > /etc/nginx/fastcgi_params <<'TS_CLOUD_FCGI_EOF'",
|
|
82987
|
+
"fastcgi_param QUERY_STRING $query_string;",
|
|
82988
|
+
"fastcgi_param REQUEST_METHOD $request_method;",
|
|
82989
|
+
"fastcgi_param CONTENT_TYPE $content_type;",
|
|
82990
|
+
"fastcgi_param CONTENT_LENGTH $content_length;",
|
|
82991
|
+
"fastcgi_param SCRIPT_NAME $fastcgi_script_name;",
|
|
82992
|
+
"fastcgi_param REQUEST_URI $request_uri;",
|
|
82993
|
+
"fastcgi_param DOCUMENT_URI $document_uri;",
|
|
82994
|
+
"fastcgi_param DOCUMENT_ROOT $document_root;",
|
|
82995
|
+
"fastcgi_param SERVER_PROTOCOL $server_protocol;",
|
|
82996
|
+
"fastcgi_param REQUEST_SCHEME $scheme;",
|
|
82997
|
+
"fastcgi_param HTTPS $https if_not_empty;",
|
|
82998
|
+
"fastcgi_param GATEWAY_INTERFACE CGI/1.1;",
|
|
82999
|
+
"fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;",
|
|
83000
|
+
"fastcgi_param REMOTE_ADDR $remote_addr;",
|
|
83001
|
+
"fastcgi_param REMOTE_PORT $remote_port;",
|
|
83002
|
+
"fastcgi_param SERVER_ADDR $server_addr;",
|
|
83003
|
+
"fastcgi_param SERVER_PORT $server_port;",
|
|
83004
|
+
"fastcgi_param SERVER_NAME $server_name;",
|
|
83005
|
+
"fastcgi_param REDIRECT_STATUS 200;",
|
|
83006
|
+
"TS_CLOUD_FCGI_EOF",
|
|
83007
|
+
"cat > /etc/nginx/nginx.conf <<'TS_CLOUD_NGINXCONF_EOF'",
|
|
83008
|
+
"user www-data;",
|
|
83009
|
+
"worker_processes auto;",
|
|
83010
|
+
"pid /run/nginx.pid;",
|
|
83011
|
+
"error_log /var/log/nginx/error.log;",
|
|
83012
|
+
"events { worker_connections 1024; }",
|
|
83013
|
+
"http {",
|
|
83014
|
+
" default_type application/octet-stream;",
|
|
83015
|
+
" types {",
|
|
83016
|
+
" text/html html htm;",
|
|
83017
|
+
" text/css css;",
|
|
83018
|
+
" application/javascript js;",
|
|
83019
|
+
" application/json json;",
|
|
83020
|
+
" image/svg+xml svg;",
|
|
83021
|
+
" image/png png;",
|
|
83022
|
+
" image/jpeg jpg jpeg;",
|
|
83023
|
+
" image/gif gif;",
|
|
83024
|
+
" image/x-icon ico;",
|
|
83025
|
+
" image/webp webp;",
|
|
83026
|
+
" font/woff2 woff2;",
|
|
83027
|
+
" font/woff woff;",
|
|
83028
|
+
" text/plain txt;",
|
|
83029
|
+
" }",
|
|
83030
|
+
" access_log /var/log/nginx/access.log;",
|
|
83031
|
+
" sendfile on;",
|
|
83032
|
+
" tcp_nopush on;",
|
|
83033
|
+
" keepalive_timeout 65;",
|
|
83034
|
+
" server_tokens off;",
|
|
83035
|
+
" client_max_body_size 100m;",
|
|
83036
|
+
" client_body_temp_path /var/lib/nginx/body;",
|
|
83037
|
+
" proxy_temp_path /var/lib/nginx/proxy;",
|
|
83038
|
+
" fastcgi_temp_path /var/lib/nginx/fastcgi;",
|
|
83039
|
+
" uwsgi_temp_path /var/lib/nginx/uwsgi;",
|
|
83040
|
+
" scgi_temp_path /var/lib/nginx/scgi;",
|
|
83041
|
+
" include /etc/nginx/sites-enabled/*;",
|
|
83042
|
+
"}",
|
|
83043
|
+
"TS_CLOUD_NGINXCONF_EOF",
|
|
83044
|
+
"cat > /etc/systemd/system/ts-cloud-nginx.service <<'TS_CLOUD_NGINXUNIT_EOF'",
|
|
83045
|
+
"[Unit]",
|
|
83046
|
+
"Description=ts-cloud nginx (pantry)",
|
|
83047
|
+
"After=network.target",
|
|
83048
|
+
"",
|
|
83049
|
+
"[Service]",
|
|
83050
|
+
"Type=simple",
|
|
83051
|
+
`ExecStartPre=${NGINX_WRAPPER} -t`,
|
|
83052
|
+
`ExecStart=${NGINX_WRAPPER} -g 'daemon off;'`,
|
|
83053
|
+
`ExecReload=${NGINX_WRAPPER} -s reload`,
|
|
83054
|
+
"Restart=always",
|
|
83055
|
+
"RestartSec=3",
|
|
83056
|
+
"",
|
|
83057
|
+
"[Install]",
|
|
83058
|
+
"WantedBy=multi-user.target",
|
|
83059
|
+
"TS_CLOUD_NGINXUNIT_EOF",
|
|
83060
|
+
"systemctl daemon-reload",
|
|
83061
|
+
"systemctl enable ts-cloud-nginx",
|
|
83062
|
+
"ls /etc/nginx/sites-enabled/* >/dev/null 2>&1 && systemctl restart ts-cloud-nginx || true"
|
|
83063
|
+
];
|
|
83064
|
+
}
|
|
81633
83065
|
|
|
81634
83066
|
// src/drivers/shared/ufw.ts
|
|
81635
83067
|
var UFW_BASE_PORTS = [80, 443];
|
|
@@ -81858,6 +83290,17 @@ function backupEntryFor(database) {
|
|
|
81858
83290
|
}
|
|
81859
83291
|
function buildBackupsConfigTs(database, backups) {
|
|
81860
83292
|
const entry = database ? backupEntryFor(database) : null;
|
|
83293
|
+
const destinations = backups.bucket ? [
|
|
83294
|
+
" destinations: [",
|
|
83295
|
+
" {",
|
|
83296
|
+
" type: 's3',",
|
|
83297
|
+
` bucket: '${backups.bucket}',`,
|
|
83298
|
+
" prefix: 'db-backups',",
|
|
83299
|
+
...backups.endpoint ? [` endpoint: '${backups.endpoint}',`] : [],
|
|
83300
|
+
" optional: false,",
|
|
83301
|
+
" },",
|
|
83302
|
+
" ],"
|
|
83303
|
+
] : [];
|
|
81861
83304
|
return [
|
|
81862
83305
|
"import type { BackupConfig } from 'ts-backups'",
|
|
81863
83306
|
"",
|
|
@@ -81871,6 +83314,7 @@ function buildBackupsConfigTs(database, backups) {
|
|
|
81871
83314
|
" databases: [",
|
|
81872
83315
|
...entry ? [entry] : [],
|
|
81873
83316
|
" ],",
|
|
83317
|
+
...destinations,
|
|
81874
83318
|
"}",
|
|
81875
83319
|
"",
|
|
81876
83320
|
"export default config",
|
|
@@ -81884,21 +83328,21 @@ function buildBackupProvisionScript(options) {
|
|
|
81884
83328
|
return [];
|
|
81885
83329
|
const schedule = backups.schedule || "0 2 * * *";
|
|
81886
83330
|
const configTs = buildBackupsConfigTs(database, backups);
|
|
81887
|
-
const syncCmd = backups.bucket ? `aws s3 sync ${BACKUP_OUTPUT_DIR} s3://${backups.bucket}/db-backups${backups.endpoint ? ` --endpoint-url ${backups.endpoint}` : ""}` : `echo "ts-cloud: no backup bucket configured; keeping ${BACKUP_OUTPUT_DIR} local only"`;
|
|
81888
83331
|
return [
|
|
81889
83332
|
"export DEBIAN_FRONTEND=noninteractive",
|
|
81890
83333
|
`mkdir -p /etc/ts-cloud ${BACKUP_OUTPUT_DIR}`,
|
|
81891
83334
|
"command -v bun >/dev/null 2>&1 || (curl -fsSL https://bun.sh/install | bash && ln -sf /root/.bun/bin/bun /usr/local/bin/bun)",
|
|
83335
|
+
"bun add -g ts-backups || true",
|
|
81892
83336
|
`cat > ${BACKUP_CONFIG_PATH} <<'TS_CLOUD_BACKUP_CFG_EOF'`,
|
|
81893
83337
|
configTs.replace(/\n$/, ""),
|
|
81894
83338
|
"TS_CLOUD_BACKUP_CFG_EOF",
|
|
81895
83339
|
`cat > ${BACKUP_RUNNER_PATH} <<'TS_CLOUD_BACKUP_RUN_EOF'`,
|
|
81896
83340
|
"#!/bin/bash",
|
|
81897
83341
|
"set -uo pipefail",
|
|
83342
|
+
'export PATH="/root/.bun/bin:/usr/local/bin:$PATH"',
|
|
81898
83343
|
'notify() { [ -x /usr/local/bin/ts-cloud-notify ] && /usr/local/bin/ts-cloud-notify "$1" || true; }',
|
|
81899
83344
|
"cd /etc/ts-cloud",
|
|
81900
|
-
'if !
|
|
81901
|
-
`if ! ${syncCmd}; then notify "❌ ts-cloud backup failed (upload)"; exit 1; fi`,
|
|
83345
|
+
'if ! ts-backups backup --config /etc/ts-cloud/backups.config.ts; then notify "❌ ts-cloud backup failed"; exit 1; fi',
|
|
81902
83346
|
"TS_CLOUD_BACKUP_RUN_EOF",
|
|
81903
83347
|
`chmod +x ${BACKUP_RUNNER_PATH}`,
|
|
81904
83348
|
`cat > ${BACKUP_CRON_PATH} <<'TS_CLOUD_BACKUP_CRON_EOF'`,
|
|
@@ -81912,13 +83356,22 @@ function buildBackupProvisionScript(options) {
|
|
|
81912
83356
|
function buildComputeProvisionScripts(config6) {
|
|
81913
83357
|
const compute = config6.infrastructure?.compute ?? {};
|
|
81914
83358
|
const phpBox = compute.runtime === "php" || !!compute.php;
|
|
81915
|
-
const
|
|
81916
|
-
|
|
81917
|
-
|
|
81918
|
-
|
|
81919
|
-
|
|
81920
|
-
|
|
83359
|
+
const needsPantry = phpBox || !!compute.managedServices;
|
|
83360
|
+
const pantryBootstrap = needsPantry ? buildPantryBootstrapScript() : [];
|
|
83361
|
+
const useNginx = compute.webServer !== "rpx";
|
|
83362
|
+
const phpProvision = phpBox ? [
|
|
83363
|
+
...pantryBootstrap,
|
|
83364
|
+
...buildPhpProvisionScript({
|
|
83365
|
+
versions: compute.php?.versions,
|
|
83366
|
+
default: compute.php?.default,
|
|
83367
|
+
extensions: compute.php?.extensions,
|
|
83368
|
+
installNginx: useNginx
|
|
83369
|
+
}),
|
|
83370
|
+
...useNginx ? buildNginxServiceScript() : []
|
|
83371
|
+
] : undefined;
|
|
81921
83372
|
const extras = [];
|
|
83373
|
+
if (!phpBox && needsPantry)
|
|
83374
|
+
extras.push(...pantryBootstrap);
|
|
81922
83375
|
extras.push(...buildNotifierScript(config6.notifications));
|
|
81923
83376
|
if (compute.managedServices) {
|
|
81924
83377
|
extras.push(...buildServicesProvisionScript(compute.managedServices), ...buildDatabaseSetupScript(config6.infrastructure?.appDatabase, compute.managedServices));
|
|
@@ -82165,7 +83618,7 @@ class AwsDriver {
|
|
|
82165
83618
|
GroupId: groupId,
|
|
82166
83619
|
IpPermissions: rules.map((r) => ({ IpProtocol: r.protocol, FromPort: r.port, ToPort: r.port, IpRanges: [{ CidrIp: r.cidr }] }))
|
|
82167
83620
|
}).catch((e) => {
|
|
82168
|
-
if (!/InvalidPermission\.Duplicate/.test(e
|
|
83621
|
+
if (!/InvalidPermission\.Duplicate/.test(e instanceof Error ? e.message : ""))
|
|
82169
83622
|
throw e;
|
|
82170
83623
|
});
|
|
82171
83624
|
const userData = encodeUserData(buildAwsUserData(config6));
|
|
@@ -82245,7 +83698,7 @@ class AwsDriver {
|
|
|
82245
83698
|
sshUser: "ec2-user"
|
|
82246
83699
|
};
|
|
82247
83700
|
} catch (err) {
|
|
82248
|
-
if (!/does not exist|ValidationError/i.test(err
|
|
83701
|
+
if (!/does not exist|ValidationError/i.test(err instanceof Error ? err.message : ""))
|
|
82249
83702
|
throw err;
|
|
82250
83703
|
const targets = await this.findComputeTargets({
|
|
82251
83704
|
slug: options.config.project.slug,
|
|
@@ -82275,7 +83728,7 @@ class AwsDriver {
|
|
|
82275
83728
|
await s32.putObject({
|
|
82276
83729
|
bucket,
|
|
82277
83730
|
key: options.remoteKey,
|
|
82278
|
-
body:
|
|
83731
|
+
body: readFileSync12(options.localPath),
|
|
82279
83732
|
contentType: "application/gzip"
|
|
82280
83733
|
});
|
|
82281
83734
|
return { artifactRef: `s3://${bucket}/${options.remoteKey}` };
|
|
@@ -82355,7 +83808,7 @@ class AwsDriver {
|
|
|
82355
83808
|
const terminalStatuses = new Set(["Success", "Failed", "Cancelled", "TimedOut"]);
|
|
82356
83809
|
let lastInvocations = [];
|
|
82357
83810
|
while (Date.now() - startTime < maxWait) {
|
|
82358
|
-
await new Promise((
|
|
83811
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
82359
83812
|
try {
|
|
82360
83813
|
const invocations = await ssm2.listCommandInvocations({ CommandId: commandId, Details: true });
|
|
82361
83814
|
lastInvocations = invocations;
|
|
@@ -82381,10 +83834,10 @@ class AwsDriver {
|
|
|
82381
83834
|
}
|
|
82382
83835
|
|
|
82383
83836
|
// src/drivers/hetzner/driver.ts
|
|
82384
|
-
import { existsSync as existsSync17, readFileSync as
|
|
83837
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
|
|
82385
83838
|
import { homedir as homedir7 } from "node:os";
|
|
82386
|
-
import { join as
|
|
82387
|
-
import { execSync } from "node:child_process";
|
|
83839
|
+
import { join as join15 } from "node:path";
|
|
83840
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
82388
83841
|
|
|
82389
83842
|
// src/drivers/hetzner/client.ts
|
|
82390
83843
|
var DEFAULT_API_URL = "https://api.hetzner.cloud/v1";
|
|
@@ -82483,7 +83936,15 @@ class HetznerClient {
|
|
|
82483
83936
|
services: options.services.map((s) => ({
|
|
82484
83937
|
protocol: s.protocol ?? "tcp",
|
|
82485
83938
|
listen_port: s.listenPort,
|
|
82486
|
-
destination_port: s.destinationPort
|
|
83939
|
+
destination_port: s.destinationPort,
|
|
83940
|
+
health_check: {
|
|
83941
|
+
protocol: "http",
|
|
83942
|
+
port: 80,
|
|
83943
|
+
interval: 15,
|
|
83944
|
+
timeout: 10,
|
|
83945
|
+
retries: 3,
|
|
83946
|
+
http: { path: "/", status_codes: ["2??", "3??"] }
|
|
83947
|
+
}
|
|
82487
83948
|
}))
|
|
82488
83949
|
});
|
|
82489
83950
|
return data.load_balancer;
|
|
@@ -82546,7 +84007,7 @@ class HetznerClient {
|
|
|
82546
84007
|
if (data.action.status === "error") {
|
|
82547
84008
|
throw new Error(data.action.error?.message || "Hetzner action failed");
|
|
82548
84009
|
}
|
|
82549
|
-
await new Promise((
|
|
84010
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
82550
84011
|
}
|
|
82551
84012
|
throw new Error(`Timed out waiting for Hetzner action ${actionId}`);
|
|
82552
84013
|
}
|
|
@@ -82558,7 +84019,7 @@ class HetznerClient {
|
|
|
82558
84019
|
const server = await this.getServer(serverId);
|
|
82559
84020
|
if (server.status === "running")
|
|
82560
84021
|
return server;
|
|
82561
|
-
await new Promise((
|
|
84022
|
+
await new Promise((resolve14) => setTimeout(resolve14, pollInterval));
|
|
82562
84023
|
}
|
|
82563
84024
|
throw new Error(`Timed out waiting for server ${serverId} to reach running state`);
|
|
82564
84025
|
}
|
|
@@ -82805,10 +84266,10 @@ function matchesTsCloudLabels(labels, slug, environment, role = "app") {
|
|
|
82805
84266
|
|
|
82806
84267
|
// src/drivers/hetzner/state.ts
|
|
82807
84268
|
import { mkdir as mkdir4, readFile, writeFile as writeFile4 } from "node:fs/promises";
|
|
82808
|
-
import { join as
|
|
84269
|
+
import { join as join14 } from "node:path";
|
|
82809
84270
|
var STATE_DIR = ".ts-cloud/state";
|
|
82810
84271
|
function driverStatePath(stackName) {
|
|
82811
|
-
return
|
|
84272
|
+
return join14(process.cwd(), STATE_DIR, `${stackName}.json`);
|
|
82812
84273
|
}
|
|
82813
84274
|
async function readDriverState(stackName) {
|
|
82814
84275
|
try {
|
|
@@ -82820,7 +84281,7 @@ async function readDriverState(stackName) {
|
|
|
82820
84281
|
}
|
|
82821
84282
|
async function writeDriverState(stackName, state) {
|
|
82822
84283
|
const path = driverStatePath(stackName);
|
|
82823
|
-
await mkdir4(
|
|
84284
|
+
await mkdir4(join14(process.cwd(), STATE_DIR), { recursive: true });
|
|
82824
84285
|
await writeFile4(path, `${JSON.stringify(state, null, 2)}
|
|
82825
84286
|
`, "utf8");
|
|
82826
84287
|
}
|
|
@@ -82828,7 +84289,7 @@ async function writeDriverState(stackName, state) {
|
|
|
82828
84289
|
// src/drivers/hetzner/driver.ts
|
|
82829
84290
|
var SSH_MAX_BUFFER = 1024 * 1024 * 256;
|
|
82830
84291
|
function expandHome(path) {
|
|
82831
|
-
return path.startsWith("~/") ?
|
|
84292
|
+
return path.startsWith("~/") ? join15(homedir7(), path.slice(2)) : path;
|
|
82832
84293
|
}
|
|
82833
84294
|
|
|
82834
84295
|
class HetznerDriver {
|
|
@@ -82960,12 +84421,12 @@ class HetznerDriver {
|
|
|
82960
84421
|
const baked = compute.bakedImage === true;
|
|
82961
84422
|
const existingState = await readDriverState(stackName);
|
|
82962
84423
|
if (existingState?.loadBalancerId) {
|
|
82963
|
-
const
|
|
82964
|
-
const
|
|
82965
|
-
if (
|
|
84424
|
+
const lbs = await this.client.listLoadBalancers().catch(() => []);
|
|
84425
|
+
const lb = lbs.find((l) => l.id === existingState.loadBalancerId);
|
|
84426
|
+
if (lb) {
|
|
82966
84427
|
return {
|
|
82967
|
-
appPublicIp:
|
|
82968
|
-
loadBalancerIp:
|
|
84428
|
+
appPublicIp: lb.public_net?.ipv4?.ip,
|
|
84429
|
+
loadBalancerIp: lb.public_net?.ipv4?.ip,
|
|
82969
84430
|
servicesPrivateIp: existingState.servicesPrivateIp,
|
|
82970
84431
|
deployStoragePath: "/var/ts-cloud/staging",
|
|
82971
84432
|
sshUser: this.sshUser
|
|
@@ -82992,20 +84453,27 @@ class HetznerDriver {
|
|
|
82992
84453
|
...buildAuthorizedKeysScript(compute.sshKeys)
|
|
82993
84454
|
];
|
|
82994
84455
|
const servicesUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", servicesProvision, baked }));
|
|
82995
|
-
const
|
|
82996
|
-
|
|
82997
|
-
|
|
82998
|
-
|
|
82999
|
-
|
|
83000
|
-
|
|
83001
|
-
|
|
83002
|
-
|
|
83003
|
-
|
|
83004
|
-
|
|
83005
|
-
|
|
83006
|
-
|
|
83007
|
-
|
|
83008
|
-
|
|
84456
|
+
const all = await this.client.listServers().catch(() => []);
|
|
84457
|
+
const newServerIds = [];
|
|
84458
|
+
let svcServer = all.find((s) => matchesTsCloudLabels(s.labels, slug, environment, "services"));
|
|
84459
|
+
if (!svcServer) {
|
|
84460
|
+
const { server, action } = await this.client.createServer({
|
|
84461
|
+
name: `${slug}-${environment}-services`,
|
|
84462
|
+
serverType: resolveHetznerServerType(typeof compute.servicesServer === "object" ? compute.servicesServer.size : compute.size),
|
|
84463
|
+
image,
|
|
84464
|
+
location,
|
|
84465
|
+
userData: servicesUserData,
|
|
84466
|
+
labels: tsCloudLabels(slug, environment, "services"),
|
|
84467
|
+
sshKeys: sshKeyId ? [sshKeyId] : undefined,
|
|
84468
|
+
firewalls: [{ firewall: svcFw.id }],
|
|
84469
|
+
networks: [network.id]
|
|
84470
|
+
});
|
|
84471
|
+
await this.client.waitForAction(action.id);
|
|
84472
|
+
svcServer = await this.client.waitForServerRunning(server.id);
|
|
84473
|
+
newServerIds.push(server.id);
|
|
84474
|
+
}
|
|
84475
|
+
const servicesServerId = svcServer.id;
|
|
84476
|
+
const servicesPrivateIp = svcServer.private_net?.[0]?.ip ?? (await this.client.getServer(servicesServerId)).private_net?.[0]?.ip;
|
|
83009
84477
|
const appProvision = [
|
|
83010
84478
|
...buildAutoUpdatesScript(true),
|
|
83011
84479
|
...buildMonitoringScript(true),
|
|
@@ -83019,8 +84487,15 @@ class HetznerDriver {
|
|
|
83019
84487
|
installNginx: compute.webServer !== "rpx"
|
|
83020
84488
|
});
|
|
83021
84489
|
const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked }));
|
|
83022
|
-
const
|
|
83023
|
-
|
|
84490
|
+
const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
|
|
84491
|
+
const appServerIds = existingApp.map((s) => s.id);
|
|
84492
|
+
if (existingApp.length > topology.appServers) {
|
|
84493
|
+
for (const extra of existingApp.slice(topology.appServers)) {
|
|
84494
|
+
await this.client.deleteServer(extra.id).catch(() => {});
|
|
84495
|
+
appServerIds.splice(appServerIds.indexOf(extra.id), 1);
|
|
84496
|
+
}
|
|
84497
|
+
}
|
|
84498
|
+
for (let i = existingApp.length;i < topology.appServers; i++) {
|
|
83024
84499
|
const { server, action } = await this.client.createServer({
|
|
83025
84500
|
name: `${slug}-${environment}-app-${i + 1}`,
|
|
83026
84501
|
serverType,
|
|
@@ -83034,10 +84509,10 @@ class HetznerDriver {
|
|
|
83034
84509
|
});
|
|
83035
84510
|
await this.client.waitForAction(action.id);
|
|
83036
84511
|
appServerIds.push(server.id);
|
|
84512
|
+
newServerIds.push(server.id);
|
|
83037
84513
|
}
|
|
83038
|
-
if (this.waitForBoot) {
|
|
83039
|
-
|
|
83040
|
-
await Promise.all(waitIds.map(async (id) => {
|
|
84514
|
+
if (this.waitForBoot && newServerIds.length > 0) {
|
|
84515
|
+
await Promise.all(newServerIds.map(async (id) => {
|
|
83041
84516
|
const running = await this.client.waitForServerRunning(id);
|
|
83042
84517
|
const ip = running.public_net.ipv4?.ip;
|
|
83043
84518
|
if (ip) {
|
|
@@ -83047,33 +84522,42 @@ class HetznerDriver {
|
|
|
83047
84522
|
}));
|
|
83048
84523
|
}
|
|
83049
84524
|
const lbName = `${slug}-${environment}-lb`;
|
|
83050
|
-
|
|
83051
|
-
|
|
83052
|
-
|
|
83053
|
-
|
|
83054
|
-
|
|
83055
|
-
|
|
83056
|
-
|
|
83057
|
-
|
|
83058
|
-
|
|
83059
|
-
|
|
83060
|
-
|
|
83061
|
-
|
|
84525
|
+
let lbIp;
|
|
84526
|
+
let lbId;
|
|
84527
|
+
if (topology.loadBalancer) {
|
|
84528
|
+
const lbs = await this.client.listLoadBalancers().catch(() => []);
|
|
84529
|
+
const lb = lbs.find((l) => l.name === lbName) ?? await this.client.createLoadBalancer({
|
|
84530
|
+
name: lbName,
|
|
84531
|
+
location,
|
|
84532
|
+
network: network.id,
|
|
84533
|
+
labels: tsCloudLabels(slug, environment, "lb"),
|
|
84534
|
+
labelSelector: `ts-cloud/project=${slug},ts-cloud/environment=${environment},ts-cloud/role=app`,
|
|
84535
|
+
services: [
|
|
84536
|
+
{ listenPort: 80, destinationPort: 80 },
|
|
84537
|
+
{ listenPort: 443, destinationPort: 443 }
|
|
84538
|
+
]
|
|
84539
|
+
});
|
|
84540
|
+
lbId = lb.id;
|
|
84541
|
+
lbIp = lb.public_net?.ipv4?.ip;
|
|
84542
|
+
}
|
|
84543
|
+
const appPublicIp = lbIp ?? (await this.client.getServer(appServerIds[0]).catch(() => {
|
|
84544
|
+
return;
|
|
84545
|
+
}))?.public_net.ipv4?.ip;
|
|
83062
84546
|
const state = {
|
|
83063
84547
|
provider: "hetzner",
|
|
83064
84548
|
stackName,
|
|
83065
84549
|
networkId: network.id,
|
|
83066
|
-
loadBalancerId:
|
|
83067
|
-
servicesServerId
|
|
84550
|
+
loadBalancerId: lbId,
|
|
84551
|
+
servicesServerId,
|
|
83068
84552
|
servicesPrivateIp,
|
|
83069
|
-
publicIp:
|
|
84553
|
+
publicIp: appPublicIp,
|
|
83070
84554
|
deployStoragePath: "/var/ts-cloud/staging",
|
|
83071
84555
|
sshUser: this.sshUser
|
|
83072
84556
|
};
|
|
83073
84557
|
await writeDriverState(stackName, state);
|
|
83074
84558
|
return {
|
|
83075
|
-
appPublicIp
|
|
83076
|
-
loadBalancerIp:
|
|
84559
|
+
appPublicIp,
|
|
84560
|
+
loadBalancerIp: lbIp,
|
|
83077
84561
|
servicesPrivateIp,
|
|
83078
84562
|
deployStoragePath: "/var/ts-cloud/staging",
|
|
83079
84563
|
sshUser: this.sshUser
|
|
@@ -83089,8 +84573,10 @@ class HetznerDriver {
|
|
|
83089
84573
|
const lbs = await this.client.listLoadBalancers().catch(() => []);
|
|
83090
84574
|
const lb = lbs.find((l) => l.name === lbName);
|
|
83091
84575
|
if (lb) {
|
|
83092
|
-
|
|
83093
|
-
|
|
84576
|
+
try {
|
|
84577
|
+
await this.client.deleteLoadBalancer(lb.id);
|
|
84578
|
+
destroyed.push(`load balancer ${lbName}`);
|
|
84579
|
+
} catch {}
|
|
83094
84580
|
}
|
|
83095
84581
|
const allServers = await this.client.listServers().catch(() => []);
|
|
83096
84582
|
const serverIds = new Set;
|
|
@@ -83103,8 +84589,10 @@ class HetznerDriver {
|
|
|
83103
84589
|
if (state?.servicesServerId)
|
|
83104
84590
|
serverIds.add(state.servicesServerId);
|
|
83105
84591
|
for (const id of serverIds) {
|
|
83106
|
-
|
|
83107
|
-
|
|
84592
|
+
try {
|
|
84593
|
+
await this.client.deleteServer(id);
|
|
84594
|
+
destroyed.push(`server ${id}`);
|
|
84595
|
+
} catch {}
|
|
83108
84596
|
}
|
|
83109
84597
|
const firewalls = await this.client.listFirewalls().catch(() => []);
|
|
83110
84598
|
for (const name of [`${slug}-${environment}-app-fw`, `${slug}-${environment}-services-fw`]) {
|
|
@@ -83232,7 +84720,7 @@ class HetznerDriver {
|
|
|
83232
84720
|
if (!existsSync17(this.sshPublicKeyPath)) {
|
|
83233
84721
|
throw new Error(`SSH public key not found at ${this.sshPublicKeyPath}. ts-cloud deploys to Hetzner over SSH and needs a public key to authorize on the server. ` + `Generate one (\`ssh-keygen -t ed25519\`) or set hetzner.sshPrivateKeyPath / HCLOUD_SSH_PUBLIC_KEY.`);
|
|
83234
84722
|
}
|
|
83235
|
-
const publicKey =
|
|
84723
|
+
const publicKey = readFileSync13(this.sshPublicKeyPath, "utf8").trim();
|
|
83236
84724
|
const normalized = normalizeSshPublicKey(publicKey);
|
|
83237
84725
|
const existing = await this.client.listSshKeys();
|
|
83238
84726
|
const match = existing.find((key) => normalizeSshPublicKey(key.public_key) === normalized);
|
|
@@ -83275,7 +84763,7 @@ class HetznerDriver {
|
|
|
83275
84763
|
return [...ports].filter((port) => ![80, 443].includes(port));
|
|
83276
84764
|
}
|
|
83277
84765
|
async sleep(ms) {
|
|
83278
|
-
await new Promise((
|
|
84766
|
+
await new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
83279
84767
|
}
|
|
83280
84768
|
async waitForSshReady(host) {
|
|
83281
84769
|
const { sshIntervalMs, sshTimeoutMs } = this.bootWait;
|
|
@@ -83283,7 +84771,7 @@ class HetznerDriver {
|
|
|
83283
84771
|
let lastErr;
|
|
83284
84772
|
while (Date.now() - start < sshTimeoutMs) {
|
|
83285
84773
|
try {
|
|
83286
|
-
|
|
84774
|
+
execSync3(`ssh ${this.sshBaseArgs(host, ["-o", "ConnectTimeout=5"]).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} true`, {
|
|
83287
84775
|
stdio: "pipe",
|
|
83288
84776
|
maxBuffer: SSH_MAX_BUFFER
|
|
83289
84777
|
});
|
|
@@ -83343,7 +84831,7 @@ ${out}`);
|
|
|
83343
84831
|
];
|
|
83344
84832
|
}
|
|
83345
84833
|
scpToHost(host, localPath, remotePath) {
|
|
83346
|
-
|
|
84834
|
+
execSync3([
|
|
83347
84835
|
"scp",
|
|
83348
84836
|
"-i",
|
|
83349
84837
|
this.sshPrivateKeyPath,
|
|
@@ -83356,7 +84844,7 @@ ${out}`);
|
|
|
83356
84844
|
}
|
|
83357
84845
|
sshExec(host, script) {
|
|
83358
84846
|
const escaped = script.replace(/'/g, `'\\''`);
|
|
83359
|
-
return
|
|
84847
|
+
return execSync3(`ssh ${this.sshBaseArgs(host).map((a) => `"${a.replace(/"/g, "\\\"")}"`).join(" ")} '${escaped}'`, {
|
|
83360
84848
|
encoding: "utf8",
|
|
83361
84849
|
stdio: ["pipe", "pipe", "pipe"],
|
|
83362
84850
|
maxBuffer: SSH_MAX_BUFFER
|
|
@@ -83678,8 +85166,7 @@ function buildLaravelDeployScript(options) {
|
|
|
83678
85166
|
if (!site.repository?.url)
|
|
83679
85167
|
throw new Error(`Site '${siteName}' is a PHP/git site but has no repository.url to clone`);
|
|
83680
85168
|
const base = options.appBase ?? `/var/www/${siteName}`;
|
|
83681
|
-
const
|
|
83682
|
-
const phpBin = `php${phpVersion}`;
|
|
85169
|
+
const phpBin = "php";
|
|
83683
85170
|
const paths = releasePaths(base, releaseId);
|
|
83684
85171
|
const sharedPaths = site.sharedPaths ?? DEFAULT_SHARED_PATHS;
|
|
83685
85172
|
const keepReleases = site.keepReleases ?? DEFAULT_KEEP_RELEASES;
|
|
@@ -83688,7 +85175,8 @@ function buildLaravelDeployScript(options) {
|
|
|
83688
85175
|
"set -euo pipefail",
|
|
83689
85176
|
'export HOME="${HOME:-/root}"',
|
|
83690
85177
|
'export COMPOSER_HOME="${COMPOSER_HOME:-/root/.composer}"',
|
|
83691
|
-
"export COMPOSER_ALLOW_SUPERUSER=1"
|
|
85178
|
+
"export COMPOSER_ALLOW_SUPERUSER=1",
|
|
85179
|
+
pantryEnvActivation()
|
|
83692
85180
|
];
|
|
83693
85181
|
out.push(...buildEnsureReleaseLayout(paths, sharedPaths));
|
|
83694
85182
|
if (site.env && Object.keys(site.env).length > 0)
|
|
@@ -83703,7 +85191,7 @@ function buildLaravelDeployScript(options) {
|
|
|
83703
85191
|
} else if (line === MACRO_ACTIVATE_RELEASE) {
|
|
83704
85192
|
out.push(...buildActivateRelease(paths));
|
|
83705
85193
|
out.push(...buildPruneReleases(paths, keepReleases));
|
|
83706
|
-
out.push(`
|
|
85194
|
+
out.push(`(cd ${PANTRY_PROJECT_DIR} && pantry restart php-fpm) 2>/dev/null || true`);
|
|
83707
85195
|
} else if (line === MACRO_RESTART_QUEUES) {
|
|
83708
85196
|
out.push(`${phpBin} artisan queue:restart || true`);
|
|
83709
85197
|
} else {
|
|
@@ -83714,6 +85202,10 @@ function buildLaravelDeployScript(options) {
|
|
|
83714
85202
|
}
|
|
83715
85203
|
|
|
83716
85204
|
// src/drivers/shared/laravel-services.ts
|
|
85205
|
+
var PANTRY_ENV_EVAL = `eval "$(cd ${PANTRY_PROJECT_DIR} && pantry env 2>/dev/null)"`;
|
|
85206
|
+
function pantryExec(cmd) {
|
|
85207
|
+
return `/bin/sh -lc '${PANTRY_ENV_EVAL}; exec ${cmd}'`;
|
|
85208
|
+
}
|
|
83717
85209
|
function reEscape(value) {
|
|
83718
85210
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
83719
85211
|
}
|
|
@@ -83777,8 +85269,7 @@ function schedulerCronPath(slug, siteName) {
|
|
|
83777
85269
|
}
|
|
83778
85270
|
function buildSiteServicesScript(options) {
|
|
83779
85271
|
const { slug, siteName, site } = options;
|
|
83780
|
-
const
|
|
83781
|
-
const phpBin = `php${phpVersion}`;
|
|
85272
|
+
const phpBin = "php";
|
|
83782
85273
|
const base = options.appBase ?? `/var/www/${siteName}`;
|
|
83783
85274
|
const current = `${base}/current`;
|
|
83784
85275
|
const artisan = `${current}/artisan`;
|
|
@@ -83793,7 +85284,7 @@ function buildSiteServicesScript(options) {
|
|
|
83793
85284
|
out.push(...writeUnitScript(name, systemdUnit({
|
|
83794
85285
|
description: `${siteName} queue worker ${qIndex}.${p} (managed by ts-cloud)`,
|
|
83795
85286
|
workingDir: current,
|
|
83796
|
-
execStart: queueExecStart(worker, phpBin, artisan),
|
|
85287
|
+
execStart: pantryExec(queueExecStart(worker, phpBin, artisan)),
|
|
83797
85288
|
stopWaitSecs: worker.stopWaitSecs ?? 90
|
|
83798
85289
|
})));
|
|
83799
85290
|
}
|
|
@@ -83807,7 +85298,7 @@ function buildSiteServicesScript(options) {
|
|
|
83807
85298
|
out.push(...writeUnitScript(name, systemdUnit({
|
|
83808
85299
|
description: `${siteName} daemon ${daemon.name || daemon.command} (managed by ts-cloud)`,
|
|
83809
85300
|
workingDir: daemon.directory || current,
|
|
83810
|
-
execStart: daemon.command,
|
|
85301
|
+
execStart: pantryExec(daemon.command),
|
|
83811
85302
|
restart: daemon.restart,
|
|
83812
85303
|
user: daemon.user
|
|
83813
85304
|
})));
|
|
@@ -83820,7 +85311,7 @@ function buildSiteServicesScript(options) {
|
|
|
83820
85311
|
}
|
|
83821
85312
|
const cronPath = schedulerCronPath(slug, siteName);
|
|
83822
85313
|
if (site.scheduler) {
|
|
83823
|
-
const cron = `* * * * * root cd ${current} && ${phpBin} artisan schedule:run >> /dev/null 2>&1
|
|
85314
|
+
const cron = `* * * * * root cd ${current} && ${PANTRY_ENV_EVAL} && ${phpBin} artisan schedule:run >> /dev/null 2>&1
|
|
83824
85315
|
`;
|
|
83825
85316
|
out.push(`cat > ${cronPath} <<'TS_CLOUD_CRON_EOF'`, cron.replace(/\n$/, ""), "TS_CLOUD_CRON_EOF", `chmod 644 ${cronPath}`);
|
|
83826
85317
|
} else {
|
|
@@ -83832,111 +85323,6 @@ function siteHasServices(site) {
|
|
|
83832
85323
|
return !!(site.queues?.length || site.daemons?.length || site.scheduler);
|
|
83833
85324
|
}
|
|
83834
85325
|
|
|
83835
|
-
// src/drivers/shared/nginx-vhost.ts
|
|
83836
|
-
function htpasswdPath(siteName) {
|
|
83837
|
-
return `/etc/nginx/.htpasswd-${siteName}`;
|
|
83838
|
-
}
|
|
83839
|
-
var PHP_TYPES = new Set(["laravel", "php", "statamic", "wordpress"]);
|
|
83840
|
-
function isPhpSiteType(type) {
|
|
83841
|
-
return PHP_TYPES.has(type);
|
|
83842
|
-
}
|
|
83843
|
-
function defaultWebDirectory(type) {
|
|
83844
|
-
return type === "laravel" || type === "statamic" || type === "wordpress" ? "public" : "";
|
|
83845
|
-
}
|
|
83846
|
-
function resolveRoot(appDir, webDirectory) {
|
|
83847
|
-
const base = appDir.replace(/\/+$/, "");
|
|
83848
|
-
const sub = webDirectory.replace(/^\/+|\/+$/g, "");
|
|
83849
|
-
return sub ? `${base}/${sub}` : base;
|
|
83850
|
-
}
|
|
83851
|
-
function vhostBody(options) {
|
|
83852
|
-
const type = options.type ?? "laravel";
|
|
83853
|
-
const webDirectory = options.webDirectory ?? defaultWebDirectory(type);
|
|
83854
|
-
const root = resolveRoot(options.appDir, webDirectory);
|
|
83855
|
-
const isPhp = isPhpSiteType(type);
|
|
83856
|
-
const phpVersion = options.phpVersion ?? "8.3";
|
|
83857
|
-
const lines = [
|
|
83858
|
-
` root ${root};`,
|
|
83859
|
-
"",
|
|
83860
|
-
' add_header X-Frame-Options "SAMEORIGIN";',
|
|
83861
|
-
' add_header X-Content-Type-Options "nosniff";',
|
|
83862
|
-
"",
|
|
83863
|
-
` index ${isPhp ? "index.php index.html" : "index.html index.htm"};`,
|
|
83864
|
-
"",
|
|
83865
|
-
" charset utf-8;",
|
|
83866
|
-
""
|
|
83867
|
-
];
|
|
83868
|
-
if (options.auth) {
|
|
83869
|
-
lines.push(` auth_basic "${options.auth.realm || "Restricted"}";`, ` auth_basic_user_file ${htpasswdPath(options.siteName)};`, "");
|
|
83870
|
-
}
|
|
83871
|
-
for (const [from, to] of Object.entries(options.redirects || {})) {
|
|
83872
|
-
lines.push(` location = ${from} { return 301 ${to}; }`);
|
|
83873
|
-
}
|
|
83874
|
-
if (Object.keys(options.redirects || {}).length > 0)
|
|
83875
|
-
lines.push("");
|
|
83876
|
-
if (isPhp) {
|
|
83877
|
-
lines.push(" location / {", " try_files $uri $uri/ /index.php?$query_string;", " }", "", " location = /favicon.ico { access_log off; log_not_found off; }", " location = /robots.txt { access_log off; log_not_found off; }", "", " error_page 404 /index.php;", "", " location ~ \\.php$ {", ` fastcgi_pass unix:${phpFpmSocketPath(phpVersion)};`, " fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;", " include fastcgi_params;", " }", "", " location ~ /\\.(?!well-known).* {", " deny all;", " }");
|
|
83878
|
-
} else if (type === "spa") {
|
|
83879
|
-
lines.push(" location / {", " try_files $uri $uri/ /index.html;", " }");
|
|
83880
|
-
} else {
|
|
83881
|
-
lines.push(" location / {", " try_files $uri $uri/ =404;", " }");
|
|
83882
|
-
}
|
|
83883
|
-
return lines;
|
|
83884
|
-
}
|
|
83885
|
-
function buildNginxVhost(options) {
|
|
83886
|
-
const serverNames = [options.domain, ...options.aliases || []].filter(Boolean).join(" ");
|
|
83887
|
-
const body = vhostBody(options);
|
|
83888
|
-
if (options.ssl) {
|
|
83889
|
-
const redirect = [
|
|
83890
|
-
"server {",
|
|
83891
|
-
" listen 80;",
|
|
83892
|
-
" listen [::]:80;",
|
|
83893
|
-
` server_name ${serverNames};`,
|
|
83894
|
-
" return 301 https://$host$request_uri;",
|
|
83895
|
-
"}"
|
|
83896
|
-
];
|
|
83897
|
-
const tls3 = [
|
|
83898
|
-
"server {",
|
|
83899
|
-
" listen 443 ssl;",
|
|
83900
|
-
" listen [::]:443 ssl;",
|
|
83901
|
-
` server_name ${serverNames};`,
|
|
83902
|
-
` ssl_certificate ${options.ssl.certPath};`,
|
|
83903
|
-
` ssl_certificate_key ${options.ssl.keyPath};`,
|
|
83904
|
-
"",
|
|
83905
|
-
...body,
|
|
83906
|
-
"}"
|
|
83907
|
-
];
|
|
83908
|
-
return `${[...redirect, "", ...tls3].join(`
|
|
83909
|
-
`)}
|
|
83910
|
-
`;
|
|
83911
|
-
}
|
|
83912
|
-
const lines = [
|
|
83913
|
-
"server {",
|
|
83914
|
-
" listen 80;",
|
|
83915
|
-
" listen [::]:80;",
|
|
83916
|
-
` server_name ${serverNames};`,
|
|
83917
|
-
...body,
|
|
83918
|
-
"}"
|
|
83919
|
-
];
|
|
83920
|
-
return `${lines.join(`
|
|
83921
|
-
`)}
|
|
83922
|
-
`;
|
|
83923
|
-
}
|
|
83924
|
-
function buildNginxVhostScript(options) {
|
|
83925
|
-
const available = `/etc/nginx/sites-available/${options.siteName}`;
|
|
83926
|
-
const enabled2 = `/etc/nginx/sites-enabled/${options.siteName}`;
|
|
83927
|
-
const vhost = buildNginxVhost(options);
|
|
83928
|
-
const out = [];
|
|
83929
|
-
if (options.auth) {
|
|
83930
|
-
const file = htpasswdPath(options.siteName);
|
|
83931
|
-
const sq2 = (v) => v.split("'").join("'\\''");
|
|
83932
|
-
const pw = sq2(options.auth.password);
|
|
83933
|
-
const user = sq2(options.auth.username);
|
|
83934
|
-
out.push(`TS_CLOUD_HTPASS=$(openssl passwd -apr1 '${pw}')`, `printf '%s:%s\\n' '${user}' "$TS_CLOUD_HTPASS" > ${file}`, `chmod 640 ${file}`, `chown root:www-data ${file} 2>/dev/null || true`);
|
|
83935
|
-
}
|
|
83936
|
-
out.push(`cat > ${available} <<'TS_CLOUD_NGINX_EOF'`, vhost.replace(/\n$/, ""), "TS_CLOUD_NGINX_EOF", `ln -sf ${available} ${enabled2}`, "rm -f /etc/nginx/sites-enabled/default", "nginx -t", "systemctl reload nginx");
|
|
83937
|
-
return out;
|
|
83938
|
-
}
|
|
83939
|
-
|
|
83940
85326
|
// src/drivers/shared/compute-deploy.ts
|
|
83941
85327
|
var noopLogger = {
|
|
83942
85328
|
info: () => {},
|
|
@@ -84210,6 +85596,7 @@ export {
|
|
|
84210
85596
|
stackDependencyManager,
|
|
84211
85597
|
signRequestAsync,
|
|
84212
85598
|
signRequest,
|
|
85599
|
+
sha256,
|
|
84213
85600
|
setupDns01Challenge,
|
|
84214
85601
|
serviceMeshManager,
|
|
84215
85602
|
sequence,
|
|
@@ -84220,8 +85607,11 @@ export {
|
|
|
84220
85607
|
secretsManager,
|
|
84221
85608
|
searchCommands,
|
|
84222
85609
|
sanitizeName,
|
|
85610
|
+
runPhpBuildHooks,
|
|
85611
|
+
runBuildHooks,
|
|
84223
85612
|
route53RoutingManager,
|
|
84224
85613
|
route53ResolverManager,
|
|
85614
|
+
responseToResult,
|
|
84225
85615
|
resourceManagementManager,
|
|
84226
85616
|
resolveStorageBucketName,
|
|
84227
85617
|
resolveSiteStackName,
|
|
@@ -84229,7 +85619,11 @@ export {
|
|
|
84229
85619
|
resolveSiteKind,
|
|
84230
85620
|
resolveSiteDeployTarget,
|
|
84231
85621
|
resolveSiteBucketName,
|
|
85622
|
+
resolveServerlessAssetBucketName,
|
|
85623
|
+
resolveServerlessArtifactBucketName,
|
|
85624
|
+
resolveServerlessAppStackName,
|
|
84232
85625
|
resolveRegion,
|
|
85626
|
+
resolveQueueNames,
|
|
84233
85627
|
resolveProjectStackName,
|
|
84234
85628
|
resolveObjectStorage,
|
|
84235
85629
|
resolveHetznerApiToken,
|
|
@@ -84237,6 +85631,7 @@ export {
|
|
|
84237
85631
|
resolveDeployBucketName,
|
|
84238
85632
|
resolveCredentials,
|
|
84239
85633
|
resolveCloudProvider,
|
|
85634
|
+
resolveApp,
|
|
84240
85635
|
requiresReplacement,
|
|
84241
85636
|
replicaManager,
|
|
84242
85637
|
remapKey,
|
|
@@ -84248,12 +85643,17 @@ export {
|
|
|
84248
85643
|
processInChunks,
|
|
84249
85644
|
previewNotifications,
|
|
84250
85645
|
previewManager,
|
|
85646
|
+
phpRuntimeLayerAssets,
|
|
85647
|
+
phpLayerPackages,
|
|
85648
|
+
phpLayerBuildStage,
|
|
84251
85649
|
performanceManager,
|
|
84252
85650
|
parseXMLResponse,
|
|
84253
85651
|
parseJSONResponse,
|
|
84254
85652
|
parallelWithRetry,
|
|
84255
85653
|
parallelMap,
|
|
84256
85654
|
parallel,
|
|
85655
|
+
packageServerlessApp,
|
|
85656
|
+
packagePhpApp,
|
|
84257
85657
|
organizationManager,
|
|
84258
85658
|
networkSecurityManager,
|
|
84259
85659
|
needsRenewal,
|
|
@@ -84268,6 +85668,7 @@ export {
|
|
|
84268
85668
|
makeAWSRequest,
|
|
84269
85669
|
logsManager,
|
|
84270
85670
|
loadCloudConfig,
|
|
85671
|
+
laravelServerlessEnvDefaults,
|
|
84271
85672
|
lambdaVersionsManager,
|
|
84272
85673
|
lambdaVPCManager,
|
|
84273
85674
|
lambdaLayersManager,
|
|
@@ -84323,6 +85724,8 @@ export {
|
|
|
84323
85724
|
generateResourceName,
|
|
84324
85725
|
generatePreviewWorkflow,
|
|
84325
85726
|
generatePreviewPipeline,
|
|
85727
|
+
generatePhpLayerDockerfile,
|
|
85728
|
+
generatePhpFpmConfig,
|
|
84326
85729
|
generateParallelConfig,
|
|
84327
85730
|
generatePRPreviewWorkflow,
|
|
84328
85731
|
generateMultiEnvWorkflow,
|
|
@@ -84340,7 +85743,9 @@ export {
|
|
|
84340
85743
|
generateCrossAccountRoleCF,
|
|
84341
85744
|
generateCostReportWorkflow,
|
|
84342
85745
|
generateCleanupWorkflow,
|
|
85746
|
+
generateBootstrap,
|
|
84343
85747
|
generateApprovalConfig,
|
|
85748
|
+
generateAppImageDockerfile,
|
|
84344
85749
|
fromWebIdentity,
|
|
84345
85750
|
fromSharedCredentials,
|
|
84346
85751
|
fromEnvironment,
|
|
@@ -84364,6 +85769,7 @@ export {
|
|
|
84364
85769
|
findChangedFiles,
|
|
84365
85770
|
fifoQueueManager,
|
|
84366
85771
|
extendPreset,
|
|
85772
|
+
eventToRequest,
|
|
84367
85773
|
emailTemplateManager,
|
|
84368
85774
|
emailAnalyticsManager,
|
|
84369
85775
|
drManager,
|
|
@@ -84386,12 +85792,16 @@ export {
|
|
|
84386
85792
|
defaultConfig4 as defaultConfig,
|
|
84387
85793
|
databaseUserManager,
|
|
84388
85794
|
crossRegionReferenceManager,
|
|
85795
|
+
createZip,
|
|
84389
85796
|
createWordPressPreset,
|
|
84390
85797
|
createTraditionalWebAppPreset,
|
|
84391
85798
|
createStaticSitePreset,
|
|
85799
|
+
createServerlessNodePreset,
|
|
85800
|
+
createServerlessLaravelPreset,
|
|
84392
85801
|
createS3Client,
|
|
84393
85802
|
createRoute53Validator,
|
|
84394
85803
|
createRealtimeAppPreset,
|
|
85804
|
+
createQueueHandler,
|
|
84395
85805
|
createPresignedUrlAsync,
|
|
84396
85806
|
createPresignedUrl,
|
|
84397
85807
|
createPreset,
|
|
@@ -84404,6 +85814,8 @@ export {
|
|
|
84404
85814
|
createMLApiPreset,
|
|
84405
85815
|
createLaravelPreset,
|
|
84406
85816
|
createJamstackPreset,
|
|
85817
|
+
createHttpHandler,
|
|
85818
|
+
createHandlers,
|
|
84407
85819
|
createGoDaddyValidator,
|
|
84408
85820
|
createFullStackAppPreset,
|
|
84409
85821
|
createError,
|
|
@@ -84412,10 +85824,13 @@ export {
|
|
|
84412
85824
|
createDashboardSite,
|
|
84413
85825
|
createCredentialProvider,
|
|
84414
85826
|
createCloudDriver,
|
|
85827
|
+
createCliHandler,
|
|
84415
85828
|
createApiBackendPreset,
|
|
84416
85829
|
containerRegistryManager,
|
|
84417
85830
|
config5 as config,
|
|
85831
|
+
composeServerlessAppTemplate,
|
|
84418
85832
|
composePresets,
|
|
85833
|
+
collectPhpAppEntries,
|
|
84419
85834
|
cloudTrailManager,
|
|
84420
85835
|
cloudDrivers,
|
|
84421
85836
|
cloud_config_schema_default as cloudConfigSchema,
|
|
@@ -84429,6 +85844,7 @@ export {
|
|
|
84429
85844
|
canaryManager,
|
|
84430
85845
|
buildStaticSiteDeployScript,
|
|
84431
85846
|
buildSiteDeployScript,
|
|
85847
|
+
buildPhpRuntimeLayerZip,
|
|
84432
85848
|
buildOptimizationManager,
|
|
84433
85849
|
buildCloudFormationTemplate,
|
|
84434
85850
|
bounceComplaintHandler,
|
|
@@ -84438,6 +85854,7 @@ export {
|
|
|
84438
85854
|
backupManager,
|
|
84439
85855
|
awsConfigManager,
|
|
84440
85856
|
autocomplete,
|
|
85857
|
+
artifactKey,
|
|
84441
85858
|
analyzeStackDiff,
|
|
84442
85859
|
abTestManager,
|
|
84443
85860
|
XRayManager,
|
|
@@ -84517,6 +85934,8 @@ export {
|
|
|
84517
85934
|
Permissions,
|
|
84518
85935
|
PerformanceManager,
|
|
84519
85936
|
ParameterStore,
|
|
85937
|
+
PHP_LAYER_EXTENSIONS,
|
|
85938
|
+
PHP_DEFAULT_EXCLUDES,
|
|
84520
85939
|
OrganizationManager,
|
|
84521
85940
|
OpenSearchClient,
|
|
84522
85941
|
NetworkSecurityManager,
|
|
@@ -84539,6 +85958,7 @@ export {
|
|
|
84539
85958
|
LambdaDLQManager,
|
|
84540
85959
|
LambdaConcurrencyManager,
|
|
84541
85960
|
LambdaClient,
|
|
85961
|
+
LARAVEL_SERVERLESS_BUILD_STEPS,
|
|
84542
85962
|
KendraClient,
|
|
84543
85963
|
JobLoader,
|
|
84544
85964
|
InfrastructureGenerator,
|