openship 0.4.7 → 0.5.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/README.md +1 -0
- package/dist/{bare-2HYDXQEH.js → bare-VRGE5MXH.js} +7 -7
- package/dist/{chunk-TGIINPVM.js → chunk-6LKHXB6T.js} +22 -7
- package/dist/{chunk-R5BHFFAV.js → chunk-6PIYRG5I.js} +56 -17
- package/dist/{chunk-IMBD5PZ5.js → chunk-BS2AXYZX.js} +430 -86
- package/dist/{chunk-SJIFLLX4.js → chunk-CUJXGK7R.js} +321 -85
- package/dist/{chunk-VFINNTUL.js → chunk-EAAGSQXN.js} +23 -8
- package/dist/{chunk-4Z5QO7N6.js → chunk-I6PSTQIR.js} +2 -2
- package/dist/{chunk-XFCDYZUQ.js → chunk-JLKKYUZA.js} +31 -49
- package/dist/{chunk-4TJC4JZQ.js → chunk-K2RPVGPN.js} +23 -4
- package/dist/{chunk-MZ2WEHHY.js → chunk-KNTJ7PSF.js} +1 -1
- package/dist/{chunk-L2VOGB2X.js → chunk-KZOBYJUW.js} +1 -1
- package/dist/{chunk-IPYPMI3S.js → chunk-NNPEG6HM.js} +1 -1
- package/dist/{chunk-UXWAVM4C.js → chunk-NXFIRK2Z.js} +398 -112
- package/dist/{chunk-FXTBHTWY.js → chunk-RB35YN3J.js} +271 -35
- package/dist/{chunk-6APULWSU.js → chunk-UMQP7SYT.js} +429 -66
- package/dist/{chunk-V6AN6XLZ.js → chunk-V7BZMGOT.js} +123 -45
- package/dist/{chunk-VDMQJHPI.js → chunk-YWMODKWL.js} +67 -4
- package/dist/{chunk-OXWDY22G.js → chunk-ZCOQJCZO.js} +1 -1
- package/dist/{cloud-2KXREXMO.js → cloud-3M2WZ624.js} +12 -12
- package/dist/{detect-2XSCFFTF.js → detect-BAJMY6KM.js} +7 -5
- package/dist/{docker-LODK5Y2S.js → docker-2NGBRJFJ.js} +9 -7
- package/dist/{docker-edge-executor-VHW3JPJS.js → docker-edge-executor-5I4M5QT7.js} +1 -1
- package/dist/{edge-import-ZQ4Y5HEA.js → edge-import-RIWNEIXQ.js} +21 -19
- package/dist/{ensure-container-edge-ED25XL3Z.js → ensure-container-edge-3NSYKHRX.js} +6 -6
- package/dist/{executor-YPT6RH6O.js → executor-LOYMPU67.js} +3 -3
- package/dist/index.js +309 -338
- package/dist/{nginx-XWJAOGS4.js → nginx-QE4DOM5J.js} +4 -4
- package/dist/{openresty-lua-4WLGDSER.js → openresty-lua-BTAKECCF.js} +2 -2
- package/dist/server/index.js +51705 -45369
- package/dist/server/migrations/0073_project_volumes_object_storage.sql +3 -0
- package/dist/server/migrations/0074_project_compose_path.sql +1 -0
- package/dist/server/migrations/0075_service_command_argv.sql +1 -0
- package/dist/server/migrations/0076_rollback_retention.sql +17 -0
- package/dist/server/migrations/0077_domain_redirect.sql +13 -0
- package/dist/server/migrations/0078_grant_source_scope.sql +30 -0
- package/dist/server/migrations/0079_project_readiness.sql +18 -0
- package/dist/server/migrations/0080_deployment_error_classification.sql +26 -0
- package/dist/server/migrations/0081_project_readiness_repair.sql +34 -0
- package/dist/server/migrations/meta/_journal.json +63 -0
- package/dist/{setup-2DMFIA74.js → setup-KELALK7H.js} +7 -7
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { createRequire as __ospCreateRequire } from "node:module";
|
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
6
|
resolveEnvironment
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KZOBYJUW.js";
|
|
8
8
|
import {
|
|
9
9
|
formatDuration,
|
|
10
10
|
isRemoteConnectionError,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
readDeclaredPair,
|
|
18
18
|
readMaybeInContainer,
|
|
19
19
|
validateCertFor
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RB35YN3J.js";
|
|
21
21
|
import {
|
|
22
22
|
sq
|
|
23
23
|
} from "./chunk-FPRHYBY2.js";
|
|
@@ -31,9 +31,10 @@ import {
|
|
|
31
31
|
ourLuaOnHost,
|
|
32
32
|
probeEdge,
|
|
33
33
|
resolveOurEdgeContainer,
|
|
34
|
+
sanitizeEdgeVhosts,
|
|
34
35
|
sq as sq2,
|
|
35
36
|
stopTargetsForStatus
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-6PIYRG5I.js";
|
|
37
38
|
import {
|
|
38
39
|
waitForPortListening
|
|
39
40
|
} from "./chunk-ILFETZOM.js";
|
|
@@ -42,12 +43,12 @@ import {
|
|
|
42
43
|
EDGE_HOST_PATHS,
|
|
43
44
|
OPENRESTY_DEFAULT_PATHS,
|
|
44
45
|
detectOpenRestyPaths
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-EAAGSQXN.js";
|
|
46
47
|
import {
|
|
47
48
|
buildEdgeImageRef,
|
|
48
49
|
compareSemver,
|
|
49
50
|
safeErrorMessage
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-UMQP7SYT.js";
|
|
51
52
|
|
|
52
53
|
// ../../packages/adapters/src/system/proxy/import/parse-utils.ts
|
|
53
54
|
async function tryExec(executor, command) {
|
|
@@ -81,6 +82,24 @@ function extractBlocks(text, keyword) {
|
|
|
81
82
|
}
|
|
82
83
|
return blocks;
|
|
83
84
|
}
|
|
85
|
+
function collapseByHost(items, hostsOf, score) {
|
|
86
|
+
const groups = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const item of items) {
|
|
88
|
+
const key = [...hostsOf(item)].sort().join(" ");
|
|
89
|
+
const bag = groups.get(key);
|
|
90
|
+
if (bag) bag.push(item);
|
|
91
|
+
else groups.set(key, [item]);
|
|
92
|
+
}
|
|
93
|
+
const kept = [];
|
|
94
|
+
const dropped = [];
|
|
95
|
+
for (const bag of groups.values()) {
|
|
96
|
+
let winner = bag[0];
|
|
97
|
+
for (const cand of bag) if (score(cand) > score(winner)) winner = cand;
|
|
98
|
+
kept.push(winner);
|
|
99
|
+
for (const loser of bag) if (loser !== winner) dropped.push(loser);
|
|
100
|
+
}
|
|
101
|
+
return { kept, dropped };
|
|
102
|
+
}
|
|
84
103
|
function sq3(value) {
|
|
85
104
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
86
105
|
}
|
|
@@ -121,7 +140,10 @@ function resolveProxyTarget(proxyPass, upstreams) {
|
|
|
121
140
|
const host = authority.replace(/:\d+$/, "");
|
|
122
141
|
if (upstreams.has(host)) return { url: `${scheme}${upstreams.get(host)}` };
|
|
123
142
|
const isIp = /^\d{1,3}(\.\d{1,3}){3}$/.test(host);
|
|
124
|
-
if (
|
|
143
|
+
if (host === "localhost" || host === "ip6-localhost") {
|
|
144
|
+
return { url: raw.replace(/^(https?:\/\/)[^/:]+/i, "$1127.0.0.1") };
|
|
145
|
+
}
|
|
146
|
+
if (isIp || host.includes(".")) return { url: raw };
|
|
125
147
|
return { reason: `proxy_pass host "${host}" is an undeclared upstream \u2014 not migratable` };
|
|
126
148
|
}
|
|
127
149
|
function extractLocationProxies(serverBody) {
|
|
@@ -159,6 +181,16 @@ function isHttpsUpgradeForSelf(body, names) {
|
|
|
159
181
|
return names.includes(host);
|
|
160
182
|
});
|
|
161
183
|
}
|
|
184
|
+
function isAcmeWebrootOnly(body) {
|
|
185
|
+
const paths = [...body.matchAll(/(?:^|[\s;}])location\s+([^{]+?)\s*\{/g)].map(
|
|
186
|
+
(m) => m[1].trim()
|
|
187
|
+
);
|
|
188
|
+
if (paths.length === 0) {
|
|
189
|
+
const root = firstDirective(body, "root") ?? "";
|
|
190
|
+
return /letsencrypt|acme|certbot/i.test(root);
|
|
191
|
+
}
|
|
192
|
+
return paths.every((p) => /\.well-known\/acme-challenge/i.test(p));
|
|
193
|
+
}
|
|
162
194
|
function parseServer(body, source, upstreams) {
|
|
163
195
|
const warnings = [];
|
|
164
196
|
const names = firstDirective(body, "server_name")?.split(/\s+/).filter((n) => n && n !== "_" && !n.startsWith("~")) ?? [];
|
|
@@ -185,10 +217,12 @@ function parseServer(body, source, upstreams) {
|
|
|
185
217
|
const primary = resolved.find((r) => r.path === "/") ?? resolved[0];
|
|
186
218
|
target = { kind: "proxy", url: primary.url };
|
|
187
219
|
routes = resolved;
|
|
188
|
-
} else if (root) {
|
|
189
|
-
target = { kind: "static", root: root.replace(/;$/, "") };
|
|
190
220
|
} else if (isHttpsUpgradeForSelf(body, names)) {
|
|
191
221
|
return { warnings: [] };
|
|
222
|
+
} else if (root && !isAcmeWebrootOnly(body)) {
|
|
223
|
+
target = { kind: "static", root: root.replace(/;$/, "") };
|
|
224
|
+
} else if (root) {
|
|
225
|
+
return { warnings: [] };
|
|
192
226
|
} else {
|
|
193
227
|
warnings.push(`nginx: ${names[0]} has neither proxy_pass nor root \u2014 skipped (${source})`);
|
|
194
228
|
return { warnings };
|
|
@@ -215,7 +249,12 @@ function parseNginxConfig(raw) {
|
|
|
215
249
|
warnings.push(...blockWarnings);
|
|
216
250
|
if (site) sites.push(site);
|
|
217
251
|
}
|
|
218
|
-
|
|
252
|
+
const { kept } = collapseByHost(
|
|
253
|
+
sites,
|
|
254
|
+
(s) => s.serverNames,
|
|
255
|
+
(s) => (s.ssl ? 2 : 0) + (s.target.kind === "proxy" ? 1 : 0)
|
|
256
|
+
);
|
|
257
|
+
return { proxy: "nginx", sites: kept, warnings };
|
|
219
258
|
}
|
|
220
259
|
async function scanNginx(executor) {
|
|
221
260
|
return parseNginxConfig(await loadNginxConfig(executor));
|
|
@@ -244,23 +283,61 @@ async function loadCaddyJson(executor) {
|
|
|
244
283
|
}
|
|
245
284
|
return null;
|
|
246
285
|
}
|
|
247
|
-
function flattenHandlers(handlers, out = []) {
|
|
286
|
+
function flattenHandlers(handlers, scoped = false, out = []) {
|
|
248
287
|
for (const h of handlers ?? []) {
|
|
249
288
|
if (h.handler === "subroute") {
|
|
250
|
-
for (const r of h.routes ?? [])
|
|
289
|
+
for (const r of h.routes ?? []) {
|
|
290
|
+
flattenHandlers(r.handle, scoped || (r.match?.length ?? 0) > 0, out);
|
|
291
|
+
}
|
|
251
292
|
} else {
|
|
252
|
-
out.push(h);
|
|
293
|
+
out.push({ h, scoped });
|
|
253
294
|
}
|
|
254
295
|
}
|
|
255
296
|
return out;
|
|
256
297
|
}
|
|
298
|
+
var UNMIGRATABLE_HANDLERS = {
|
|
299
|
+
php_fastcgi: "a PHP-FPM site (php_fastcgi)",
|
|
300
|
+
fastcgi: "a FastCGI backend",
|
|
301
|
+
static_response: "a static inline response (respond)",
|
|
302
|
+
templates: "Caddy templates",
|
|
303
|
+
encode: "",
|
|
304
|
+
headers: "",
|
|
305
|
+
authentication: "Caddy authentication",
|
|
306
|
+
rewrite: "",
|
|
307
|
+
acme_server: "an embedded ACME server"
|
|
308
|
+
};
|
|
257
309
|
function targetFromRoute(route) {
|
|
258
310
|
const flat = flattenHandlers(route.handle);
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
|
|
311
|
+
const proxies = flat.filter((f) => f.h.handler === "reverse_proxy");
|
|
312
|
+
const chosen = proxies.find((f) => !f.scoped) ?? proxies[0];
|
|
313
|
+
const proxy = chosen?.h;
|
|
314
|
+
if (proxy?.transport?.protocol === "fastcgi") {
|
|
315
|
+
return { note: "serves a PHP-FPM/FastCGI site (php_fastcgi) \u2014 not an HTTP upstream" };
|
|
316
|
+
}
|
|
317
|
+
const pathScopedOnly = Boolean(chosen?.scoped);
|
|
318
|
+
const hasScopedSibling = proxies.some((f) => f.scoped);
|
|
319
|
+
const dials = (proxy?.upstreams ?? []).map((u) => u.dial).filter((d) => Boolean(d));
|
|
320
|
+
if (dials.length > 0) {
|
|
321
|
+
const dial = dials[0];
|
|
322
|
+
if (/^unix\//i.test(dial)) {
|
|
323
|
+
return { note: `proxies to the unix socket "${dial}", which needs a manual upstream` };
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
target: { kind: "proxy", url: /^https?:\/\//.test(dial) ? dial : `http://${dial}` },
|
|
327
|
+
extraUpstreams: dials.length - 1,
|
|
328
|
+
pathScopedOnly,
|
|
329
|
+
hasScopedSibling
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const root = flat.find((f) => f.h.handler === "vars" && typeof f.h.root === "string")?.h.root;
|
|
333
|
+
if (root && flat.some((f) => f.h.handler === "file_server")) {
|
|
334
|
+
return { target: { kind: "static", root } };
|
|
335
|
+
}
|
|
336
|
+
for (const { h } of flat) {
|
|
337
|
+
const label = h.handler ? UNMIGRATABLE_HANDLERS[h.handler] : void 0;
|
|
338
|
+
if (label) return { note: `serves ${label}` };
|
|
339
|
+
}
|
|
340
|
+
return {};
|
|
264
341
|
}
|
|
265
342
|
function parseCaddyJson(raw) {
|
|
266
343
|
let cfg;
|
|
@@ -272,17 +349,47 @@ function parseCaddyJson(raw) {
|
|
|
272
349
|
const servers = cfg.apps?.http?.servers;
|
|
273
350
|
if (!servers) return null;
|
|
274
351
|
const sites = [];
|
|
352
|
+
const warnings = [];
|
|
275
353
|
for (const srv of Object.values(servers)) {
|
|
276
354
|
const ssl = (srv.listen ?? []).some((l) => l.endsWith(":443"));
|
|
277
355
|
for (const route of srv.routes ?? []) {
|
|
278
356
|
const hosts = (route.match ?? []).flatMap((m) => m.host ?? []).filter((h) => h && h !== "*");
|
|
279
357
|
if (hosts.length === 0) continue;
|
|
280
|
-
const
|
|
281
|
-
if (
|
|
282
|
-
|
|
358
|
+
const outcome = targetFromRoute(route);
|
|
359
|
+
if (outcome.note) {
|
|
360
|
+
warnings.push(`caddy: ${hosts.join(", ")} ${outcome.note} \u2014 re-add it manually`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (!outcome.target) continue;
|
|
364
|
+
if (outcome.extraUpstreams) {
|
|
365
|
+
warnings.push(
|
|
366
|
+
`caddy: ${hosts.join(", ")} load-balances across ${outcome.extraUpstreams + 1} upstreams \u2014 only the first is migrated`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (outcome.hasScopedSibling || (route.match ?? []).some((m) => m.path?.length)) {
|
|
370
|
+
warnings.push(
|
|
371
|
+
`caddy: ${hosts.join(", ")} routes some paths to other upstreams \u2014 only the catch-all is migrated; re-add path rules manually`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (outcome.pathScopedOnly) {
|
|
375
|
+
warnings.push(
|
|
376
|
+
`caddy: ${hosts.join(", ")} has no catch-all upstream \u2014 used its path-scoped one; verify the target`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
sites.push({ serverNames: hosts, ssl, target: outcome.target, source: "caddy (adapt)" });
|
|
283
380
|
}
|
|
284
381
|
}
|
|
285
|
-
|
|
382
|
+
const { kept, dropped } = collapseByHost(
|
|
383
|
+
sites,
|
|
384
|
+
(s) => s.serverNames,
|
|
385
|
+
(s) => s.ssl ? 1 : 0
|
|
386
|
+
);
|
|
387
|
+
for (const d of dropped) {
|
|
388
|
+
warnings.push(
|
|
389
|
+
`caddy: ${d.serverNames.join(", ")} had a second (non-TLS) block \u2014 kept the TLS one`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return { sites: kept, warnings };
|
|
286
393
|
}
|
|
287
394
|
async function loadCaddyfile(executor) {
|
|
288
395
|
for (const p of CADDYFILE_PATHS) {
|
|
@@ -326,7 +433,7 @@ async function scanCaddy(executor) {
|
|
|
326
433
|
const json = await loadCaddyJson(executor);
|
|
327
434
|
if (json) {
|
|
328
435
|
const parsed = parseCaddyJson(json);
|
|
329
|
-
if (parsed && parsed.sites.length > 0) {
|
|
436
|
+
if (parsed && (parsed.sites.length > 0 || parsed.warnings.length > 0)) {
|
|
330
437
|
return { proxy: "caddy", sites: parsed.sites, warnings: parsed.warnings };
|
|
331
438
|
}
|
|
332
439
|
}
|
|
@@ -356,19 +463,44 @@ async function scanCaddyText(executor) {
|
|
|
356
463
|
warnings.push(`caddy: skipped a block with no usable address (${header.slice(0, 40)})`);
|
|
357
464
|
continue;
|
|
358
465
|
}
|
|
359
|
-
const
|
|
466
|
+
const inlineProxy = body.match(/(?:^|\s)reverse_proxy\s+([^\n{]+)/);
|
|
467
|
+
const blockProxy = body.match(/(?:^|\s)reverse_proxy\s*\{([\s\S]*?)\}/);
|
|
468
|
+
const blockTo = blockProxy?.[1].match(/(?:^|\n)\s*to\s+([^\n]+)/);
|
|
360
469
|
const rootMatch = body.match(/(?:^|\s)root\s+\*?\s*([^\n\s]+)/);
|
|
361
470
|
const tlsMatch = body.match(/(?:^|\s)tls\s+(\S+)\s+(\S+)/);
|
|
471
|
+
const upstreams = (inlineProxy?.[1] ?? blockTo?.[1] ?? "").trim().split(/\s+/).filter(Boolean);
|
|
472
|
+
const host = addrs[0].host;
|
|
362
473
|
let target;
|
|
363
|
-
if (
|
|
364
|
-
const upstream =
|
|
474
|
+
if (upstreams.length > 0) {
|
|
475
|
+
const upstream = upstreams[0];
|
|
476
|
+
if (/^unix\//i.test(upstream)) {
|
|
477
|
+
warnings.push(
|
|
478
|
+
`caddy: ${host} proxies to the unix socket "${upstream}" \u2014 needs a manual upstream, skipped`
|
|
479
|
+
);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (upstreams.length > 1) {
|
|
483
|
+
warnings.push(
|
|
484
|
+
`caddy: ${host} load-balances across ${upstreams.length} upstreams \u2014 only the first is migrated`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
365
487
|
target = { kind: "proxy", url: /^https?:\/\//.test(upstream) ? upstream : `http://${upstream}` };
|
|
488
|
+
} else if (/(?:^|\s)php_fastcgi\s/.test(body)) {
|
|
489
|
+
warnings.push(
|
|
490
|
+
`caddy: ${host} serves a PHP-FPM site (php_fastcgi) \u2014 not migratable as a static root; re-add it manually`
|
|
491
|
+
);
|
|
492
|
+
continue;
|
|
366
493
|
} else if (rootMatch) {
|
|
367
494
|
target = { kind: "static", root: rootMatch[1] };
|
|
368
495
|
} else {
|
|
369
|
-
warnings.push(`caddy: ${
|
|
496
|
+
warnings.push(`caddy: ${host} has no reverse_proxy or root \u2014 skipped`);
|
|
370
497
|
continue;
|
|
371
498
|
}
|
|
499
|
+
if (/(?:^|\s)(handle_path|handle)\s+\//.test(body)) {
|
|
500
|
+
warnings.push(
|
|
501
|
+
`caddy: ${host} routes by path (handle/handle_path) \u2014 only one upstream is migrated; re-add path rules manually`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
372
504
|
const site = {
|
|
373
505
|
serverNames: addrs.map((a) => a.host),
|
|
374
506
|
ssl: addrs.some((a) => a.ssl),
|
|
@@ -380,7 +512,17 @@ async function scanCaddyText(executor) {
|
|
|
380
512
|
}
|
|
381
513
|
sites.push(site);
|
|
382
514
|
}
|
|
383
|
-
|
|
515
|
+
const { kept, dropped } = collapseByHost(
|
|
516
|
+
sites,
|
|
517
|
+
(s) => s.serverNames,
|
|
518
|
+
(s) => s.ssl ? 1 : 0
|
|
519
|
+
);
|
|
520
|
+
for (const d of dropped) {
|
|
521
|
+
warnings.push(
|
|
522
|
+
`caddy: ${d.serverNames.join(", ")} had a second (non-TLS) block \u2014 kept the TLS one`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
return { proxy: "caddy", sites: kept, warnings };
|
|
384
526
|
}
|
|
385
527
|
|
|
386
528
|
// ../../packages/adapters/src/system/proxy/import/apache.ts
|
|
@@ -418,24 +560,51 @@ function directiveAll(body, name) {
|
|
|
418
560
|
(m) => m[1].trim()
|
|
419
561
|
);
|
|
420
562
|
}
|
|
563
|
+
function isHttpsRedirectOnly(body) {
|
|
564
|
+
const hasProxy = /(?:^|\n)\s*ProxyPass\s+\S+\s+(?!!)\S/i.test(body);
|
|
565
|
+
if (hasProxy) return false;
|
|
566
|
+
const redirects = [
|
|
567
|
+
...body.matchAll(/(?:^|\n)\s*Redirect(?:Permanent|Match)?\s+(?:\d{3}\s+|permanent\s+|temp\s+)?\S*\s*(\S+)/gi),
|
|
568
|
+
...body.matchAll(/(?:^|\n)\s*RewriteRule\s+\S+\s+(\S+)\s*\[[^\]]*R=?3\d\d[^\]]*\]/gi)
|
|
569
|
+
].map((m) => m[1]);
|
|
570
|
+
return redirects.length > 0 && redirects.some((t) => /^https:\/\//i.test(t));
|
|
571
|
+
}
|
|
572
|
+
function hostOnly(name) {
|
|
573
|
+
return name.trim().replace(/^https?:\/\//i, "").replace(/:\d+$/, "");
|
|
574
|
+
}
|
|
421
575
|
function parseVhost(body, portHint) {
|
|
422
576
|
const serverName = directive(body, "ServerName")?.split(/\s+/)[0];
|
|
423
577
|
const aliases = directiveAll(body, "ServerAlias").flatMap((line) => line.split(/\s+/));
|
|
424
|
-
const names = [serverName, ...aliases].filter((n) => Boolean(n));
|
|
578
|
+
const names = [serverName, ...aliases].filter((n) => Boolean(n)).map(hostOnly).filter(Boolean);
|
|
425
579
|
if (names.length === 0) {
|
|
426
580
|
return { warning: "apache: skipped a VirtualHost with no ServerName" };
|
|
427
581
|
}
|
|
582
|
+
if (isHttpsRedirectOnly(body)) return null;
|
|
428
583
|
const routes = [...body.matchAll(/(?:^|\n)\s*ProxyPass\s+(\S+)\s+(\S+)/gi)].map((m) => ({ path: m[1], url: m[2] })).filter((p) => p.url && p.url !== "!");
|
|
429
584
|
const docRoot = directive(body, "DocumentRoot")?.replace(/^["']|["']$/g, "");
|
|
585
|
+
const fcgiMatch = /(?:^|\n)\s*ProxyPassMatch\s+[^\n]*(fcgi:|php-fpm|php\d*-fpm|\.sock)/i.test(body);
|
|
430
586
|
const certPath = directive(body, "SSLCertificateFile");
|
|
431
587
|
const keyPath = directive(body, "SSLCertificateKeyFile");
|
|
432
588
|
const ssl = /:443/.test(portHint) || /SSLEngine\s+on/i.test(body) || Boolean(certPath);
|
|
433
589
|
let target;
|
|
434
590
|
if (routes.length > 0) {
|
|
435
591
|
const primary = routes.find((r) => r.path === "/") ?? routes[0];
|
|
592
|
+
if (/^balancer:\/\//i.test(primary.url)) {
|
|
593
|
+
return {
|
|
594
|
+
warning: `apache: ${names[0]} proxies to an mod_proxy_balancer cluster (${primary.url}) \u2014 re-add its members manually`
|
|
595
|
+
};
|
|
596
|
+
}
|
|
436
597
|
target = { kind: "proxy", url: primary.url };
|
|
598
|
+
} else if (fcgiMatch) {
|
|
599
|
+
return {
|
|
600
|
+
warning: `apache: ${names[0]} is a PHP-FPM site (ProxyPassMatch \u2192 FastCGI) \u2014 not migratable as a static root; re-add it manually`
|
|
601
|
+
};
|
|
437
602
|
} else if (docRoot) {
|
|
438
603
|
target = { kind: "static", root: docRoot };
|
|
604
|
+
} else if (/(?:^|\n)\s*ProxyPassMatch\s/i.test(body)) {
|
|
605
|
+
return {
|
|
606
|
+
warning: `apache: ${names[0]} uses ProxyPassMatch (regex proxying) \u2014 re-add it manually`
|
|
607
|
+
};
|
|
439
608
|
} else {
|
|
440
609
|
return { warning: `apache: ${names[0]} has neither ProxyPass nor DocumentRoot \u2014 skipped` };
|
|
441
610
|
}
|
|
@@ -444,6 +613,11 @@ function parseVhost(body, portHint) {
|
|
|
444
613
|
if (certPath && keyPath) site.tls = { certPath, keyPath };
|
|
445
614
|
return site;
|
|
446
615
|
}
|
|
616
|
+
function redirectVhostNames(body) {
|
|
617
|
+
const serverName = directive(body, "ServerName")?.split(/\s+/)[0];
|
|
618
|
+
const aliases = directiveAll(body, "ServerAlias").flatMap((l) => l.split(/\s+/));
|
|
619
|
+
return [serverName, ...aliases].filter((n) => Boolean(n)).map(hostOnly).filter(Boolean);
|
|
620
|
+
}
|
|
447
621
|
function vhostBlocks(text) {
|
|
448
622
|
const out = [];
|
|
449
623
|
const re = /<VirtualHost([^>]*)>([\s\S]*?)<\/VirtualHost>/gi;
|
|
@@ -463,15 +637,44 @@ async function scanApache(executor) {
|
|
|
463
637
|
const text = stripComments(raw);
|
|
464
638
|
const blocks = vhostBlocks(text);
|
|
465
639
|
if (blocks.length === 0) warnings.push("apache: no <VirtualHost> blocks found");
|
|
640
|
+
const redirectOnlyHosts = [];
|
|
466
641
|
for (const { args, body } of blocks) {
|
|
467
642
|
const parsed = parseVhost(body, args);
|
|
643
|
+
if (parsed === null) {
|
|
644
|
+
redirectOnlyHosts.push(redirectVhostNames(body));
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
468
647
|
if ("warning" in parsed) warnings.push(parsed.warning);
|
|
469
648
|
else sites.push(parsed);
|
|
470
649
|
}
|
|
471
|
-
|
|
650
|
+
const served = new Set(sites.flatMap((s) => s.serverNames));
|
|
651
|
+
for (const names of redirectOnlyHosts) {
|
|
652
|
+
const orphaned = names.filter((n) => !served.has(n));
|
|
653
|
+
if (orphaned.length > 0) {
|
|
654
|
+
warnings.push(
|
|
655
|
+
`apache: ${orphaned.join(", ")} only redirected to HTTPS and has no TLS VirtualHost here \u2014 nothing to serve it after migration`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
const { kept, dropped } = collapseByHost(
|
|
660
|
+
sites,
|
|
661
|
+
(s) => s.serverNames,
|
|
662
|
+
(s) => (s.ssl ? 2 : 0) + (s.target.kind === "proxy" ? 1 : 0)
|
|
663
|
+
);
|
|
664
|
+
for (const d of dropped) {
|
|
665
|
+
warnings.push(
|
|
666
|
+
`apache: ${d.serverNames.join(", ")} had a second VirtualHost (${d.ssl ? "TLS" : "plain HTTP"}, ${d.target.kind}) \u2014 kept the TLS/proxy one`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
return { proxy: "apache", sites: kept, warnings };
|
|
472
670
|
}
|
|
473
671
|
|
|
474
672
|
// ../../packages/adapters/src/system/proxy/import/traefik.ts
|
|
673
|
+
function lowerKeys(labels) {
|
|
674
|
+
const out = {};
|
|
675
|
+
for (const [k, v] of Object.entries(labels)) out[k.toLowerCase()] = v;
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
475
678
|
function extractHosts(rule) {
|
|
476
679
|
const hosts = [];
|
|
477
680
|
for (const call of rule.matchAll(/\bHost\(([^)]*)\)/gi)) {
|
|
@@ -480,7 +683,7 @@ function extractHosts(rule) {
|
|
|
480
683
|
if (h) hosts.push(h);
|
|
481
684
|
}
|
|
482
685
|
}
|
|
483
|
-
return hosts;
|
|
686
|
+
return [...new Set(hosts)];
|
|
484
687
|
}
|
|
485
688
|
function extraMatchers(rule) {
|
|
486
689
|
const found = /* @__PURE__ */ new Set();
|
|
@@ -491,23 +694,59 @@ function extraMatchers(rule) {
|
|
|
491
694
|
}
|
|
492
695
|
return [...found];
|
|
493
696
|
}
|
|
697
|
+
function collectDefinitions(containers) {
|
|
698
|
+
const services = /* @__PURE__ */ new Map();
|
|
699
|
+
const redirectMiddlewares = /* @__PURE__ */ new Set();
|
|
700
|
+
const streamRouters = [];
|
|
701
|
+
let fileProvider = false;
|
|
702
|
+
let exposedByDefault = true;
|
|
703
|
+
for (const c of containers) {
|
|
704
|
+
const labels = lowerKeys(c.labels);
|
|
705
|
+
for (const [k, v] of Object.entries(labels)) {
|
|
706
|
+
let m = k.match(/^traefik\.http\.services\.([^.]+)\.loadbalancer\.server\.(port|scheme)$/);
|
|
707
|
+
if (m) {
|
|
708
|
+
const def = services.get(m[1]) ?? {};
|
|
709
|
+
if (m[2] === "port") def.port = v;
|
|
710
|
+
else def.scheme = v;
|
|
711
|
+
services.set(m[1], def);
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
m = k.match(/^traefik\.http\.middlewares\.([^.]+)\.redirectscheme\./);
|
|
715
|
+
if (m) {
|
|
716
|
+
redirectMiddlewares.add(m[1]);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
m = k.match(/^traefik\.(tcp|udp)\.routers\.([^.]+)\.rule$/);
|
|
720
|
+
if (m) streamRouters.push(`${m[1]} router "${m[2]}"`);
|
|
721
|
+
}
|
|
722
|
+
const cmd = c.cmd ?? "";
|
|
723
|
+
if (/--?providers\.file/i.test(cmd)) fileProvider = true;
|
|
724
|
+
if (/--?providers\.docker\.exposedbydefault[= ]?false/i.test(cmd)) exposedByDefault = false;
|
|
725
|
+
}
|
|
726
|
+
return { services, redirectMiddlewares, streamRouters, fileProvider, exposedByDefault };
|
|
727
|
+
}
|
|
728
|
+
function resolveIp(c, labels) {
|
|
729
|
+
const wanted = labels["traefik.docker.network"];
|
|
730
|
+
if (wanted && c.networks?.[wanted]) return c.networks[wanted];
|
|
731
|
+
if (c.ip) return c.ip;
|
|
732
|
+
const first = Object.values(c.networks ?? {}).find(Boolean);
|
|
733
|
+
return first || void 0;
|
|
734
|
+
}
|
|
494
735
|
function parseTraefikLabels(containers) {
|
|
495
|
-
const sites = [];
|
|
496
736
|
const warnings = [];
|
|
737
|
+
const defs = collectDefinitions(containers);
|
|
738
|
+
const candidates = [];
|
|
497
739
|
for (const c of containers) {
|
|
498
|
-
|
|
740
|
+
const labels = lowerKeys(c.labels);
|
|
741
|
+
if (labels["traefik.enable"] === "false") continue;
|
|
742
|
+
if (!defs.exposedByDefault && labels["traefik.enable"] !== "true") continue;
|
|
499
743
|
const routers = /* @__PURE__ */ new Map();
|
|
500
|
-
const
|
|
501
|
-
for (const [key, val] of Object.entries(c.labels)) {
|
|
744
|
+
for (const [key, val] of Object.entries(labels)) {
|
|
502
745
|
const r = key.match(/^traefik\.http\.routers\.([^.]+)\.(.+)$/);
|
|
503
|
-
if (r)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
const s = key.match(/^traefik\.http\.services\.([^.]+)\.loadbalancer\.server\.port$/);
|
|
510
|
-
if (s) servicePorts.set(s[1], val);
|
|
746
|
+
if (!r) continue;
|
|
747
|
+
const bag = routers.get(r[1]) ?? {};
|
|
748
|
+
bag[r[2]] = val;
|
|
749
|
+
routers.set(r[1], bag);
|
|
511
750
|
}
|
|
512
751
|
for (const [rname, props] of routers) {
|
|
513
752
|
const rule = props.rule;
|
|
@@ -519,47 +758,92 @@ function parseTraefikLabels(containers) {
|
|
|
519
758
|
);
|
|
520
759
|
continue;
|
|
521
760
|
}
|
|
522
|
-
|
|
761
|
+
const ip = resolveIp(c, labels);
|
|
762
|
+
if (!ip) {
|
|
523
763
|
warnings.push(
|
|
524
764
|
`traefik: ${hosts.join(", ")} \u2014 couldn't resolve container ${c.name}'s IP; re-add the upstream manually`
|
|
525
765
|
);
|
|
526
766
|
continue;
|
|
527
767
|
}
|
|
528
|
-
const svc = props.service;
|
|
529
|
-
const
|
|
768
|
+
const svc = props.service?.replace(/@.*$/, "");
|
|
769
|
+
const def = (svc ? defs.services.get(svc) : void 0) ?? (defs.services.size === 1 ? [...defs.services.values()][0] : void 0);
|
|
770
|
+
const onlyExposed = c.exposedPorts?.length === 1 ? c.exposedPorts[0].split("/")[0] : void 0;
|
|
771
|
+
const rawPort = def?.port ?? onlyExposed;
|
|
530
772
|
const port = /^\d{1,5}$/.test(String(rawPort)) ? String(rawPort) : "80";
|
|
531
|
-
const
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
serverNames: hosts,
|
|
544
|
-
ssl,
|
|
545
|
-
target: { kind: "proxy", url },
|
|
546
|
-
source: `traefik container ${c.name}`
|
|
773
|
+
const scheme = def?.scheme?.toLowerCase() === "https" ? "https" : "http";
|
|
774
|
+
const middlewares = (props.middlewares ?? "").split(",").map((m) => m.trim().replace(/@.*$/, "")).filter(Boolean);
|
|
775
|
+
candidates.push({
|
|
776
|
+
hosts,
|
|
777
|
+
ssl: Object.keys(props).some((p) => p === "tls" || p.startsWith("tls.")),
|
|
778
|
+
url: `${scheme}://${ip}:${port}`,
|
|
779
|
+
container: c.name,
|
|
780
|
+
router: rname,
|
|
781
|
+
rule,
|
|
782
|
+
middlewares,
|
|
783
|
+
redirectOnly: middlewares.length > 0 && middlewares.every((m) => defs.redirectMiddlewares.has(m)),
|
|
784
|
+
portGuessed: rawPort === void 0
|
|
547
785
|
});
|
|
548
786
|
}
|
|
549
787
|
}
|
|
550
|
-
|
|
788
|
+
const { kept: sites, dropped } = collapseByHost(
|
|
789
|
+
candidates,
|
|
790
|
+
(c) => c.hosts,
|
|
791
|
+
(c) => c.redirectOnly ? 0 : c.ssl ? 2 : 1
|
|
792
|
+
);
|
|
793
|
+
for (const cand of sites) {
|
|
794
|
+
const extras = extraMatchers(cand.rule);
|
|
795
|
+
if (extras.length > 0) {
|
|
796
|
+
warnings.push(
|
|
797
|
+
`traefik: ${cand.hosts.join(", ")} also matches ${extras.join(", ")} \u2014 only the Host() part is migrated; re-add path/header rules manually`
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
const carried = cand.middlewares.filter((m) => !defs.redirectMiddlewares.has(m));
|
|
801
|
+
if (carried.length > 0) {
|
|
802
|
+
warnings.push(
|
|
803
|
+
`traefik: ${cand.hosts.join(", ")} uses middleware(s) "${carried.join(", ")}" \u2014 not migrated`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
if (cand.portGuessed) {
|
|
807
|
+
warnings.push(
|
|
808
|
+
`traefik: ${cand.hosts.join(", ")} \u2014 no service port label and no single exposed port on ${cand.container}; assumed :80, check the upstream`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
for (const d of dropped) {
|
|
813
|
+
warnings.push(
|
|
814
|
+
`traefik: ${d.hosts.join(", ")} router "${d.router}" is an http\u2192https redirect \u2014 Openship's edge does that itself (nothing to migrate)`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
for (const s of defs.streamRouters) {
|
|
818
|
+
warnings.push(`traefik: ${s} can't migrate \u2014 Openship's edge routes HTTP(S), not raw TCP/UDP`);
|
|
819
|
+
}
|
|
820
|
+
if (defs.fileProvider) {
|
|
821
|
+
warnings.push(
|
|
822
|
+
"traefik: a file provider is configured \u2014 routes defined in its YAML/TOML files are NOT read here, only container labels. Re-add those domains manually."
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
return {
|
|
826
|
+
proxy: "traefik",
|
|
827
|
+
sites: sites.map((cand) => ({
|
|
828
|
+
serverNames: cand.hosts,
|
|
829
|
+
ssl: cand.ssl,
|
|
830
|
+
target: { kind: "proxy", url: cand.url },
|
|
831
|
+
source: `traefik container ${cand.container}`
|
|
832
|
+
})),
|
|
833
|
+
warnings
|
|
834
|
+
};
|
|
551
835
|
}
|
|
552
836
|
async function scanTraefik(executor) {
|
|
553
837
|
const out = await tryExec(
|
|
554
838
|
executor,
|
|
555
|
-
|
|
839
|
+
`docker ps -q 2>/dev/null | xargs -r docker inspect --format '{{.Name}} {{json .Config.Labels}} {{range $n, $v := .NetworkSettings.Networks}}{{$n}}={{$v.IPAddress}} {{end}} {{range $p, $v := .Config.ExposedPorts}}{{$p}} {{end}} {{if .Config.Cmd}}{{join .Config.Cmd " "}}{{end}}' 2>/dev/null`
|
|
556
840
|
);
|
|
557
841
|
if (!out) {
|
|
558
842
|
return { proxy: "traefik", sites: [], warnings: ["traefik: couldn't read container labels via docker inspect"] };
|
|
559
843
|
}
|
|
560
844
|
const containers = [];
|
|
561
845
|
for (const line of out.split("\n")) {
|
|
562
|
-
const [rawName, rawLabels,
|
|
846
|
+
const [rawName, rawLabels, rawNets, rawPorts, rawCmd] = line.split(" ");
|
|
563
847
|
if (!rawName || !rawLabels) continue;
|
|
564
848
|
let labels;
|
|
565
849
|
try {
|
|
@@ -567,9 +851,30 @@ async function scanTraefik(executor) {
|
|
|
567
851
|
} catch {
|
|
568
852
|
continue;
|
|
569
853
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
854
|
+
const networks = {};
|
|
855
|
+
let bareIp;
|
|
856
|
+
for (const pair of (rawNets ?? "").trim().split(/\s+/)) {
|
|
857
|
+
if (!pair) continue;
|
|
858
|
+
const eq = pair.indexOf("=");
|
|
859
|
+
if (eq > 0) {
|
|
860
|
+
const ip = pair.slice(eq + 1);
|
|
861
|
+
if (ip) networks[pair.slice(0, eq)] = ip;
|
|
862
|
+
} else if (!bareIp) {
|
|
863
|
+
bareIp = pair;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const exposedPorts = (rawPorts ?? "").trim().split(/\s+/).filter(Boolean);
|
|
867
|
+
const hasTraefikLabels = Object.keys(labels).some((k) => k.toLowerCase().startsWith("traefik."));
|
|
868
|
+
const looksLikeTraefik = /traefik/i.test(rawCmd ?? "") || /--?providers\./i.test(rawCmd ?? "");
|
|
869
|
+
if (!hasTraefikLabels && !looksLikeTraefik) continue;
|
|
870
|
+
containers.push({
|
|
871
|
+
name: rawName.replace(/^\//, ""),
|
|
872
|
+
labels,
|
|
873
|
+
ip: Object.values(networks)[0] ?? bareIp,
|
|
874
|
+
networks,
|
|
875
|
+
exposedPorts,
|
|
876
|
+
cmd: rawCmd ?? void 0
|
|
877
|
+
});
|
|
573
878
|
}
|
|
574
879
|
return parseTraefikLabels(containers);
|
|
575
880
|
}
|
|
@@ -1842,7 +2147,7 @@ async function runningImage(executor, container) {
|
|
|
1842
2147
|
return out.trim() || null;
|
|
1843
2148
|
}
|
|
1844
2149
|
async function containerEdgeProvider(executor, container, opts) {
|
|
1845
|
-
const { NginxProvider: Provider } = await import("./nginx-
|
|
2150
|
+
const { NginxProvider: Provider } = await import("./nginx-QE4DOM5J.js");
|
|
1846
2151
|
return new Provider({
|
|
1847
2152
|
...opts,
|
|
1848
2153
|
// Vhosts go to the BIND-MOUNTED HOST dir (so every host-side reader — the
|
|
@@ -1852,21 +2157,38 @@ async function containerEdgeProvider(executor, container, opts) {
|
|
|
1852
2157
|
executor: edgeContainerExecutor(executor, container),
|
|
1853
2158
|
// MUST be pinned: re-detection answers from inside the container and would
|
|
1854
2159
|
// repoint sitesDir at a host dir the edge never reads.
|
|
1855
|
-
pinPaths: true
|
|
2160
|
+
pinPaths: true,
|
|
2161
|
+
// Reload runs INSIDE the container, where the master is pid 1 — so a failed
|
|
2162
|
+
// reload must never fall back to killing it (#292).
|
|
2163
|
+
containerEdge: true
|
|
1856
2164
|
});
|
|
1857
2165
|
}
|
|
1858
2166
|
async function localContainerEdgeProvider(container, opts) {
|
|
1859
|
-
const { DockerEdgeExecutor } = await import("./docker-edge-executor-
|
|
1860
|
-
const { NginxProvider: Provider } = await import("./nginx-
|
|
2167
|
+
const { DockerEdgeExecutor } = await import("./docker-edge-executor-5I4M5QT7.js");
|
|
2168
|
+
const { NginxProvider: Provider } = await import("./nginx-QE4DOM5J.js");
|
|
1861
2169
|
return new Provider({
|
|
1862
2170
|
...opts,
|
|
1863
2171
|
paths: OPENRESTY_DEFAULT_PATHS,
|
|
1864
2172
|
executor: new DockerEdgeExecutor({ containerName: container }),
|
|
1865
2173
|
// nginx.conf + the Lua are BAKED into the image — nothing to detect, install
|
|
1866
2174
|
// or patch, and detection would answer from inside the container anyway.
|
|
1867
|
-
pinPaths: true
|
|
2175
|
+
pinPaths: true,
|
|
2176
|
+
// `docker exec`s into the edge, so the master is pid 1 there too (#292).
|
|
2177
|
+
containerEdge: true
|
|
1868
2178
|
});
|
|
1869
2179
|
}
|
|
2180
|
+
async function startEdgeContainer(executor, container, image, onLog) {
|
|
2181
|
+
await sanitizeEdgeVhosts(executor, EDGE_HOST_PATHS.sitesDir, onLog).catch(() => {
|
|
2182
|
+
});
|
|
2183
|
+
await executor.exec(`docker rm -f ${sq(container)} 2>/dev/null || true`).catch(() => {
|
|
2184
|
+
});
|
|
2185
|
+
const run = await executor.streamExec(
|
|
2186
|
+
buildEdgeRunCommand(container, image),
|
|
2187
|
+
onLog
|
|
2188
|
+
);
|
|
2189
|
+
invalidateEdgeContainer(executor);
|
|
2190
|
+
return run.code === 0;
|
|
2191
|
+
}
|
|
1870
2192
|
function buildEdgeRunCommand(container, image) {
|
|
1871
2193
|
const mounts = EDGE_CONTAINER_MOUNTS.map(
|
|
1872
2194
|
// `:z` relabels for SELinux-enforcing hosts; a no-op elsewhere.
|
|
@@ -1890,14 +2212,7 @@ async function swapEdgeImage(executor, container, from, to, opts) {
|
|
|
1890
2212
|
return { swapped: false, edgeDown: false };
|
|
1891
2213
|
}
|
|
1892
2214
|
const start = async (image) => {
|
|
1893
|
-
await executor
|
|
1894
|
-
});
|
|
1895
|
-
const run = await executor.streamExec(
|
|
1896
|
-
buildEdgeRunCommand(container, image),
|
|
1897
|
-
onLog
|
|
1898
|
-
);
|
|
1899
|
-
invalidateEdgeContainer(executor);
|
|
1900
|
-
if (run.code !== 0) return false;
|
|
2215
|
+
if (!await startEdgeContainer(executor, container, image, onLog)) return false;
|
|
1901
2216
|
const listening = await waitForPortListening(executor, 80, {
|
|
1902
2217
|
timeoutMs: opts.verifyTimeoutMs ?? 3e4
|
|
1903
2218
|
});
|
|
@@ -1920,29 +2235,6 @@ async function listDir(executor, dir) {
|
|
|
1920
2235
|
out.split("\n").map((l) => l.trim()).filter(Boolean)
|
|
1921
2236
|
);
|
|
1922
2237
|
}
|
|
1923
|
-
async function sanitizeCarriedVhosts(executor, sitesDir, onLog) {
|
|
1924
|
-
const script = [
|
|
1925
|
-
`for f in ${sq(sitesDir)}/*.conf; do`,
|
|
1926
|
-
` [ -f "$f" ] || continue;`,
|
|
1927
|
-
// A real server_name is anything that isn't the `_` wildcard.
|
|
1928
|
-
` if ! grep -qE '^[[:space:]]*server_name[[:space:]]+[^_;[:space:]]' "$f"; then`,
|
|
1929
|
-
` echo "dropped-catchall $f"; rm -f "$f"; continue;`,
|
|
1930
|
-
` fi;`,
|
|
1931
|
-
` if grep -qE '[[:space:]]default_server' "$f"; then`,
|
|
1932
|
-
` sed -i -E 's/([[:space:]]listen[^;]*)[[:space:]]+default_server/\\1/g' "$f" && echo "unset-default $f";`,
|
|
1933
|
-
` fi;`,
|
|
1934
|
-
`done`
|
|
1935
|
-
].join(" ");
|
|
1936
|
-
const out = await executor.exec(script).catch(() => "");
|
|
1937
|
-
for (const line of out.split("\n").map((l) => l.trim()).filter(Boolean)) {
|
|
1938
|
-
const [action, file] = [line.slice(0, line.indexOf(" ")), line.slice(line.indexOf(" ") + 1)];
|
|
1939
|
-
if (action === "dropped-catchall") {
|
|
1940
|
-
onLog(log4(`Dropped the bare edge's catch-all ${file} \u2014 the edge image provides it.`, "warn"));
|
|
1941
|
-
} else if (action === "unset-default") {
|
|
1942
|
-
onLog(log4(`Removed default_server from ${file} \u2014 the edge image owns it.`, "warn"));
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
2238
|
async function ensureContainerEdge(executor, opts) {
|
|
1947
2239
|
const { onLog } = opts;
|
|
1948
2240
|
const container = opts.container?.trim() || EDGE_CONTAINER_NAME;
|
|
@@ -1989,7 +2281,6 @@ async function ensureContainerEdge(executor, opts) {
|
|
|
1989
2281
|
beforeCarry = await listDir(executor, sitesTarget);
|
|
1990
2282
|
await executor.exec(`cp -a ${sq(`${barePaths.sitesDir}/.`)} ${sq(`${sitesTarget}/`)} 2>/dev/null || true`).catch(() => {
|
|
1991
2283
|
});
|
|
1992
|
-
await sanitizeCarriedVhosts(executor, sitesTarget, onLog);
|
|
1993
2284
|
}
|
|
1994
2285
|
}
|
|
1995
2286
|
const restoreBare = async () => {
|
|
@@ -2015,15 +2306,10 @@ async function ensureContainerEdge(executor, opts) {
|
|
|
2015
2306
|
await executor.exec("systemctl reset-failed openresty 2>/dev/null || true").catch(() => {
|
|
2016
2307
|
});
|
|
2017
2308
|
}
|
|
2018
|
-
await executor.exec(`docker rm -f ${sq(container)} 2>/dev/null || true`).catch(() => {
|
|
2019
|
-
});
|
|
2020
2309
|
onLog(log4("Starting the edge container..."));
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
);
|
|
2025
|
-
invalidateEdgeContainer(executor);
|
|
2026
|
-
if (run.code !== 0) throw new Error("the edge container failed to start");
|
|
2310
|
+
if (!await startEdgeContainer(executor, container, image, onLog)) {
|
|
2311
|
+
throw new Error("the edge container failed to start");
|
|
2312
|
+
}
|
|
2027
2313
|
await executor.exec(containerCommand(container, "openresty -t"));
|
|
2028
2314
|
const listening = await waitForPortListening(executor, 80, {
|
|
2029
2315
|
timeoutMs: opts.verifyTimeoutMs ?? 3e4
|