openship 0.6.9 → 0.7.1

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 (62) hide show
  1. package/dist/bare-AB4SMTNJ.js +29 -0
  2. package/dist/{chunk-P7RCYELG.js → chunk-2BAKEGGF.js} +1 -1
  3. package/dist/{chunk-VAI5VOOI.js → chunk-2YAY5XHK.js} +1 -1
  4. package/dist/{chunk-X53JXRS4.js → chunk-37XZTLBJ.js} +3 -3
  5. package/dist/{chunk-R5DC5PEF.js → chunk-3K65MMYV.js} +39 -10
  6. package/dist/{chunk-D3OEMXYP.js → chunk-3WBKLDGC.js} +12 -7
  7. package/dist/{chunk-ENKJ7XIF.js → chunk-3YSSXSC2.js} +12 -12
  8. package/dist/{chunk-3MP6ACKM.js → chunk-7MCPDYKP.js} +799 -165
  9. package/dist/{chunk-D6HU4VUZ.js → chunk-7TQP6723.js} +2 -2
  10. package/dist/{chunk-SK4FFCFR.js → chunk-AMADTKC6.js} +1 -1
  11. package/dist/{chunk-NPSRGIHV.js → chunk-B2BQX6JX.js} +6 -5
  12. package/dist/{chunk-MUWVC5R4.js → chunk-BBPKAQ54.js} +12 -9
  13. package/dist/{chunk-WEZFTL4V.js → chunk-COI4VFRW.js} +217 -2
  14. package/dist/{chunk-5DJOB7IX.js → chunk-CQ2JOXEQ.js} +3 -3
  15. package/dist/{chunk-7PEXM2UI.js → chunk-DQR5TPFB.js} +6 -6
  16. package/dist/{chunk-IAMOUDHY.js → chunk-ETK6IBCH.js} +24 -15
  17. package/dist/{chunk-T46BQOLY.js → chunk-FNWNUYS3.js} +3 -3
  18. package/dist/{chunk-UAWY6X7V.js → chunk-GPTBEPUE.js} +485 -67
  19. package/dist/{chunk-634DDFT5.js → chunk-H36ASAYI.js} +8 -8
  20. package/dist/{chunk-AHJYP7DG.js → chunk-IVMPA6RC.js} +74 -17
  21. package/dist/{chunk-5UXHT6ED.js → chunk-IXBOV7BE.js} +113 -20
  22. package/dist/{chunk-GNOMEMNI.js → chunk-KATXZ5AB.js} +1 -1
  23. package/dist/{chunk-T6DVUX4F.js → chunk-KCMEWNP7.js} +14 -3
  24. package/dist/{chunk-N5YZMO33.js → chunk-L4DRH6ZA.js} +12 -1
  25. package/dist/{chunk-BO6CY4V2.js → chunk-NF7NQOAP.js} +47 -4
  26. package/dist/{chunk-FCGJN5Q5.js → chunk-NIOLZENU.js} +9 -9
  27. package/dist/{chunk-7YBWXEM6.js → chunk-OEYXXSAM.js} +4 -4
  28. package/dist/{chunk-6GU6JSK6.js → chunk-S5GU345P.js} +3 -3
  29. package/dist/{chunk-TAZZZKYO.js → chunk-TKK5NPDQ.js} +2 -2
  30. package/dist/{chunk-NUT6HKK6.js → chunk-WG5PDWZ3.js} +5 -5
  31. package/dist/{chunk-NIYD7MD2.js → chunk-XJSLJLNY.js} +90 -46
  32. package/dist/{cloud-REV3DPJH.js → cloud-T2JOFEQL.js} +20 -20
  33. package/dist/{detect-BBFT5GEU.js → detect-SWLZURYX.js} +5 -5
  34. package/dist/{docker-EG3QSE6I.js → docker-SQNZRZXN.js} +11 -11
  35. package/dist/{docker-edge-executor-E5TOTLMR.js → docker-edge-executor-BSSQQS76.js} +8 -8
  36. package/dist/{edge-import-YJVJUGYC.js → edge-import-LUDX77C2.js} +27 -27
  37. package/dist/{edge-preflight-QUW4BM7N.js → edge-preflight-JAEFWBRI.js} +14 -14
  38. package/dist/{ensure-container-edge-FOEFQ3PI.js → ensure-container-edge-U7FTLFVJ.js} +12 -12
  39. package/dist/{executor-NTWDJ3DB.js → executor-GYRZ7RIU.js} +7 -7
  40. package/dist/host-channel-preflight-U4IZCRXR.js +54 -0
  41. package/dist/index.js +72 -50
  42. package/dist/{local-executor-OQ2GRN7K.js → local-executor-JCR46VAP.js} +3 -3
  43. package/dist/{nginx-4U4C2XUS.js → nginx-YA6IRPJT.js} +10 -10
  44. package/dist/{openresty-lua-LLWT5XQW.js → openresty-lua-DUOB64TP.js} +8 -8
  45. package/dist/{privilege-MNDVMDST.js → privilege-4B2TMVW2.js} +6 -6
  46. package/dist/server/index.js +13286 -5381
  47. package/dist/server/lua/site_logger.lua +18 -1
  48. package/dist/server/migrations/0117_github_installation_workspace_scope.sql +24 -0
  49. package/dist/server/migrations/0118_git_sources.sql +66 -0
  50. package/dist/server/migrations/0119_backup_run_batch.sql +8 -0
  51. package/dist/server/migrations/0120_domain_primary_unique.sql +24 -0
  52. package/dist/server/migrations/0121_host_port_claim_overlap.sql +14 -0
  53. package/dist/server/migrations/0122_data_transfer_staging.sql +45 -0
  54. package/dist/server/migrations/meta/_journal.json +42 -0
  55. package/dist/setup-T3WN3HRS.js +25 -0
  56. package/dist/{ssh-client-2XWZCMGI.js → ssh-client-FZDWP5PQ.js} +3 -3
  57. package/dist/up-U73RUN5R.js +61 -0
  58. package/package.json +1 -1
  59. package/dist/bare-FA66N7GC.js +0 -29
  60. package/dist/host-channel-preflight-OI2TRHSG.js +0 -54
  61. package/dist/setup-IFIMPQZJ.js +0 -25
  62. package/dist/up-RC5ZQIAD.js +0 -61
@@ -10,28 +10,28 @@ import {
10
10
  readComposeEnvFile,
11
11
  resolveLocalEnvironmentSync,
12
12
  thisHost
13
- } from "./chunk-5UXHT6ED.js";
13
+ } from "./chunk-IXBOV7BE.js";
14
14
  import {
15
15
  OS_DIR
16
16
  } from "./chunk-ELGRMZOW.js";
17
17
  import {
18
18
  systemCatalog
19
- } from "./chunk-NIYD7MD2.js";
19
+ } from "./chunk-XJSLJLNY.js";
20
20
  import {
21
21
  EDGE_CONTAINER_MOUNTS,
22
22
  EDGE_HOST_STATE_DIR
23
- } from "./chunk-7PEXM2UI.js";
23
+ } from "./chunk-DQR5TPFB.js";
24
24
  import {
25
25
  DEFAULT_DOCKER_SOCKET_PATH,
26
26
  resolveLocalDockerSocketPath
27
- } from "./chunk-3MP6ACKM.js";
27
+ } from "./chunk-7MCPDYKP.js";
28
28
  import {
29
29
  LocalExecutor
30
- } from "./chunk-T6DVUX4F.js";
30
+ } from "./chunk-KCMEWNP7.js";
31
31
  import {
32
32
  invalidateEdgeContainer,
33
33
  sanitizeEdgeVhosts
34
- } from "./chunk-X53JXRS4.js";
34
+ } from "./chunk-37XZTLBJ.js";
35
35
  import {
36
36
  DEFAULT_IMAGE_REGISTRY,
37
37
  HOST_CHANNEL_BLOCKED,
@@ -51,7 +51,7 @@ import {
51
51
  hostFirewallRule,
52
52
  hostFirewallRuleRemoval,
53
53
  wrapText
54
- } from "./chunk-WEZFTL4V.js";
54
+ } from "./chunk-COI4VFRW.js";
55
55
 
56
56
  // src/lib/host-channel-preflight.ts
57
57
  import { spawnSync as spawnSync4 } from "child_process";
@@ -1627,7 +1627,7 @@ function operatorEnvPassthrough(managed, prev) {
1627
1627
  }
1628
1628
  var PRESERVED_ENV_HEADER = "# Preserved from your existing .env \u2014 set by you, not by `openship up` (#485).";
1629
1629
  function resolveImageVersion(opts) {
1630
- return opts.version?.trim() || process.env.OPENSHIP_VERSION?.trim() || (true ? "0.6.9" : "latest");
1630
+ return opts.version?.trim() || process.env.OPENSHIP_VERSION?.trim() || (true ? "0.7.1" : "latest");
1631
1631
  }
1632
1632
  function managedEnvLines(opts, host, cfg, prev) {
1633
1633
  const lines = [
@@ -5,7 +5,7 @@ const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  getTarCreateEnv,
7
7
  prepareSourceTarArgs
8
- } from "./chunk-VAI5VOOI.js";
8
+ } from "./chunk-2YAY5XHK.js";
9
9
  import {
10
10
  TRANSFER_EXCLUDES,
11
11
  nodeBinDirs,
@@ -13,7 +13,7 @@ import {
13
13
  packageManagerEnsureCommand,
14
14
  safeErrorMessage,
15
15
  shellQuote
16
- } from "./chunk-WEZFTL4V.js";
16
+ } from "./chunk-COI4VFRW.js";
17
17
 
18
18
  // ../../packages/adapters/src/runtime/git-clone.ts
19
19
  var sq = shellQuote;
@@ -75,18 +75,48 @@ function assembleGitClone(auth) {
75
75
  );
76
76
  }
77
77
  if (auth.gitCredentialHelperPath) {
78
- return invocation(
78
+ const base = invocation(
79
79
  auth.repoUrl,
80
80
  [
81
81
  TERMINAL_PROMPT,
82
- ["GIT_CONFIG_COUNT", "2", false],
82
+ // An empty helper value resets helpers inherited from the host before
83
+ // adding the deployment-scoped relay. Without the reset, a server's
84
+ // stale/global helper can answer first and silently override the
85
+ // forwarded identity named in the deployment log.
86
+ ["GIT_CONFIG_COUNT", "3", false],
83
87
  ["GIT_CONFIG_KEY_0", "credential.helper", false],
84
- ["GIT_CONFIG_VALUE_0", auth.gitCredentialHelperPath, true],
85
- ["GIT_CONFIG_KEY_1", "credential.useHttpPath", false],
86
- ["GIT_CONFIG_VALUE_1", "true", false]
88
+ ["GIT_CONFIG_VALUE_0", "", true],
89
+ ["GIT_CONFIG_KEY_1", "credential.helper", false],
90
+ ["GIT_CONFIG_VALUE_1", auth.gitCredentialHelperPath, true],
91
+ ["GIT_CONFIG_KEY_2", "credential.useHttpPath", false],
92
+ ["GIT_CONFIG_VALUE_2", "true", false]
87
93
  ],
88
94
  false
89
95
  );
96
+ let url;
97
+ try {
98
+ url = new URL(auth.repoUrl);
99
+ } catch {
100
+ throw new Error("Git credential forwarding requires an absolute HTTPS repository URL");
101
+ }
102
+ if (url.protocol !== "https:") {
103
+ throw new Error("Git credential forwarding requires an HTTPS repository URL");
104
+ }
105
+ const protocol = url.protocol.slice(0, -1);
106
+ const path = url.pathname.replace(/^\/+/, "");
107
+ const headerVar = "OPENSHIP_GIT_AUTH_HEADER";
108
+ const headerCommand = `${sq(auth.gitCredentialHelperPath)} auth-header ${sq(protocol)} ${sq(url.hostname)} ${sq(path)}`;
109
+ return {
110
+ ...base,
111
+ // `auth-header` returns only the Basic header, never prints it to the
112
+ // build log, and keeps the token out of the command text and filesystem.
113
+ // The value exists only in the remote shell/git process for this command.
114
+ shellPrelude: `${headerVar}="$(${headerCommand})" && test -n "$${headerVar}"`,
115
+ // The argv representation above remains a valid challenge-based helper
116
+ // invocation for local spawn users. Remote shell users call
117
+ // gitShellCommand(), which adds the preemptive header as config slot 3.
118
+ gitEnv: `${base.gitEnv.replace("GIT_CONFIG_COUNT=3", "GIT_CONFIG_COUNT=4")} GIT_CONFIG_KEY_3=http.extraHeader GIT_CONFIG_VALUE_3="$${headerVar}"`
119
+ };
90
120
  }
91
121
  if (auth.ambient) {
92
122
  if (auth.ambient.via === "ssh") {
@@ -112,6 +142,12 @@ function assembleGitClone(auth) {
112
142
  true
113
143
  );
114
144
  }
145
+ function gitShellCommand(invocation2, args) {
146
+ const env = invocation2.gitEnv ? `${invocation2.gitEnv} ` : "";
147
+ const flags = invocation2.credFlag ? `${invocation2.credFlag} ` : "";
148
+ const command = `${env}git ${flags}${args}`;
149
+ return invocation2.shellPrelude ? `${invocation2.shellPrelude} && ${command}` : command;
150
+ }
115
151
 
116
152
  // ../../packages/adapters/src/runtime/git-ssh-material.ts
117
153
  import { mkdir, rm, writeFile } from "fs/promises";
@@ -253,31 +289,49 @@ async function runBuildPipeline(env, config, logger) {
253
289
  config.gitSsh
254
290
  );
255
291
  }
256
- const { cloneUrl, gitEnv: GIT_ENV, credFlag: CRED } = assembleGitClone({
292
+ const gitInvocation = assembleGitClone({
257
293
  repoUrl: config.repoUrl,
258
294
  gitToken: config.gitToken,
259
295
  gitCredentialHelperPath: config.gitCredentialHelperPath,
260
296
  ssh: sshMaterial,
261
297
  ambient: config.gitAmbient
262
298
  });
299
+ const { cloneUrl, credFlag: CRED } = gitInvocation;
263
300
  try {
264
301
  if (config.commitSha) {
265
- try {
266
- await exec(
267
- `${GIT_ENV} git ${CRED} clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)} && cd ${sq(env.projectDir)} && git ${CRED} -c advice.detachedHead=false checkout ${sq(config.commitSha)}`
268
- );
269
- } catch {
302
+ await exec(
303
+ gitShellCommand(
304
+ gitInvocation,
305
+ `clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)}`
306
+ )
307
+ );
308
+ const commitPresent = await exec(
309
+ `cd ${sq(env.projectDir)} && git ${CRED} cat-file -e ${sq(`${config.commitSha}^{commit}`)}`
310
+ ).then(
311
+ () => true,
312
+ () => false
313
+ );
314
+ if (!commitPresent) {
270
315
  logger.log(
271
- `Checkout of ${config.commitSha} failed inside the depth-50 clone; running git fetch --unshallow and retrying.`,
316
+ `Commit ${config.commitSha} is not in the depth-50 clone; fetching full history before checkout.`,
272
317
  "warn"
273
318
  );
274
319
  await exec(
275
- `cd ${sq(env.projectDir)} && ${GIT_ENV} git ${CRED} fetch --progress --unshallow && git ${CRED} -c advice.detachedHead=false checkout ${sq(config.commitSha)}`
320
+ `cd ${sq(env.projectDir)} && ${gitShellCommand(gitInvocation, "fetch --progress --unshallow")}`
276
321
  );
277
322
  }
323
+ await exec(
324
+ `cd ${sq(env.projectDir)} && ${gitShellCommand(
325
+ gitInvocation,
326
+ `-c advice.detachedHead=false checkout ${sq(config.commitSha)}`
327
+ )}`
328
+ );
278
329
  } else {
279
330
  await exec(
280
- `${GIT_ENV} git ${CRED} clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)}`
331
+ gitShellCommand(
332
+ gitInvocation,
333
+ `clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)}`
334
+ )
281
335
  );
282
336
  }
283
337
  } finally {
@@ -342,7 +396,9 @@ function parseLogLevel(message) {
342
396
  function detectBuildKillHint(output) {
343
397
  if (!output) return null;
344
398
  const tail = output.slice(-4096);
345
- if (/\bsigkill\b|\bKilled\b|out of memory|JavaScript heap out of memory|Allocation failed/i.test(tail)) {
399
+ if (/\bsigkill\b|\bKilled\b|out of memory|JavaScript heap out of memory|Allocation failed/i.test(
400
+ tail
401
+ )) {
346
402
  return "Build process was killed - typically because the target ran out of memory during the build. Increase RAM on the target, add swap, or build locally and ship the dist.";
347
403
  }
348
404
  return null;
@@ -913,6 +969,7 @@ export {
913
969
  sq,
914
970
  injectGitToken,
915
971
  assembleGitClone,
972
+ gitShellCommand,
916
973
  materializeGitSsh,
917
974
  shellGitSshWriter,
918
975
  localGitSshWriter,
@@ -134,10 +134,10 @@ import {
134
134
  } from "./chunk-ZWLDLTJP.js";
135
135
  import {
136
136
  CloudRuntime
137
- } from "./chunk-MUWVC5R4.js";
137
+ } from "./chunk-BBPKAQ54.js";
138
138
  import {
139
139
  OPENRESTY_DEFAULT_PATHS
140
- } from "./chunk-7PEXM2UI.js";
140
+ } from "./chunk-DQR5TPFB.js";
141
141
  import {
142
142
  DockerRuntime,
143
143
  daemonConnectionFrom,
@@ -146,26 +146,26 @@ import {
146
146
  resolveExecExitCode,
147
147
  startAttachStream,
148
148
  startExecStream
149
- } from "./chunk-3MP6ACKM.js";
149
+ } from "./chunk-7MCPDYKP.js";
150
150
  import {
151
151
  BareRuntime
152
- } from "./chunk-ENKJ7XIF.js";
152
+ } from "./chunk-3YSSXSC2.js";
153
153
  import {
154
154
  createExecutor
155
- } from "./chunk-UAWY6X7V.js";
155
+ } from "./chunk-GPTBEPUE.js";
156
156
  import {
157
157
  envOps
158
- } from "./chunk-GNOMEMNI.js";
158
+ } from "./chunk-KATXZ5AB.js";
159
159
  import {
160
160
  ENVIRONMENT_PROBE_SCRIPT,
161
161
  ENVIRONMENT_PROBE_TIMEOUT_MS,
162
162
  cacheEnvironment,
163
163
  parseEnvironmentProbe,
164
164
  profileIsFresh
165
- } from "./chunk-NPSRGIHV.js";
165
+ } from "./chunk-B2BQX6JX.js";
166
166
  import {
167
167
  MAIL_HOST_STATE_DIR
168
- } from "./chunk-D6HU4VUZ.js";
168
+ } from "./chunk-7TQP6723.js";
169
169
  import {
170
170
  FAMILY_BY_DISTRO,
171
171
  isAwsS3Endpoint,
@@ -177,7 +177,7 @@ import {
177
177
  validateBackupPath,
178
178
  validateClearPath,
179
179
  withTimeout
180
- } from "./chunk-WEZFTL4V.js";
180
+ } from "./chunk-COI4VFRW.js";
181
181
 
182
182
  // ../../packages/adapters/src/runtime/image-transfer.ts
183
183
  import { Transform } from "stream";
@@ -1141,6 +1141,8 @@ function demuxDockerStream(source, stdout, stderr, onError) {
1141
1141
 
1142
1142
  // ../../packages/adapters/src/backup/executors/docker.ts
1143
1143
  var HELPER_IMAGE = "alpine:3";
1144
+ var BIND_PROBE_TARGET = "/__openship_bind_source";
1145
+ var DOCKER_MODE_DIRECTORY = 2 ** 31;
1144
1146
  var ARTIFACT_BUFFER_BYTES = 4 * 1024 * 1024;
1145
1147
  var DEFAULT_HELPER_IDLE_MS = 10 * 60 * 1e3;
1146
1148
  var DEFAULT_HELPER_TIMEOUT_MS = 6 * 60 * 60 * 1e3;
@@ -1216,6 +1218,29 @@ function parseVolumeSpec(spec) {
1216
1218
  const type = isHostPathSource(source) ? "bind" : "volume";
1217
1219
  return { source, target, type };
1218
1220
  }
1221
+ function archivePathIsDirectory(response, path) {
1222
+ const rawHeader = response.headers?.["x-docker-container-path-stat"];
1223
+ const encoded = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
1224
+ if (!encoded) {
1225
+ throw new Error(`Docker returned no path-stat header while inspecting ${path}`);
1226
+ }
1227
+ let stat;
1228
+ try {
1229
+ stat = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
1230
+ } catch (err) {
1231
+ throw new Error(`Docker returned an invalid path-stat header while inspecting ${path}`, {
1232
+ cause: err
1233
+ });
1234
+ }
1235
+ const mode = stat?.mode;
1236
+ if (typeof mode !== "number" || !Number.isSafeInteger(mode)) {
1237
+ throw new Error(`Docker returned no valid path mode while inspecting ${path}`);
1238
+ }
1239
+ return mode >= DOCKER_MODE_DIRECTORY;
1240
+ }
1241
+ function isMissingBindSourceError(err) {
1242
+ return err?.statusCode === 400 && /bind source path does not exist/i.test(safeErrorMessage(err));
1243
+ }
1219
1244
  var DockerBackupExecutor = class {
1220
1245
  constructor(runtime) {
1221
1246
  this.runtime = runtime;
@@ -1244,23 +1269,82 @@ var DockerBackupExecutor = class {
1244
1269
  );
1245
1270
  }
1246
1271
  }
1272
+ /**
1273
+ * Ask the Docker daemon about a path in a container's mount namespace.
1274
+ *
1275
+ * The API may itself run in a container or connect to Docker over SSH/TLS, so
1276
+ * `node:fs` would inspect the wrong filesystem. Docker's archive-info endpoint
1277
+ * returns the daemon-side path's Go FileMode without copying any archive bytes.
1278
+ */
1279
+ async containerPathIsDirectory(container, path) {
1280
+ let response;
1281
+ try {
1282
+ response = await container.infoArchive({ path });
1283
+ return archivePathIsDirectory(response, path);
1284
+ } finally {
1285
+ response?.resume?.();
1286
+ }
1287
+ }
1288
+ /**
1289
+ * Classify a recorded bind source when there is no service container to inspect.
1290
+ * A never-started helper gives Docker a mount namespace, and archive-info reports
1291
+ * the source type from the daemon host. `Mounts` (rather than legacy `Binds`) is
1292
+ * deliberate: a missing source is rejected instead of being created as a directory.
1293
+ */
1294
+ async declaredBindIsDirectory(sourcePath) {
1295
+ try {
1296
+ return await this.withHelper(
1297
+ {
1298
+ Image: HELPER_IMAGE,
1299
+ NetworkDisabled: true,
1300
+ HostConfig: {
1301
+ Mounts: [
1302
+ {
1303
+ Type: "bind",
1304
+ Source: sourcePath,
1305
+ Target: BIND_PROBE_TARGET,
1306
+ ReadOnly: true
1307
+ }
1308
+ ]
1309
+ }
1310
+ },
1311
+ (helper) => this.containerPathIsDirectory(helper, BIND_PROBE_TARGET)
1312
+ );
1313
+ } catch (err) {
1314
+ if (isMissingBindSourceError(err)) return true;
1315
+ throw new Error(
1316
+ `Could not inspect bind mount source "${sourcePath}" on the Docker host: ` + safeErrorMessage(err),
1317
+ { cause: err }
1318
+ );
1319
+ }
1320
+ }
1247
1321
  async listSources(service) {
1248
1322
  if (service.containerId) {
1323
+ const container = this.dockerode.getContainer(service.containerId);
1324
+ let data = null;
1249
1325
  try {
1250
- const data = await this.dockerode.getContainer(service.containerId).inspect();
1251
- const mounts = data.Mounts ?? [];
1252
- return mounts.filter((m2) => m2.Type === "volume" || m2.Type === "bind").map(
1253
- (m2, i2) => ({
1254
- id: m2.Name ?? m2.Source ?? `mount-${i2}`,
1255
- target: m2.Destination ?? "",
1256
- source: m2.Name ?? m2.Source ?? "",
1257
- type: m2.Type ?? "volume"
1258
- })
1259
- );
1326
+ data = await container.inspect();
1260
1327
  } catch {
1261
1328
  }
1329
+ if (data) {
1330
+ const mounts = data.Mounts ?? [];
1331
+ const sources2 = [];
1332
+ for (const [i2, mount] of mounts.entries()) {
1333
+ if (mount.Type !== "volume" && mount.Type !== "bind") continue;
1334
+ if (mount.Type === "bind" && (!mount.Destination || !await this.containerPathIsDirectory(container, mount.Destination))) {
1335
+ continue;
1336
+ }
1337
+ sources2.push({
1338
+ id: mount.Name ?? mount.Source ?? `mount-${i2}`,
1339
+ target: mount.Destination ?? "",
1340
+ source: mount.Name ?? mount.Source ?? "",
1341
+ type: mount.Type
1342
+ });
1343
+ }
1344
+ return sources2;
1345
+ }
1262
1346
  }
1263
- return service.volumes.map((spec, i2) => {
1347
+ const sources = service.volumes.map((spec, i2) => {
1264
1348
  const parsed = parseVolumeSpec(spec);
1265
1349
  if (!parsed || !parsed.source) return null;
1266
1350
  const source = parsed.type === "volume" && service.namespaceVolumes ? ensureScopedVolumeName(service.projectSlug, parsed.source) : parsed.source;
@@ -1271,6 +1355,15 @@ var DockerBackupExecutor = class {
1271
1355
  type: parsed.type
1272
1356
  };
1273
1357
  }).filter((x2) => x2 !== null);
1358
+ if (!sources.some((source) => source.type === "bind")) return sources;
1359
+ await this.ensureImage(HELPER_IMAGE);
1360
+ const backupable = [];
1361
+ for (const source of sources) {
1362
+ if (source.type !== "bind" || await this.declaredBindIsDirectory(source.source)) {
1363
+ backupable.push(source);
1364
+ }
1365
+ }
1366
+ return backupable;
1274
1367
  }
1275
1368
  /**
1276
1369
  * The container's own `Config.Env`, as `KEY=value` pairs already split.
@@ -9,7 +9,7 @@ import {
9
9
  firewallPersists,
10
10
  refused,
11
11
  shellQuote
12
- } from "./chunk-WEZFTL4V.js";
12
+ } from "./chunk-COI4VFRW.js";
13
13
 
14
14
  // ../../packages/adapters/src/system/environment-ops.ts
15
15
  function opScript(steps) {
@@ -8,13 +8,14 @@ import {
8
8
  getLocalShellPath,
9
9
  logEntry,
10
10
  sq
11
- } from "./chunk-P7RCYELG.js";
11
+ } from "./chunk-2BAKEGGF.js";
12
12
 
13
13
  // ../../packages/adapters/src/system/local-executor.ts
14
14
  import { exec, spawn } from "child_process";
15
15
  import {
16
16
  access,
17
17
  mkdir as fsMkdir,
18
+ open as fsOpen,
18
19
  readFile as fsReadFile,
19
20
  rename as fsRename,
20
21
  rm as fsRm,
@@ -82,9 +83,19 @@ var LocalExecutor = class {
82
83
  });
83
84
  });
84
85
  }
85
- async writeFile(path, content) {
86
+ async writeFile(path, content, opts) {
86
87
  await fsMkdir(dirname(path), { recursive: true });
87
- await fsWriteFile(path, content, "utf-8");
88
+ if (opts?.mode === void 0) {
89
+ await fsWriteFile(path, content, "utf-8");
90
+ return;
91
+ }
92
+ const file = await fsOpen(path, "w", opts.mode);
93
+ try {
94
+ await file.chmod(opts.mode);
95
+ await file.writeFile(content, "utf-8");
96
+ } finally {
97
+ await file.close();
98
+ }
88
99
  }
89
100
  async rename(from, to) {
90
101
  await fsRename(from, to);
@@ -44,6 +44,15 @@ var SshDisconnectedError = class extends Error {
44
44
  this.name = "SshDisconnectedError";
45
45
  }
46
46
  };
47
+ var SshExecRequestError = class extends Error {
48
+ constructor(cause) {
49
+ super(cause.message, { cause });
50
+ this.name = "SshExecRequestError";
51
+ }
52
+ };
53
+ function isSshExecRequestError(err) {
54
+ return err instanceof SshExecRequestError || err instanceof Error && err.name === "SshExecRequestError";
55
+ }
47
56
  function isSshDisconnectedError(err) {
48
57
  return err instanceof SshDisconnectedError || err instanceof Error && err.name === "SshDisconnectedError";
49
58
  }
@@ -67,7 +76,7 @@ function isRetryableRemoteConnectionError(err) {
67
76
  );
68
77
  }
69
78
  function isRemoteConnectionError(err) {
70
- return isSshAuthError(err) || isRetryableRemoteConnectionError(err);
79
+ return isSshAuthError(err) || isRetryableRemoteConnectionError(err) || isSshExecRequestError(err);
71
80
  }
72
81
  var MAX_COMMAND_CHARS = 120;
73
82
  function commandForError(command) {
@@ -100,6 +109,8 @@ function formatDuration(startMs) {
100
109
 
101
110
  export {
102
111
  SshDisconnectedError,
112
+ SshExecRequestError,
113
+ isSshExecRequestError,
103
114
  isSshAuthError,
104
115
  HostChannelUnavailableError,
105
116
  isRetryableRemoteConnectionError,
@@ -7,13 +7,43 @@ import {
7
7
  isContainerPortForwarder,
8
8
  managedDeploymentIdFromUnit,
9
9
  tryExec
10
- } from "./chunk-D6HU4VUZ.js";
10
+ } from "./chunk-7TQP6723.js";
11
11
  import {
12
12
  shellQuote
13
- } from "./chunk-WEZFTL4V.js";
13
+ } from "./chunk-COI4VFRW.js";
14
14
 
15
15
  // ../../packages/adapters/src/system/port-listen.ts
16
16
  var PROC_NET_TCP_FILES = ["/proc/net/tcp", "/proc/net/tcp6"];
17
+ var PROBE_LISTENING = "__OPENSHIP_PORT_LISTENING__";
18
+ var PROBE_FREE = "__OPENSHIP_PORT_FREE__";
19
+ var PROBE_UNAVAILABLE = "__OPENSHIP_PORT_UNAVAILABLE__";
20
+ function buildPortProbeCommand(port) {
21
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
22
+ throw new RangeError(`Invalid TCP port: ${port}`);
23
+ }
24
+ return [
25
+ `lsof_output="$(lsof -nP -t -iTCP:${port} -sTCP:LISTEN 2>&1)"`,
26
+ `lsof_status=$?`,
27
+ `if [ "$lsof_status" -eq 0 ] && [ -n "$lsof_output" ] && printf '%s\\n' "$lsof_output" | grep -Eqv '^[0-9]+$'; then`,
28
+ ` : # Unexpected lsof output: fall through to ss instead of guessing.`,
29
+ `elif [ "$lsof_status" -eq 0 ] && [ -n "$lsof_output" ]; then`,
30
+ ` printf '${PROBE_LISTENING}\\n'`,
31
+ ` exit 0`,
32
+ `elif [ "$lsof_status" -eq 1 ] && [ -z "$lsof_output" ]; then`,
33
+ ` printf '${PROBE_FREE}\\n'`,
34
+ ` exit 0`,
35
+ `fi`,
36
+ `ss_output="$(ss -tln "sport = :${port}" 2>&1)"`,
37
+ `ss_status=$?`,
38
+ `if [ "$ss_status" -eq 0 ] && printf '%s\\n' "$ss_output" | grep -q LISTEN; then`,
39
+ ` printf '${PROBE_LISTENING}\\n'`,
40
+ `elif [ "$ss_status" -eq 0 ]; then`,
41
+ ` printf '${PROBE_FREE}\\n'`,
42
+ `else`,
43
+ ` printf '${PROBE_UNAVAILABLE}\\n'`,
44
+ `fi`
45
+ ].join("\n");
46
+ }
17
47
  var TCP_LISTEN = "0A";
18
48
  function parseListeningPorts(procText) {
19
49
  const ports = /* @__PURE__ */ new Set();
@@ -33,7 +63,15 @@ function parseListeningPorts(procText) {
33
63
  }
34
64
  return ports;
35
65
  }
66
+ function parsePortProbeOutput(out) {
67
+ const trimmed = out.trim();
68
+ if (trimmed === PROBE_LISTENING) return true;
69
+ if (trimmed === PROBE_FREE) return false;
70
+ if (trimmed === PROBE_UNAVAILABLE) return null;
71
+ return null;
72
+ }
36
73
  async function probePortListeningOnce(executor, port) {
74
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
37
75
  const readable = [];
38
76
  for (const file of PROC_NET_TCP_FILES) {
39
77
  try {
@@ -41,8 +79,13 @@ async function probePortListeningOnce(executor, port) {
41
79
  } catch {
42
80
  }
43
81
  }
44
- if (readable.length === 0) return null;
45
- return parseListeningPorts(readable.join("\n")).has(port);
82
+ if (readable.length > 0) return parseListeningPorts(readable.join("\n")).has(port);
83
+ try {
84
+ const out = await executor.exec(buildPortProbeCommand(port), { timeout: 5e3 });
85
+ return parsePortProbeOutput(out);
86
+ } catch {
87
+ return null;
88
+ }
46
89
  }
47
90
  function delay(ms) {
48
91
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -28,7 +28,7 @@ import {
28
28
  resolvePorts,
29
29
  sourceBuildDir,
30
30
  verifyHostChannel
31
- } from "./chunk-634DDFT5.js";
31
+ } from "./chunk-H36ASAYI.js";
32
32
  import {
33
33
  bootstrapAdmin,
34
34
  importMigratedSites,
@@ -36,18 +36,18 @@ import {
36
36
  internalPost,
37
37
  mintBareInternalToken,
38
38
  waitHealthy
39
- } from "./chunk-7YBWXEM6.js";
39
+ } from "./chunk-OEYXXSAM.js";
40
40
  import {
41
41
  startUnitHint,
42
42
  thisHost
43
- } from "./chunk-5UXHT6ED.js";
43
+ } from "./chunk-IXBOV7BE.js";
44
44
  import {
45
45
  completeHostEdge,
46
46
  markStoppedProxyImported,
47
47
  planAndApplyHostEdge,
48
48
  previewHostEdge,
49
49
  rollbackHostEdge
50
- } from "./chunk-NUT6HKK6.js";
50
+ } from "./chunk-WG5PDWZ3.js";
51
51
  import {
52
52
  AUTH_SECRET_FILE,
53
53
  DATA_DIR,
@@ -58,17 +58,17 @@ import {
58
58
  } from "./chunk-ELGRMZOW.js";
59
59
  import {
60
60
  LocalExecutor
61
- } from "./chunk-T6DVUX4F.js";
61
+ } from "./chunk-KCMEWNP7.js";
62
62
  import {
63
63
  edgeCrashReason,
64
64
  edgeIsBroken
65
- } from "./chunk-X53JXRS4.js";
65
+ } from "./chunk-37XZTLBJ.js";
66
66
  import {
67
67
  EDGE_CONTAINER_NAME
68
- } from "./chunk-D6HU4VUZ.js";
68
+ } from "./chunk-7TQP6723.js";
69
69
  import {
70
70
  isValidEmail
71
- } from "./chunk-WEZFTL4V.js";
71
+ } from "./chunk-COI4VFRW.js";
72
72
 
73
73
  // src/commands/up.ts
74
74
  import { Command } from "commander";
@@ -1564,7 +1564,7 @@ async function runForeground(opts, source) {
1564
1564
  const uiSpinner = ora("Preparing the dashboard\u2026").start();
1565
1565
  try {
1566
1566
  const bundle = await ensureDashboard({
1567
- tag: source ? "local" : opts.uiVersion || `v${"0.6.9"}`,
1567
+ tag: source ? "local" : opts.uiVersion || `v${"0.7.1"}`,
1568
1568
  onProgress: (received, total) => {
1569
1569
  if (total) {
1570
1570
  uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
@@ -7,20 +7,20 @@ import {
7
7
  composeInstallExists,
8
8
  readComposeEnvFile,
9
9
  startUnitHint
10
- } from "./chunk-5UXHT6ED.js";
10
+ } from "./chunk-IXBOV7BE.js";
11
11
  import {
12
12
  INTERNAL_TOKEN_FILE,
13
13
  OS_DIR
14
14
  } from "./chunk-ELGRMZOW.js";
15
15
  import {
16
16
  LocalExecutor
17
- } from "./chunk-T6DVUX4F.js";
17
+ } from "./chunk-KCMEWNP7.js";
18
18
  import {
19
19
  edgeCrashReason
20
- } from "./chunk-X53JXRS4.js";
20
+ } from "./chunk-37XZTLBJ.js";
21
21
  import {
22
22
  EDGE_CONTAINER_NAME
23
- } from "./chunk-D6HU4VUZ.js";
23
+ } from "./chunk-7TQP6723.js";
24
24
 
25
25
  // src/lib/edge-import.ts
26
26
  import chalk from "chalk";
@@ -13,14 +13,14 @@ import {
13
13
  resolveDockerRootDirectory,
14
14
  resolveDockerfileCandidates,
15
15
  resolveWithinDirectory
16
- } from "./chunk-AHJYP7DG.js";
16
+ } from "./chunk-IVMPA6RC.js";
17
17
  import {
18
18
  getTarCreateEnv,
19
19
  prepareSourceTarArgs
20
- } from "./chunk-VAI5VOOI.js";
20
+ } from "./chunk-2YAY5XHK.js";
21
21
  import {
22
22
  isValidEnvKey
23
- } from "./chunk-WEZFTL4V.js";
23
+ } from "./chunk-COI4VFRW.js";
24
24
  import {
25
25
  __commonJS,
26
26
  __toESM