openship 0.4.6 → 0.4.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.
@@ -21998,13 +21998,102 @@ async function openStreamlocalUpstream(opts) {
21998
21998
  throw error51;
21999
21999
  }
22000
22000
  }
22001
+ function pipeThrough(client, upstream) {
22002
+ const teardown = () => {
22003
+ client.destroy();
22004
+ upstream.destroy();
22005
+ };
22006
+ client.on("error", teardown);
22007
+ upstream.on("error", teardown);
22008
+ client.once("close", () => upstream.destroy());
22009
+ upstream.once("close", () => client.destroy());
22010
+ client.pipe(upstream);
22011
+ upstream.pipe(client);
22012
+ }
22013
+ function awaitFirstByte(upstream, ms) {
22014
+ return new Promise((resolve2) => {
22015
+ const finish = (chunk) => {
22016
+ clearTimeout(timer);
22017
+ upstream.removeListener("data", onData);
22018
+ upstream.removeListener("error", onFail);
22019
+ upstream.removeListener("close", onFail);
22020
+ resolve2(chunk);
22021
+ };
22022
+ const onData = (chunk) => finish(chunk);
22023
+ const onFail = () => finish(null);
22024
+ const timer = setTimeout(() => finish(null), ms);
22025
+ upstream.on("data", onData);
22026
+ upstream.once("error", onFail);
22027
+ upstream.once("close", onFail);
22028
+ });
22029
+ }
22030
+ function captureClient(client) {
22031
+ const chunks = [];
22032
+ let bytes = 0;
22033
+ let overflowed = false;
22034
+ let closed = false;
22035
+ let live = null;
22036
+ const onData = (chunk) => {
22037
+ if (live)
22038
+ live.write(chunk);
22039
+ if (overflowed)
22040
+ return;
22041
+ chunks.push(chunk);
22042
+ bytes += chunk.length;
22043
+ if (bytes >= VERIFY_BUFFER_CAP_BYTES) {
22044
+ overflowed = true;
22045
+ client.pause();
22046
+ }
22047
+ };
22048
+ const onGone = () => {
22049
+ closed = true;
22050
+ };
22051
+ client.on("data", onData);
22052
+ client.once("error", onGone);
22053
+ client.once("close", onGone);
22054
+ const detach = () => {
22055
+ client.removeListener("data", onData);
22056
+ client.removeListener("error", onGone);
22057
+ client.removeListener("close", onGone);
22058
+ };
22059
+ return {
22060
+ get closed() {
22061
+ return closed;
22062
+ },
22063
+ get overflowed() {
22064
+ return overflowed;
22065
+ },
22066
+ feed(upstream) {
22067
+ for (const chunk of chunks)
22068
+ upstream.write(chunk);
22069
+ live = upstream;
22070
+ },
22071
+ stop() {
22072
+ live = null;
22073
+ },
22074
+ commit(upstream) {
22075
+ detach();
22076
+ chunks.length = 0;
22077
+ if (closed) {
22078
+ upstream.destroy();
22079
+ return;
22080
+ }
22081
+ pipeThrough(client, upstream);
22082
+ },
22083
+ fail(err2) {
22084
+ detach();
22085
+ client.destroy(err2 instanceof Error ? err2 : new Error(String(err2)));
22086
+ }
22087
+ };
22088
+ }
22001
22089
  function createDockerSshBridge(opts) {
22002
22090
  const clients = new Set;
22003
22091
  let upstreamMode = null;
22004
22092
  let modeDecision = null;
22005
22093
  let dialClient = null;
22006
- const openDialStdioUpstream = async () => {
22007
- if (opts.executor?.openDockerDialStdio) {
22094
+ let pooledUnreliable = false;
22095
+ const openDialStdioUpstream = async (forceEphemeral = false) => {
22096
+ if (!forceEphemeral && opts.executor?.openDockerDialStdio) {
22008
22097
  const stream = await opts.executor.openDockerDialStdio();
22009
22098
  stream.once("error", (e) => console.warn(`[docker-ssh] dial-stdio channel error: ${safeErrorMessage(e)}`));
22010
22099
  return stream;
@@ -22061,29 +22150,52 @@ Host: localhost\r
22061
22150
  modeDecision ??= decideMode().then((mode) => upstreamMode = mode);
22062
22151
  return modeDecision;
22063
22152
  };
22064
- const openUpstream = async () => {
22065
- const mode = await ensureMode();
22066
- return mode === "dialstdio" ? openDialStdioUpstream() : openStreamlocalUpstream(opts);
22153
+ const downgradeToDialStdio = async (capture, reason) => {
22154
+ console.warn(`[docker-ssh] streamlocal unusable (${opts.host ?? "?"}): ${reason} — ` + "downgrading this bridge to dial-stdio (fresh connection) and replaying the buffered request.");
22155
+ upstreamMode = "dialstdio";
22156
+ pooledUnreliable = true;
22157
+ const upstream = await openDialStdioUpstream(true);
22158
+ capture.feed(upstream);
22159
+ capture.commit(upstream);
22160
+ };
22161
+ const bridgeClient = async (client) => {
22162
+ const capture = captureClient(client);
22163
+ try {
22164
+ const mode = await ensureMode();
22165
+ if (mode === "dialstdio") {
22166
+ const upstream = await openDialStdioUpstream(pooledUnreliable);
22167
+ capture.feed(upstream);
22168
+ capture.commit(upstream);
22169
+ return;
22170
+ }
22171
+ let channel;
22172
+ try {
22173
+ channel = await withTimeout(openStreamlocalUpstream(opts), STREAMLOCAL_DATA_VERIFY_TIMEOUT_MS, `channel open timed out after ${STREAMLOCAL_DATA_VERIFY_TIMEOUT_MS / 1000}s`);
22174
+ } catch (err2) {
22175
+ await downgradeToDialStdio(capture, safeErrorMessage(err2));
22176
+ return;
22177
+ }
22178
+ capture.feed(channel);
22179
+ const firstByte = capture.overflowed ? null : await awaitFirstByte(channel, STREAMLOCAL_DATA_VERIFY_TIMEOUT_MS);
22180
+ if (capture.overflowed || firstByte) {
22181
+ if (firstByte)
22182
+ client.write(firstByte);
22183
+ capture.commit(channel);
22184
+ return;
22185
+ }
22186
+ capture.stop();
22187
+ channel.destroy();
22188
+ await downgradeToDialStdio(capture, "channel opened but no data flowed");
22189
+ } catch (err2) {
22190
+ console.warn(`[docker-ssh] bridge client failed (${opts.host ?? "?"}): ${safeErrorMessage(err2)}`);
22191
+ capture.fail(err2);
22192
+ }
22067
22193
  };
22068
22194
  const server = net2.createServer((client) => {
22069
22195
  clients.add(client);
22070
22196
  client.setNoDelay(true);
22071
22197
  client.once("close", () => clients.delete(client));
22072
- openUpstream().then((upstream) => {
22073
- const teardown = () => {
22074
- client.destroy();
22075
- upstream.destroy();
22076
- };
22077
- client.on("error", teardown);
22078
- upstream.on("error", teardown);
22079
- client.once("close", () => upstream.destroy());
22080
- upstream.once("close", () => client.destroy());
22081
- client.pipe(upstream);
22082
- upstream.pipe(client);
22083
- }).catch((error51) => {
22084
- console.warn(`[docker-ssh] bridge upstream open failed: ${safeErrorMessage(error51)}`);
22085
- client.destroy(error51 instanceof Error ? error51 : new Error(String(error51)));
22086
- });
22198
+ bridgeClient(client);
22087
22199
  });
22088
22200
  return {
22089
22201
  start: () => new Promise((resolve2, reject) => {
@@ -22113,11 +22225,12 @@ Host: localhost\r
22113
22225
  }
22114
22226
  };
22115
22227
  }
22116
- var DEFAULT_REMOTE_DOCKER_SOCKET_PATH = "/var/run/docker.sock", resolvedDockerSocketPathCache, DOCKER_DIAL_STDIO_COMMAND = "docker system dial-stdio", STREAMLOCAL_PROBE_TIMEOUT_MS = 8000, DOCKER_SOCKET_DISCOVERY_SCRIPT;
22228
+ var DEFAULT_REMOTE_DOCKER_SOCKET_PATH = "/var/run/docker.sock", resolvedDockerSocketPathCache, DOCKER_DIAL_STDIO_COMMAND = "docker system dial-stdio", STREAMLOCAL_PROBE_TIMEOUT_MS = 8000, STREAMLOCAL_DATA_VERIFY_TIMEOUT_MS = 3000, VERIFY_BUFFER_CAP_BYTES, DOCKER_SOCKET_DISCOVERY_SCRIPT;
22117
22229
  var init_docker_ssh_agent = __esm(() => {
22118
22230
  init_ssh_client();
22119
22231
  init_src();
22120
22232
  resolvedDockerSocketPathCache = new WeakMap;
22233
+ VERIFY_BUFFER_CAP_BYTES = 8 * 1024 * 1024;
22121
22234
  DOCKER_SOCKET_DISCOVERY_SCRIPT = [
22122
22235
  "set -eu",
22123
22236
  'uid="$(id -u 2>/dev/null || printf 0)"',
@@ -25651,7 +25764,7 @@ __export(exports_executor, {
25651
25764
  SshExecutor: () => SshExecutor,
25652
25765
  LocalExecutor: () => LocalExecutor
25653
25766
  });
25654
- import { readFileSync } from "node:fs";
25767
+ import { existsSync, readFileSync } from "node:fs";
25655
25768
  function createExecutor(ssh) {
25656
25769
  if (ssh) {
25657
25770
  if (ssh.useSystemSsh)
@@ -25663,13 +25776,30 @@ function createExecutor(ssh) {
25663
25776
  function hostControlDisabled() {
25664
25777
  return process.env.OPENSHIP_HOST_CONTROL?.trim().toLowerCase() === "false";
25665
25778
  }
25779
+ function runningInContainer() {
25780
+ if (process.env.OPENSHIP_IN_CONTAINER?.trim().toLowerCase() === "true")
25781
+ return true;
25782
+ try {
25783
+ if (existsSync("/.dockerenv"))
25784
+ return true;
25785
+ } catch {}
25786
+ try {
25787
+ return /\b(docker|containerd|podman|kubepods)\b/.test(readFileSync("/proc/1/cgroup", "utf8"));
25788
+ } catch {
25789
+ return false;
25790
+ }
25791
+ }
25666
25792
  function createHostExecutor() {
25667
25793
  if (hostControlDisabled()) {
25668
25794
  throw new Error("Host control is disabled on this instance (OPENSHIP_HOST_CONTROL=false). " + "Re-run `openship up` without --no-host-control to allow host operations.");
25669
25795
  }
25670
25796
  const host = process.env.OPENSHIP_HOST_SSH_HOST?.trim();
25671
- if (!host)
25797
+ if (!host) {
25798
+ if (runningInContainer()) {
25799
+ throw new Error("This operation targets the HOST machine, but no host channel is configured " + "(OPENSHIP_HOST_SSH_HOST is unset) and Openship is running in a container — " + "so it would have run inside the container instead, against the wrong " + "filesystem. Re-run `openship up` to provision the host channel.");
25800
+ }
25672
25801
  return localExecutor;
25802
+ }
25673
25803
  const keyPath = process.env.OPENSHIP_HOST_SSH_KEY?.trim();
25674
25804
  const portRaw = Number(process.env.OPENSHIP_HOST_SSH_PORT || "22");
25675
25805
  const port = Number.isInteger(portRaw) && portRaw > 0 && portRaw < 65536 ? portRaw : 22;
@@ -28968,7 +29098,7 @@ __export(exports_openresty_lua, {
28968
29098
  ACME_CHALLENGE_LOCATION: () => ACME_CHALLENGE_LOCATION
28969
29099
  });
28970
29100
  import { createHash } from "node:crypto";
28971
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
29101
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
28972
29102
  import { dirname as dirname4, join as join10 } from "node:path";
28973
29103
  import { fileURLToPath } from "node:url";
28974
29104
  async function detectOpenRestyPaths(executor) {
@@ -29051,11 +29181,11 @@ http {
29051
29181
  `;
29052
29182
  }
29053
29183
  function luaSourceAvailable() {
29054
- return VHOST_REFERENCED_LUA.every((f) => EMBEDDED_LUA[f] !== undefined || existsSync(join10(LUA_SRC_DIR, f)));
29184
+ return VHOST_REFERENCED_LUA.every((f) => EMBEDDED_LUA[f] !== undefined || existsSync2(join10(LUA_SRC_DIR, f)));
29055
29185
  }
29056
29186
  function readLua(filename) {
29057
29187
  const onDisk = join10(LUA_SRC_DIR, filename);
29058
- if (existsSync(onDisk))
29188
+ if (existsSync2(onDisk))
29059
29189
  return readFileSync2(onDisk, "utf-8");
29060
29190
  const embedded = EMBEDDED_LUA[filename];
29061
29191
  if (embedded !== undefined)
@@ -29255,6 +29385,7 @@ var exports_detect = {};
29255
29385
  __export(exports_detect, {
29256
29386
  stopTargetsForStatus: () => stopTargetsForStatus,
29257
29387
  sq: () => sq3,
29388
+ sanitizeEdgeVhosts: () => sanitizeEdgeVhosts,
29258
29389
  resolveOurEdgeContainer: () => resolveOurEdgeContainer,
29259
29390
  probeEdge: () => probeEdge,
29260
29391
  ourLuaOnHost: () => ourLuaOnHost,
@@ -29262,9 +29393,13 @@ __export(exports_detect, {
29262
29393
  isOurEdgeContainer: () => isOurEdgeContainer,
29263
29394
  invalidateEdgeContainer: () => invalidateEdgeContainer,
29264
29395
  freeEdgeTargets: () => freeEdgeTargets,
29396
+ edgeIsBroken: () => edgeIsBroken,
29397
+ edgeFailureReason: () => edgeFailureReason,
29398
+ edgeCrashReason: () => edgeCrashReason,
29265
29399
  classifyProxy: () => classifyProxy,
29266
29400
  EdgeMigrateRequested: () => EdgeMigrateRequested,
29267
- EdgeConflictError: () => EdgeConflictError
29401
+ EdgeConflictError: () => EdgeConflictError,
29402
+ EDGE_CONTAINER_NAME: () => EDGE_CONTAINER_NAME
29268
29403
  });
29269
29404
  async function tryExec3(executor, command) {
29270
29405
  try {
@@ -29297,6 +29432,49 @@ function sq3(value) {
29297
29432
  function isOurEdgeContainer(name2, image) {
29298
29433
  return /openship-edge/i.test(`${name2 ?? ""} ${image ?? ""}`);
29299
29434
  }
29435
+ function vhostLog(message, level = "info") {
29436
+ return { timestamp: new Date().toISOString(), message, level };
29437
+ }
29438
+ async function sanitizeEdgeVhosts(executor, sitesDir, onLog) {
29439
+ const script = [
29440
+ `for f in ${sq3(sitesDir)}/*.conf; do`,
29441
+ ` [ -f "$f" ] || continue;`,
29442
+ ` if ! grep -qE '^[[:space:]]*server_name[[:space:]]+[^_;[:space:]]' "$f"; then`,
29443
+ ` echo "dropped-catchall $f"; rm -f "$f"; continue;`,
29444
+ ` fi;`,
29445
+ ` if grep -qE '[[:space:]]default_server' "$f"; then`,
29446
+ ` sed -E 's/([[:space:]]listen[^;]*)[[:space:]]+default_server/\\1/g' "$f" > "$f.osh" && mv "$f.osh" "$f" && echo "unset-default $f";`,
29447
+ ` fi;`,
29448
+ `done`
29449
+ ].join(" ");
29450
+ const out2 = await executor.exec(script).catch(() => "");
29451
+ for (const line of out2.split(`
29452
+ `).map((l) => l.trim()).filter(Boolean)) {
29453
+ const [action, file3] = [line.slice(0, line.indexOf(" ")), line.slice(line.indexOf(" ") + 1)];
29454
+ if (action === "dropped-catchall") {
29455
+ onLog(vhostLog(`Dropped catch-all vhost ${file3} — the edge image provides it.`, "warn"));
29456
+ } else if (action === "unset-default") {
29457
+ onLog(vhostLog(`Removed default_server from ${file3} — the edge image owns it.`, "warn"));
29458
+ }
29459
+ }
29460
+ }
29461
+ async function edgeIsBroken(executor) {
29462
+ const status = await tryExec3(executor, `docker inspect -f '{{.State.Status}}' ${sq3(EDGE_CONTAINER_NAME)} 2>/dev/null`);
29463
+ const state = (status ?? "").trim();
29464
+ return state === "restarting" || state === "exited" || state === "dead";
29465
+ }
29466
+ async function edgeCrashReason(executor) {
29467
+ const logs = await tryExec3(executor, `docker logs --tail 40 ${sq3(EDGE_CONTAINER_NAME)} 2>&1`);
29468
+ return logs ? edgeFailureReason(logs) : null;
29469
+ }
29470
+ function edgeFailureReason(containerLog) {
29471
+ const lines = containerLog.split(`
29472
+ `).map((l) => l.trim()).filter(Boolean);
29473
+ const emerg = lines.find((l) => l.includes("[emerg]"));
29474
+ if (emerg)
29475
+ return emerg.replace(/^.*\[emerg\]\s*\d*#\d*:\s*/, "");
29476
+ return null;
29477
+ }
29300
29478
  async function ourEdgeContainerRunning(executor) {
29301
29479
  return Boolean(await resolveOurEdgeContainer(executor));
29302
29480
  }
@@ -29466,7 +29644,7 @@ async function freeEdgeTargets(executor, targets, onLog) {
29466
29644
  }
29467
29645
  await new Promise((r) => setTimeout(r, 1000));
29468
29646
  }
29469
- var EDGE_PORTS, EdgeConflictError, EdgeMigrateRequested, EDGE_CONTAINER_TTL_MS = 20000, edgeContainerMemo, edgeMemoGeneration = 0, WORKER_RE, MASTER_RE;
29647
+ var EDGE_PORTS, EdgeConflictError, EdgeMigrateRequested, EDGE_CONTAINER_NAME = "openship-edge", EDGE_CONTAINER_TTL_MS = 20000, edgeContainerMemo, edgeMemoGeneration = 0, WORKER_RE, MASTER_RE;
29470
29648
  var init_detect2 = __esm(() => {
29471
29649
  init_src();
29472
29650
  init_port_conflict();
@@ -31222,6 +31400,8 @@ async function readJournal(executor) {
31222
31400
  }
31223
31401
  async function rollback(executor, journal, onLog) {
31224
31402
  onLog(log("Rolling back — restoring the previous proxy...", "warn"));
31403
+ await tryExec7(executor, `docker update --restart=no ${sq3(EDGE_CONTAINER_NAME)} 2>/dev/null || true`);
31404
+ await tryExec7(executor, `docker stop ${sq3(EDGE_CONTAINER_NAME)} 2>/dev/null || true`);
31225
31405
  await tryExec7(executor, "systemctl disable --now openresty 2>/dev/null || systemctl stop openresty 2>/dev/null || true; " + "systemctl reset-failed openresty 2>/dev/null || true");
31226
31406
  for (const u of journal.units) {
31227
31407
  await tryExec7(executor, u.wasEnabled ? `systemctl enable --now ${sq3(u.unit)} 2>/dev/null || true` : `systemctl start ${sq3(u.unit)} 2>/dev/null || true`);
@@ -31237,6 +31417,16 @@ async function rollback(executor, journal, onLog) {
31237
31417
  onLog(log(`Could not restore process ${p.pid} — no command captured.`, "warn"));
31238
31418
  }
31239
31419
  }
31420
+ const restored = await portIsServed(executor, 80);
31421
+ if (!restored) {
31422
+ onLog(log("Nothing is listening on :80 after the restore — the box is NOT serving. " + "Start your proxy by hand (e.g. `systemctl start nginx`, or `docker start <name>`).", "error"));
31423
+ }
31424
+ return restored;
31425
+ }
31426
+ async function portIsServed(executor, port) {
31427
+ const hex3 = port.toString(16).toUpperCase().padStart(4, "0");
31428
+ const out2 = await tryExec7(executor, `(command -v ss >/dev/null 2>&1 && ss -ltn 2>/dev/null | grep -qE ':${port}[[:space:]]' && echo yes) || ` + `(grep -qiE '^[[:space:]]*[0-9]+:[[:space:]]*[0-9A-F]{8}:${hex3}[[:space:]]+[0-9A-F]{8}:0000[[:space:]]+0A' /proc/net/tcp 2>/dev/null && echo yes) || true`);
31429
+ return (out2 ?? "").includes("yes");
31240
31430
  }
31241
31431
  async function beginEdgeTakeover(executor, status, onLog) {
31242
31432
  await writeJournal(executor, await buildJournal(executor, status));
@@ -31246,9 +31436,9 @@ async function rollbackEdgeTakeover(executor, onLog) {
31246
31436
  const journal = await readJournal(executor);
31247
31437
  if (!journal || journal.completed)
31248
31438
  return false;
31249
- await rollback(executor, journal, onLog);
31439
+ const restored = await rollback(executor, journal, onLog);
31250
31440
  await clearJournal(executor);
31251
- return true;
31441
+ return restored;
31252
31442
  }
31253
31443
  async function completeEdgeTakeover(executor) {
31254
31444
  const journal = await readJournal(executor);
@@ -32152,8 +32342,7 @@ __export(exports_ensure_container_edge, {
32152
32342
  ensureContainerEdge: () => ensureContainerEdge,
32153
32343
  dockerAvailable: () => dockerAvailable,
32154
32344
  containerEdgeProvider: () => containerEdgeProvider,
32155
- buildEdgeRunCommand: () => buildEdgeRunCommand,
32156
- EDGE_CONTAINER_NAME: () => EDGE_CONTAINER_NAME
32345
+ buildEdgeRunCommand: () => buildEdgeRunCommand
32157
32346
  });
32158
32347
  function log2(message, level = "info") {
32159
32348
  return { timestamp: new Date().toISOString(), message, level };
@@ -32190,6 +32379,13 @@ async function localContainerEdgeProvider(container, opts) {
32190
32379
  pinPaths: true
32191
32380
  });
32192
32381
  }
32382
+ async function startEdgeContainer(executor, container, image, onLog) {
32383
+ await sanitizeEdgeVhosts(executor, EDGE_HOST_PATHS.sitesDir, onLog).catch(() => {});
32384
+ await executor.exec(`docker rm -f ${sq2(container)} 2>/dev/null || true`).catch(() => {});
32385
+ const run2 = await executor.streamExec(buildEdgeRunCommand(container, image), onLog);
32386
+ invalidateEdgeContainer(executor);
32387
+ return run2.code === 0;
32388
+ }
32193
32389
  function buildEdgeRunCommand(container, image) {
32194
32390
  const mounts = EDGE_CONTAINER_MOUNTS.map((m) => `-v ${sq2(`${m.host}:${m.container}:z`)}`).join(" ");
32195
32391
  return [
@@ -32210,10 +32406,7 @@ async function swapEdgeImage(executor, container, from, to, opts) {
32210
32406
  return { swapped: false, edgeDown: false };
32211
32407
  }
32212
32408
  const start2 = async (image) => {
32213
- await executor.exec(`docker rm -f ${sq2(container)} 2>/dev/null || true`).catch(() => {});
32214
- const run2 = await executor.streamExec(buildEdgeRunCommand(container, image), onLog);
32215
- invalidateEdgeContainer(executor);
32216
- if (run2.code !== 0)
32409
+ if (!await startEdgeContainer(executor, container, image, onLog))
32217
32410
  return false;
32218
32411
  const listening = await waitForPortListening(executor, 80, {
32219
32412
  timeoutMs: opts.verifyTimeoutMs ?? 30000
@@ -32231,6 +32424,11 @@ async function swapEdgeImage(executor, container, from, to, opts) {
32231
32424
  onLog(log2(`Rollback to ${from} ALSO failed — the edge is down on this server.`, "error"));
32232
32425
  return { swapped: false, edgeDown: true };
32233
32426
  }
32427
+ async function listDir(executor, dir) {
32428
+ const out2 = await executor.exec(`ls -1 ${sq2(dir)} 2>/dev/null || true`).catch(() => "");
32429
+ return new Set(out2.split(`
32430
+ `).map((l) => l.trim()).filter(Boolean));
32431
+ }
32234
32432
  async function ensureContainerEdge(executor, opts) {
32235
32433
  const { onLog } = opts;
32236
32434
  const container = opts.container?.trim() || EDGE_CONTAINER_NAME;
@@ -32264,16 +32462,26 @@ async function ensureContainerEdge(executor, opts) {
32264
32462
  await executor.exec(`mkdir -p ${sq2(mount.host)}`).catch(() => {});
32265
32463
  }
32266
32464
  const sitesTarget = EDGE_HOST_PATHS.sitesDir;
32465
+ let beforeCarry = null;
32267
32466
  if (bareWasOurs) {
32268
32467
  const barePaths = await detectOpenRestyPaths(executor).catch(() => OPENRESTY_DEFAULT_PATHS);
32269
32468
  if (barePaths.sitesDir !== sitesTarget && await executor.exists(barePaths.sitesDir)) {
32270
32469
  onLog(log2(`Carrying vhosts from ${barePaths.sitesDir}...`));
32470
+ beforeCarry = await listDir(executor, sitesTarget);
32271
32471
  await executor.exec(`cp -a ${sq2(`${barePaths.sitesDir}/.`)} ${sq2(`${sitesTarget}/`)} 2>/dev/null || true`).catch(() => {});
32272
32472
  }
32273
32473
  }
32274
32474
  const restoreBare = async () => {
32275
32475
  if (!bareWasOurs)
32276
32476
  return;
32477
+ if (beforeCarry) {
32478
+ const now2 = await listDir(executor, sitesTarget);
32479
+ const added = [...now2].filter((name2) => !beforeCarry.has(name2));
32480
+ if (added.length > 0) {
32481
+ await executor.exec(`rm -f ${added.map((n) => sq2(`${sitesTarget}/${n}`)).join(" ")}`).catch(() => {});
32482
+ onLog(log2(`Removed ${added.length} carried vhost(s) so the next edge start is clean.`, "warn"));
32483
+ }
32484
+ }
32277
32485
  onLog(log2("Restoring the host OpenResty edge...", "warn"));
32278
32486
  await executor.exec("systemctl enable --now openresty 2>/dev/null || true").catch(() => {});
32279
32487
  };
@@ -32283,12 +32491,10 @@ async function ensureContainerEdge(executor, opts) {
32283
32491
  await executor.exec("systemctl disable --now openresty 2>/dev/null || true").catch(() => {});
32284
32492
  await executor.exec("systemctl reset-failed openresty 2>/dev/null || true").catch(() => {});
32285
32493
  }
32286
- await executor.exec(`docker rm -f ${sq2(container)} 2>/dev/null || true`).catch(() => {});
32287
32494
  onLog(log2("Starting the edge container..."));
32288
- const run2 = await executor.streamExec(buildEdgeRunCommand(container, image), onLog);
32289
- invalidateEdgeContainer(executor);
32290
- if (run2.code !== 0)
32495
+ if (!await startEdgeContainer(executor, container, image, onLog)) {
32291
32496
  throw new Error("the edge container failed to start");
32497
+ }
32292
32498
  await executor.exec(containerCommand(container, "openresty -t"));
32293
32499
  const listening = await waitForPortListening(executor, 80, {
32294
32500
  timeoutMs: opts.verifyTimeoutMs ?? 30000
@@ -32309,10 +32515,11 @@ ${logs}`, "error"));
32309
32515
  await executor.exec(`docker rm -f ${sq2(container)} 2>/dev/null || true`).catch(() => {});
32310
32516
  invalidateEdgeContainer(executor);
32311
32517
  await restoreBare();
32312
- throw new Error(`Edge container setup failed: ${msg}`);
32518
+ const reason = edgeFailureReason(logs);
32519
+ throw new Error(`Edge container setup failed: ${reason ? `${reason} (${msg})` : msg}`);
32313
32520
  }
32314
32521
  }
32315
- var EDGE_CONTAINER_NAME = "openship-edge", injectedDefaultImage;
32522
+ var injectedDefaultImage;
32316
32523
  var init_ensure_container_edge = __esm(() => {
32317
32524
  init_src();
32318
32525
  init_openresty_lua();
@@ -32417,9 +32624,9 @@ async function runEdgeTakeover(executor, opts, onLog) {
32417
32624
  edgeImage: opts.edgeImage
32418
32625
  });
32419
32626
  if (!install.success) {
32420
- await rollback(executor, journal, onLog);
32627
+ const rolledBack = await rollback(executor, journal, onLog);
32421
32628
  await clearJournal(executor);
32422
- return { ok: false, rolledBack: true, registered: [], warnings: [install.error ?? "Edge install failed"] };
32629
+ return { ok: false, rolledBack, registered: [], warnings: [install.error ?? "Edge install failed"] };
32423
32630
  }
32424
32631
  try {
32425
32632
  const container = await resolveOurEdgeContainer(executor, { fresh: true });
@@ -32454,9 +32661,11 @@ async function runEdgeTakeover(executor, opts, onLog) {
32454
32661
  return { ok: true, rolledBack: false, registered, warnings };
32455
32662
  } catch (err2) {
32456
32663
  warnings.push(safeErrorMessage(err2));
32457
- await rollback(executor, journal, onLog);
32664
+ const rolledBack = await rollback(executor, journal, onLog);
32458
32665
  await clearJournal(executor);
32459
- return { ok: false, rolledBack: true, registered: [], warnings };
32666
+ if (!rolledBack)
32667
+ warnings.push("The previous proxy did NOT come back — nothing is serving :80.");
32668
+ return { ok: false, rolledBack, registered: [], warnings };
32460
32669
  }
32461
32670
  }
32462
32671
  var DOMAIN_RE2;
@@ -80000,7 +80209,6 @@ __export(exports_src, {
80000
80209
  EdgeConflictError: () => EdgeConflictError,
80001
80210
  EDGE_HOST_STATE_DIR: () => EDGE_HOST_STATE_DIR,
80002
80211
  EDGE_HOST_PATHS: () => EDGE_HOST_PATHS,
80003
- EDGE_CONTAINER_NAME: () => EDGE_CONTAINER_NAME,
80004
80212
  EDGE_CONTAINER_MOUNTS: () => EDGE_CONTAINER_MOUNTS,
80005
80213
  DockerRuntime: () => DockerRuntime,
80006
80214
  DockerEdgeExecutor: () => DockerEdgeExecutor,
@@ -99322,7 +99530,7 @@ var init_migrator3 = __esm(() => {
99322
99530
  });
99323
99531
 
99324
99532
  // ../../packages/db/src/client.ts
99325
- import { mkdirSync as mkdirSync2, existsSync as existsSync3, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
99533
+ import { mkdirSync as mkdirSync2, existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
99326
99534
  import { resolve as resolve5, dirname as dirname8, join as join18 } from "path";
99327
99535
  import { fileURLToPath as fileURLToPath2 } from "url";
99328
99536
  function getDriver() {
@@ -99397,7 +99605,7 @@ async function createPgClient(url2) {
99397
99605
  }
99398
99606
  function clearStalePgliteControlFile(dataDir) {
99399
99607
  const controlPath = join18(dataDir, "postmaster.pid");
99400
- if (!existsSync3(controlPath))
99608
+ if (!existsSync4(controlPath))
99401
99609
  return;
99402
99610
  try {
99403
99611
  unlinkSync2(controlPath);
@@ -99426,7 +99634,7 @@ async function createPgliteClient() {
99426
99634
  return memDb;
99427
99635
  }
99428
99636
  const dataDir = resolvePgliteDataDir();
99429
- if (!existsSync3(dataDir)) {
99637
+ if (!existsSync4(dataDir)) {
99430
99638
  mkdirSync2(dataDir, { recursive: true });
99431
99639
  }
99432
99640
  await acquirePgliteLock(dataDir, { waitMs: 30000, pollMs: 250 });
@@ -191353,7 +191561,7 @@ var package_default;
191353
191561
  var init_package = __esm(() => {
191354
191562
  package_default = {
191355
191563
  name: "@repo/api",
191356
- version: "0.4.6",
191564
+ version: "0.4.8",
191357
191565
  license: "Apache-2.0",
191358
191566
  private: true,
191359
191567
  type: "module",
@@ -246432,7 +246640,7 @@ var init_safe_fetch = __esm(() => {
246432
246640
 
246433
246641
  // ../api/src/lib/release-download.ts
246434
246642
  import { createHash as createHash11 } from "node:crypto";
246435
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync, rmSync } from "node:fs";
246643
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, renameSync, rmSync } from "node:fs";
246436
246644
  import { readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
246437
246645
  import { join as join20, resolve as resolve8 } from "node:path";
246438
246646
  import { spawn as spawn5 } from "node:child_process";
@@ -246446,7 +246654,7 @@ async function fetchAndExtractRelease(input) {
246446
246654
  const external4 = Boolean(input.assetUrl);
246447
246655
  const envOverride = input.envOverride ?? (input.asset ? envOverrideFor(input.asset) : "OPENSHIP_RELEASE_DIST_PATH");
246448
246656
  const targetDir = resolve8(cacheDir, tag2);
246449
- if (existsSync4(targetDir)) {
246657
+ if (existsSync5(targetDir)) {
246450
246658
  return { path: targetDir, downloaded: false };
246451
246659
  }
246452
246660
  let assetUrl;
@@ -246504,7 +246712,7 @@ async function fetchAndExtractRelease(input) {
246504
246712
  try {
246505
246713
  renameSync(scratchDir, targetDir);
246506
246714
  } catch (err2) {
246507
- if (existsSync4(targetDir)) {
246715
+ if (existsSync5(targetDir)) {
246508
246716
  rmSync(scratchDir, { recursive: true, force: true });
246509
246717
  return { path: targetDir, downloaded: true };
246510
246718
  }
@@ -246516,7 +246724,7 @@ async function fetchAndExtractRelease(input) {
246516
246724
  }
246517
246725
  return { path: targetDir, downloaded: true };
246518
246726
  } catch (err2) {
246519
- if (existsSync4(scratchDir)) {
246727
+ if (existsSync5(scratchDir)) {
246520
246728
  rmSync(scratchDir, { recursive: true, force: true });
246521
246729
  }
246522
246730
  throw err2;
@@ -246807,7 +247015,7 @@ var init_release_download = __esm(() => {
246807
247015
  });
246808
247016
 
246809
247017
  // ../api/src/lib/release-resolver.ts
246810
- import { existsSync as existsSync5 } from "node:fs";
247018
+ import { existsSync as existsSync6 } from "node:fs";
246811
247019
  import { homedir as homedir4 } from "node:os";
246812
247020
  import { join as join21, resolve as resolve9 } from "node:path";
246813
247021
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -246827,17 +247035,17 @@ async function resolveReleaseDist(spec) {
246827
247035
  const raw2 = process.env[spec.envOverride];
246828
247036
  if (raw2) {
246829
247037
  const dir = spec.envOverrideSubdir ? resolve9(raw2, spec.envOverrideSubdir) : resolve9(raw2);
246830
- if (existsSync5(dir))
247038
+ if (existsSync6(dir))
246831
247039
  return { dir, version: version5, origin: "env" };
246832
247040
  throw new ReleaseDistMissingError(spec.name, dir);
246833
247041
  }
246834
247042
  }
246835
- if (spec.repoLocalPath && existsSync5(spec.repoLocalPath)) {
247043
+ if (spec.repoLocalPath && existsSync6(spec.repoLocalPath)) {
246836
247044
  return { dir: spec.repoLocalPath, version: version5, origin: "repo-local" };
246837
247045
  }
246838
247046
  const cacheDir = join21(spec.dataDir ?? computeDataDir(), `${spec.name}-dist`);
246839
247047
  const cachedTarget = join21(cacheDir, tag2);
246840
- if (existsSync5(cachedTarget))
247048
+ if (existsSync6(cachedTarget))
246841
247049
  return { dir: cachedTarget, version: version5, origin: "cache-hit" };
246842
247050
  const src = spec.source;
246843
247051
  try {
@@ -246872,13 +247080,13 @@ function resolveReleaseDistOrNull(spec) {
246872
247080
  const raw2 = process.env[spec.envOverride];
246873
247081
  if (raw2) {
246874
247082
  const dir = spec.envOverrideSubdir ? resolve9(raw2, spec.envOverrideSubdir) : resolve9(raw2);
246875
- return existsSync5(dir) ? dir : null;
247083
+ return existsSync6(dir) ? dir : null;
246876
247084
  }
246877
247085
  }
246878
- if (spec.repoLocalPath && existsSync5(spec.repoLocalPath))
247086
+ if (spec.repoLocalPath && existsSync6(spec.repoLocalPath))
246879
247087
  return spec.repoLocalPath;
246880
247088
  const cached6 = join21(spec.dataDir ?? computeDataDir(), `${spec.name}-dist`, `v${version5}`);
246881
- return existsSync5(cached6) ? cached6 : null;
247089
+ return existsSync6(cached6) ? cached6 : null;
246882
247090
  }
246883
247091
  function subst(s4, version5, tag2) {
246884
247092
  return s4.replaceAll("{version}", version5).replaceAll("{tag}", tag2);
@@ -278158,7 +278366,7 @@ var require_lib5 = __commonJS((exports) => {
278158
278366
 
278159
278367
  // ../api/src/lib/geo-ip.ts
278160
278368
  import { isIP } from "node:net";
278161
- import { existsSync as existsSync7 } from "node:fs";
278369
+ import { existsSync as existsSync8 } from "node:fs";
278162
278370
  import { mkdir as mkdir2, writeFile as writeFile5 } from "node:fs/promises";
278163
278371
  import { homedir as homedir7 } from "node:os";
278164
278372
  import { dirname as dirname9, join as join25 } from "node:path";
@@ -278180,7 +278388,7 @@ function candidatePaths() {
278180
278388
  async function resolveDbPath() {
278181
278389
  for (const p4 of candidatePaths()) {
278182
278390
  try {
278183
- if (existsSync7(p4))
278391
+ if (existsSync8(p4))
278184
278392
  return p4;
278185
278393
  } catch {}
278186
278394
  }
@@ -296920,7 +297128,7 @@ await __promiseAll([
296920
297128
  init_github_access(),
296921
297129
  init_rollback()
296922
297130
  ]);
296923
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
297131
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
296924
297132
  async function assertGitHubAccessForDeployment(ctx2, deploymentId, organizationId) {
296925
297133
  const dep = await getDeployment(deploymentId, organizationId);
296926
297134
  const project2 = await repos.project.findById(dep.projectId);
@@ -297080,7 +297288,7 @@ async function skipPortCheck(deploymentId, organizationId, target) {
297080
297288
  }
297081
297289
  function readInstanceLog(tail) {
297082
297290
  const path2 = process.env.OPENSHIP_INSTANCE_LOG;
297083
- if (!path2 || !existsSync6(path2))
297291
+ if (!path2 || !existsSync7(path2))
297084
297292
  return [];
297085
297293
  let text3;
297086
297294
  try {
@@ -4,13 +4,13 @@ import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  SystemManager
7
- } from "./chunk-C5OHTYEF.js";
8
- import "./chunk-SQ77DZOI.js";
7
+ } from "./chunk-GKHUSZCD.js";
8
+ import "./chunk-SLTYVHU7.js";
9
9
  import "./chunk-L2VOGB2X.js";
10
10
  import "./chunk-RPHRPFEH.js";
11
11
  import "./chunk-FXTBHTWY.js";
12
12
  import "./chunk-FPRHYBY2.js";
13
- import "./chunk-FQGJZ2HS.js";
13
+ import "./chunk-2D3I5G6P.js";
14
14
  import "./chunk-ILFETZOM.js";
15
15
  import "./chunk-VFINNTUL.js";
16
16
  import "./chunk-6APULWSU.js";