openship 0.6.7 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bare-FA66N7GC.js +29 -0
- package/dist/{chunk-LHFXSC36.js → chunk-3MP6ACKM.js} +183 -87
- package/dist/{chunk-MEE6OZFP.js → chunk-5DJOB7IX.js} +3 -3
- package/dist/{chunk-VQM6JHJB.js → chunk-5UXHT6ED.js} +9 -9
- package/dist/{chunk-PPEBIY5F.js → chunk-634DDFT5.js} +35 -12
- package/dist/{chunk-XWHWTHEL.js → chunk-6GU6JSK6.js} +21 -2
- package/dist/{chunk-QHMDCYZ3.js → chunk-7PEXM2UI.js} +213 -45
- package/dist/{chunk-RDXT4BQB.js → chunk-7YBWXEM6.js} +4 -4
- package/dist/{chunk-H4FS5BED.js → chunk-AHJYP7DG.js} +13 -11
- package/dist/{chunk-VSTFHUCV.js → chunk-BO6CY4V2.js} +109 -23
- package/dist/{chunk-J3K6XABC.js → chunk-D3OEMXYP.js} +3 -3
- package/dist/{chunk-UWEV6XTS.js → chunk-D6HU4VUZ.js} +1 -1
- package/dist/{chunk-S6B2Z3OO.js → chunk-ENKJ7XIF.js} +21 -11
- package/dist/{chunk-DDFUESPJ.js → chunk-FCGJN5Q5.js} +9 -9
- package/dist/{chunk-BLEXTYRK.js → chunk-GNOMEMNI.js} +1 -1
- package/dist/{chunk-NRTJRNAE.js → chunk-IAMOUDHY.js} +138 -54
- package/dist/{chunk-CIJV43LH.js → chunk-MUWVC5R4.js} +161 -39
- package/dist/{chunk-ZPJ74LXE.js → chunk-NIYD7MD2.js} +197 -45
- package/dist/{chunk-FZ6QCF6B.js → chunk-NPSRGIHV.js} +2 -2
- package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
- package/dist/{chunk-J2XM3QSP.js → chunk-NUT6HKK6.js} +5 -5
- package/dist/{chunk-H3WT3A6R.js → chunk-P7RCYELG.js} +1 -1
- package/dist/{chunk-FYPFEDPZ.js → chunk-R5DC5PEF.js} +1 -1
- package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
- package/dist/{chunk-WU54BXFE.js → chunk-SK4FFCFR.js} +1 -1
- package/dist/{chunk-PVP4RGWT.js → chunk-T46BQOLY.js} +3 -3
- package/dist/{chunk-E2X26PXR.js → chunk-T6DVUX4F.js} +1 -1
- package/dist/{chunk-5T4UVOM3.js → chunk-TAZZZKYO.js} +1 -1
- package/dist/{chunk-GGIAIGUL.js → chunk-UAWY6X7V.js} +34 -12
- package/dist/{chunk-2ANV5ZQ5.js → chunk-VAI5VOOI.js} +1 -1
- package/dist/{chunk-2VSW6Q6C.js → chunk-WEZFTL4V.js} +655 -62
- package/dist/{chunk-GIPJNNI5.js → chunk-X53JXRS4.js} +3 -3
- package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
- package/dist/cloud-REV3DPJH.js +42 -0
- package/dist/{detect-G4DR3U2N.js → detect-BBFT5GEU.js} +4 -4
- package/dist/{docker-YOVPCIB4.js → docker-EG3QSE6I.js} +10 -10
- package/dist/{docker-edge-executor-QFXD3337.js → docker-edge-executor-E5TOTLMR.js} +7 -7
- package/dist/edge-import-YJVJUGYC.js +56 -0
- package/dist/{edge-preflight-MXERVAMG.js → edge-preflight-QUW4BM7N.js} +13 -13
- package/dist/{ensure-container-edge-F4JUNQIK.js → ensure-container-edge-FOEFQ3PI.js} +13 -11
- package/dist/{executor-CVPAYYHR.js → executor-NTWDJ3DB.js} +6 -6
- package/dist/host-channel-preflight-OI2TRHSG.js +54 -0
- package/dist/index.js +158 -72
- package/dist/{local-executor-ODC6RANU.js → local-executor-OQ2GRN7K.js} +3 -3
- package/dist/{nginx-KLFF43TC.js → nginx-4U4C2XUS.js} +9 -9
- package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
- package/dist/{openresty-lua-VKWUV4GB.js → openresty-lua-LLWT5XQW.js} +7 -5
- package/dist/{privilege-7D3SSOL3.js → privilege-MNDVMDST.js} +5 -5
- package/dist/server/index.js +31801 -25560
- package/dist/server/migrations/0109_service_build_args.sql +9 -0
- package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
- package/dist/server/migrations/0111_host_port_claim.sql +381 -0
- package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
- package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
- package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
- package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
- package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
- package/dist/server/migrations/meta/_journal.json +56 -0
- package/dist/{setup-I5AJC6NJ.js → setup-IFIMPQZJ.js} +12 -12
- package/dist/{ssh-client-625VWGKP.js → ssh-client-2XWZCMGI.js} +2 -2
- package/dist/up-RC5ZQIAD.js +61 -0
- package/package.json +1 -1
- package/dist/bare-7QD623QV.js +0 -29
- package/dist/cloud-KNNABE3O.js +0 -40
- package/dist/edge-import-CRFVSR6P.js +0 -56
- package/dist/host-channel-preflight-QTDNGLCK.js +0 -54
- package/dist/up-QXTOOVXC.js +0 -61
|
@@ -17,8 +17,9 @@ import {
|
|
|
17
17
|
detectOpenRestyPaths,
|
|
18
18
|
edgeChallengeVhostConf,
|
|
19
19
|
isCloudFrontedHost,
|
|
20
|
-
luaSourceAvailable
|
|
21
|
-
|
|
20
|
+
luaSourceAvailable,
|
|
21
|
+
reloadBareOpenResty
|
|
22
|
+
} from "./chunk-7PEXM2UI.js";
|
|
22
23
|
import {
|
|
23
24
|
edgeDownExplanation,
|
|
24
25
|
explainEdgeDown,
|
|
@@ -29,17 +30,17 @@ import {
|
|
|
29
30
|
} from "./chunk-R4VDPFLS.js";
|
|
30
31
|
import {
|
|
31
32
|
EXECUTOR_DELEGATE
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NPSRGIHV.js";
|
|
33
34
|
import {
|
|
34
35
|
logEntry,
|
|
35
36
|
sq
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-P7RCYELG.js";
|
|
37
38
|
import {
|
|
38
39
|
edgeFailureReason
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-X53JXRS4.js";
|
|
40
41
|
import {
|
|
41
42
|
probeListeningPort
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-BO6CY4V2.js";
|
|
43
44
|
import {
|
|
44
45
|
PROXY_DIRECTIVES,
|
|
45
46
|
parseProxyValue,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
resolveRedirectStatus,
|
|
48
49
|
safeErrorMessage,
|
|
49
50
|
sanitizeProxySettings
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-WEZFTL4V.js";
|
|
51
52
|
|
|
52
53
|
// ../../packages/adapters/src/infra/nginx.ts
|
|
53
54
|
import {
|
|
@@ -593,7 +594,7 @@ function acmeKeyArgs(keyType) {
|
|
|
593
594
|
return [];
|
|
594
595
|
}
|
|
595
596
|
}
|
|
596
|
-
var DOMAIN_RE = /^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$/;
|
|
597
|
+
var DOMAIN_RE = /^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$/;
|
|
597
598
|
function assertValidDomain(domain) {
|
|
598
599
|
if (!DOMAIN_RE.test(domain) || domain.length > 253) {
|
|
599
600
|
throw new Error(`Invalid domain: ${domain}`);
|
|
@@ -601,7 +602,9 @@ function assertValidDomain(domain) {
|
|
|
601
602
|
}
|
|
602
603
|
function assertNoNginxInjection(value, what) {
|
|
603
604
|
if (/[\s;{}#\\'"]/.test(value)) {
|
|
604
|
-
throw new Error(
|
|
605
|
+
throw new Error(
|
|
606
|
+
`Invalid ${what} (contains characters that could inject nginx config): ${value}`
|
|
607
|
+
);
|
|
605
608
|
}
|
|
606
609
|
}
|
|
607
610
|
function assertNoRuntimeVariable(value, what, allowCaptures = false) {
|
|
@@ -698,7 +701,9 @@ function certbotDiagnosis(output, domain) {
|
|
|
698
701
|
diagnosis = `Couldn't start the certificate challenge listener for ${domain} on port ${ACME_HTTP01_PORT}: something on this server is already holding it. That port is used only while a certificate is being issued, so this is almost always a certificate run from an earlier attempt that hasn't exited \u2014 retry, and if it persists find the holder with \`ss -ltnp | grep ${ACME_HTTP01_PORT}\` and stop it. Nothing was requested from Let's Encrypt, so this is not a DNS or routing problem.`;
|
|
699
702
|
} else if (/timeout during connect|connection refused|failed to connect|Timeout/i.test(text)) {
|
|
700
703
|
diagnosis = `Port 80 for ${domain} isn't reachable from the internet \u2014 a firewall / cloud security group is blocking it, the domain doesn't point at this server, or another proxy is still bound to :80.`;
|
|
701
|
-
} else if (/NXDOMAIN|no\s+(A|AAAA)\s+record|DNS problem|could not be resolved|no records? found/i.test(
|
|
704
|
+
} else if (/NXDOMAIN|no\s+(A|AAAA)\s+record|DNS problem|could not be resolved|no records? found/i.test(
|
|
705
|
+
text
|
|
706
|
+
)) {
|
|
702
707
|
diagnosis = `${domain} doesn't resolve to this server yet \u2014 the DNS A record is missing or hasn't propagated. Wait for propagation, then retry from the Domains tab.`;
|
|
703
708
|
} else if (/\b52[56]\b|ssl handshake failed/i.test(text)) {
|
|
704
709
|
diagnosis = `Cloudflare's proxy is answering the certificate challenge for ${domain} instead of forwarding it to this server: it redirects the request to HTTPS, then rejects the origin's TLS. Fix it one of three ways \u2014 set SSL/TLS \u2192 Overview to "Full" (not "Full (strict)") until the first certificate is issued, or turn the orange cloud off for this record until it is, or upload a Cloudflare Origin CA certificate from the Domains tab and skip Let's Encrypt entirely.`;
|
|
@@ -721,6 +726,7 @@ var NginxProvider = class {
|
|
|
721
726
|
acmeTosAgreed;
|
|
722
727
|
certDir;
|
|
723
728
|
executor;
|
|
729
|
+
paths;
|
|
724
730
|
reloadCommand;
|
|
725
731
|
pinPaths;
|
|
726
732
|
containerEdge;
|
|
@@ -744,8 +750,9 @@ var NginxProvider = class {
|
|
|
744
750
|
this.acmeTosAgreed = opts.acmeTosAgreed ?? true;
|
|
745
751
|
this.certDir = opts.certDir ?? DEFAULT_CERT_DIR;
|
|
746
752
|
this.executor = opts.executor ?? null;
|
|
753
|
+
this.paths = opts.paths;
|
|
747
754
|
this.containerEdge = opts.containerEdge ?? false;
|
|
748
|
-
this.reloadCommand = buildReloadCommand(opts.paths
|
|
755
|
+
this.reloadCommand = buildReloadCommand(opts.paths);
|
|
749
756
|
this.pinPaths = opts.pinPaths ?? false;
|
|
750
757
|
this.challengeDir = opts.challengeDir ?? EDGE_CHALLENGE_DIR;
|
|
751
758
|
}
|
|
@@ -1094,6 +1101,31 @@ eab-hmac-key = ${this.acmeEabHmacKey}
|
|
|
1094
1101
|
}
|
|
1095
1102
|
return path;
|
|
1096
1103
|
}
|
|
1104
|
+
/** Materialize generated DNS hooks on the same target where Certbot runs. */
|
|
1105
|
+
async createEphemeralDnsHooks(opts) {
|
|
1106
|
+
if (!opts?.dnsAuthHookScript) return null;
|
|
1107
|
+
const dir = join(dirname(this.certDir), `.openship-dns-${randomBytes(12).toString("hex")}`);
|
|
1108
|
+
const authPath = join(dir, "auth.sh");
|
|
1109
|
+
const cleanupPath = join(dir, "cleanup.sh");
|
|
1110
|
+
const recordPath = join(dir, "record-id.txt");
|
|
1111
|
+
await this._mkdir(dir);
|
|
1112
|
+
try {
|
|
1113
|
+
await this._chmod(dir, 448);
|
|
1114
|
+
await this._writeFile(authPath, opts.dnsAuthHookScript, 448);
|
|
1115
|
+
if (opts.dnsCleanupHookScript) {
|
|
1116
|
+
await this._writeFile(cleanupPath, opts.dnsCleanupHookScript, 448);
|
|
1117
|
+
}
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
await this._rm(dir).catch(() => void 0);
|
|
1120
|
+
throw err;
|
|
1121
|
+
}
|
|
1122
|
+
const recordEnv = `OPENSHIP_DNS_RECORD_FILE=${sq(recordPath)}`;
|
|
1123
|
+
return {
|
|
1124
|
+
dir,
|
|
1125
|
+
authCommand: `${recordEnv} ${sq(authPath)}`,
|
|
1126
|
+
cleanupCommand: opts.dnsCleanupHookScript ? `${recordEnv} ${sq(cleanupPath)}` : void 0
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1097
1129
|
async _captureFile(path) {
|
|
1098
1130
|
if (!await this._exists(path)) {
|
|
1099
1131
|
return { exists: false };
|
|
@@ -1347,7 +1379,9 @@ ${serveLocation}
|
|
|
1347
1379
|
*/
|
|
1348
1380
|
async _lsSites() {
|
|
1349
1381
|
if (!this.executor) return fsReaddir(this.sitesDir);
|
|
1350
|
-
const out = await this.executor.exec(
|
|
1382
|
+
const out = await this.executor.exec(
|
|
1383
|
+
`ls -1 ${sq(this._commandSitesDir())} 2>/dev/null || true`
|
|
1384
|
+
);
|
|
1351
1385
|
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1352
1386
|
}
|
|
1353
1387
|
/**
|
|
@@ -1410,8 +1444,11 @@ ${serveLocation}
|
|
|
1410
1444
|
* from the old in-memory config (an inconsistent half-state that would silently
|
|
1411
1445
|
* vanish on the next unrelated reload). The error is re-thrown for retry.
|
|
1412
1446
|
*/
|
|
1413
|
-
async removeRoute(domain) {
|
|
1447
|
+
async removeRoute(domain, opts) {
|
|
1414
1448
|
assertValidDomain(domain);
|
|
1449
|
+
if (opts?.signal?.aborted) {
|
|
1450
|
+
throw new Error(`Route removal aborted before it started: ${domain}`);
|
|
1451
|
+
}
|
|
1415
1452
|
const slug = await this.resolveSlug(domain);
|
|
1416
1453
|
const configPath = join(this.sitesDir, `${slug}.conf`);
|
|
1417
1454
|
const statePath = this.routeStatePath(slug);
|
|
@@ -1420,11 +1457,20 @@ ${serveLocation}
|
|
|
1420
1457
|
await this._rm(configPath);
|
|
1421
1458
|
await this._rm(statePath).catch(() => void 0);
|
|
1422
1459
|
try {
|
|
1423
|
-
await this.reload();
|
|
1460
|
+
await this.reload({ allowStopped: true });
|
|
1424
1461
|
} catch (err) {
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1462
|
+
try {
|
|
1463
|
+
if (!opts?.signal?.aborted) await this._restoreFile(configPath, confSnapshot);
|
|
1464
|
+
if (!opts?.signal?.aborted) await this._restoreFile(statePath, stateSnapshot);
|
|
1465
|
+
if (!opts?.signal?.aborted) {
|
|
1466
|
+
await this.reload({ allowStopped: true }).catch(() => void 0);
|
|
1467
|
+
}
|
|
1468
|
+
} finally {
|
|
1469
|
+
if (opts?.signal?.aborted) {
|
|
1470
|
+
await this._rm(configPath).catch(() => void 0);
|
|
1471
|
+
await this._rm(statePath).catch(() => void 0);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1428
1474
|
throw err;
|
|
1429
1475
|
}
|
|
1430
1476
|
await this._rm(this.bootstrapCertDir(domain)).catch(() => void 0);
|
|
@@ -1531,31 +1577,47 @@ ${serveLocation}
|
|
|
1531
1577
|
const emailArgs = validEmail ? ["--email", this.acmeEmail] : ["--register-unsafely-without-email"];
|
|
1532
1578
|
let certonlyOut = "";
|
|
1533
1579
|
const eabConfig = await this.createEphemeralEabConfig();
|
|
1580
|
+
let generatedDnsHooks = null;
|
|
1534
1581
|
try {
|
|
1535
|
-
|
|
1536
|
-
|
|
1582
|
+
generatedDnsHooks = await this.createEphemeralDnsHooks(opts);
|
|
1583
|
+
const isDnsChallenge = opts?.challenge === "dns-01" || domain.startsWith("*.");
|
|
1584
|
+
const challengeArgs = isDnsChallenge ? [
|
|
1537
1585
|
"certonly",
|
|
1538
|
-
"--
|
|
1539
|
-
"--
|
|
1540
|
-
|
|
1541
|
-
"--
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1586
|
+
"--manual",
|
|
1587
|
+
"--preferred-challenges",
|
|
1588
|
+
"dns",
|
|
1589
|
+
...generatedDnsHooks?.authCommand || opts?.dnsAuthHook ? ["--manual-auth-hook", generatedDnsHooks?.authCommand ?? opts.dnsAuthHook] : [],
|
|
1590
|
+
...generatedDnsHooks?.cleanupCommand || opts?.dnsCleanupHook ? [
|
|
1591
|
+
"--manual-cleanup-hook",
|
|
1592
|
+
generatedDnsHooks?.cleanupCommand ?? opts.dnsCleanupHook
|
|
1593
|
+
] : []
|
|
1594
|
+
] : ["certonly", "--standalone", "--http-01-port", String(ACME_HTTP01_PORT)];
|
|
1595
|
+
certonlyOut = await this._execCertbot(
|
|
1596
|
+
[
|
|
1597
|
+
...eabConfig ? ["--config", eabConfig] : [],
|
|
1598
|
+
...challengeArgs,
|
|
1599
|
+
"--cert-name",
|
|
1600
|
+
domain,
|
|
1601
|
+
"-d",
|
|
1602
|
+
domain,
|
|
1603
|
+
...this.acmeDirectoryUrl ? ["--server", this.acmeDirectoryUrl] : [],
|
|
1604
|
+
...acmeKeyArgs(this.acmeKeyType),
|
|
1605
|
+
...emailArgs,
|
|
1606
|
+
...this.acmeTosAgreed ? ["--agree-tos"] : [],
|
|
1607
|
+
"--non-interactive",
|
|
1608
|
+
// Forced reissue: certbot would otherwise print "not due for renewal"
|
|
1609
|
+
// (exit 0) when a lineage exists, leaving the stale cert in place.
|
|
1610
|
+
...opts?.force ? ["--force-renewal"] : []
|
|
1611
|
+
],
|
|
1612
|
+
opts?.onLog
|
|
1613
|
+
);
|
|
1554
1614
|
} catch (err) {
|
|
1555
1615
|
const raw = this.redactAcmeSecrets(safeErrorMessage(err));
|
|
1556
1616
|
let summary = summarizeCertbotFailure(raw, domain);
|
|
1557
1617
|
if (isAcmePortBindFailure(raw) && this.executor) {
|
|
1558
|
-
const occupant = await probeListeningPort(this.executor, ACME_HTTP01_PORT).catch(
|
|
1618
|
+
const occupant = await probeListeningPort(this.executor, ACME_HTTP01_PORT).catch(
|
|
1619
|
+
() => null
|
|
1620
|
+
);
|
|
1559
1621
|
if (occupant) {
|
|
1560
1622
|
summary += ` Currently held by ${occupant.command}` + (occupant.pid !== null ? ` (pid ${occupant.pid})` : "") + (occupant.systemdUnit ? ` via ${occupant.systemdUnit}` : "") + `.`;
|
|
1561
1623
|
}
|
|
@@ -1563,6 +1625,7 @@ ${serveLocation}
|
|
|
1563
1625
|
throw new Error(summary);
|
|
1564
1626
|
} finally {
|
|
1565
1627
|
if (eabConfig) await this._rm(dirname(eabConfig)).catch(() => void 0);
|
|
1628
|
+
if (generatedDnsHooks) await this._rm(generatedDnsHooks.dir).catch(() => void 0);
|
|
1566
1629
|
}
|
|
1567
1630
|
const slug = await this.resolveSlug(domain);
|
|
1568
1631
|
const configPath = join(this.sitesDir, `${slug}.conf`);
|
|
@@ -1630,24 +1693,24 @@ ${tail}` : "")
|
|
|
1630
1693
|
/**
|
|
1631
1694
|
* Renew a TLS certificate using certbot.
|
|
1632
1695
|
*/
|
|
1633
|
-
async renewCert(domain) {
|
|
1696
|
+
async renewCert(domain, opts) {
|
|
1634
1697
|
assertValidDomain(domain);
|
|
1698
|
+
if (opts?.challenge === "dns-01" || domain.startsWith("*.")) {
|
|
1699
|
+
return this.provisionCert(domain, { ...opts, challenge: "dns-01", force: true });
|
|
1700
|
+
}
|
|
1635
1701
|
if (!await this.certsExist(domain)) {
|
|
1636
|
-
return this.provisionCert(domain);
|
|
1702
|
+
return this.provisionCert(domain, opts);
|
|
1637
1703
|
}
|
|
1638
1704
|
if (!await this.hasCertbotLineage(domain)) {
|
|
1639
|
-
return this.provisionCert(domain, { force: true });
|
|
1705
|
+
return this.provisionCert(domain, { ...opts, force: true });
|
|
1640
1706
|
}
|
|
1641
1707
|
if (!await this.lineageServerMatches(domain)) {
|
|
1642
|
-
return this.provisionCert(domain, { force: true });
|
|
1708
|
+
return this.provisionCert(domain, { ...opts, force: true });
|
|
1643
1709
|
}
|
|
1644
|
-
await this._execCertbot(
|
|
1645
|
-
"renew",
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
...acmeKeyArgs(this.acmeKeyType),
|
|
1649
|
-
"--non-interactive"
|
|
1650
|
-
]);
|
|
1710
|
+
await this._execCertbot(
|
|
1711
|
+
["renew", "--cert-name", domain, ...acmeKeyArgs(this.acmeKeyType), "--non-interactive"],
|
|
1712
|
+
opts?.onLog
|
|
1713
|
+
);
|
|
1651
1714
|
await this.reload();
|
|
1652
1715
|
return this.readCertInfo(domain);
|
|
1653
1716
|
}
|
|
@@ -1805,23 +1868,42 @@ ${tail}` : "")
|
|
|
1805
1868
|
* config may move). Re-detecting on every reload keeps the provider
|
|
1806
1869
|
* in sync without requiring an API restart.
|
|
1807
1870
|
*/
|
|
1808
|
-
async reload() {
|
|
1871
|
+
async reload(opts = {}) {
|
|
1809
1872
|
if (this.executor) {
|
|
1810
1873
|
if (!this.pinPaths) {
|
|
1811
1874
|
try {
|
|
1812
1875
|
const freshPaths = await detectOpenRestyPaths(this.executor);
|
|
1876
|
+
this.paths = freshPaths;
|
|
1813
1877
|
this.sitesDir = freshPaths.sitesDir;
|
|
1814
1878
|
this.nginxVersion = freshPaths.nginxVersion ?? this.nginxVersion;
|
|
1815
|
-
this.reloadCommand = buildReloadCommand(freshPaths
|
|
1816
|
-
containerEdge: this.containerEdge
|
|
1817
|
-
});
|
|
1879
|
+
this.reloadCommand = buildReloadCommand(freshPaths);
|
|
1818
1880
|
} catch {
|
|
1819
1881
|
}
|
|
1820
1882
|
}
|
|
1821
|
-
|
|
1883
|
+
if (this.containerEdge) {
|
|
1884
|
+
await this.executor.exec(this.reloadCommand, { timeout: 15e3 });
|
|
1885
|
+
} else {
|
|
1886
|
+
await reloadBareOpenResty(this.executor, this.paths, {
|
|
1887
|
+
allowStopped: opts.allowStopped ?? false
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1822
1890
|
return;
|
|
1823
1891
|
}
|
|
1824
|
-
|
|
1892
|
+
if (this.containerEdge) {
|
|
1893
|
+
await execFileAsync("sh", ["-lc", this.reloadCommand], { timeout: 15e3 });
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const localExecutor = {
|
|
1897
|
+
exec: async (command, execOpts) => {
|
|
1898
|
+
const { stdout } = await execFileAsync("sh", ["-lc", command], {
|
|
1899
|
+
timeout: execOpts?.timeout
|
|
1900
|
+
});
|
|
1901
|
+
return String(stdout);
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
await reloadBareOpenResty(localExecutor, this.paths, {
|
|
1905
|
+
allowStopped: opts.allowStopped ?? false
|
|
1906
|
+
});
|
|
1825
1907
|
}
|
|
1826
1908
|
async certsExist(domain) {
|
|
1827
1909
|
const certPath = join(this.certDir, domain, "fullchain.pem");
|
|
@@ -2003,7 +2085,9 @@ ${geoEntries.join("\n")}
|
|
|
2003
2085
|
if (!foundDesiredInclude) {
|
|
2004
2086
|
const httpIndex = nextLines.findIndex((line) => /^\s*http\s*\{\s*$/.test(line));
|
|
2005
2087
|
if (httpIndex === -1) {
|
|
2006
|
-
throw new Error(
|
|
2088
|
+
throw new Error(
|
|
2089
|
+
`Failed to ensure rate-limit include: ${confPath} is missing an http block`
|
|
2090
|
+
);
|
|
2007
2091
|
}
|
|
2008
2092
|
const indent = nextLines[httpIndex].match(/^\s*/)?.[0] ?? "";
|
|
2009
2093
|
nextLines.splice(httpIndex + 1, 0, `${indent} ${desiredIncludeLine}`);
|
|
@@ -5,16 +5,17 @@ const require = __ospCreateRequire(import.meta.url);
|
|
|
5
5
|
import {
|
|
6
6
|
checkGit,
|
|
7
7
|
installGit
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NIYD7MD2.js";
|
|
9
9
|
import {
|
|
10
|
-
createDockerBuildContext
|
|
11
|
-
|
|
10
|
+
createDockerBuildContext,
|
|
11
|
+
resolveDockerBuildArgs
|
|
12
|
+
} from "./chunk-6GU6JSK6.js";
|
|
12
13
|
import {
|
|
13
14
|
prepareStackOutput,
|
|
14
15
|
resolveProjectDir,
|
|
15
16
|
resolveStaticOutputPath,
|
|
16
17
|
runLocalBuild
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-5DJOB7IX.js";
|
|
18
19
|
import {
|
|
19
20
|
BuildLogger,
|
|
20
21
|
dockerBuildContextDirectory,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
splitRuntimeEnv,
|
|
29
30
|
sq,
|
|
30
31
|
transferLocalDirectory
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-AHJYP7DG.js";
|
|
32
33
|
import {
|
|
33
34
|
isRuntimeNotFoundError
|
|
34
35
|
} from "./chunk-N5YZMO33.js";
|
|
@@ -43,7 +44,7 @@ import {
|
|
|
43
44
|
packageManagerEnsureCommand,
|
|
44
45
|
safeErrorMessage,
|
|
45
46
|
shellSplitWords
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-WEZFTL4V.js";
|
|
47
48
|
|
|
48
49
|
// ../../packages/adapters/src/runtime/cloud.ts
|
|
49
50
|
import { readFile } from "fs/promises";
|
|
@@ -1332,6 +1333,11 @@ function resolveCopyDestinationPath(copy, sourcePath, destination, destinationIs
|
|
|
1332
1333
|
function cloudBuildContextPath(config) {
|
|
1333
1334
|
return dockerBuildContextDirectory(config) || normalizeDockerRelativePath(config.rootDirectory);
|
|
1334
1335
|
}
|
|
1336
|
+
function compileCloudDockerfilePlan(source, config) {
|
|
1337
|
+
return compileDockerfileToWorkspacePlan(source, {
|
|
1338
|
+
buildArgs: resolveDockerBuildArgs(config)
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1335
1341
|
var PAGE_CONTAINER_PREFIX = "page:";
|
|
1336
1342
|
async function provisionCloudWorkspace(client, config, logger) {
|
|
1337
1343
|
logger?.log(`Creating workspace from image "${config.image}"...
|
|
@@ -1350,15 +1356,22 @@ async function provisionCloudWorkspace(client, config, logger) {
|
|
|
1350
1356
|
}
|
|
1351
1357
|
});
|
|
1352
1358
|
} catch (err) {
|
|
1353
|
-
logger?.log(
|
|
1354
|
-
|
|
1359
|
+
logger?.log(
|
|
1360
|
+
`Failed to create workspace from image "${config.image}": ${formatCloudError(err)}
|
|
1361
|
+
`,
|
|
1362
|
+
"error"
|
|
1363
|
+
);
|
|
1355
1364
|
throw err;
|
|
1356
1365
|
}
|
|
1357
1366
|
const ws = client.workspace(wsData.id);
|
|
1358
1367
|
try {
|
|
1359
1368
|
if (config.mode === "temporary" && config.ttl) {
|
|
1360
1369
|
try {
|
|
1361
|
-
await ws.lifecycle.makeTemporary({
|
|
1370
|
+
await ws.lifecycle.makeTemporary({
|
|
1371
|
+
ttl: config.ttl,
|
|
1372
|
+
ttl_action: "remove",
|
|
1373
|
+
remove_on_exit: true
|
|
1374
|
+
});
|
|
1362
1375
|
} catch {
|
|
1363
1376
|
}
|
|
1364
1377
|
}
|
|
@@ -1398,6 +1411,7 @@ var CloudRuntime = class {
|
|
|
1398
1411
|
name = "cloud";
|
|
1399
1412
|
capabilities = /* @__PURE__ */ new Set([
|
|
1400
1413
|
"build",
|
|
1414
|
+
"prebuiltImage",
|
|
1401
1415
|
"deploy",
|
|
1402
1416
|
"multiServiceDeploy",
|
|
1403
1417
|
"stop",
|
|
@@ -1549,6 +1563,94 @@ var CloudRuntime = class {
|
|
|
1549
1563
|
this.activeBuilds.get(sessionId)?.workspaceIds.delete(workspaceId);
|
|
1550
1564
|
}
|
|
1551
1565
|
// ── Build lifecycle ────────────────────────────────────────────────────
|
|
1566
|
+
/**
|
|
1567
|
+
* Materialize a prebuilt container image as the same opaque artifact Cloud's
|
|
1568
|
+
* deploy path already understands: a temporary workspace id.
|
|
1569
|
+
*
|
|
1570
|
+
* Unlike a source build, no command is executed in the workspace. Creating it
|
|
1571
|
+
* directly from the application image preserves the image's own ENTRYPOINT,
|
|
1572
|
+
* CMD and WORKDIR; deploy keeps those defaults when `prebuiltImage` is set.
|
|
1573
|
+
*/
|
|
1574
|
+
async prepareImage(config, logger) {
|
|
1575
|
+
const log = logger ?? new BuildLogger();
|
|
1576
|
+
const startedAt = Date.now();
|
|
1577
|
+
const activeBuild = this.createActiveBuild(config.sessionId);
|
|
1578
|
+
let workspaceId;
|
|
1579
|
+
const discardWorkspace = async () => {
|
|
1580
|
+
if (!workspaceId) return;
|
|
1581
|
+
await this.ws(workspaceId).delete().catch(() => {
|
|
1582
|
+
});
|
|
1583
|
+
this.builtArtifacts.delete(workspaceId);
|
|
1584
|
+
this.untrackActiveBuildWorkspace(config.sessionId, workspaceId);
|
|
1585
|
+
};
|
|
1586
|
+
const cancelled = async () => {
|
|
1587
|
+
await discardWorkspace();
|
|
1588
|
+
log.step("build", "failed", "Image preparation cancelled");
|
|
1589
|
+
return {
|
|
1590
|
+
sessionId: config.sessionId,
|
|
1591
|
+
status: "cancelled",
|
|
1592
|
+
// Keep the ref even after our best-effort delete: the pipeline can make
|
|
1593
|
+
// an idempotent cleanup retry if that delete failed mid-cancellation.
|
|
1594
|
+
imageRef: workspaceId,
|
|
1595
|
+
durationMs: Date.now() - startedAt,
|
|
1596
|
+
artifactOwned: true
|
|
1597
|
+
};
|
|
1598
|
+
};
|
|
1599
|
+
try {
|
|
1600
|
+
if (activeBuild.abort.signal.aborted) return await cancelled();
|
|
1601
|
+
const imageRef = config.imageRef.trim();
|
|
1602
|
+
if (!imageRef) throw new Error("Prebuilt image reference is required");
|
|
1603
|
+
log.step("build", "running", `Creating workspace from prebuilt image ${imageRef}`);
|
|
1604
|
+
const provisioned = await this.provisionWorkspace(
|
|
1605
|
+
{
|
|
1606
|
+
name: config.slug ?? `image-${config.projectId.slice(0, 20)}`,
|
|
1607
|
+
image: imageRef,
|
|
1608
|
+
mode: "temporary",
|
|
1609
|
+
resources: config.resources,
|
|
1610
|
+
env: config.envVars,
|
|
1611
|
+
ttl: "15m"
|
|
1612
|
+
},
|
|
1613
|
+
log
|
|
1614
|
+
);
|
|
1615
|
+
workspaceId = provisioned.workspaceId;
|
|
1616
|
+
this.trackActiveBuildWorkspace(config.sessionId, workspaceId);
|
|
1617
|
+
if (activeBuild.abort.signal.aborted) return await cancelled();
|
|
1618
|
+
this.builtArtifacts.set(workspaceId, {
|
|
1619
|
+
workspaceId,
|
|
1620
|
+
runtime: {
|
|
1621
|
+
workdir: "/",
|
|
1622
|
+
env: {},
|
|
1623
|
+
exposedPorts: [],
|
|
1624
|
+
baseImage: imageRef
|
|
1625
|
+
}
|
|
1626
|
+
});
|
|
1627
|
+
log.step("build", "completed", `Prebuilt image workspace ${workspaceId} is ready`);
|
|
1628
|
+
return {
|
|
1629
|
+
sessionId: config.sessionId,
|
|
1630
|
+
status: "deploying",
|
|
1631
|
+
imageRef: workspaceId,
|
|
1632
|
+
durationMs: Date.now() - startedAt,
|
|
1633
|
+
artifactOwned: true
|
|
1634
|
+
};
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
if (activeBuild.abort.signal.aborted) return await cancelled();
|
|
1637
|
+
await discardWorkspace();
|
|
1638
|
+
const message = formatCloudError(err);
|
|
1639
|
+
log.step("build", "failed", `Image preparation failed: ${message}`);
|
|
1640
|
+
return {
|
|
1641
|
+
sessionId: config.sessionId,
|
|
1642
|
+
status: "failed",
|
|
1643
|
+
// Same retry contract as cancellation. Undefined means provisioning
|
|
1644
|
+
// failed before Oblien handed us an owned workspace.
|
|
1645
|
+
imageRef: workspaceId,
|
|
1646
|
+
durationMs: Date.now() - startedAt,
|
|
1647
|
+
errorMessage: `Image preparation failed: ${message}`,
|
|
1648
|
+
artifactOwned: true
|
|
1649
|
+
};
|
|
1650
|
+
} finally {
|
|
1651
|
+
this.activeBuilds.delete(config.sessionId);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1552
1654
|
async build(config, logger) {
|
|
1553
1655
|
const log = logger ?? new BuildLogger();
|
|
1554
1656
|
const activeBuild = this.createActiveBuild(config.sessionId);
|
|
@@ -1607,7 +1709,9 @@ var CloudRuntime = class {
|
|
|
1607
1709
|
resolveProjectDir(buildDir, config.rootDirectory)
|
|
1608
1710
|
);
|
|
1609
1711
|
if (selfContained) {
|
|
1610
|
-
log.log(
|
|
1712
|
+
log.log(
|
|
1713
|
+
"Detected self-contained build output \u2014 uploading the bundle (no install on cloud).\n"
|
|
1714
|
+
);
|
|
1611
1715
|
await transferLocalDirectory(
|
|
1612
1716
|
selfContained.bundleDir,
|
|
1613
1717
|
{ kind: "cloud-runtime", runtime: rt, path: "/app" },
|
|
@@ -1645,7 +1749,11 @@ var CloudRuntime = class {
|
|
|
1645
1749
|
log.log("Installing production dependencies on cloud...\n");
|
|
1646
1750
|
const pmEnsure = packageManagerEnsureCommand(config.packageManager);
|
|
1647
1751
|
const fullInstall = pmEnsure ? `${pmEnsure} && ${installCmd}` : installCmd;
|
|
1648
|
-
await this.execAndStream(
|
|
1752
|
+
await this.execAndStream(
|
|
1753
|
+
rt,
|
|
1754
|
+
["sh", "-c", `cd /app && ${fullInstall}`],
|
|
1755
|
+
log.callback
|
|
1756
|
+
);
|
|
1649
1757
|
}
|
|
1650
1758
|
}
|
|
1651
1759
|
});
|
|
@@ -1730,7 +1838,7 @@ var CloudRuntime = class {
|
|
|
1730
1838
|
try {
|
|
1731
1839
|
log.log("Build strategy: Dockerfile plan (build in Oblien workspaces)\n");
|
|
1732
1840
|
source = await this.resolveDockerfileBuildSource(config, log);
|
|
1733
|
-
const plan =
|
|
1841
|
+
const plan = compileCloudDockerfilePlan(source.dockerfile, config);
|
|
1734
1842
|
const blocking = plan.diagnostics.filter(
|
|
1735
1843
|
(item) => item.severity === "error" || item.severity === "unsupported"
|
|
1736
1844
|
);
|
|
@@ -1819,10 +1927,7 @@ var CloudRuntime = class {
|
|
|
1819
1927
|
requireRepositoryDockerfile: true,
|
|
1820
1928
|
onLog: logger.callback
|
|
1821
1929
|
});
|
|
1822
|
-
const dockerfilePath = resolveWithinDirectory(
|
|
1823
|
-
context.buildContextDir,
|
|
1824
|
-
context.dockerfileName
|
|
1825
|
-
);
|
|
1930
|
+
const dockerfilePath = resolveWithinDirectory(context.buildContextDir, context.dockerfileName);
|
|
1826
1931
|
const dockerfile = await readFile(dockerfilePath, "utf-8");
|
|
1827
1932
|
const contextRoot = context.contextSubdir ? context.buildContextDir : resolveWithinDirectory(context.contextDir, context.rootDirectory);
|
|
1828
1933
|
return {
|
|
@@ -1885,11 +1990,9 @@ var CloudRuntime = class {
|
|
|
1885
1990
|
logger.log(`Checking Dockerfile candidate: ${candidate}
|
|
1886
1991
|
`);
|
|
1887
1992
|
const dockerfile = await executor.exec(
|
|
1888
|
-
[
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
`cat ${sq(candidate)}`
|
|
1892
|
-
].join(" && "),
|
|
1993
|
+
[`cd ${sq(sourceDir)}`, `test -f ${sq(candidate)}`, `cat ${sq(candidate)}`].join(
|
|
1994
|
+
" && "
|
|
1995
|
+
),
|
|
1893
1996
|
{ timeout: 3e4 }
|
|
1894
1997
|
).catch(() => null);
|
|
1895
1998
|
if (dockerfile !== null) {
|
|
@@ -2119,8 +2222,10 @@ var CloudRuntime = class {
|
|
|
2119
2222
|
...prepareContextCommands,
|
|
2120
2223
|
'echo "Dockerfile context prepared."'
|
|
2121
2224
|
].join("\n");
|
|
2122
|
-
logger.log(
|
|
2123
|
-
`)
|
|
2225
|
+
logger.log(
|
|
2226
|
+
`Preparing Dockerfile build workspace for repository clone (branch: ${config.branch})...
|
|
2227
|
+
`
|
|
2228
|
+
);
|
|
2124
2229
|
await this.ensureWorkspaceGit(targetRuntime, logger, "Dockerfile build workspace");
|
|
2125
2230
|
logger.log(`Cloning Dockerfile context in build workspace (branch: ${config.branch})...
|
|
2126
2231
|
`);
|
|
@@ -2373,8 +2478,8 @@ var CloudRuntime = class {
|
|
|
2373
2478
|
}
|
|
2374
2479
|
}
|
|
2375
2480
|
const builtArtifact = this.builtArtifacts.get(workspaceId);
|
|
2376
|
-
const prodPaths = config.productionPaths;
|
|
2377
|
-
const workDir = builtArtifact?.runtime.workdir ?? (prodPaths?.length ? "/app/production" : "/app");
|
|
2481
|
+
const prodPaths = config.prebuiltImage ? void 0 : config.productionPaths;
|
|
2482
|
+
const workDir = config.prebuiltImage ? "/" : builtArtifact?.runtime.workdir ?? (prodPaths?.length ? "/app/production" : "/app");
|
|
2378
2483
|
if (prodPaths?.length) {
|
|
2379
2484
|
try {
|
|
2380
2485
|
const rt = await ws.runtime();
|
|
@@ -2421,7 +2526,7 @@ fi`;
|
|
|
2421
2526
|
);
|
|
2422
2527
|
}
|
|
2423
2528
|
}
|
|
2424
|
-
const startCommand = builtArtifact?.runtime.startCommand || config.startCommand || "npm start";
|
|
2529
|
+
const startCommand = builtArtifact?.runtime.startCommand || config.startCommand || (config.prebuiltImage ? void 0 : "npm start");
|
|
2425
2530
|
const projectEnv = splitRuntimeEnv(config.envVars);
|
|
2426
2531
|
if (projectEnv.dropped.length > 0) {
|
|
2427
2532
|
onLog?.({
|
|
@@ -2438,19 +2543,31 @@ fi`;
|
|
|
2438
2543
|
const workloadCmd = resolveCloudWorkloadCmd({
|
|
2439
2544
|
startCommand,
|
|
2440
2545
|
workdir: workDir,
|
|
2441
|
-
binPathExport: builtArtifact ? "" : nodeBinPathExport(config.packageManager, [workDir, "/app"])
|
|
2546
|
+
binPathExport: builtArtifact || config.prebuiltImage ? "" : nodeBinPathExport(config.packageManager, [workDir, "/app"])
|
|
2442
2547
|
});
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2548
|
+
if (workloadCmd) {
|
|
2549
|
+
try {
|
|
2550
|
+
if (config.prebuiltImage) {
|
|
2551
|
+
await ws.workloads.delete("app").catch(() => {
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
await ws.workloads.create({
|
|
2555
|
+
name: "app",
|
|
2556
|
+
cmd: workloadCmd,
|
|
2557
|
+
working_dir: workDir,
|
|
2558
|
+
env: [...envArray, `PORT=${config.port}`],
|
|
2559
|
+
restart_policy: restartPolicy,
|
|
2560
|
+
max_restarts: 10
|
|
2561
|
+
});
|
|
2562
|
+
} catch (err) {
|
|
2563
|
+
throw new Error(`Failed to create workload: ${err instanceof Error ? err.message : err}`);
|
|
2564
|
+
}
|
|
2565
|
+
} else {
|
|
2566
|
+
log({
|
|
2567
|
+
timestamp: now2(),
|
|
2568
|
+
level: "info",
|
|
2569
|
+
message: "Using the prebuilt image's default command and working directory.\n"
|
|
2451
2570
|
});
|
|
2452
|
-
} catch (err) {
|
|
2453
|
-
throw new Error(`Failed to create workload: ${err instanceof Error ? err.message : err}`);
|
|
2454
2571
|
}
|
|
2455
2572
|
let url;
|
|
2456
2573
|
const primaryEndpoint = primaryPublicEndpoint(config);
|
|
@@ -2588,7 +2705,9 @@ fi`;
|
|
|
2588
2705
|
pg = result.page;
|
|
2589
2706
|
} catch (err) {
|
|
2590
2707
|
if (isOutputPathError(err)) throw outputDirError();
|
|
2591
|
-
throw new Error(
|
|
2708
|
+
throw new Error(
|
|
2709
|
+
`Failed to create static page for slug "${pageSlug}": ${safeErrorMessage(err)}`
|
|
2710
|
+
);
|
|
2592
2711
|
}
|
|
2593
2712
|
return { ...pg, url: void 0 };
|
|
2594
2713
|
};
|
|
@@ -2603,7 +2722,9 @@ fi`;
|
|
|
2603
2722
|
});
|
|
2604
2723
|
page = result.page;
|
|
2605
2724
|
} catch (err) {
|
|
2606
|
-
throw new Error(
|
|
2725
|
+
throw new Error(
|
|
2726
|
+
`Failed to redeploy static page for slug "${pageSlug}": ${safeErrorMessage(err)}`
|
|
2727
|
+
);
|
|
2607
2728
|
}
|
|
2608
2729
|
} else {
|
|
2609
2730
|
if (existingPage) {
|
|
@@ -3222,6 +3343,7 @@ function clampShellWindow(value, fallback, min, max) {
|
|
|
3222
3343
|
}
|
|
3223
3344
|
|
|
3224
3345
|
export {
|
|
3346
|
+
compileCloudDockerfilePlan,
|
|
3225
3347
|
PAGE_CONTAINER_PREFIX,
|
|
3226
3348
|
provisionCloudWorkspace,
|
|
3227
3349
|
HostBuildForbiddenError,
|