@stacksjs/ts-cloud 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +667 -661
- package/dist/deploy/serverless-app.d.ts +7 -0
- package/dist/drivers/shared/certbot.d.ts +21 -7
- package/dist/drivers/shared/compute-ops.d.ts +59 -0
- package/dist/drivers/shared/laravel-deploy.d.ts +6 -1
- package/dist/drivers/shared/nginx-vhost.d.ts +16 -1
- package/dist/drivers/shared/php-provision.d.ts +26 -2
- package/dist/drivers/shared/releases.d.ts +39 -0
- package/dist/drivers/shared/server-recipes.d.ts +26 -0
- package/dist/index.js +395 -44
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -25946,15 +25946,21 @@ function composeServerlessAppTemplate(opts) {
|
|
|
25946
25946
|
TSCLOUD_LAMBDA_MODE: mode,
|
|
25947
25947
|
TSCLOUD_ENV: environment,
|
|
25948
25948
|
...app.octane ? { TSCLOUD_OCTANE: "1" } : {},
|
|
25949
|
+
...app.scheduler === "sub-minute" ? { TSCLOUD_SCHEDULER: "sub-minute" } : {},
|
|
25949
25950
|
...cacheEnabled ? { TSCLOUD_CACHE_TABLE: `${slug}-${environment}-cache` } : {},
|
|
25950
25951
|
...hasQueue ? { TSCLOUD_QUEUE: queueNames[0] } : {},
|
|
25951
25952
|
...app.env ?? {}
|
|
25952
25953
|
});
|
|
25954
|
+
const efsEnabled = Boolean(app.efs);
|
|
25955
|
+
const efsOpts = typeof app.efs === "object" ? app.efs : {};
|
|
25956
|
+
const efsMountPath = efsOpts.mountPath ?? "/mnt/local";
|
|
25957
|
+
const efsProvision = efsEnabled && !efsOpts.accessPointArn;
|
|
25958
|
+
const efsAccessPoint = efsOpts.accessPointArn ?? (efsProvision ? Fn2.getAtt("EfsAccessPoint", "Arn") : undefined);
|
|
25953
25959
|
const subnets = app.vpc?.subnets ?? [];
|
|
25954
25960
|
const hasVpc = subnets.length > 0;
|
|
25955
|
-
const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy);
|
|
25961
|
+
const needsDataVpc = app.cache?.driver === "elasticache" || app.database?.connection === "aurora-serverless" || Boolean(app.rdsProxy) || efsEnabled;
|
|
25956
25962
|
if (needsDataVpc && !hasVpc) {
|
|
25957
|
-
throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy require app.vpc.subnets (private subnets) to be set.");
|
|
25963
|
+
throw new Error("serverless app: elasticache / aurora-serverless / rdsProxy / efs require app.vpc.subnets (private subnets) to be set.");
|
|
25958
25964
|
}
|
|
25959
25965
|
const vpcConfig = hasVpc ? {
|
|
25960
25966
|
VpcConfig: {
|
|
@@ -25965,7 +25971,16 @@ function composeServerlessAppTemplate(opts) {
|
|
|
25965
25971
|
]
|
|
25966
25972
|
}
|
|
25967
25973
|
} : {};
|
|
25968
|
-
|
|
25974
|
+
const efsDependsOn = efsProvision ? subnets.map((_, i) => `EfsMountTarget${i}`) : [];
|
|
25975
|
+
const efsConfig = efsEnabled ? { FileSystemConfigs: [{ Arn: efsAccessPoint, LocalMountPath: efsMountPath }] } : {};
|
|
25976
|
+
if (efsEnabled) {
|
|
25977
|
+
inlinePolicies[0].PolicyDocument.Statement.push({
|
|
25978
|
+
Effect: "Allow",
|
|
25979
|
+
Action: ["elasticfilesystem:ClientMount", "elasticfilesystem:ClientWrite", "elasticfilesystem:ClientRootAccess", "elasticfilesystem:DescribeMountTargets"],
|
|
25980
|
+
Resource: "*"
|
|
25981
|
+
});
|
|
25982
|
+
}
|
|
25983
|
+
function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency, tmp = tmpStorage) {
|
|
25969
25984
|
resources[`${logicalId}LogGroup`] = {
|
|
25970
25985
|
Type: "AWS::Logs::LogGroup",
|
|
25971
25986
|
Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
|
|
@@ -25982,7 +25997,7 @@ function composeServerlessAppTemplate(opts) {
|
|
|
25982
25997
|
};
|
|
25983
25998
|
resources[logicalId] = {
|
|
25984
25999
|
Type: "AWS::Lambda::Function",
|
|
25985
|
-
DependsOn: [`${logicalId}LogGroup
|
|
26000
|
+
DependsOn: [`${logicalId}LogGroup`, ...efsDependsOn],
|
|
25986
26001
|
Properties: {
|
|
25987
26002
|
FunctionName: name,
|
|
25988
26003
|
Architectures: [architecture],
|
|
@@ -25990,17 +26005,18 @@ function composeServerlessAppTemplate(opts) {
|
|
|
25990
26005
|
Timeout: timeout,
|
|
25991
26006
|
Role: Fn2.getAtt("AppRole", "Arn"),
|
|
25992
26007
|
Environment: { Variables: baseEnv(mode) },
|
|
25993
|
-
EphemeralStorage: { Size:
|
|
26008
|
+
EphemeralStorage: { Size: tmp },
|
|
25994
26009
|
...codeProps,
|
|
25995
26010
|
...reservedConcurrency !== undefined ? { ReservedConcurrentExecutions: reservedConcurrency } : {},
|
|
25996
|
-
...vpcConfig
|
|
26011
|
+
...vpcConfig,
|
|
26012
|
+
...efsConfig
|
|
25997
26013
|
}
|
|
25998
26014
|
};
|
|
25999
26015
|
}
|
|
26000
|
-
addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency);
|
|
26001
|
-
addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900);
|
|
26016
|
+
addFunction("HttpFunction", functionNames.http, handlers.http, "http", app.memory ?? 1024, app.timeout ?? 28, app.concurrency, tmpStorage);
|
|
26017
|
+
addFunction("CliFunction", functionNames.cli, handlers.cli, "cli", app.cliMemory ?? 1024, app.cliTimeout ?? 900, undefined, app.cliTmpStorage ?? tmpStorage);
|
|
26002
26018
|
if (hasQueue)
|
|
26003
|
-
addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120);
|
|
26019
|
+
addFunction("QueueFunction", functionNames.queue, handlers.queue, "queue", app.queueMemory ?? 1024, app.queueTimeout ?? 120, undefined, app.queueTmpStorage ?? tmpStorage);
|
|
26004
26020
|
resources.HttpApi = {
|
|
26005
26021
|
Type: "AWS::ApiGatewayV2::Api",
|
|
26006
26022
|
Properties: {
|
|
@@ -26047,6 +26063,59 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26047
26063
|
Value: Fn2.getAtt("HttpApi", "ApiEndpoint")
|
|
26048
26064
|
};
|
|
26049
26065
|
outputs.HttpApiId = { Description: "HTTP API id", Value: Fn2.ref("HttpApi") };
|
|
26066
|
+
const domains = (Array.isArray(app.domain) ? app.domain : app.domain ? [app.domain] : []).filter(Boolean);
|
|
26067
|
+
if (domains.length) {
|
|
26068
|
+
if (!app.certificateArn && !app.hostedZoneId) {
|
|
26069
|
+
throw new Error("serverless app: a custom `domain` needs either `certificateArn` (pre-issued, regional) or `hostedZoneId` (to auto-issue + validate an ACM cert).");
|
|
26070
|
+
}
|
|
26071
|
+
let certRef = app.certificateArn;
|
|
26072
|
+
if (!certRef) {
|
|
26073
|
+
resources.HttpCertificate = {
|
|
26074
|
+
Type: "AWS::CertificateManager::Certificate",
|
|
26075
|
+
Properties: {
|
|
26076
|
+
DomainName: domains[0],
|
|
26077
|
+
...domains.length > 1 ? { SubjectAlternativeNames: domains.slice(1) } : {},
|
|
26078
|
+
ValidationMethod: "DNS",
|
|
26079
|
+
DomainValidationOptions: domains.map((d) => ({ DomainName: d, HostedZoneId: app.hostedZoneId }))
|
|
26080
|
+
}
|
|
26081
|
+
};
|
|
26082
|
+
certRef = Fn2.ref("HttpCertificate");
|
|
26083
|
+
}
|
|
26084
|
+
domains.forEach((d, i) => {
|
|
26085
|
+
const dn = `HttpDomain${i}`;
|
|
26086
|
+
resources[dn] = {
|
|
26087
|
+
Type: "AWS::ApiGatewayV2::DomainName",
|
|
26088
|
+
Properties: {
|
|
26089
|
+
DomainName: d,
|
|
26090
|
+
DomainNameConfigurations: [{ CertificateArn: certRef, EndpointType: "REGIONAL" }]
|
|
26091
|
+
}
|
|
26092
|
+
};
|
|
26093
|
+
resources[`HttpApiMapping${i}`] = {
|
|
26094
|
+
Type: "AWS::ApiGatewayV2::ApiMapping",
|
|
26095
|
+
DependsOn: ["HttpStage"],
|
|
26096
|
+
Properties: { ApiId: Fn2.ref("HttpApi"), DomainName: Fn2.ref(dn), Stage: "$default" }
|
|
26097
|
+
};
|
|
26098
|
+
if (app.hostedZoneId) {
|
|
26099
|
+
resources[`HttpDomainRecord${i}`] = {
|
|
26100
|
+
Type: "AWS::Route53::RecordSet",
|
|
26101
|
+
Properties: {
|
|
26102
|
+
HostedZoneId: app.hostedZoneId,
|
|
26103
|
+
Name: d,
|
|
26104
|
+
Type: "A",
|
|
26105
|
+
AliasTarget: {
|
|
26106
|
+
DNSName: Fn2.getAtt(dn, "RegionalDomainName"),
|
|
26107
|
+
HostedZoneId: Fn2.getAtt(dn, "RegionalHostedZoneId")
|
|
26108
|
+
}
|
|
26109
|
+
}
|
|
26110
|
+
};
|
|
26111
|
+
}
|
|
26112
|
+
outputs[`CustomDomain${i}`] = { Description: `Custom domain ${d}`, Value: d };
|
|
26113
|
+
outputs[`CustomDomainTarget${i}`] = {
|
|
26114
|
+
Description: `Point ${d} (CNAME/alias) at this APIGW regional domain`,
|
|
26115
|
+
Value: Fn2.getAtt(dn, "RegionalDomainName")
|
|
26116
|
+
};
|
|
26117
|
+
});
|
|
26118
|
+
}
|
|
26050
26119
|
if (hasQueue) {
|
|
26051
26120
|
resources.AppQueueDlq = {
|
|
26052
26121
|
Type: "AWS::SQS::Queue",
|
|
@@ -26189,10 +26258,37 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26189
26258
|
DomainName: Fn2.getAtt("AssetsBucket", "RegionalDomainName"),
|
|
26190
26259
|
OriginAccessControlId: Fn2.ref("AssetsOAC"),
|
|
26191
26260
|
S3OriginConfig: { OriginAccessIdentity: "" }
|
|
26192
|
-
}]
|
|
26261
|
+
}],
|
|
26262
|
+
...app.assetDomain ? {
|
|
26263
|
+
Aliases: [app.assetDomain],
|
|
26264
|
+
ViewerCertificate: {
|
|
26265
|
+
AcmCertificateArn: app.assetCertificateArn,
|
|
26266
|
+
SslSupportMethod: "sni-only",
|
|
26267
|
+
MinimumProtocolVersion: "TLSv1.2_2021"
|
|
26268
|
+
}
|
|
26269
|
+
} : {}
|
|
26193
26270
|
}
|
|
26194
26271
|
}
|
|
26195
26272
|
};
|
|
26273
|
+
if (app.assetDomain) {
|
|
26274
|
+
if (!app.assetCertificateArn)
|
|
26275
|
+
throw new Error("serverless app: `assetDomain` requires `assetCertificateArn` (a us-east-1 ACM cert — CloudFront only accepts certs from us-east-1).");
|
|
26276
|
+
if (app.hostedZoneId) {
|
|
26277
|
+
resources.AssetsDomainRecord = {
|
|
26278
|
+
Type: "AWS::Route53::RecordSet",
|
|
26279
|
+
Properties: {
|
|
26280
|
+
HostedZoneId: app.hostedZoneId,
|
|
26281
|
+
Name: app.assetDomain,
|
|
26282
|
+
Type: "A",
|
|
26283
|
+
AliasTarget: {
|
|
26284
|
+
DNSName: Fn2.getAtt("AssetsDistribution", "DomainName"),
|
|
26285
|
+
HostedZoneId: "Z2FDTNDATAQYW2"
|
|
26286
|
+
}
|
|
26287
|
+
}
|
|
26288
|
+
};
|
|
26289
|
+
}
|
|
26290
|
+
outputs.AssetDomain = { Description: "Custom asset CDN host", Value: app.assetDomain };
|
|
26291
|
+
}
|
|
26196
26292
|
resources.AssetsBucketPolicy = {
|
|
26197
26293
|
Type: "AWS::S3::BucketPolicy",
|
|
26198
26294
|
Properties: {
|
|
@@ -26271,6 +26367,37 @@ function composeServerlessAppTemplate(opts) {
|
|
|
26271
26367
|
}
|
|
26272
26368
|
};
|
|
26273
26369
|
}
|
|
26370
|
+
if (efsProvision) {
|
|
26371
|
+
resources.EfsFileSystem = {
|
|
26372
|
+
Type: "AWS::EFS::FileSystem",
|
|
26373
|
+
Properties: {
|
|
26374
|
+
Encrypted: true,
|
|
26375
|
+
FileSystemTags: [{ Key: "Name", Value: `${slug}-${environment}-efs` }]
|
|
26376
|
+
}
|
|
26377
|
+
};
|
|
26378
|
+
subnets.forEach((subnetId, i) => {
|
|
26379
|
+
resources[`EfsMountTarget${i}`] = {
|
|
26380
|
+
Type: "AWS::EFS::MountTarget",
|
|
26381
|
+
Properties: {
|
|
26382
|
+
FileSystemId: Fn2.ref("EfsFileSystem"),
|
|
26383
|
+
SubnetId: subnetId,
|
|
26384
|
+
SecurityGroups: [Fn2.getAtt("DataSecurityGroup", "GroupId")]
|
|
26385
|
+
}
|
|
26386
|
+
};
|
|
26387
|
+
});
|
|
26388
|
+
resources.EfsAccessPoint = {
|
|
26389
|
+
Type: "AWS::EFS::AccessPoint",
|
|
26390
|
+
Properties: {
|
|
26391
|
+
FileSystemId: Fn2.ref("EfsFileSystem"),
|
|
26392
|
+
PosixUser: { Uid: 1001, Gid: 1001 },
|
|
26393
|
+
RootDirectory: {
|
|
26394
|
+
Path: "/lambda",
|
|
26395
|
+
CreationInfo: { OwnerUid: 1001, OwnerGid: 1001, Permissions: "0755" }
|
|
26396
|
+
}
|
|
26397
|
+
}
|
|
26398
|
+
};
|
|
26399
|
+
outputs.EfsFileSystemId = { Description: "EFS file system id", Value: Fn2.ref("EfsFileSystem") };
|
|
26400
|
+
}
|
|
26274
26401
|
if (app.cache?.driver === "elasticache") {
|
|
26275
26402
|
resources.CacheSubnetGroup = {
|
|
26276
26403
|
Type: "AWS::ElastiCache::SubnetGroup",
|
|
@@ -83078,6 +83205,8 @@ function buildFunctionEnv(app, ctx, environment, mode, secrets, assetUrl, queueN
|
|
|
83078
83205
|
TSCLOUD_ENV: environment,
|
|
83079
83206
|
MAINTENANCE_MODE: "0",
|
|
83080
83207
|
...app.octane ? { TSCLOUD_OCTANE: "1" } : {},
|
|
83208
|
+
...app.serveAssets ? { TSCLOUD_SERVE_ASSETS: "1" } : {},
|
|
83209
|
+
...app.redirectRobotsTxt === false ? { TSCLOUD_REDIRECT_ROBOTS_TXT: "0" } : {},
|
|
83081
83210
|
...laravelDefaults,
|
|
83082
83211
|
...infraEnv,
|
|
83083
83212
|
...ctx.app.cache?.driver !== "elasticache" ? { TSCLOUD_CACHE_TABLE: `${ctx.slug}-${environment}-cache` } : {},
|
|
@@ -83118,13 +83247,15 @@ function* walk3(dir) {
|
|
|
83118
83247
|
yield full;
|
|
83119
83248
|
}
|
|
83120
83249
|
}
|
|
83121
|
-
async function uploadAssets(s32, bucket, dir, prefix) {
|
|
83250
|
+
async function uploadAssets(s32, bucket, dir, prefix, includeDotfiles = false) {
|
|
83122
83251
|
let count = 0;
|
|
83123
83252
|
for (const file of walk3(dir)) {
|
|
83124
|
-
const
|
|
83253
|
+
const rel = relative6(dir, file).replace(/\\/g, "/");
|
|
83254
|
+
if (!includeDotfiles && rel.split("/").some((seg) => seg.startsWith(".")))
|
|
83255
|
+
continue;
|
|
83125
83256
|
await s32.putObject({
|
|
83126
83257
|
bucket,
|
|
83127
|
-
key
|
|
83258
|
+
key: `${prefix}/${rel}`,
|
|
83128
83259
|
body: readFileSync13(file),
|
|
83129
83260
|
contentType: contentType(file),
|
|
83130
83261
|
cacheControl: "public, max-age=31536000, immutable"
|
|
@@ -83230,10 +83361,10 @@ async function deployServerlessApp(config6, environment, opts = {}) {
|
|
|
83230
83361
|
if (app.assets) {
|
|
83231
83362
|
const assetsDir3 = join16(projectRoot, app.assets);
|
|
83232
83363
|
if (existsSync17(assetsDir3)) {
|
|
83233
|
-
const cdn = outputs.AssetsCdnDomain;
|
|
83364
|
+
const cdn = app.assetDomain || outputs.AssetsCdnDomain;
|
|
83234
83365
|
assetUrl = cdn ? `https://${cdn}/${artifactSha}` : undefined;
|
|
83235
83366
|
step(`Syncing assets from ${app.assets}`);
|
|
83236
|
-
const n = await uploadAssets(s32, ctx.assetsBucket, assetsDir3, artifactSha);
|
|
83367
|
+
const n = await uploadAssets(s32, ctx.assetsBucket, assetsDir3, artifactSha, app.dotFilesAsAssets);
|
|
83237
83368
|
info(`Uploaded ${n} asset(s)${assetUrl ? ` → ${assetUrl}` : ""}`);
|
|
83238
83369
|
} else {
|
|
83239
83370
|
warn(`Assets directory not found: ${assetsDir3}`);
|
|
@@ -83487,31 +83618,63 @@ function buildDatabaseSetupScript(database, services = {}) {
|
|
|
83487
83618
|
if (usePostgres && !useMysql && !useMariadb) {
|
|
83488
83619
|
const pgIdent = (v) => `"${v.replace(/"/g, '""')}"`;
|
|
83489
83620
|
const pgLit = (v) => `'${v.replace(/'/g, "''")}'`;
|
|
83621
|
+
const pgEnsureRole = (u, p) => [
|
|
83622
|
+
"DO $$ BEGIN",
|
|
83623
|
+
` IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = ${pgLit(u)}) THEN`,
|
|
83624
|
+
` CREATE ROLE ${pgIdent(u)} LOGIN PASSWORD ${pgLit(p)};`,
|
|
83625
|
+
" ELSE",
|
|
83626
|
+
` ALTER ROLE ${pgIdent(u)} LOGIN PASSWORD ${pgLit(p)};`,
|
|
83627
|
+
" END IF;",
|
|
83628
|
+
"END $$;"
|
|
83629
|
+
];
|
|
83630
|
+
const pgGrant = (u) => {
|
|
83631
|
+
const dbs = u.databases && u.databases.length > 0 ? u.databases : [name];
|
|
83632
|
+
const lines = [];
|
|
83633
|
+
for (const db of dbs) {
|
|
83634
|
+
if (u.access === "readonly") {
|
|
83635
|
+
lines.push(`GRANT CONNECT ON DATABASE ${pgIdent(db)} TO ${pgIdent(u.username)};`, `\\connect ${pgIdent(db)}`, `GRANT USAGE ON SCHEMA public TO ${pgIdent(u.username)};`, `GRANT SELECT ON ALL TABLES IN SCHEMA public TO ${pgIdent(u.username)};`, `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO ${pgIdent(u.username)};`, "\\connect postgres");
|
|
83636
|
+
} else {
|
|
83637
|
+
lines.push(`GRANT ALL PRIVILEGES ON DATABASE ${pgIdent(db)} TO ${pgIdent(u.username)};`);
|
|
83638
|
+
}
|
|
83639
|
+
}
|
|
83640
|
+
return lines;
|
|
83641
|
+
};
|
|
83642
|
+
const extraUsers2 = database.users || [];
|
|
83490
83643
|
return [
|
|
83491
83644
|
pantryEnvActivation(),
|
|
83492
83645
|
"for i in $(seq 1 30); do pg_isready -h 127.0.0.1 -p 5432 -q && break; sleep 2; done",
|
|
83493
83646
|
"psql -h 127.0.0.1 -p 5432 -U postgres <<'TS_CLOUD_PG_EOF'",
|
|
83494
|
-
|
|
83495
|
-
` IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = ${pgLit(user)}) THEN`,
|
|
83496
|
-
` CREATE ROLE ${pgIdent(user)} LOGIN PASSWORD ${pgLit(pass)};`,
|
|
83497
|
-
" END IF;",
|
|
83498
|
-
"END $$;",
|
|
83647
|
+
...pgEnsureRole(user, pass),
|
|
83499
83648
|
`SELECT 'CREATE DATABASE ${pgIdent(name)} OWNER ${pgIdent(user)}' ` + `WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = ${pgLit(name)})\\gexec`,
|
|
83649
|
+
...extraUsers2.flatMap((u) => [...pgEnsureRole(u.username, u.password), ...pgGrant(u)]),
|
|
83500
83650
|
"TS_CLOUD_PG_EOF"
|
|
83501
83651
|
];
|
|
83502
83652
|
}
|
|
83503
83653
|
const sock = useMariadb ? "/var/lib/pantry/mariadb/mariadbd.sock" : "/var/lib/pantry/mysql/mysqld.sock";
|
|
83504
83654
|
const ident = (v) => v.replace(/`/g, "``");
|
|
83505
83655
|
const lit = (v) => v.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
83656
|
+
const mysqlUser = (u) => {
|
|
83657
|
+
const dbs = u.databases && u.databases.length > 0 ? u.databases : [name];
|
|
83658
|
+
const priv = u.access === "readonly" ? "SELECT" : "ALL PRIVILEGES";
|
|
83659
|
+
const lines = [
|
|
83660
|
+
`CREATE USER IF NOT EXISTS '${lit(u.username)}'@'%' IDENTIFIED BY '${lit(u.password)}';`,
|
|
83661
|
+
`CREATE USER IF NOT EXISTS '${lit(u.username)}'@'localhost' IDENTIFIED BY '${lit(u.password)}';`,
|
|
83662
|
+
`ALTER USER '${lit(u.username)}'@'%' IDENTIFIED BY '${lit(u.password)}';`,
|
|
83663
|
+
`ALTER USER '${lit(u.username)}'@'localhost' IDENTIFIED BY '${lit(u.password)}';`
|
|
83664
|
+
];
|
|
83665
|
+
for (const db of dbs) {
|
|
83666
|
+
lines.push(`GRANT ${priv} ON \`${ident(db)}\`.* TO '${lit(u.username)}'@'%';`, `GRANT ${priv} ON \`${ident(db)}\`.* TO '${lit(u.username)}'@'localhost';`);
|
|
83667
|
+
}
|
|
83668
|
+
return lines;
|
|
83669
|
+
};
|
|
83670
|
+
const extraUsers = database.users || [];
|
|
83506
83671
|
return [
|
|
83507
83672
|
pantryEnvActivation(),
|
|
83508
83673
|
`for i in $(seq 1 30); do mysqladmin --socket=${sock} -u root ping 2>/dev/null | grep -q alive && break; sleep 2; done`,
|
|
83509
83674
|
`mysql --socket=${sock} -u root <<'TS_CLOUD_SQL_EOF'`,
|
|
83510
83675
|
`CREATE DATABASE IF NOT EXISTS \`${ident(name)}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`,
|
|
83511
|
-
|
|
83512
|
-
|
|
83513
|
-
`GRANT ALL PRIVILEGES ON \`${ident(name)}\`.* TO '${lit(user)}'@'%';`,
|
|
83514
|
-
`GRANT ALL PRIVILEGES ON \`${ident(name)}\`.* TO '${lit(user)}'@'localhost';`,
|
|
83676
|
+
...mysqlUser({ username: user, password: pass }),
|
|
83677
|
+
...extraUsers.flatMap(mysqlUser),
|
|
83515
83678
|
"FLUSH PRIVILEGES;",
|
|
83516
83679
|
"TS_CLOUD_SQL_EOF"
|
|
83517
83680
|
];
|
|
@@ -83535,6 +83698,19 @@ function buildManagedDbEnv(database) {
|
|
|
83535
83698
|
}
|
|
83536
83699
|
|
|
83537
83700
|
// src/drivers/shared/php-provision.ts
|
|
83701
|
+
var PRODUCTION_PHP_INI = {
|
|
83702
|
+
"opcache.enable": "1",
|
|
83703
|
+
"opcache.enable_cli": "1",
|
|
83704
|
+
"opcache.memory_consumption": "256",
|
|
83705
|
+
"opcache.interned_strings_buffer": "16",
|
|
83706
|
+
"opcache.max_accelerated_files": "20000",
|
|
83707
|
+
"opcache.validate_timestamps": "0",
|
|
83708
|
+
"opcache.revalidate_freq": "0",
|
|
83709
|
+
"opcache.save_comments": "1",
|
|
83710
|
+
"opcache.fast_shutdown": "1",
|
|
83711
|
+
realpath_cache_size: "4096K",
|
|
83712
|
+
realpath_cache_ttl: "600"
|
|
83713
|
+
};
|
|
83538
83714
|
var PHP_FPM_LISTEN = "127.0.0.1:9074";
|
|
83539
83715
|
function phpFpmSocketPath(_version) {
|
|
83540
83716
|
return PHP_FPM_LISTEN;
|
|
@@ -83543,6 +83719,45 @@ function resolveDefaultPhpVersion(options = {}) {
|
|
|
83543
83719
|
const versions = options.versions?.length ? options.versions : ["8.3"];
|
|
83544
83720
|
return options.default && versions.includes(options.default) ? options.default : versions[0];
|
|
83545
83721
|
}
|
|
83722
|
+
function resolvePhpIni(options = {}) {
|
|
83723
|
+
const base = options.optimizeForProduction === false ? {} : { ...PRODUCTION_PHP_INI };
|
|
83724
|
+
return { ...base, ...options.ini || {} };
|
|
83725
|
+
}
|
|
83726
|
+
function buildPhpTuningScript(options = {}) {
|
|
83727
|
+
const ini = resolvePhpIni(options);
|
|
83728
|
+
const keys = Object.keys(ini);
|
|
83729
|
+
if (keys.length === 0)
|
|
83730
|
+
return [];
|
|
83731
|
+
const body = keys.map((k) => `${k}=${ini[k]}`);
|
|
83732
|
+
const marker = "ts-cloud-managed";
|
|
83733
|
+
return [
|
|
83734
|
+
pantryEnvActivation(),
|
|
83735
|
+
"TS_CLOUD_SCAN_DIR=$(php -i 2>/dev/null | awk -F' => ' '/^Scan this dir for additional .ini files/{print $2}' | head -1)",
|
|
83736
|
+
`TS_CLOUD_LOADED_INI=$(php -r 'echo php_ini_loaded_file() ?: "";' 2>/dev/null)`,
|
|
83737
|
+
'if [ -n "$TS_CLOUD_SCAN_DIR" ] && [ "$TS_CLOUD_SCAN_DIR" != "(none)" ]; then',
|
|
83738
|
+
' mkdir -p "$TS_CLOUD_SCAN_DIR"',
|
|
83739
|
+
' TS_CLOUD_PHP_INI="$TS_CLOUD_SCAN_DIR/zz-ts-cloud.ini"',
|
|
83740
|
+
` cat > "$TS_CLOUD_PHP_INI" <<'TS_CLOUD_PHPINI_EOF'`,
|
|
83741
|
+
`; ${marker} — production PHP tuning`,
|
|
83742
|
+
...body,
|
|
83743
|
+
"TS_CLOUD_PHPINI_EOF",
|
|
83744
|
+
"else",
|
|
83745
|
+
' if [ -z "$TS_CLOUD_LOADED_INI" ]; then',
|
|
83746
|
+
" TS_CLOUD_INI_DIR=$(php -i 2>/dev/null | awk -F' => ' '/^Configuration File \\(php.ini\\) Path/{print $2}' | head -1)",
|
|
83747
|
+
' TS_CLOUD_LOADED_INI="$TS_CLOUD_INI_DIR/php.ini"',
|
|
83748
|
+
' mkdir -p "$TS_CLOUD_INI_DIR"',
|
|
83749
|
+
' touch "$TS_CLOUD_LOADED_INI"',
|
|
83750
|
+
" fi",
|
|
83751
|
+
` sed -i '/; ${marker} BEGIN/,/; ${marker} END/d' "$TS_CLOUD_LOADED_INI"`,
|
|
83752
|
+
` cat >> "$TS_CLOUD_LOADED_INI" <<'TS_CLOUD_PHPINI_EOF'`,
|
|
83753
|
+
`; ${marker} BEGIN`,
|
|
83754
|
+
...body,
|
|
83755
|
+
`; ${marker} END`,
|
|
83756
|
+
"TS_CLOUD_PHPINI_EOF",
|
|
83757
|
+
"fi",
|
|
83758
|
+
`(cd ${PANTRY_PROJECT_DIR} && pantry restart php-fpm) 2>/dev/null || true`
|
|
83759
|
+
];
|
|
83760
|
+
}
|
|
83546
83761
|
function buildPhpProvisionScript(options = {}) {
|
|
83547
83762
|
const defaultVersion = resolveDefaultPhpVersion(options);
|
|
83548
83763
|
const installNginx = options.installNginx !== false;
|
|
@@ -83554,11 +83769,22 @@ function buildPhpProvisionScript(options = {}) {
|
|
|
83554
83769
|
specs.push("nginx.org");
|
|
83555
83770
|
return [
|
|
83556
83771
|
...buildPantryInstallScript(specs),
|
|
83557
|
-
...buildPantryServiceScript(["php-fpm"])
|
|
83772
|
+
...buildPantryServiceScript(["php-fpm"]),
|
|
83773
|
+
...buildPhpTuningScript(options)
|
|
83558
83774
|
];
|
|
83559
83775
|
}
|
|
83560
83776
|
|
|
83561
83777
|
// src/drivers/shared/nginx-vhost.ts
|
|
83778
|
+
function resolveNginxSnippet(nginx, templates) {
|
|
83779
|
+
if (!nginx)
|
|
83780
|
+
return [];
|
|
83781
|
+
const out = [];
|
|
83782
|
+
if (nginx.template && templates?.[nginx.template])
|
|
83783
|
+
out.push(...templates[nginx.template]);
|
|
83784
|
+
if (nginx.serverSnippet)
|
|
83785
|
+
out.push(...nginx.serverSnippet);
|
|
83786
|
+
return out;
|
|
83787
|
+
}
|
|
83562
83788
|
function htpasswdPath(siteName) {
|
|
83563
83789
|
return `/etc/nginx/.htpasswd-${siteName}`;
|
|
83564
83790
|
}
|
|
@@ -83591,6 +83817,9 @@ function vhostBody(options) {
|
|
|
83591
83817
|
" charset utf-8;",
|
|
83592
83818
|
""
|
|
83593
83819
|
];
|
|
83820
|
+
if (options.clientMaxBodySize) {
|
|
83821
|
+
lines.push(` client_max_body_size ${options.clientMaxBodySize};`, "");
|
|
83822
|
+
}
|
|
83594
83823
|
if (options.auth) {
|
|
83595
83824
|
lines.push(` auth_basic "${options.auth.realm || "Restricted"}";`, ` auth_basic_user_file ${htpasswdPath(options.siteName)};`, "");
|
|
83596
83825
|
}
|
|
@@ -83606,6 +83835,11 @@ function vhostBody(options) {
|
|
|
83606
83835
|
} else {
|
|
83607
83836
|
lines.push(" location / {", " try_files $uri $uri/ =404;", " }");
|
|
83608
83837
|
}
|
|
83838
|
+
if (options.serverSnippet && options.serverSnippet.length > 0) {
|
|
83839
|
+
lines.push("");
|
|
83840
|
+
for (const line of options.serverSnippet)
|
|
83841
|
+
lines.push(line ? ` ${line}` : "");
|
|
83842
|
+
}
|
|
83609
83843
|
return lines;
|
|
83610
83844
|
}
|
|
83611
83845
|
function buildNginxVhost(options) {
|
|
@@ -83730,6 +83964,12 @@ function buildNginxServiceScript(projectDir = "/opt/pantry") {
|
|
|
83730
83964
|
" fastcgi_temp_path /var/lib/nginx/fastcgi;",
|
|
83731
83965
|
" uwsgi_temp_path /var/lib/nginx/uwsgi;",
|
|
83732
83966
|
" scgi_temp_path /var/lib/nginx/scgi;",
|
|
83967
|
+
" server {",
|
|
83968
|
+
" listen 80 default_server;",
|
|
83969
|
+
" listen [::]:80 default_server;",
|
|
83970
|
+
" server_name _;",
|
|
83971
|
+
" return 444;",
|
|
83972
|
+
" }",
|
|
83733
83973
|
" include /etc/nginx/sites-enabled/*;",
|
|
83734
83974
|
"}",
|
|
83735
83975
|
"TS_CLOUD_NGINXCONF_EOF",
|
|
@@ -84057,7 +84297,9 @@ function buildComputeProvisionScripts(config6) {
|
|
|
84057
84297
|
versions: compute.php?.versions,
|
|
84058
84298
|
default: compute.php?.default,
|
|
84059
84299
|
extensions: compute.php?.extensions,
|
|
84060
|
-
installNginx: useNginx
|
|
84300
|
+
installNginx: useNginx,
|
|
84301
|
+
optimizeForProduction: compute.php?.optimizeForProduction,
|
|
84302
|
+
ini: compute.php?.ini
|
|
84061
84303
|
}),
|
|
84062
84304
|
...useNginx ? buildNginxServiceScript() : []
|
|
84063
84305
|
] : undefined;
|
|
@@ -85177,7 +85419,9 @@ class HetznerDriver {
|
|
|
85177
85419
|
versions: compute.php?.versions,
|
|
85178
85420
|
default: compute.php?.default,
|
|
85179
85421
|
extensions: compute.php?.extensions,
|
|
85180
|
-
installNginx: compute.webServer !== "rpx"
|
|
85422
|
+
installNginx: compute.webServer !== "rpx",
|
|
85423
|
+
optimizeForProduction: compute.php?.optimizeForProduction,
|
|
85424
|
+
ini: compute.php?.ini
|
|
85181
85425
|
});
|
|
85182
85426
|
const appUserData = wrapCloudInitUserData(buildUbuntuBootstrapScript({ runtime: "php", phpProvision: appPhp, servicesProvision: appProvision, baked }));
|
|
85183
85427
|
const existingApp = all.filter((s) => matchesTsCloudLabels(s.labels, slug, environment, "app"));
|
|
@@ -85673,10 +85917,22 @@ function resolveSslProvider(site) {
|
|
|
85673
85917
|
return site.ssl.provider;
|
|
85674
85918
|
return site.domain ? "letsencrypt" : "none";
|
|
85675
85919
|
}
|
|
85676
|
-
|
|
85920
|
+
var DNS_PLUGINS = {
|
|
85921
|
+
cloudflare: { pkg: "python3-certbot-dns-cloudflare", plugin: "dns-cloudflare" },
|
|
85922
|
+
route53: { pkg: "python3-certbot-dns-route53", plugin: "dns-route53" },
|
|
85923
|
+
digitalocean: { pkg: "python3-certbot-dns-digitalocean", plugin: "dns-digitalocean" },
|
|
85924
|
+
google: { pkg: "python3-certbot-dns-google", plugin: "dns-google" }
|
|
85925
|
+
};
|
|
85926
|
+
function dnsCredentialsPath(provider) {
|
|
85927
|
+
return `/etc/letsencrypt/ts-cloud-${provider}.ini`;
|
|
85928
|
+
}
|
|
85929
|
+
function buildCertbotInstallScript(dns2) {
|
|
85930
|
+
const plugin = dns2 ? DNS_PLUGINS[dns2.provider] : undefined;
|
|
85931
|
+
const pkgs = ["certbot", "python3-certbot-nginx", ...plugin ? [plugin.pkg] : []].join(" ");
|
|
85932
|
+
const installLine = plugin ? `apt-get update -y && apt-get install -y ${pkgs}` : `command -v certbot >/dev/null 2>&1 || { apt-get update -y && apt-get install -y ${pkgs}; }`;
|
|
85677
85933
|
return [
|
|
85678
85934
|
"export DEBIAN_FRONTEND=noninteractive",
|
|
85679
|
-
|
|
85935
|
+
installLine,
|
|
85680
85936
|
"mkdir -p /etc/letsencrypt/renewal-hooks/deploy",
|
|
85681
85937
|
"cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'TS_CLOUD_HOOK_EOF'",
|
|
85682
85938
|
"#!/bin/sh",
|
|
@@ -85687,15 +85943,34 @@ function buildCertbotInstallScript() {
|
|
|
85687
85943
|
"systemctl start certbot.timer 2>/dev/null || true"
|
|
85688
85944
|
];
|
|
85689
85945
|
}
|
|
85690
|
-
function
|
|
85691
|
-
|
|
85692
|
-
|
|
85693
|
-
|
|
85694
|
-
|
|
85695
|
-
|
|
85696
|
-
|
|
85697
|
-
|
|
85946
|
+
function buildDnsCredentialsScript(dns2) {
|
|
85947
|
+
if (!dns2.credentials || Object.keys(dns2.credentials).length === 0)
|
|
85948
|
+
return [];
|
|
85949
|
+
const file = dnsCredentialsPath(dns2.provider);
|
|
85950
|
+
const lines = Object.entries(dns2.credentials).map(([k, v]) => `${k} = ${v}`).join(`
|
|
85951
|
+
`);
|
|
85952
|
+
return [
|
|
85953
|
+
`cat > ${file} <<'TS_CLOUD_DNSCREDS_EOF'`,
|
|
85954
|
+
lines,
|
|
85955
|
+
"TS_CLOUD_DNSCREDS_EOF",
|
|
85956
|
+
`chmod 600 ${file}`
|
|
85698
85957
|
];
|
|
85958
|
+
}
|
|
85959
|
+
function buildCertbotIssueScript(options) {
|
|
85960
|
+
const dns2 = options.dns;
|
|
85961
|
+
const domains = options.wildcard ? [`*.${options.domain}`, options.domain] : [options.domain, ...options.aliases || []].filter(Boolean);
|
|
85962
|
+
const args = ["certbot", "--non-interactive", "--agree-tos", "--keep-until-expiring"];
|
|
85963
|
+
if (dns2) {
|
|
85964
|
+
const plugin = DNS_PLUGINS[dns2.provider].plugin;
|
|
85965
|
+
args.push(`--${plugin}`);
|
|
85966
|
+
if (dns2.provider !== "route53" && dns2.credentials)
|
|
85967
|
+
args.push(`--${plugin}-credentials ${dnsCredentialsPath(dns2.provider)}`);
|
|
85968
|
+
if (typeof dns2.propagationSeconds === "number" && dns2.provider !== "route53")
|
|
85969
|
+
args.push(`--${plugin}-propagation-seconds ${dns2.propagationSeconds}`);
|
|
85970
|
+
args.push("certonly");
|
|
85971
|
+
} else {
|
|
85972
|
+
args.push("--nginx", options.redirect === false ? "--no-redirect" : "--redirect");
|
|
85973
|
+
}
|
|
85699
85974
|
if (options.email)
|
|
85700
85975
|
args.push(`-m ${options.email}`);
|
|
85701
85976
|
else
|
|
@@ -85707,12 +85982,20 @@ function buildCertbotIssueScript(options) {
|
|
|
85707
85982
|
function buildSslScript(site) {
|
|
85708
85983
|
if (resolveSslProvider(site) !== "letsencrypt" || !site.domain)
|
|
85709
85984
|
return [];
|
|
85985
|
+
const ssl = site.ssl;
|
|
85986
|
+
const dns2 = ssl?.dns;
|
|
85987
|
+
const wildcard = ssl?.wildcard === true;
|
|
85988
|
+
if (wildcard && !dns2)
|
|
85989
|
+
return [];
|
|
85710
85990
|
return [
|
|
85711
|
-
...buildCertbotInstallScript(),
|
|
85991
|
+
...buildCertbotInstallScript(dns2),
|
|
85992
|
+
...dns2 ? buildDnsCredentialsScript(dns2) : [],
|
|
85712
85993
|
...buildCertbotIssueScript({
|
|
85713
85994
|
domain: site.domain,
|
|
85714
85995
|
aliases: site.aliases,
|
|
85715
|
-
email:
|
|
85996
|
+
email: ssl?.email,
|
|
85997
|
+
wildcard,
|
|
85998
|
+
dns: dns2
|
|
85716
85999
|
})
|
|
85717
86000
|
];
|
|
85718
86001
|
}
|
|
@@ -85752,6 +86035,16 @@ function shellQuote(value) {
|
|
|
85752
86035
|
// src/drivers/shared/releases.ts
|
|
85753
86036
|
var DEFAULT_SHARED_PATHS = ["storage", ".env"];
|
|
85754
86037
|
var DEFAULT_KEEP_RELEASES = 4;
|
|
86038
|
+
var DEFAULT_KEEP_DEPLOY_LOGS = 20;
|
|
86039
|
+
function deployMetaDir(base) {
|
|
86040
|
+
return `${base.replace(/\/+$/, "")}/.ts-cloud`;
|
|
86041
|
+
}
|
|
86042
|
+
function deployHistoryPath(base) {
|
|
86043
|
+
return `${deployMetaDir(base)}/deploy-history.log`;
|
|
86044
|
+
}
|
|
86045
|
+
function deployLogPath(base, releaseId) {
|
|
86046
|
+
return `${deployMetaDir(base)}/deploys/${releaseId}.log`;
|
|
86047
|
+
}
|
|
85755
86048
|
function releasePaths(base, releaseId) {
|
|
85756
86049
|
const root = base.replace(/\/+$/, "");
|
|
85757
86050
|
return {
|
|
@@ -85805,6 +86098,26 @@ function buildPruneReleases(paths, keep = DEFAULT_KEEP_RELEASES) {
|
|
|
85805
86098
|
"done"
|
|
85806
86099
|
];
|
|
85807
86100
|
}
|
|
86101
|
+
function buildDeployHistoryHeader(base, options) {
|
|
86102
|
+
const meta = deployMetaDir(base);
|
|
86103
|
+
const log4 = deployLogPath(base, options.releaseId);
|
|
86104
|
+
const history = deployHistoryPath(base);
|
|
86105
|
+
const keepLogs = Math.max(1, options.keepLogs ?? DEFAULT_KEEP_DEPLOY_LOGS);
|
|
86106
|
+
const commit = options.commit || "";
|
|
86107
|
+
const branch = options.branch || "";
|
|
86108
|
+
return [
|
|
86109
|
+
`mkdir -p ${meta}/deploys`,
|
|
86110
|
+
`exec > >(tee -a ${log4}) 2>&1`,
|
|
86111
|
+
`echo "[ts-cloud] deploy ${options.releaseId} commit=${commit} branch=${branch} starting $(date -u +%Y-%m-%dT%H:%M:%SZ)"`,
|
|
86112
|
+
"ts_cloud_record_deploy() {",
|
|
86113
|
+
" TS_CLOUD_RC=$?",
|
|
86114
|
+
' if [ "$TS_CLOUD_RC" -eq 0 ]; then TS_CLOUD_ST=success; else TS_CLOUD_ST=failed; fi',
|
|
86115
|
+
` printf '%s\\t%s\\t%s\\t%s\\trc=%s\\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${options.releaseId}" "${commit}" "$TS_CLOUD_ST" "$TS_CLOUD_RC" >> ${history}`,
|
|
86116
|
+
"}",
|
|
86117
|
+
"trap ts_cloud_record_deploy EXIT",
|
|
86118
|
+
`ls -1t ${meta}/deploys/*.log 2>/dev/null | tail -n +${keepLogs + 1} | while read -r TS_CLOUD_OLDLOG; do rm -f "$TS_CLOUD_OLDLOG"; done`
|
|
86119
|
+
];
|
|
86120
|
+
}
|
|
85808
86121
|
|
|
85809
86122
|
// src/drivers/shared/laravel-deploy.ts
|
|
85810
86123
|
var MACRO_CREATE_RELEASE = "$CREATE_RELEASE";
|
|
@@ -85857,6 +86170,21 @@ function writeSharedEnv(sharedEnvPath, env2) {
|
|
|
85857
86170
|
`chmod 600 ${sharedEnvPath}`
|
|
85858
86171
|
];
|
|
85859
86172
|
}
|
|
86173
|
+
function writeFileHeredoc2(path, body, marker) {
|
|
86174
|
+
return [`cat > ${path} <<'${marker}'`, body.replace(/\n$/, ""), marker, `chmod 600 ${path}`];
|
|
86175
|
+
}
|
|
86176
|
+
function buildCredentialFiles(releaseDir, creds) {
|
|
86177
|
+
if (!creds)
|
|
86178
|
+
return [];
|
|
86179
|
+
const out = [];
|
|
86180
|
+
if (creds.composerAuth) {
|
|
86181
|
+
const json = typeof creds.composerAuth === "string" ? creds.composerAuth : JSON.stringify(creds.composerAuth, null, 2);
|
|
86182
|
+
out.push(...writeFileHeredoc2(`${releaseDir}/auth.json`, json, "TS_CLOUD_AUTHJSON_EOF"));
|
|
86183
|
+
}
|
|
86184
|
+
if (creds.npmrc)
|
|
86185
|
+
out.push(...writeFileHeredoc2(`${releaseDir}/.npmrc`, creds.npmrc, "TS_CLOUD_NPMRC_EOF"));
|
|
86186
|
+
return out;
|
|
86187
|
+
}
|
|
85860
86188
|
function buildLaravelDeployScript(options) {
|
|
85861
86189
|
const { siteName, site, releaseId, commit } = options;
|
|
85862
86190
|
if (!site.repository?.url)
|
|
@@ -85874,6 +86202,12 @@ function buildLaravelDeployScript(options) {
|
|
|
85874
86202
|
"export COMPOSER_ALLOW_SUPERUSER=1",
|
|
85875
86203
|
pantryEnvActivation()
|
|
85876
86204
|
];
|
|
86205
|
+
out.push(...buildDeployHistoryHeader(base, {
|
|
86206
|
+
releaseId,
|
|
86207
|
+
commit,
|
|
86208
|
+
branch: site.repository.branch,
|
|
86209
|
+
keepLogs: keepReleases
|
|
86210
|
+
}));
|
|
85877
86211
|
out.push(...buildEnsureReleaseLayout(paths, sharedPaths));
|
|
85878
86212
|
if (site.env && Object.keys(site.env).length > 0)
|
|
85879
86213
|
out.push(...writeSharedEnv(`${paths.shared}/.env`, site.env));
|
|
@@ -85883,6 +86217,7 @@ function buildLaravelDeployScript(options) {
|
|
|
85883
86217
|
out.push(...buildGitCheckoutScript({ repository: site.repository, releaseDir: paths.release, commit }));
|
|
85884
86218
|
out.push(...buildLinkSharedPaths(paths, sharedPaths));
|
|
85885
86219
|
out.push(`cd ${paths.release}`);
|
|
86220
|
+
out.push(...buildCredentialFiles(paths.release, site.credentials));
|
|
85886
86221
|
out.push(`chown -R www-data:www-data ${paths.shared}/storage 2>/dev/null || true`, `[ -d ${paths.release}/bootstrap/cache ] && chown -R www-data:www-data ${paths.release}/bootstrap/cache 2>/dev/null || true`, `chmod -R ug+rwX ${paths.shared}/storage 2>/dev/null || true`);
|
|
85887
86222
|
} else if (line === MACRO_ACTIVATE_RELEASE) {
|
|
85888
86223
|
out.push(...buildActivateRelease(paths));
|
|
@@ -85905,6 +86240,9 @@ function pantryExec(cmd) {
|
|
|
85905
86240
|
function reEscape(value) {
|
|
85906
86241
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
85907
86242
|
}
|
|
86243
|
+
function cronQuote(value) {
|
|
86244
|
+
return `'${value.split("'").join("'\\''")}'`;
|
|
86245
|
+
}
|
|
85908
86246
|
function slugify(value) {
|
|
85909
86247
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unnamed";
|
|
85910
86248
|
}
|
|
@@ -86006,9 +86344,18 @@ function buildSiteServicesScript(options) {
|
|
|
86006
86344
|
out.push(`systemctl enable ${name}.service`, `systemctl restart ${name}.service`);
|
|
86007
86345
|
}
|
|
86008
86346
|
const cronPath = schedulerCronPath(slug, siteName);
|
|
86009
|
-
|
|
86010
|
-
|
|
86011
|
-
|
|
86347
|
+
const scheduler2 = site.scheduler;
|
|
86348
|
+
const schedulerEnabled = scheduler2 === true || typeof scheduler2 === "object" && scheduler2 !== null;
|
|
86349
|
+
if (schedulerEnabled) {
|
|
86350
|
+
const heartbeat = typeof scheduler2 === "object" && scheduler2 !== null ? scheduler2 : undefined;
|
|
86351
|
+
let command = `cd ${current} && ${PANTRY_ENV_EVAL} && ${phpBin} artisan schedule:run >> /dev/null 2>&1`;
|
|
86352
|
+
if (heartbeat?.heartbeatUrl) {
|
|
86353
|
+
const method = heartbeat.heartbeatMethod || "GET";
|
|
86354
|
+
const methodFlag = method === "GET" ? "" : `-X ${method} `;
|
|
86355
|
+
command += ` && curl -fsS -m 10 ${methodFlag}${cronQuote(heartbeat.heartbeatUrl)} >/dev/null 2>&1`;
|
|
86356
|
+
}
|
|
86357
|
+
const cron = `* * * * * root ${command}
|
|
86358
|
+
`.replace(/%/g, "\\%");
|
|
86012
86359
|
out.push(`cat > ${cronPath} <<'TS_CLOUD_CRON_EOF'`, cron.replace(/\n$/, ""), "TS_CLOUD_CRON_EOF", `chmod 644 ${cronPath}`);
|
|
86013
86360
|
} else {
|
|
86014
86361
|
out.push(`rm -f ${cronPath}`);
|
|
@@ -86076,7 +86423,9 @@ async function deploySiteRelease(driver, options, logger4 = noopLogger) {
|
|
|
86076
86423
|
phpVersion,
|
|
86077
86424
|
redirects: site.redirects,
|
|
86078
86425
|
ssl: customCert,
|
|
86079
|
-
auth: site.auth && site.auth.enabled !== false && site.auth.password ? { username: site.auth.username || "admin", password: site.auth.password, realm: site.auth.realm } : undefined
|
|
86426
|
+
auth: site.auth && site.auth.enabled !== false && site.auth.password ? { username: site.auth.username || "admin", password: site.auth.password, realm: site.auth.realm } : undefined,
|
|
86427
|
+
serverSnippet: resolveNginxSnippet(site.nginx, compute2?.nginxTemplates),
|
|
86428
|
+
clientMaxBodySize: site.nginx?.clientMaxBodySize
|
|
86080
86429
|
}) : [];
|
|
86081
86430
|
const sslScript = useNginx ? buildSslScript(site) : [];
|
|
86082
86431
|
const servicesScript = siteHasServices(site) ? buildSiteServicesScript({ slug, siteName, site, phpVersion, appBase }) : [];
|
|
@@ -86139,7 +86488,9 @@ async function deploySiteRelease(driver, options, logger4 = noopLogger) {
|
|
|
86139
86488
|
appDir: `/var/www/${siteName}`,
|
|
86140
86489
|
webDirectory: "",
|
|
86141
86490
|
redirects: site.redirects,
|
|
86142
|
-
auth: site.auth && site.auth.enabled !== false && site.auth.password ? { username: site.auth.username || "admin", password: site.auth.password, realm: site.auth.realm } : undefined
|
|
86491
|
+
auth: site.auth && site.auth.enabled !== false && site.auth.password ? { username: site.auth.username || "admin", password: site.auth.password, realm: site.auth.realm } : undefined,
|
|
86492
|
+
serverSnippet: resolveNginxSnippet(site.nginx, compute?.nginxTemplates),
|
|
86493
|
+
clientMaxBodySize: site.nginx?.clientMaxBodySize
|
|
86143
86494
|
}) : [];
|
|
86144
86495
|
const staticSsl = wantsNginxStatic ? buildSslScript(site) : [];
|
|
86145
86496
|
const remoteScript = [...baseScript, ...staticVhost, ...staticSsl];
|