openship 0.6.6 → 0.6.8

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.
Files changed (72) hide show
  1. package/dist/bare-FA66N7GC.js +29 -0
  2. package/dist/{chunk-77QDF6UD.js → chunk-3MP6ACKM.js} +587 -156
  3. package/dist/{chunk-YIMOCZBZ.js → chunk-5DJOB7IX.js} +3 -3
  4. package/dist/{chunk-WVAML4H6.js → chunk-5I3COAFH.js} +274 -65
  5. package/dist/{chunk-5DMWSTOM.js → chunk-5UXHT6ED.js} +940 -224
  6. package/dist/{chunk-LUKYPVJ4.js → chunk-6GU6JSK6.js} +329 -466
  7. package/dist/{chunk-RSR7PFIQ.js → chunk-7PEXM2UI.js} +213 -45
  8. package/dist/{chunk-VTGPEC6B.js → chunk-7YBWXEM6.js} +6 -4
  9. package/dist/chunk-AHJYP7DG.js +938 -0
  10. package/dist/chunk-BO6CY4V2.js +265 -0
  11. package/dist/{chunk-FE7ZZQSE.js → chunk-D3OEMXYP.js} +3 -3
  12. package/dist/chunk-D6HU4VUZ.js +249 -0
  13. package/dist/{chunk-LFE5KQ7Y.js → chunk-ENKJ7XIF.js} +152 -57
  14. package/dist/{chunk-YMMQRE5J.js → chunk-GNOMEMNI.js} +1 -1
  15. package/dist/{chunk-QVW76WDL.js → chunk-IAMOUDHY.js} +190 -64
  16. package/dist/{chunk-OBPKYXQL.js → chunk-MUWVC5R4.js} +219 -58
  17. package/dist/chunk-N5YZMO33.js +111 -0
  18. package/dist/{chunk-A4GXYE4L.js → chunk-NIYD7MD2.js} +206 -52
  19. package/dist/{chunk-UVNMQM7W.js → chunk-NPSRGIHV.js} +8 -6
  20. package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
  21. package/dist/{chunk-CBLD2HS5.js → chunk-NUT6HKK6.js} +8 -5
  22. package/dist/{chunk-ZFJXPCQP.js → chunk-P7RCYELG.js} +4 -3
  23. package/dist/chunk-R4VDPFLS.js +106 -0
  24. package/dist/{chunk-HKRQXPTI.js → chunk-R5DC5PEF.js} +1 -1
  25. package/dist/{chunk-NK5N5LXN.js → chunk-RCMERVBL.js} +24 -9
  26. package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
  27. package/dist/chunk-SK4FFCFR.js +70 -0
  28. package/dist/{chunk-4I2BM2DT.js → chunk-T46BQOLY.js} +3 -3
  29. package/dist/{chunk-6FH4AXVY.js → chunk-T6DVUX4F.js} +1 -1
  30. package/dist/{chunk-PCPVTH6M.js → chunk-TAZZZKYO.js} +6 -4
  31. package/dist/{chunk-MLYSEZQB.js → chunk-UAWY6X7V.js} +43 -72
  32. package/dist/{chunk-Q7WGTQQ5.js → chunk-VAI5VOOI.js} +1 -1
  33. package/dist/{chunk-UJ6JZM65.js → chunk-WEZFTL4V.js} +1325 -513
  34. package/dist/{chunk-2YM3NI2E.js → chunk-X53JXRS4.js} +38 -44
  35. package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
  36. package/dist/cloud-REV3DPJH.js +42 -0
  37. package/dist/{detect-OIE5V3VZ.js → detect-BBFT5GEU.js} +9 -6
  38. package/dist/{docker-VTR2GK5Y.js → docker-EG3QSE6I.js} +11 -8
  39. package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-E5TOTLMR.js} +8 -6
  40. package/dist/edge-import-YJVJUGYC.js +56 -0
  41. package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-QUW4BM7N.js} +15 -12
  42. package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-FOEFQ3PI.js} +15 -10
  43. package/dist/{executor-7LXXZPBR.js → executor-NTWDJ3DB.js} +8 -6
  44. package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
  45. package/dist/index.js +198 -76
  46. package/dist/{local-executor-WOFZD72X.js → local-executor-OQ2GRN7K.js} +3 -2
  47. package/dist/{nginx-KUFMLMAC.js → nginx-4U4C2XUS.js} +11 -8
  48. package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
  49. package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-LLWT5XQW.js} +8 -5
  50. package/dist/{privilege-I4W3ZNZK.js → privilege-MNDVMDST.js} +6 -5
  51. package/dist/server/index.js +40049 -30378
  52. package/dist/server/migrations/0109_service_build_args.sql +9 -0
  53. package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
  54. package/dist/server/migrations/0111_host_port_claim.sql +381 -0
  55. package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
  56. package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
  57. package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
  58. package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
  59. package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
  60. package/dist/server/migrations/meta/_journal.json +56 -0
  61. package/dist/setup-IFIMPQZJ.js +25 -0
  62. package/dist/{ssh-client-JBMA7SN4.js → ssh-client-2XWZCMGI.js} +3 -2
  63. package/dist/up-HQS7IR7H.js +61 -0
  64. package/package.json +1 -1
  65. package/dist/bare-OXJVW3AK.js +0 -25
  66. package/dist/chunk-LH2PLRQO.js +0 -434
  67. package/dist/chunk-SM26HDS2.js +0 -185
  68. package/dist/cloud-75VIT4KV.js +0 -37
  69. package/dist/edge-import-WJUB4M7F.js +0 -52
  70. package/dist/host-channel-preflight-KE6OONYK.js +0 -50
  71. package/dist/setup-XGLGUUFW.js +0 -22
  72. package/dist/up-K4BV7466.js +0 -57
@@ -9,32 +9,32 @@ import {
9
9
  readDeclaredPair,
10
10
  readMaybeInContainer,
11
11
  validateCertFor
12
- } from "./chunk-QVW76WDL.js";
12
+ } from "./chunk-IAMOUDHY.js";
13
13
  import {
14
14
  EDGE_CHALLENGE_HOST_DIR,
15
15
  EDGE_CONTAINER_MOUNTS,
16
16
  EDGE_HOST_PATHS,
17
17
  OPENRESTY_DEFAULT_PATHS
18
- } from "./chunk-RSR7PFIQ.js";
18
+ } from "./chunk-7PEXM2UI.js";
19
19
  import {
20
20
  privilegedExecutor,
21
21
  rootChecked,
22
22
  rootOrDegrade
23
- } from "./chunk-4I2BM2DT.js";
23
+ } from "./chunk-T46BQOLY.js";
24
24
  import {
25
25
  envOps,
26
26
  opScript
27
- } from "./chunk-YMMQRE5J.js";
27
+ } from "./chunk-GNOMEMNI.js";
28
28
  import {
29
29
  resolveEnvironment
30
- } from "./chunk-UVNMQM7W.js";
30
+ } from "./chunk-NPSRGIHV.js";
31
31
  import {
32
32
  sq
33
- } from "./chunk-ZFJXPCQP.js";
33
+ } from "./chunk-P7RCYELG.js";
34
34
  import {
35
- EDGE_CONTAINER_NAME,
36
35
  EdgeConflictError,
37
36
  EdgeMigrateRequested,
37
+ detectEdgeContainer,
38
38
  edgeCrashReason,
39
39
  edgeFailureReason,
40
40
  edgeIsBroken,
@@ -45,25 +45,32 @@ import {
45
45
  sanitizeEdgeVhosts,
46
46
  sq as sq2,
47
47
  stopTargetsForStatus
48
- } from "./chunk-2YM3NI2E.js";
48
+ } from "./chunk-X53JXRS4.js";
49
+ import {
50
+ waitForPortListening
51
+ } from "./chunk-BO6CY4V2.js";
49
52
  import {
53
+ EDGE_CONTAINER_NAME,
50
54
  probeExec,
51
55
  tryExec,
52
- waitForPortListening,
53
56
  withReason
54
- } from "./chunk-SM26HDS2.js";
57
+ } from "./chunk-D6HU4VUZ.js";
58
+ import {
59
+ formatDuration,
60
+ systemDebug
61
+ } from "./chunk-N5YZMO33.js";
55
62
  import {
56
63
  AppError,
57
64
  PROXY_DIRECTIVES,
58
65
  answered,
59
66
  buildEdgeImageRef,
60
67
  compareSemver,
61
- formatDuration,
68
+ isLoopbackHost,
62
69
  parseProxyValue,
63
70
  safeErrorMessage,
64
71
  sanitizeProxySettings,
65
- systemDebug
66
- } from "./chunk-UJ6JZM65.js";
72
+ shellQuote
73
+ } from "./chunk-WEZFTL4V.js";
67
74
 
68
75
  // ../../packages/adapters/src/system/managed-image.ts
69
76
  import { posix } from "path";
@@ -384,9 +391,6 @@ function collapseByHost(items, hostsOf, score) {
384
391
  }
385
392
  return { kept, dropped };
386
393
  }
387
- function sq3(value) {
388
- return `'${value.replace(/'/g, "'\\''")}'`;
389
- }
390
394
 
391
395
  // ../../packages/adapters/src/system/proxy/import/nginx.ts
392
396
  async function dumpResolvedConfig(executor, bins) {
@@ -423,8 +427,9 @@ function parseUpstreams(config) {
423
427
  let m;
424
428
  while ((m = re.exec(config)) !== null) {
425
429
  const name = m[1];
426
- const server = m[2].match(/(?:^|[\s;{])server\s+([^;\s]+)/);
427
- if (name && server?.[1]) map.set(name, server[1].trim());
430
+ const body = m[2] ?? "";
431
+ const targets = [...body.matchAll(/(?:^|[\s;{])server\s+([^;\s]+)/g)].map((server) => server[1]?.trim()).filter((target) => Boolean(target));
432
+ if (name && targets.length > 0) map.set(name, targets);
428
433
  }
429
434
  return map;
430
435
  }
@@ -437,14 +442,55 @@ function resolveProxyTarget(proxyPass, upstreams) {
437
442
  const scheme = m[1];
438
443
  const authority = m[2];
439
444
  const host = authority.replace(/:\d+$/, "");
440
- if (upstreams.has(host)) return { url: `${scheme}${upstreams.get(host)}` };
445
+ const declaredUpstream = upstreams.get(authority);
446
+ if (declaredUpstream?.[0]) return { url: `${scheme}${declaredUpstream[0]}` };
441
447
  const isIp = /^\d{1,3}(\.\d{1,3}){3}$/.test(host);
442
448
  if (host === "localhost" || host === "ip6-localhost") {
443
449
  return { url: raw.replace(/^(https?:\/\/)[^/:]+/i, "$1127.0.0.1") };
444
450
  }
445
- if (isIp || host.includes(".")) return { url: raw };
451
+ if (isIp || host.includes(".") || host.startsWith("[") && host.endsWith("]")) {
452
+ return { url: raw };
453
+ }
446
454
  return { reason: `proxy_pass host "${host}" is an undeclared upstream \u2014 not migratable` };
447
455
  }
456
+ function strictLoopbackUpstreamPorts(config) {
457
+ const normalized = stripComments(config);
458
+ const upstreams = parseUpstreams(normalized);
459
+ const ports = /* @__PURE__ */ new Set();
460
+ for (const match of normalized.matchAll(/(?:^|[;{}\s])proxy_pass\s+([^;]+);/g)) {
461
+ const raw = match[1]?.trim();
462
+ if (!raw) continue;
463
+ const resolved = resolveProxyTarget(raw, upstreams);
464
+ if ("reason" in resolved) {
465
+ if (/\/\/unix:/i.test(raw)) continue;
466
+ throw new Error(`Cannot inventory Openship edge routes: ${resolved.reason}`);
467
+ }
468
+ const authority = raw.match(/^https?:\/\/([^/]+)/i)?.[1];
469
+ const scheme = raw.match(/^(https?:\/\/)/i)?.[1];
470
+ const targets = authority && scheme && upstreams.has(authority) ? upstreams.get(authority).map((target) => `${scheme}${target}`) : [resolved.url];
471
+ for (const target of targets) {
472
+ let url;
473
+ try {
474
+ url = new URL(target);
475
+ } catch {
476
+ throw new Error(`Cannot inventory Openship edge routes: invalid proxy_pass "${raw}"`);
477
+ }
478
+ if (!isLoopbackHost(url.hostname)) continue;
479
+ const port = url.port ? Number(url.port) : url.protocol === "https:" ? 443 : 80;
480
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) {
481
+ throw new Error(`Cannot inventory Openship edge routes: invalid loopback port in "${raw}"`);
482
+ }
483
+ ports.add(port);
484
+ }
485
+ }
486
+ return ports;
487
+ }
488
+ function parseStrictEdgeConfig(raw) {
489
+ return {
490
+ ...parseNginxConfig(raw),
491
+ loopbackUpstreamPorts: strictLoopbackUpstreamPorts(raw)
492
+ };
493
+ }
448
494
  function locationBlocks(serverBody) {
449
495
  const out = [];
450
496
  const re = /(?:^|[\s;}])location\s+([^{]+?)\s*\{/g;
@@ -536,7 +582,9 @@ function resolveStaticRoot(root, prefix) {
536
582
  }
537
583
  function parseServer(body, source, upstreams, prefix) {
538
584
  const warnings = [];
539
- const names = firstDirective(body, "server_name")?.split(/\s+/).filter((n) => n && n !== "_" && !n.startsWith("~") && !LOOPBACK_SERVER_NAMES.has(n.toLowerCase())) ?? [];
585
+ const names = firstDirective(body, "server_name")?.split(/\s+/).filter(
586
+ (n) => n && n !== "_" && !n.startsWith("~") && !LOOPBACK_SERVER_NAMES.has(n.toLowerCase())
587
+ ) ?? [];
540
588
  const listens = [...body.matchAll(/(?:^|[;{\s])listen\s+([^;]+);/g)].map((m) => m[1]);
541
589
  const ssl = listens.some((l) => /\bssl\b/.test(l) || /\b443\b/.test(l));
542
590
  const root = firstDirective(body, "root");
@@ -632,6 +680,35 @@ async function scanOpenshipEdge(executor) {
632
680
  const fromContainer = await tryExec(executor, containerCommand(container, cat));
633
681
  return parseNginxConfig(fromContainer ?? "");
634
682
  }
683
+ async function scanOpenshipEdgeStrict(executor, knownContainer) {
684
+ const detected = await detectEdgeContainer(executor);
685
+ if (!detected) {
686
+ const bareConfig = await dumpResolvedConfig(executor, ["openresty"]);
687
+ if (bareConfig) return parseStrictEdgeConfig(bareConfig);
688
+ throw new Error(
689
+ "Cannot read Openship edge routes: neither Docker nor bare OpenResty inventory is available"
690
+ );
691
+ }
692
+ if (!detected.exists) {
693
+ const bareConfig = await dumpResolvedConfig(executor, ["openresty"]);
694
+ if (bareConfig) return parseStrictEdgeConfig(bareConfig);
695
+ throw new Error(
696
+ "Cannot read Openship edge routes: no running Openship edge or bare OpenResty inventory is available"
697
+ );
698
+ }
699
+ if (!detected.running) {
700
+ throw new Error("Cannot read Openship edge routes: the edge container is not running");
701
+ }
702
+ const container = knownContainer ?? detected.name;
703
+ if (!container) throw new Error("Cannot read Openship edge routes: container identity missing");
704
+ const fromContainer = await executor.exec(
705
+ containerCommand(container, "openresty -T 2>/dev/null")
706
+ );
707
+ if (!fromContainer.trim() || !/server\s*\{/.test(fromContainer)) {
708
+ throw new Error("Cannot read Openship edge routes: active OpenResty config dump was empty");
709
+ }
710
+ return parseStrictEdgeConfig(fromContainer);
711
+ }
635
712
 
636
713
  // ../../packages/adapters/src/system/proxy/import/caddy.ts
637
714
  var CADDYFILE_PATHS = ["/etc/caddy/Caddyfile", "/etc/Caddyfile"];
@@ -907,7 +984,7 @@ async function vhostFilesFromDump(executor) {
907
984
  async function loadApacheConfig(executor) {
908
985
  const files = await vhostFilesFromDump(executor);
909
986
  if (files.length > 0) {
910
- const cat2 = await tryExec(executor, `cat ${files.map(sq3).join(" ")} 2>/dev/null`);
987
+ const cat2 = await tryExec(executor, `cat ${files.map(shellQuote).join(" ")} 2>/dev/null`);
911
988
  if (cat2 && cat2.trim()) return cat2;
912
989
  }
913
990
  const cat = await tryExec(
@@ -1611,6 +1688,24 @@ function makeApi(exec, kind, ours, container) {
1611
1688
  }));
1612
1689
  return scan;
1613
1690
  };
1691
+ let strictLoopbackPorts = null;
1692
+ const listLoopbackUpstreamPortsStrict = (opts = {}) => {
1693
+ if (!ours) {
1694
+ return Promise.reject(
1695
+ new Error("Strict loopback upstream inventory is only available for the Openship edge")
1696
+ );
1697
+ }
1698
+ if (opts.refresh || !strictLoopbackPorts) {
1699
+ const nextScan = scanOpenshipEdgeStrict(exec, container).then(
1700
+ ({ loopbackUpstreamPorts }) => loopbackUpstreamPorts
1701
+ );
1702
+ strictLoopbackPorts = nextScan;
1703
+ void nextScan.catch(() => {
1704
+ if (strictLoopbackPorts === nextScan) strictLoopbackPorts = null;
1705
+ });
1706
+ }
1707
+ return strictLoopbackPorts;
1708
+ };
1614
1709
  const siteFor = async (host) => {
1615
1710
  const target = host.toLowerCase();
1616
1711
  const { sites } = await listSites();
@@ -1642,6 +1737,7 @@ function makeApi(exec, kind, ours, container) {
1642
1737
  ours,
1643
1738
  container,
1644
1739
  listSites,
1740
+ listLoopbackUpstreamPortsStrict,
1645
1741
  sitesByPort: async () => buildProxyRouteIndex((await listSites()).sites),
1646
1742
  siteFor,
1647
1743
  certFor: async (host) => (await certCandidateFor(host)).cert,
@@ -2308,7 +2404,7 @@ function edgeHostExecutor(executor) {
2308
2404
  });
2309
2405
  }
2310
2406
  async function containerEdgeProvider(executor, container, opts) {
2311
- const { NginxProvider: Provider } = await import("./nginx-KUFMLMAC.js");
2407
+ const { NginxProvider: Provider } = await import("./nginx-4U4C2XUS.js");
2312
2408
  return new Provider({
2313
2409
  ...opts,
2314
2410
  // Vhosts go to the BIND-MOUNTED HOST dir (so every host-side reader — the
@@ -2332,8 +2428,8 @@ async function containerEdgeProvider(executor, container, opts) {
2332
2428
  });
2333
2429
  }
2334
2430
  async function localContainerEdgeProvider(container, opts) {
2335
- const { DockerEdgeExecutor } = await import("./docker-edge-executor-22IBXNYJ.js");
2336
- const { NginxProvider: Provider } = await import("./nginx-KUFMLMAC.js");
2431
+ const { DockerEdgeExecutor } = await import("./docker-edge-executor-E5TOTLMR.js");
2432
+ const { NginxProvider: Provider } = await import("./nginx-4U4C2XUS.js");
2337
2433
  return new Provider({
2338
2434
  ...opts,
2339
2435
  paths: OPENRESTY_DEFAULT_PATHS,
@@ -2354,20 +2450,40 @@ async function localContainerEdgeProvider(container, opts) {
2354
2450
  containerEdge: true
2355
2451
  });
2356
2452
  }
2357
- async function startEdgeContainer(executor, container, image, onLog) {
2453
+ async function startEdgeContainer(executor, container, image, onLog, mounts) {
2358
2454
  await sanitizeEdgeVhosts(executor, EDGE_HOST_PATHS.sitesDir, onLog).catch(() => {
2359
2455
  });
2360
2456
  await executor.exec(`docker rm -f ${sq(container)} 2>/dev/null || true`).catch(() => {
2361
2457
  });
2362
2458
  const run = await executor.streamExec(
2363
- buildEdgeRunCommand(container, image),
2459
+ buildEdgeRunCommand(container, image, mounts),
2364
2460
  onLog
2365
2461
  );
2366
2462
  invalidateEdgeContainer(executor);
2367
2463
  return run.code === 0;
2368
2464
  }
2369
- function buildEdgeRunCommand(container, image) {
2370
- const mounts = EDGE_CONTAINER_MOUNTS.map(
2465
+ async function resolveEdgeContainerMounts(executor) {
2466
+ return Promise.all(
2467
+ EDGE_CONTAINER_MOUNTS.map(async (mount) => {
2468
+ let host;
2469
+ try {
2470
+ host = (await executor.exec(`cd ${sq(mount.host)} && pwd -P`)).trim();
2471
+ } catch (err) {
2472
+ throw new Error(
2473
+ `Could not resolve edge bind-mount source ${mount.host} on the target host: ${safeErrorMessage(err)}`
2474
+ );
2475
+ }
2476
+ if (!host.startsWith("/") || host.includes("\n")) {
2477
+ throw new Error(
2478
+ `Could not resolve edge bind-mount source ${mount.host} on the target host: expected one absolute path, received ${JSON.stringify(host)}.`
2479
+ );
2480
+ }
2481
+ return { host, container: mount.container };
2482
+ })
2483
+ );
2484
+ }
2485
+ function buildEdgeRunCommand(container, image, edgeMounts = EDGE_CONTAINER_MOUNTS) {
2486
+ const mounts = edgeMounts.map(
2371
2487
  // `:z` relabels for SELinux-enforcing hosts; a no-op elsewhere.
2372
2488
  (m) => `-v ${sq(`${m.host}:${m.container}:z`)}`
2373
2489
  ).join(" ");
@@ -2380,10 +2496,27 @@ function buildEdgeRunCommand(container, image) {
2380
2496
  sq(image)
2381
2497
  ].join(" ");
2382
2498
  }
2383
- function makeEdgeStart(executor, container, opts) {
2499
+ async function edgeContainerMountsMatch(executor, container, expected) {
2500
+ let raw;
2501
+ try {
2502
+ raw = await executor.exec(
2503
+ `docker inspect -f '{{range .HostConfig.Binds}}{{println .}}{{end}}' ${sq(container)}`
2504
+ );
2505
+ } catch {
2506
+ return null;
2507
+ }
2508
+ const actual = /* @__PURE__ */ new Map();
2509
+ for (const line of raw.split("\n")) {
2510
+ const [source, destination] = line.trim().split(":");
2511
+ if (!source || !destination) continue;
2512
+ actual.set(destination, source);
2513
+ }
2514
+ return expected.every((mount) => actual.get(mount.container) === mount.host);
2515
+ }
2516
+ function makeEdgeStart(executor, container, opts, mounts) {
2384
2517
  const { onLog } = opts;
2385
2518
  return async (image) => {
2386
- if (!await startEdgeContainer(executor, container, image, onLog)) return false;
2519
+ if (!await startEdgeContainer(executor, container, image, onLog, mounts)) return false;
2387
2520
  const verdict = await verifyEdgeServing(executor, container, {
2388
2521
  timeoutMs: opts.verifyTimeoutMs ?? 3e4
2389
2522
  });
@@ -2418,6 +2551,17 @@ async function ensureContainerEdge(executor, opts) {
2418
2551
  const { onLog } = opts;
2419
2552
  const container = opts.container?.trim() || EDGE_CONTAINER_NAME;
2420
2553
  const image = resolveEdgeImage(opts.image);
2554
+ let resolvedMounts = null;
2555
+ const targetMounts = async (checked) => {
2556
+ if (resolvedMounts) return resolvedMounts;
2557
+ const host = checked ?? await rootOrDegrade(executor, {
2558
+ purpose: "Resolving the edge's bind-mount sources",
2559
+ consequence: "Docker may mount a different host directory than Openship writes.",
2560
+ report: (message) => onLog(log5(message, "warn"))
2561
+ });
2562
+ resolvedMounts = await resolveEdgeContainerMounts(host);
2563
+ return resolvedMounts;
2564
+ };
2421
2565
  const existing = await resolveOurEdgeContainer(executor, { fresh: true });
2422
2566
  if (existing) {
2423
2567
  const verdict = await verifyEdgeServing(executor, existing);
@@ -2425,27 +2569,35 @@ async function ensureContainerEdge(executor, opts) {
2425
2569
  onLog(log5(`The edge container exists but isn't serving: ${verdict.reason}`, "warn"));
2426
2570
  onLog(log5("Recreating it...", "warn"));
2427
2571
  } else {
2428
- const current = await containerImageRef(executor, existing);
2429
- const stale = current != null && current !== image;
2430
- if (stale) {
2431
- const start = makeEdgeStart(executor, existing, opts);
2432
- const swap = await swapManagedImage(executor, {
2433
- kind: "edge",
2434
- from: current ?? image,
2435
- to: image,
2436
- label: "edge",
2437
- onLog,
2438
- start
2439
- });
2440
- return {
2441
- container: existing,
2442
- image,
2443
- converted: false,
2444
- updated: swap.swapped,
2445
- edgeDown: swap.down
2446
- };
2572
+ const mounts2 = await targetMounts();
2573
+ const mountsMatch = await edgeContainerMountsMatch(executor, existing, mounts2);
2574
+ if (mountsMatch === false) {
2575
+ onLog(
2576
+ log5("The edge container uses stale or non-canonical bind mounts; recreating it...", "warn")
2577
+ );
2578
+ } else {
2579
+ const current = await containerImageRef(executor, existing);
2580
+ const stale = current != null && current !== image;
2581
+ if (stale) {
2582
+ const start = makeEdgeStart(executor, existing, opts, mounts2);
2583
+ const swap = await swapManagedImage(executor, {
2584
+ kind: "edge",
2585
+ from: current ?? image,
2586
+ to: image,
2587
+ label: "edge",
2588
+ onLog,
2589
+ start
2590
+ });
2591
+ return {
2592
+ container: existing,
2593
+ image,
2594
+ converted: false,
2595
+ updated: swap.swapped,
2596
+ edgeDown: swap.down
2597
+ };
2598
+ }
2599
+ return { container: existing, image, converted: false };
2447
2600
  }
2448
- return { container: existing, image, converted: false };
2449
2601
  }
2450
2602
  }
2451
2603
  if (!await dockerAvailable(executor)) {
@@ -2480,10 +2632,11 @@ async function ensureContainerEdge(executor, opts) {
2480
2632
  );
2481
2633
  });
2482
2634
  }
2635
+ const mounts = await targetMounts(hostState);
2483
2636
  const clear = await ensureEdgeClear(executor, opts.config, onLog);
2484
2637
  try {
2485
2638
  onLog(log5("Starting the edge container..."));
2486
- if (!await startEdgeContainer(executor, container, image, onLog)) {
2639
+ if (!await startEdgeContainer(executor, container, image, onLog, mounts)) {
2487
2640
  throw new Error("the edge container failed to start");
2488
2641
  }
2489
2642
  const verdict = await verifyEdgeServing(executor, container, {
@@ -2533,6 +2686,7 @@ export {
2533
2686
  resolveEdgeImage,
2534
2687
  containerEdgeProvider,
2535
2688
  localContainerEdgeProvider,
2689
+ resolveEdgeContainerMounts,
2536
2690
  buildEdgeRunCommand,
2537
2691
  verifyEdgeServing,
2538
2692
  ensureContainerEdge,
@@ -4,18 +4,20 @@ import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  sq
7
- } from "./chunk-ZFJXPCQP.js";
7
+ } from "./chunk-P7RCYELG.js";
8
8
  import {
9
- assessHostSupport,
10
- classifyDistro,
11
9
  formatDuration,
12
10
  isRemoteConnectionError,
11
+ systemDebug
12
+ } from "./chunk-N5YZMO33.js";
13
+ import {
14
+ assessHostSupport,
15
+ classifyDistro,
13
16
  parseOsRelease,
14
17
  parseSystemArch,
15
18
  parseSystemOs,
16
- safeErrorMessage,
17
- systemDebug
18
- } from "./chunk-UJ6JZM65.js";
19
+ safeErrorMessage
20
+ } from "./chunk-WEZFTL4V.js";
19
21
 
20
22
  // ../../packages/adapters/src/system/elevated-executor.ts
21
23
  import { randomBytes } from "crypto";
@@ -13,11 +13,11 @@ var CloudInfraProvider = class {
13
13
  async registerRoute(route) {
14
14
  void route;
15
15
  }
16
- async removeRoute(domain) {
16
+ async removeRoute(domain, _opts) {
17
17
  void domain;
18
18
  }
19
19
  // ── SSL ──────────────────────────────────────────────────────────────
20
- async provisionCert(domain) {
20
+ async provisionCert(domain, _opts) {
21
21
  return { domain, expiresAt: "", issuer: "oblien", verified: false };
22
22
  }
23
23
  async renewCert(domain) {
@@ -19,13 +19,16 @@ import {
19
19
  rollbackEdgeTakeover,
20
20
  scanImportableSites,
21
21
  unreachableStaticRoots
22
- } from "./chunk-A4GXYE4L.js";
22
+ } from "./chunk-NIYD7MD2.js";
23
23
  import {
24
24
  LocalExecutor
25
- } from "./chunk-6FH4AXVY.js";
25
+ } from "./chunk-T6DVUX4F.js";
26
26
  import {
27
27
  ourEdgeContainerRunning
28
- } from "./chunk-2YM3NI2E.js";
28
+ } from "./chunk-X53JXRS4.js";
29
+ import {
30
+ isOurEdgeContainer
31
+ } from "./chunk-D6HU4VUZ.js";
29
32
 
30
33
  // src/lib/edge-preflight.ts
31
34
  import { spawnSync } from "child_process";
@@ -195,7 +198,7 @@ async function diagnoseEdge() {
195
198
  } catch {
196
199
  return { ...empty, containerExists: exists, containerRunning: running };
197
200
  }
198
- const foreignOccupants = status.occupants.filter((o) => o.containerName !== "openship-edge");
201
+ const foreignOccupants = status.occupants.filter((o) => !isOurEdgeContainer(o.containerName));
199
202
  const hostProxySquatting = !running && foreignOccupants.some((o) => !o.containerName);
200
203
  let sites = [];
201
204
  if (status.occupants.length > 0) {
@@ -240,7 +243,7 @@ async function repairEdgeConflict(mode, apiPort, onLog) {
240
243
  }
241
244
  spawnSync("docker", ["restart", "openship-edge"], { stdio: "ignore" });
242
245
  if (mode === "migrate" && scan.sites.length > 0) {
243
- const { importMigratedSites } = await import("./edge-import-WJUB4M7F.js");
246
+ const { importMigratedSites } = await import("./edge-import-YJVJUGYC.js");
244
247
  const outcome = await importMigratedSites(apiPort, scan.sites, certPems);
245
248
  if (outcome.registered.length === 0) {
246
249
  return {
@@ -2,6 +2,9 @@
2
2
  ":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
3
3
  import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
+ import {
6
+ shellQuote
7
+ } from "./chunk-WEZFTL4V.js";
5
8
 
6
9
  // ../../packages/adapters/src/system/local-shell.ts
7
10
  import { execFile } from "child_process";
@@ -28,9 +31,7 @@ var LOCAL_BUILD_ENV_KEYS = [
28
31
  "XDG_CACHE_HOME",
29
32
  "XDG_DATA_HOME"
30
33
  ];
31
- function sq(value) {
32
- return `'${value.replace(/'/g, "'\\''")}'`;
33
- }
34
+ var sq = shellQuote;
34
35
  function logEntry(message, level = "info", rawData) {
35
36
  return { timestamp: (/* @__PURE__ */ new Date()).toISOString(), message, level, ...rawData ? { rawData } : {} };
36
37
  }
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env sh
2
+ ":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
3
+ import { createRequire as __ospCreateRequire } from "node:module";
4
+ const require = __ospCreateRequire(import.meta.url);
5
+
6
+ // ../../packages/adapters/src/system/reachability.ts
7
+ import { connect } from "net";
8
+ import { request } from "http";
9
+ function classifyDialError(code) {
10
+ switch (code) {
11
+ case "ECONNREFUSED":
12
+ case "ECONNRESET":
13
+ return "refused";
14
+ case "ENOTFOUND":
15
+ case "EAI_AGAIN":
16
+ return "unresolved";
17
+ case "EHOSTUNREACH":
18
+ case "ENETUNREACH":
19
+ case "ENETDOWN":
20
+ return "no_route";
21
+ // The kernel's own connect timeout, i.e. our `timeout` reported for us.
22
+ case "ETIMEDOUT":
23
+ return "timeout";
24
+ default:
25
+ return "error";
26
+ }
27
+ }
28
+ function probeTcpDetailed(host, port, timeoutMs = 2500) {
29
+ return new Promise((resolve) => {
30
+ let settled = false;
31
+ const done = (result, socket2) => {
32
+ if (settled) return;
33
+ settled = true;
34
+ try {
35
+ socket2?.destroy();
36
+ } catch {
37
+ }
38
+ resolve(result);
39
+ };
40
+ const socket = connect({ host, port });
41
+ socket.setTimeout(timeoutMs);
42
+ socket.once("connect", () => done({ ok: true }, socket));
43
+ socket.once(
44
+ "timeout",
45
+ () => done({ ok: false, reason: "timeout", message: `no response within ${timeoutMs}ms` }, socket)
46
+ );
47
+ socket.once("error", (err) => {
48
+ const errno = err;
49
+ done(
50
+ {
51
+ ok: false,
52
+ reason: classifyDialError(errno.code),
53
+ ...errno.code ? { code: errno.code } : {},
54
+ ...errno.message ? { message: errno.message } : {}
55
+ },
56
+ socket
57
+ );
58
+ });
59
+ });
60
+ }
61
+ function probeHostedHttp(opts) {
62
+ const { hostname, path = "/", port = 80, timeoutMs = 3e3 } = opts;
63
+ return new Promise((resolve) => {
64
+ let settled = false;
65
+ const done = (value) => {
66
+ if (settled) return;
67
+ settled = true;
68
+ resolve(value);
69
+ };
70
+ const req = request(
71
+ {
72
+ host: "127.0.0.1",
73
+ port,
74
+ path,
75
+ method: "GET",
76
+ timeout: timeoutMs,
77
+ // Claim https so the :80 block SERVES instead of answering our own
78
+ // HTTP→HTTPS redirect before the doc root is consulted — see the curl twin in
79
+ // output-exists.ts for why a 301 here read as a healthy site.
80
+ headers: { Host: hostname, "X-Forwarded-Proto": "https" }
81
+ },
82
+ (res) => {
83
+ const status = res.statusCode ?? 0;
84
+ res.resume();
85
+ done(
86
+ status > 0 ? { status, served: status >= 200 && status < 400 || status === 401 || status === 403 } : null
87
+ );
88
+ }
89
+ );
90
+ const fail = () => {
91
+ try {
92
+ req.destroy();
93
+ } catch {
94
+ }
95
+ done(null);
96
+ };
97
+ req.once("timeout", fail);
98
+ req.once("error", fail);
99
+ req.end();
100
+ });
101
+ }
102
+
103
+ export {
104
+ probeTcpDetailed,
105
+ probeHostedHttp
106
+ };
@@ -7,7 +7,7 @@ import {
7
7
  COMPONENT_INSTALLERS,
8
8
  checkAll,
9
9
  checkComponents
10
- } from "./chunk-A4GXYE4L.js";
10
+ } from "./chunk-NIYD7MD2.js";
11
11
 
12
12
  // ../../packages/adapters/src/system/state.ts
13
13
  var DEFAULT_STATE_PATH = "/etc/openship/setup-state.json";