@sealant/mend 0.24.1 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/dist/{dashboard-YEU7SDYB.js → dashboard-JIWYW3EM.js} +24 -7
- package/dist/main.js +225 -457
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +1 -1
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +1 -1
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect.1 +1 -1
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-man.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +1 -1
- package/man/mend-server-logs.1 +1 -1
- package/man/mend-server-restart.1 +3 -3
- package/man/mend-server-setup.1 +6 -9
- package/man/mend-server-start.1 +3 -3
- package/man/mend-server-status.1 +1 -1
- package/man/mend-server-stop.1 +1 -1
- package/man/mend-server-upgrade.1 +3 -3
- package/man/mend-server.1 +1 -1
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +1 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +1 -1
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-ssh-setup.1 +1 -1
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +1 -1
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
|
|
22
22
|
// src/main.ts
|
|
23
23
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "node:child_process";
|
|
24
|
-
import * as
|
|
24
|
+
import * as fs8 from "node:fs";
|
|
25
25
|
import { createRequire } from "node:module";
|
|
26
26
|
import * as net3 from "node:net";
|
|
27
27
|
import * as os6 from "node:os";
|
|
28
|
-
import * as
|
|
28
|
+
import * as path9 from "node:path";
|
|
29
29
|
|
|
30
30
|
// src/agent-share.ts
|
|
31
31
|
import * as net from "node:net";
|
|
@@ -40,7 +40,7 @@ var bridgeUrlOf = (serverUrl, token, host) => {
|
|
|
40
40
|
var retryDelayMs = (attempt2) => Math.min(3e4, 1e3 * 2 ** Math.min(Math.max(attempt2 - 1, 0), 5));
|
|
41
41
|
var SSH_AGENTC_REQUEST_IDENTITIES = 11;
|
|
42
42
|
var SSH_AGENTC_SIGN_REQUEST = 13;
|
|
43
|
-
var askLocalAgent = (agentSock, payload) => new Promise((
|
|
43
|
+
var askLocalAgent = (agentSock, payload) => new Promise((resolve4, reject) => {
|
|
44
44
|
const connection = net.connect(agentSock, () => {
|
|
45
45
|
connection.write(payload);
|
|
46
46
|
});
|
|
@@ -54,12 +54,12 @@ var askLocalAgent = (agentSock, payload) => new Promise((resolve5, reject) => {
|
|
|
54
54
|
if (pending.length >= 4 && pending.length >= 4 + pending.readUInt32BE(0)) {
|
|
55
55
|
clearTimeout(timer);
|
|
56
56
|
connection.end();
|
|
57
|
-
|
|
57
|
+
resolve4(pending.subarray(0, 4 + pending.readUInt32BE(0)));
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
-
connection.on("error", (
|
|
60
|
+
connection.on("error", (error) => {
|
|
61
61
|
clearTimeout(timer);
|
|
62
|
-
reject(
|
|
62
|
+
reject(error);
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
var sleep = (ms, signal) => delay(ms, void 0, { signal }).then(
|
|
@@ -74,12 +74,12 @@ var shareAgent = async (options) => {
|
|
|
74
74
|
const ws = new WebSocket(url);
|
|
75
75
|
const onAbort = () => ws.close();
|
|
76
76
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
77
|
-
const closed = new Promise((
|
|
78
|
-
ws.addEventListener("close", () =>
|
|
77
|
+
const closed = new Promise((resolve4) => {
|
|
78
|
+
ws.addEventListener("close", () => resolve4(), { once: true });
|
|
79
79
|
});
|
|
80
|
-
const opened = await new Promise((
|
|
81
|
-
ws.addEventListener("open", () =>
|
|
82
|
-
ws.addEventListener("error", () =>
|
|
80
|
+
const opened = await new Promise((resolve4) => {
|
|
81
|
+
ws.addEventListener("open", () => resolve4(true), { once: true });
|
|
82
|
+
ws.addEventListener("error", () => resolve4(false), { once: true });
|
|
83
83
|
});
|
|
84
84
|
if (opened) {
|
|
85
85
|
attempt2 = 0;
|
|
@@ -112,8 +112,8 @@ var shareAgent = async (options) => {
|
|
|
112
112
|
if (type === SSH_AGENTC_SIGN_REQUEST) {
|
|
113
113
|
onEvent({ kind: "signed", context, seconds: (Date.now() - started) / 1e3 });
|
|
114
114
|
}
|
|
115
|
-
} catch (
|
|
116
|
-
const message =
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
117
|
if (ws.readyState === WebSocket.OPEN) {
|
|
118
118
|
ws.send(JSON.stringify({ t: "err", id, message }));
|
|
119
119
|
}
|
|
@@ -171,13 +171,13 @@ var parsePngfHex = (output) => {
|
|
|
171
171
|
return Buffer.from(match[1], "hex");
|
|
172
172
|
};
|
|
173
173
|
var MAX_BYTES = 32 * 1024 * 1024;
|
|
174
|
-
var run = (command, args) => new Promise((
|
|
174
|
+
var run = (command, args) => new Promise((resolve4) => {
|
|
175
175
|
execFile(
|
|
176
176
|
command,
|
|
177
177
|
[...args],
|
|
178
178
|
{ encoding: "buffer", maxBuffer: MAX_BYTES, timeout: 3e3 },
|
|
179
|
-
(
|
|
180
|
-
|
|
179
|
+
(error, stdout) => {
|
|
180
|
+
resolve4(error === null ? stdout : null);
|
|
181
181
|
}
|
|
182
182
|
);
|
|
183
183
|
});
|
|
@@ -1037,7 +1037,7 @@ var COMMANDS = [
|
|
|
1037
1037
|
section: "this machine",
|
|
1038
1038
|
summary: "install or repair the local Mend server",
|
|
1039
1039
|
synopsis: [
|
|
1040
|
-
"[--context <name>] [--version <version|latest>] [--bind <ip>] [--url <origin>] [--origin <origin>...] [--port <n>] [--ssh-port <n>] [--
|
|
1040
|
+
"[--context <name>] [--version <version|latest>] [--bind <ip>] [--url <origin>] [--origin <origin>...] [--port <n>] [--ssh-port <n>] [--docker-socket <path>] [--assets-dir <dir>] [--offline]"
|
|
1041
1041
|
],
|
|
1042
1042
|
description: [
|
|
1043
1043
|
"Checks a local Unix-socket Docker context and the Compose plugin, downloads the compose and Postgres initialization assets for one Mend release, preserves existing data and secrets, and starts the server. Re-running repairs the same pinned version. A changed --version is refused; use mend server upgrade. Updating this CLI never updates an existing server pin.",
|
|
@@ -1061,18 +1061,17 @@ var COMMANDS = [
|
|
|
1061
1061
|
text: "additional exact browser origin; repeat for more than one"
|
|
1062
1062
|
},
|
|
1063
1063
|
{ flag: "--port <n>", text: "external web port. Default: 3105" },
|
|
1064
|
-
{ flag: "--ssh-port <n>", text: "external workspace SSH port. Default: 2222" },
|
|
1065
1064
|
{
|
|
1066
|
-
flag: "--
|
|
1067
|
-
text: "
|
|
1065
|
+
flag: "--ssh-port <n>",
|
|
1066
|
+
text: "external workspace SSH port. Default: 2222; must differ from --port"
|
|
1068
1067
|
},
|
|
1069
1068
|
{
|
|
1070
1069
|
flag: "--assets-dir <dir>",
|
|
1071
|
-
text: "copy compose.
|
|
1070
|
+
text: "copy compose.v2.yaml and postgres-init.sh from a release directory; fresh setup requires --version"
|
|
1072
1071
|
},
|
|
1073
1072
|
{
|
|
1074
1073
|
flag: "--offline",
|
|
1075
|
-
text: "use retained or supplied assets and preloaded images only; no GitHub requests or release-image pulls; exact image label
|
|
1074
|
+
text: "use retained or supplied assets and preloaded images only; no GitHub requests or release-image pulls; exact image label and health version required"
|
|
1076
1075
|
},
|
|
1077
1076
|
{ flag: "--docker-socket <path>", text: "daemon-side socket mount override for diagnostics" }
|
|
1078
1077
|
],
|
|
@@ -1083,7 +1082,7 @@ var COMMANDS = [
|
|
|
1083
1082
|
text: "explicit tailnet exposure"
|
|
1084
1083
|
},
|
|
1085
1084
|
{
|
|
1086
|
-
command: "mend server setup --version 0.
|
|
1085
|
+
command: "mend server setup --version 0.25.0 --assets-dir ./release-assets --offline",
|
|
1087
1086
|
text: "install from local release assets and preloaded images"
|
|
1088
1087
|
}
|
|
1089
1088
|
],
|
|
@@ -1105,14 +1104,9 @@ var COMMANDS = [
|
|
|
1105
1104
|
summary: "start the selected server generation",
|
|
1106
1105
|
synopsis: ["[--offline]"],
|
|
1107
1106
|
description: [
|
|
1108
|
-
"Reuses the saved context, generation, credentials and exact pin. Uses preloaded release images, with no release-image pulls or asset downloads, even without --offline. Exact image label
|
|
1109
|
-
],
|
|
1110
|
-
options: [
|
|
1111
|
-
{
|
|
1112
|
-
flag: "--offline",
|
|
1113
|
-
text: "use local release images and assets; still probe the loopback registry"
|
|
1114
|
-
}
|
|
1107
|
+
"Reuses the saved context, generation, credentials and exact pin. Uses preloaded release images, with no release-image pulls or asset downloads, even without --offline. Exact image label and health version are required."
|
|
1115
1108
|
],
|
|
1109
|
+
options: [{ flag: "--offline", text: "use local release images and assets" }],
|
|
1116
1110
|
see: ["server status", "server upgrade"]
|
|
1117
1111
|
},
|
|
1118
1112
|
{
|
|
@@ -1131,14 +1125,9 @@ var COMMANDS = [
|
|
|
1131
1125
|
summary: "restart Mend using the same generation and pin",
|
|
1132
1126
|
synopsis: ["[--offline]"],
|
|
1133
1127
|
description: [
|
|
1134
|
-
"Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact-version health
|
|
1135
|
-
],
|
|
1136
|
-
options: [
|
|
1137
|
-
{
|
|
1138
|
-
flag: "--offline",
|
|
1139
|
-
text: "use local release images and assets; still probe the loopback registry"
|
|
1140
|
-
}
|
|
1128
|
+
"Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact-version health. Postgres stays running. Connections are interrupted; workspace containers and data remain, but active work may need reconnection."
|
|
1141
1129
|
],
|
|
1130
|
+
options: [{ flag: "--offline", text: "use local release images and assets" }],
|
|
1142
1131
|
see: ["server status", "server logs"]
|
|
1143
1132
|
},
|
|
1144
1133
|
{
|
|
@@ -1169,11 +1158,11 @@ var COMMANDS = [
|
|
|
1169
1158
|
},
|
|
1170
1159
|
{
|
|
1171
1160
|
flag: "--assets-dir <dir>",
|
|
1172
|
-
text: "copy target compose.
|
|
1161
|
+
text: "copy target compose.v2.yaml and postgres-init.sh from local release assets"
|
|
1173
1162
|
},
|
|
1174
1163
|
{
|
|
1175
1164
|
flag: "--offline",
|
|
1176
|
-
text: "no GitHub requests or release-image pulls; target assets and exact-version images must be local
|
|
1165
|
+
text: "no GitHub requests or release-image pulls; target assets and exact-version images must be local"
|
|
1177
1166
|
}
|
|
1178
1167
|
],
|
|
1179
1168
|
examples: [
|
|
@@ -1677,7 +1666,7 @@ var retryAfterSecondsOf = (json) => {
|
|
|
1677
1666
|
}
|
|
1678
1667
|
return 5;
|
|
1679
1668
|
};
|
|
1680
|
-
var sleep2 = (ms) => new Promise((
|
|
1669
|
+
var sleep2 = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1681
1670
|
var resolveServerUrl = async (args, deps) => {
|
|
1682
1671
|
const given = takeFlagValue2(args, "--url") ?? deps.configuredUrl;
|
|
1683
1672
|
if (given !== null) {
|
|
@@ -1750,7 +1739,7 @@ import { randomBytes } from "node:crypto";
|
|
|
1750
1739
|
import { readFile } from "node:fs/promises";
|
|
1751
1740
|
import * as net2 from "node:net";
|
|
1752
1741
|
import * as os3 from "node:os";
|
|
1753
|
-
import * as
|
|
1742
|
+
import * as path5 from "node:path";
|
|
1754
1743
|
|
|
1755
1744
|
// src/server-docker-volumes.ts
|
|
1756
1745
|
import { createHash } from "node:crypto";
|
|
@@ -1955,223 +1944,10 @@ var ownership = async (runtime, input, mode) => {
|
|
|
1955
1944
|
var claimServerDockerVolumes = (runtime, input) => ownership(runtime, input, "claim");
|
|
1956
1945
|
var verifyServerDockerVolumes = (runtime, input) => ownership(runtime, input, "verify");
|
|
1957
1946
|
|
|
1958
|
-
// src/server-registry-probe.ts
|
|
1959
|
-
import { Buffer as Buffer2 } from "node:buffer";
|
|
1960
|
-
import * as fs3 from "node:fs/promises";
|
|
1961
|
-
import * as path3 from "node:path";
|
|
1962
|
-
var ServerRegistryProbeError = class extends Error {
|
|
1963
|
-
/** Stable error discriminator. */
|
|
1964
|
-
_tag = "ServerRegistryProbeError";
|
|
1965
|
-
/** Failed roundtrip operation, independent of the host operating system. */
|
|
1966
|
-
stage;
|
|
1967
|
-
/** Bounded, credential-scrubbed failure detail. */
|
|
1968
|
-
diagnostic;
|
|
1969
|
-
/** Explain the failed Engine operation without recommending a wider registry binding. */
|
|
1970
|
-
constructor(stage, diagnostic) {
|
|
1971
|
-
super(
|
|
1972
|
-
`Docker registry probe failed (${stage}): ${diagnostic}. The selected Docker Engine must import, push and pull through 127.0.0.1 at the configured registry port. Check bundle health, Docker context and Engine loopback registry support, then retry. Do not widen the registry binding.`
|
|
1973
|
-
);
|
|
1974
|
-
this.stage = stage;
|
|
1975
|
-
this.diagnostic = diagnostic;
|
|
1976
|
-
}
|
|
1977
|
-
};
|
|
1978
|
-
var PROBE_LABEL = "dev.sealant.mend.registry-probe";
|
|
1979
|
-
var COMMAND_TIMEOUT_MS = 6e4;
|
|
1980
|
-
var CLEANUP_TIMEOUT_MS = 15e3;
|
|
1981
|
-
var error = (stage, diagnostic) => ({ _tag: "error", error: new ServerRegistryProbeError(stage, diagnostic) });
|
|
1982
|
-
var safeDiagnostic = (text) => text.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1[redacted]@").replace(
|
|
1983
|
-
/((?:password|passwd|token|secret|authorization|credential)[\w-]*\s*[=:]\s*)[^\s,;]+/gi,
|
|
1984
|
-
"$1[redacted]"
|
|
1985
|
-
).replace(/Bearer\s+\S+/gi, "Bearer [redacted]").split("").map(
|
|
1986
|
-
(character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127 ? " " : character
|
|
1987
|
-
).join("").slice(0, 2048);
|
|
1988
|
-
var docker2 = async (runtime, context, stage, args) => {
|
|
1989
|
-
try {
|
|
1990
|
-
const output = await runtime.run("docker", ["--context", context, ...args], {
|
|
1991
|
-
timeoutMs: stage === "cleanup" ? CLEANUP_TIMEOUT_MS : COMMAND_TIMEOUT_MS
|
|
1992
|
-
});
|
|
1993
|
-
if (output.status !== 0 || output.error !== void 0)
|
|
1994
|
-
return error(
|
|
1995
|
-
stage,
|
|
1996
|
-
safeDiagnostic(
|
|
1997
|
-
output.error ?? (output.stderr.trim() || `exit ${output.status ?? "unknown"}`)
|
|
1998
|
-
)
|
|
1999
|
-
);
|
|
2000
|
-
return { _tag: "ok", value: output.stdout };
|
|
2001
|
-
} catch {
|
|
2002
|
-
return error(
|
|
2003
|
-
stage,
|
|
2004
|
-
"Process runner rejected; check Docker access and command timeout handling"
|
|
2005
|
-
);
|
|
2006
|
-
}
|
|
2007
|
-
};
|
|
2008
|
-
var tinyRootfs = (nonce) => {
|
|
2009
|
-
const body = Buffer2.from(`Mend registry connectivity probe ${nonce}
|
|
2010
|
-
`);
|
|
2011
|
-
const header = Buffer2.alloc(512);
|
|
2012
|
-
const field = (value, offset, length) => header.write(value, offset, length, "ascii");
|
|
2013
|
-
field("mend-registry-probe", 0, 100);
|
|
2014
|
-
field("0000600\0", 100, 8);
|
|
2015
|
-
field("0000000\0", 108, 8);
|
|
2016
|
-
field("0000000\0", 116, 8);
|
|
2017
|
-
field(`${body.length.toString(8).padStart(11, "0")}\0`, 124, 12);
|
|
2018
|
-
field("00000000000\0", 136, 12);
|
|
2019
|
-
field(" ", 148, 8);
|
|
2020
|
-
field("0", 156, 1);
|
|
2021
|
-
field("ustar\0", 257, 6);
|
|
2022
|
-
field("00", 263, 2);
|
|
2023
|
-
const checksum = header.reduce((sum, byte) => sum + byte, 0);
|
|
2024
|
-
field(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, 8);
|
|
2025
|
-
return Buffer2.concat([header, body, Buffer2.alloc(512 - body.length), Buffer2.alloc(1024)]);
|
|
2026
|
-
};
|
|
2027
|
-
var imageExists = async (runtime, input, reference, stage) => {
|
|
2028
|
-
const listed = await docker2(runtime, input.dockerContext, stage, [
|
|
2029
|
-
"image",
|
|
2030
|
-
"ls",
|
|
2031
|
-
"--all",
|
|
2032
|
-
"--filter",
|
|
2033
|
-
`reference=${reference}`,
|
|
2034
|
-
"--format",
|
|
2035
|
-
"{{json .ID}}"
|
|
2036
|
-
]);
|
|
2037
|
-
if (listed._tag === "error") return listed;
|
|
2038
|
-
try {
|
|
2039
|
-
const lines = listed.value.trim().split("\n").filter(Boolean);
|
|
2040
|
-
for (const line of lines) {
|
|
2041
|
-
const id = JSON.parse(line);
|
|
2042
|
-
if (typeof id !== "string" || !/^(?:sha256:)?[a-f0-9]{12,64}$/.test(id))
|
|
2043
|
-
return error(stage, "Docker returned an inconclusive image listing");
|
|
2044
|
-
}
|
|
2045
|
-
return { _tag: "ok", value: lines.length > 0 };
|
|
2046
|
-
} catch {
|
|
2047
|
-
return error(stage, "Docker returned an invalid image listing");
|
|
2048
|
-
}
|
|
2049
|
-
};
|
|
2050
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2051
|
-
var ownedImage = async (runtime, input, reference, stage) => {
|
|
2052
|
-
const inspected = await docker2(runtime, input.dockerContext, stage, [
|
|
2053
|
-
"image",
|
|
2054
|
-
"inspect",
|
|
2055
|
-
reference,
|
|
2056
|
-
"--format",
|
|
2057
|
-
"{{json .}}"
|
|
2058
|
-
]);
|
|
2059
|
-
if (inspected._tag === "error") return inspected;
|
|
2060
|
-
try {
|
|
2061
|
-
const image = JSON.parse(inspected.value);
|
|
2062
|
-
if (!isRecord(image) || typeof image.Id !== "string" || !/^sha256:[a-f0-9]{64}$/.test(image.Id) || !isRecord(image.Config) || !isRecord(image.Config.Labels) || image.Config.Labels[PROBE_LABEL] !== input.nonce)
|
|
2063
|
-
return error(stage, "Image ownership or ID could not be verified; the image was retained");
|
|
2064
|
-
return { _tag: "ok", value: image.Id };
|
|
2065
|
-
} catch {
|
|
2066
|
-
return error(stage, "Docker returned an invalid image inspection; the image was retained");
|
|
2067
|
-
}
|
|
2068
|
-
};
|
|
2069
|
-
var removeOwnedTag = async (runtime, input, reference, stage) => {
|
|
2070
|
-
const exists = await imageExists(runtime, input, reference, stage);
|
|
2071
|
-
if (exists._tag === "error") return exists;
|
|
2072
|
-
if (!exists.value) return { _tag: "ok", value: void 0 };
|
|
2073
|
-
const owned = await ownedImage(runtime, input, reference, stage);
|
|
2074
|
-
if (owned._tag === "error") return owned;
|
|
2075
|
-
const removed = await docker2(runtime, input.dockerContext, stage, ["image", "rm", reference]);
|
|
2076
|
-
return removed._tag === "error" ? removed : { _tag: "ok", value: void 0 };
|
|
2077
|
-
};
|
|
2078
|
-
var roundtrip = async (runtime, input, reference, tarPath) => {
|
|
2079
|
-
const imported = await docker2(runtime, input.dockerContext, "import", [
|
|
2080
|
-
"image",
|
|
2081
|
-
"import",
|
|
2082
|
-
"--change",
|
|
2083
|
-
`LABEL ${PROBE_LABEL}=${input.nonce}`,
|
|
2084
|
-
tarPath,
|
|
2085
|
-
reference
|
|
2086
|
-
]);
|
|
2087
|
-
if (imported._tag === "error") return imported;
|
|
2088
|
-
const original = await ownedImage(runtime, input, reference, "inspect");
|
|
2089
|
-
if (original._tag === "error") return original;
|
|
2090
|
-
const pushed = await docker2(runtime, input.dockerContext, "push", ["image", "push", reference]);
|
|
2091
|
-
if (pushed._tag === "error") return pushed;
|
|
2092
|
-
const removed = await removeOwnedTag(runtime, input, reference, "remove-local");
|
|
2093
|
-
if (removed._tag === "error") return removed;
|
|
2094
|
-
const absent = await imageExists(runtime, input, reference, "remove-local");
|
|
2095
|
-
if (absent._tag === "error") return absent;
|
|
2096
|
-
if (absent.value)
|
|
2097
|
-
return error("remove-local", "Probe tag remained local; refusing to claim a pull roundtrip");
|
|
2098
|
-
const pulled = await docker2(runtime, input.dockerContext, "pull", ["image", "pull", reference]);
|
|
2099
|
-
if (pulled._tag === "error") return pulled;
|
|
2100
|
-
const observed = await ownedImage(runtime, input, reference, "inspect");
|
|
2101
|
-
if (observed._tag === "error") return observed;
|
|
2102
|
-
return observed.value === original.value ? observed : error("inspect", "Pulled image ID differs from the imported image ID");
|
|
2103
|
-
};
|
|
2104
|
-
var parseProbeReference = (input) => {
|
|
2105
|
-
if (!input.dockerContext.trim() || !Number.isInteger(input.registryPort) || input.registryPort < 1 || input.registryPort > 65535 || !/^[a-f0-9]{32,64}$/.test(input.nonce) || !path3.isAbsolute(input.temporaryDirectory))
|
|
2106
|
-
return error(
|
|
2107
|
-
"input",
|
|
2108
|
-
"Expected an explicit context, TCP port, fresh 16 to 32 byte hex nonce and absolute temporary directory"
|
|
2109
|
-
);
|
|
2110
|
-
return {
|
|
2111
|
-
_tag: "ok",
|
|
2112
|
-
value: `127.0.0.1:${input.registryPort}/mend-registry-probe/${input.nonce}:probe`
|
|
2113
|
-
};
|
|
2114
|
-
};
|
|
2115
|
-
var probeServerRegistry = async (runtime, input) => {
|
|
2116
|
-
const cleanupWarnings = [];
|
|
2117
|
-
const parsed = parseProbeReference(input);
|
|
2118
|
-
if (parsed._tag === "error") return { ...parsed, cleanupWarnings };
|
|
2119
|
-
const reference = parsed.value;
|
|
2120
|
-
const exists = await imageExists(runtime, input, reference, "collision-check");
|
|
2121
|
-
if (exists._tag === "error") return { ...exists, cleanupWarnings };
|
|
2122
|
-
if (exists.value)
|
|
2123
|
-
return {
|
|
2124
|
-
...error(
|
|
2125
|
-
"collision-check",
|
|
2126
|
-
"Probe reference already exists; supply a fresh nonce. Existing image retained"
|
|
2127
|
-
),
|
|
2128
|
-
cleanupWarnings
|
|
2129
|
-
};
|
|
2130
|
-
let directory;
|
|
2131
|
-
try {
|
|
2132
|
-
directory = await fs3.mkdtemp(path3.join(input.temporaryDirectory, "mend-registry-probe-"));
|
|
2133
|
-
} catch {
|
|
2134
|
-
return {
|
|
2135
|
-
...error("temporary-files", "Could not create private probe directory"),
|
|
2136
|
-
cleanupWarnings
|
|
2137
|
-
};
|
|
2138
|
-
}
|
|
2139
|
-
let result;
|
|
2140
|
-
let importAttempted = false;
|
|
2141
|
-
try {
|
|
2142
|
-
const tarPath = path3.join(directory, "rootfs.tar");
|
|
2143
|
-
await fs3.writeFile(tarPath, tinyRootfs(input.nonce), { mode: 384, flag: "wx" });
|
|
2144
|
-
importAttempted = true;
|
|
2145
|
-
result = await roundtrip(runtime, input, reference, tarPath);
|
|
2146
|
-
} catch {
|
|
2147
|
-
result = error("temporary-files", "Could not write the private probe archive");
|
|
2148
|
-
} finally {
|
|
2149
|
-
if (importAttempted) {
|
|
2150
|
-
const cleanup = await removeOwnedTag(runtime, input, reference, "cleanup");
|
|
2151
|
-
if (cleanup._tag === "error") cleanupWarnings.push(cleanup.error);
|
|
2152
|
-
}
|
|
2153
|
-
try {
|
|
2154
|
-
await fs3.rm(directory, { recursive: true, force: true });
|
|
2155
|
-
} catch {
|
|
2156
|
-
cleanupWarnings.push(
|
|
2157
|
-
new ServerRegistryProbeError(
|
|
2158
|
-
"cleanup",
|
|
2159
|
-
`Could not remove private probe directory ${directory}`
|
|
2160
|
-
)
|
|
2161
|
-
);
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
return result._tag === "error" ? { ...result, cleanupWarnings } : {
|
|
2165
|
-
_tag: "ok",
|
|
2166
|
-
value: { reference, imageId: result.value },
|
|
2167
|
-
cleanupWarnings
|
|
2168
|
-
};
|
|
2169
|
-
};
|
|
2170
|
-
|
|
2171
1947
|
// src/server-runtime.ts
|
|
2172
1948
|
import { spawn as spawn2 } from "node:child_process";
|
|
2173
|
-
import * as
|
|
2174
|
-
import * as
|
|
1949
|
+
import * as fs3 from "node:fs";
|
|
1950
|
+
import * as path3 from "node:path";
|
|
2175
1951
|
var serverProcessDeadlines = {
|
|
2176
1952
|
ordinary: 3e4,
|
|
2177
1953
|
pull: 10 * 6e4,
|
|
@@ -2214,10 +1990,10 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
|
|
|
2214
1990
|
]) {
|
|
2215
1991
|
if (parentEnvironment[key] !== void 0) env[key] = parentEnvironment[key];
|
|
2216
1992
|
}
|
|
2217
|
-
return new Promise((
|
|
1993
|
+
return new Promise((resolve4) => {
|
|
2218
1994
|
let fd;
|
|
2219
1995
|
try {
|
|
2220
|
-
if (options.stdoutFile !== void 0) fd =
|
|
1996
|
+
if (options.stdoutFile !== void 0) fd = fs3.openSync(options.stdoutFile, "wx", 384);
|
|
2221
1997
|
const child = spawn2(command, [...args], {
|
|
2222
1998
|
env,
|
|
2223
1999
|
detached: process.platform !== "win32",
|
|
@@ -2262,23 +2038,23 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
|
|
|
2262
2038
|
failure2 ??= `Process timed out after ${timeoutMs}ms`;
|
|
2263
2039
|
terminate();
|
|
2264
2040
|
}, timeoutMs);
|
|
2265
|
-
child.once("error", (
|
|
2266
|
-
failure2 ??=
|
|
2041
|
+
child.once("error", (error) => {
|
|
2042
|
+
failure2 ??= error.message;
|
|
2267
2043
|
});
|
|
2268
2044
|
child.once("close", (status) => {
|
|
2269
2045
|
globalThis.clearTimeout(timer);
|
|
2270
2046
|
try {
|
|
2271
|
-
if (fd !== void 0)
|
|
2047
|
+
if (fd !== void 0) fs3.fsyncSync(fd);
|
|
2272
2048
|
} catch {
|
|
2273
2049
|
failure2 ??= "Could not durably write process output.";
|
|
2274
2050
|
} finally {
|
|
2275
2051
|
try {
|
|
2276
|
-
if (fd !== void 0)
|
|
2052
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
2277
2053
|
} catch {
|
|
2278
2054
|
failure2 ??= "Could not close process output file.";
|
|
2279
2055
|
}
|
|
2280
2056
|
}
|
|
2281
|
-
|
|
2057
|
+
resolve4({
|
|
2282
2058
|
status: failure2 === void 0 ? status : null,
|
|
2283
2059
|
stdout,
|
|
2284
2060
|
stderr,
|
|
@@ -2287,10 +2063,10 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
|
|
|
2287
2063
|
});
|
|
2288
2064
|
} catch (cause) {
|
|
2289
2065
|
try {
|
|
2290
|
-
if (fd !== void 0)
|
|
2066
|
+
if (fd !== void 0) fs3.closeSync(fd);
|
|
2291
2067
|
} catch {
|
|
2292
2068
|
}
|
|
2293
|
-
|
|
2069
|
+
resolve4({
|
|
2294
2070
|
status: null,
|
|
2295
2071
|
stdout: "",
|
|
2296
2072
|
stderr: "",
|
|
@@ -2308,17 +2084,17 @@ var serverComposeArgs = (installation, command) => [
|
|
|
2308
2084
|
"--project-directory",
|
|
2309
2085
|
installation.directory,
|
|
2310
2086
|
"--env-file",
|
|
2311
|
-
|
|
2087
|
+
path3.join(installation.directory, "server.env"),
|
|
2312
2088
|
"-f",
|
|
2313
|
-
|
|
2089
|
+
path3.join(installation.directory, "compose.yaml"),
|
|
2314
2090
|
...command
|
|
2315
2091
|
];
|
|
2316
2092
|
|
|
2317
2093
|
// src/server-store.ts
|
|
2318
2094
|
import { randomUUID } from "node:crypto";
|
|
2319
|
-
import * as
|
|
2095
|
+
import * as fs4 from "node:fs";
|
|
2320
2096
|
import * as os2 from "node:os";
|
|
2321
|
-
import * as
|
|
2097
|
+
import * as path4 from "node:path";
|
|
2322
2098
|
var ServerStoreError = class extends Error {
|
|
2323
2099
|
_tag = "ServerStoreError";
|
|
2324
2100
|
};
|
|
@@ -2339,28 +2115,28 @@ var attempt = (operation) => {
|
|
|
2339
2115
|
var hasCode = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
|
|
2340
2116
|
var readOptional = (file) => {
|
|
2341
2117
|
try {
|
|
2342
|
-
return
|
|
2118
|
+
return fs4.readFileSync(file, "utf8");
|
|
2343
2119
|
} catch (cause) {
|
|
2344
2120
|
if (hasCode(cause, "ENOENT")) return null;
|
|
2345
2121
|
throw cause;
|
|
2346
2122
|
}
|
|
2347
2123
|
};
|
|
2348
2124
|
var syncDirectory = (directory) => {
|
|
2349
|
-
const fd =
|
|
2125
|
+
const fd = fs4.openSync(directory, "r");
|
|
2350
2126
|
try {
|
|
2351
|
-
|
|
2127
|
+
fs4.fsyncSync(fd);
|
|
2352
2128
|
} finally {
|
|
2353
|
-
|
|
2129
|
+
fs4.closeSync(fd);
|
|
2354
2130
|
}
|
|
2355
2131
|
};
|
|
2356
2132
|
var writeDurable = (file, content, mode = 384) => {
|
|
2357
|
-
const fd =
|
|
2133
|
+
const fd = fs4.openSync(file, "wx", mode);
|
|
2358
2134
|
try {
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2135
|
+
fs4.writeFileSync(fd, content, "utf8");
|
|
2136
|
+
fs4.fchmodSync(fd, mode);
|
|
2137
|
+
fs4.fsyncSync(fd);
|
|
2362
2138
|
} finally {
|
|
2363
|
-
|
|
2139
|
+
fs4.closeSync(fd);
|
|
2364
2140
|
}
|
|
2365
2141
|
};
|
|
2366
2142
|
var fileKeys = ["identity", "config", "env", "compose", "postgresInit"];
|
|
@@ -2380,7 +2156,7 @@ var parseLockOwner = (raw) => {
|
|
|
2380
2156
|
};
|
|
2381
2157
|
var describeOwner = (lockDir) => {
|
|
2382
2158
|
try {
|
|
2383
|
-
const raw = JSON.parse(
|
|
2159
|
+
const raw = JSON.parse(fs4.readFileSync(path4.join(lockDir, "owner.json"), "utf8"));
|
|
2384
2160
|
const owner = parseLockOwner(raw);
|
|
2385
2161
|
if (owner === null) return "owner metadata is invalid";
|
|
2386
2162
|
const label = `PID ${owner.pid} on ${owner.hostname}`;
|
|
@@ -2400,28 +2176,28 @@ var lockGuidance = (lockDir) => new ServerStoreError(
|
|
|
2400
2176
|
);
|
|
2401
2177
|
var acquireLock = (configDir, create) => {
|
|
2402
2178
|
if (create) {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
} else if (!
|
|
2179
|
+
fs4.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
2180
|
+
fs4.chmodSync(configDir, 448);
|
|
2181
|
+
} else if (!fs4.existsSync(configDir)) {
|
|
2406
2182
|
throw new ServerStoreError(
|
|
2407
2183
|
"No Mend server is configured. Run mend server setup explicitly to install one."
|
|
2408
2184
|
);
|
|
2409
2185
|
}
|
|
2410
|
-
const lockDir =
|
|
2186
|
+
const lockDir = path4.join(configDir, "server.lock");
|
|
2411
2187
|
try {
|
|
2412
|
-
|
|
2188
|
+
fs4.mkdirSync(lockDir, { mode: 448 });
|
|
2413
2189
|
} catch (cause) {
|
|
2414
2190
|
if (hasCode(cause, "EEXIST")) throw lockGuidance(lockDir);
|
|
2415
2191
|
throw cause;
|
|
2416
2192
|
}
|
|
2417
|
-
const owned =
|
|
2418
|
-
const ownerFile =
|
|
2193
|
+
const owned = fs4.statSync(lockDir);
|
|
2194
|
+
const ownerFile = path4.join(lockDir, "owner.json");
|
|
2419
2195
|
const owner = `${JSON.stringify({ pid: process.pid, hostname: os2.hostname(), token: randomUUID() })}
|
|
2420
2196
|
`;
|
|
2421
2197
|
let open = true;
|
|
2422
2198
|
const assertOwned = () => {
|
|
2423
|
-
const current =
|
|
2424
|
-
if (!open || current.dev !== owned.dev || current.ino !== owned.ino ||
|
|
2199
|
+
const current = fs4.statSync(lockDir);
|
|
2200
|
+
if (!open || current.dev !== owned.dev || current.ino !== owned.ino || fs4.readFileSync(ownerFile, "utf8") !== owner) {
|
|
2425
2201
|
throw new ServerStoreError(
|
|
2426
2202
|
"Server lock ownership was lost. Stop and check for another server command before retrying."
|
|
2427
2203
|
);
|
|
@@ -2437,8 +2213,8 @@ var acquireLock = (configDir, create) => {
|
|
|
2437
2213
|
release: () => {
|
|
2438
2214
|
const result = attempt(() => {
|
|
2439
2215
|
assertOwned();
|
|
2440
|
-
|
|
2441
|
-
|
|
2216
|
+
fs4.unlinkSync(ownerFile);
|
|
2217
|
+
fs4.rmdirSync(lockDir);
|
|
2442
2218
|
});
|
|
2443
2219
|
open = false;
|
|
2444
2220
|
return result;
|
|
@@ -2447,7 +2223,7 @@ var acquireLock = (configDir, create) => {
|
|
|
2447
2223
|
};
|
|
2448
2224
|
var readIdentity = (paths) => {
|
|
2449
2225
|
const identity = readOptional(paths.identity);
|
|
2450
|
-
if (identity === null && (
|
|
2226
|
+
if (identity === null && (fs4.existsSync(paths.active) || fs4.existsSync(paths.generations) && fs4.readdirSync(paths.generations).length > 0)) {
|
|
2451
2227
|
throw new ServerStoreError(
|
|
2452
2228
|
"Server identity.env is missing. Restore it from a retained generation or backup; refusing to generate replacement credentials."
|
|
2453
2229
|
);
|
|
@@ -2457,7 +2233,7 @@ var readIdentity = (paths) => {
|
|
|
2457
2233
|
var readActive = (paths) => {
|
|
2458
2234
|
let target;
|
|
2459
2235
|
try {
|
|
2460
|
-
target =
|
|
2236
|
+
target = fs4.readlinkSync(paths.active);
|
|
2461
2237
|
} catch (cause) {
|
|
2462
2238
|
if (hasCode(cause, "ENOENT")) return null;
|
|
2463
2239
|
throw cause;
|
|
@@ -2467,13 +2243,13 @@ var readActive = (paths) => {
|
|
|
2467
2243
|
"Server active pointer is invalid. Restore the symlink to a retained generation before retrying."
|
|
2468
2244
|
);
|
|
2469
2245
|
}
|
|
2470
|
-
const directory =
|
|
2246
|
+
const directory = path4.join(paths.configDir, target);
|
|
2471
2247
|
const files = {
|
|
2472
|
-
identity:
|
|
2473
|
-
config:
|
|
2474
|
-
env:
|
|
2475
|
-
compose:
|
|
2476
|
-
postgresInit:
|
|
2248
|
+
identity: fs4.readFileSync(path4.join(directory, fileNames.identity), "utf8"),
|
|
2249
|
+
config: fs4.readFileSync(path4.join(directory, fileNames.config), "utf8"),
|
|
2250
|
+
env: fs4.readFileSync(path4.join(directory, fileNames.env), "utf8"),
|
|
2251
|
+
compose: fs4.readFileSync(path4.join(directory, fileNames.compose), "utf8"),
|
|
2252
|
+
postgresInit: fs4.readFileSync(path4.join(directory, fileNames.postgresInit), "utf8")
|
|
2477
2253
|
};
|
|
2478
2254
|
if (readIdentity(paths) !== files.identity) {
|
|
2479
2255
|
throw new ServerStoreError(
|
|
@@ -2483,20 +2259,20 @@ var readActive = (paths) => {
|
|
|
2483
2259
|
return { directory, files };
|
|
2484
2260
|
};
|
|
2485
2261
|
var publishIdentity = (paths, identity) => {
|
|
2486
|
-
const temporary =
|
|
2262
|
+
const temporary = path4.join(paths.configDir, `.identity-${randomUUID()}`);
|
|
2487
2263
|
writeDurable(temporary, identity);
|
|
2488
|
-
|
|
2264
|
+
fs4.linkSync(temporary, paths.identity);
|
|
2489
2265
|
syncDirectory(paths.configDir);
|
|
2490
|
-
|
|
2266
|
+
fs4.unlinkSync(temporary);
|
|
2491
2267
|
};
|
|
2492
2268
|
var prepareGeneration = (paths, files) => {
|
|
2493
|
-
|
|
2269
|
+
fs4.mkdirSync(paths.generations, { recursive: true, mode: 448 });
|
|
2494
2270
|
const generationName = `gen-${randomUUID()}`;
|
|
2495
|
-
const directory =
|
|
2496
|
-
|
|
2271
|
+
const directory = path4.join(paths.generations, generationName);
|
|
2272
|
+
fs4.mkdirSync(directory, { mode: 448 });
|
|
2497
2273
|
for (const key of fileKeys) {
|
|
2498
2274
|
writeDurable(
|
|
2499
|
-
|
|
2275
|
+
path4.join(directory, fileNames[key]),
|
|
2500
2276
|
files[key],
|
|
2501
2277
|
key === "postgresInit" ? 493 : 384
|
|
2502
2278
|
);
|
|
@@ -2507,18 +2283,18 @@ var prepareGeneration = (paths, files) => {
|
|
|
2507
2283
|
return { directory, files };
|
|
2508
2284
|
};
|
|
2509
2285
|
var activateGeneration = (paths, generation) => {
|
|
2510
|
-
const relative3 =
|
|
2511
|
-
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative3) || readIdentity(paths) !==
|
|
2286
|
+
const relative3 = path4.relative(paths.configDir, generation.directory);
|
|
2287
|
+
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative3) || readIdentity(paths) !== fs4.readFileSync(path4.join(generation.directory, "identity.env"), "utf8")) {
|
|
2512
2288
|
throw new ServerStoreError("Cannot activate a generation outside this installation identity.");
|
|
2513
2289
|
}
|
|
2514
2290
|
for (const key of fileKeys) {
|
|
2515
|
-
if (
|
|
2291
|
+
if (fs4.readFileSync(path4.join(generation.directory, fileNames[key]), "utf8") !== generation.files[key]) {
|
|
2516
2292
|
throw new ServerStoreError("Cannot activate an incomplete or changed server generation.");
|
|
2517
2293
|
}
|
|
2518
2294
|
}
|
|
2519
|
-
const pointer =
|
|
2520
|
-
|
|
2521
|
-
|
|
2295
|
+
const pointer = path4.join(paths.configDir, `.active-${randomUUID()}`);
|
|
2296
|
+
fs4.symlinkSync(relative3, pointer);
|
|
2297
|
+
fs4.renameSync(pointer, paths.active);
|
|
2522
2298
|
syncDirectory(paths.configDir);
|
|
2523
2299
|
};
|
|
2524
2300
|
var prepareFiles = (paths, files) => {
|
|
@@ -2527,7 +2303,7 @@ var prepareFiles = (paths, files) => {
|
|
|
2527
2303
|
throw new ServerStoreError("Server identity already exists; refusing to replace credentials.");
|
|
2528
2304
|
}
|
|
2529
2305
|
const active = readActive(paths);
|
|
2530
|
-
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (
|
|
2306
|
+
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (fs4.statSync(path4.join(active.directory, fileNames.postgresInit)).mode & 4095) === 493)
|
|
2531
2307
|
return active;
|
|
2532
2308
|
if (identity === null) publishIdentity(paths, files.identity);
|
|
2533
2309
|
return prepareGeneration(paths, files);
|
|
@@ -2540,9 +2316,9 @@ var commitGeneration = (paths, files) => {
|
|
|
2540
2316
|
var createStore = (configDir, lock) => {
|
|
2541
2317
|
const paths = {
|
|
2542
2318
|
configDir,
|
|
2543
|
-
identity:
|
|
2544
|
-
generations:
|
|
2545
|
-
active:
|
|
2319
|
+
identity: path4.join(configDir, "identity.env"),
|
|
2320
|
+
generations: path4.join(configDir, "generations"),
|
|
2321
|
+
active: path4.join(configDir, "active")
|
|
2546
2322
|
};
|
|
2547
2323
|
const whileOwned = (operation) => attempt(() => {
|
|
2548
2324
|
lock.assertOwned();
|
|
@@ -2555,13 +2331,13 @@ var createStore = (configDir, lock) => {
|
|
|
2555
2331
|
prepare: (files) => whileOwned(() => prepareFiles(paths, files)),
|
|
2556
2332
|
activate: (generation) => whileOwned(() => activateGeneration(paths, generation)),
|
|
2557
2333
|
createBackup: (previous, target) => whileOwned(() => {
|
|
2558
|
-
const backups =
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
const directory =
|
|
2562
|
-
|
|
2334
|
+
const backups = path4.join(configDir, "backups");
|
|
2335
|
+
fs4.mkdirSync(backups, { recursive: true, mode: 448 });
|
|
2336
|
+
fs4.chmodSync(backups, 448);
|
|
2337
|
+
const directory = path4.join(backups, `upgrade-${randomUUID()}`);
|
|
2338
|
+
fs4.mkdirSync(directory, { mode: 448 });
|
|
2563
2339
|
writeDurable(
|
|
2564
|
-
|
|
2340
|
+
path4.join(directory, "recovery.json"),
|
|
2565
2341
|
`${JSON.stringify(
|
|
2566
2342
|
{
|
|
2567
2343
|
previousGeneration: previous.directory,
|
|
@@ -2577,21 +2353,21 @@ var createStore = (configDir, lock) => {
|
|
|
2577
2353
|
syncDirectory(directory);
|
|
2578
2354
|
syncDirectory(backups);
|
|
2579
2355
|
syncDirectory(configDir);
|
|
2580
|
-
const partialFile =
|
|
2356
|
+
const partialFile = path4.join(directory, "database.sql.partial");
|
|
2581
2357
|
return {
|
|
2582
2358
|
directory,
|
|
2583
2359
|
partialFile,
|
|
2584
2360
|
complete: () => whileOwned(() => {
|
|
2585
|
-
if (
|
|
2361
|
+
if (fs4.statSync(partialFile).size === 0)
|
|
2586
2362
|
throw new ServerStoreError("Database backup is empty; refusing target startup.");
|
|
2587
|
-
const fd =
|
|
2363
|
+
const fd = fs4.openSync(partialFile, "r");
|
|
2588
2364
|
try {
|
|
2589
|
-
|
|
2590
|
-
|
|
2365
|
+
fs4.fchmodSync(fd, 384);
|
|
2366
|
+
fs4.fsyncSync(fd);
|
|
2591
2367
|
} finally {
|
|
2592
|
-
|
|
2368
|
+
fs4.closeSync(fd);
|
|
2593
2369
|
}
|
|
2594
|
-
|
|
2370
|
+
fs4.renameSync(partialFile, path4.join(directory, "database.sql"));
|
|
2595
2371
|
syncDirectory(directory);
|
|
2596
2372
|
})
|
|
2597
2373
|
};
|
|
@@ -2600,7 +2376,7 @@ var createStore = (configDir, lock) => {
|
|
|
2600
2376
|
};
|
|
2601
2377
|
var refuseFlatLayout = (configDir) => {
|
|
2602
2378
|
if (["server.json", "server.env", "compose.yaml", "postgres-init.sh"].some(
|
|
2603
|
-
(name) =>
|
|
2379
|
+
(name) => fs4.existsSync(path4.join(configDir, name))
|
|
2604
2380
|
)) {
|
|
2605
2381
|
throw new ServerStoreError(
|
|
2606
2382
|
"Unreleased flat server configuration found. Preserve its credentials and volumes; migrate it to the generation layout before retrying. Setup will not replace this identity."
|
|
@@ -2608,7 +2384,7 @@ var refuseFlatLayout = (configDir) => {
|
|
|
2608
2384
|
}
|
|
2609
2385
|
};
|
|
2610
2386
|
var withServerStore = async (directory, operation, options = {}) => {
|
|
2611
|
-
const configDir =
|
|
2387
|
+
const configDir = path4.resolve(directory);
|
|
2612
2388
|
let lock;
|
|
2613
2389
|
let result;
|
|
2614
2390
|
try {
|
|
@@ -2625,9 +2401,9 @@ var withServerStore = async (directory, operation, options = {}) => {
|
|
|
2625
2401
|
};
|
|
2626
2402
|
|
|
2627
2403
|
// src/version.ts
|
|
2628
|
-
import * as
|
|
2404
|
+
import * as fs5 from "node:fs";
|
|
2629
2405
|
var cliVersion = () => {
|
|
2630
|
-
const raw =
|
|
2406
|
+
const raw = fs5.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
2631
2407
|
const parsed = JSON.parse(raw);
|
|
2632
2408
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
2633
2409
|
const version = parsed.version;
|
|
@@ -2661,12 +2437,13 @@ var versionLines = (cli, server) => {
|
|
|
2661
2437
|
|
|
2662
2438
|
// src/server-setup.ts
|
|
2663
2439
|
var CONFIG_SCHEMA_VERSION = 1;
|
|
2664
|
-
var ASSET_CONTRACT = "mend-docker-
|
|
2440
|
+
var ASSET_CONTRACT = "mend-docker-v2";
|
|
2441
|
+
var LEGACY_ASSET_CONTRACTS = /* @__PURE__ */ new Set(["mend-docker-v1"]);
|
|
2665
2442
|
var MINIMUM_DOCKER_API = "1.45";
|
|
2666
2443
|
var DEFAULT_APP_PORT = 3105;
|
|
2667
2444
|
var DEFAULT_SSH_PORT = 2222;
|
|
2668
2445
|
var DEFAULT_BIND = "127.0.0.1";
|
|
2669
|
-
var COMPOSE_ASSET = "compose.
|
|
2446
|
+
var COMPOSE_ASSET = "compose.v2.yaml";
|
|
2670
2447
|
var POSTGRES_INIT_ASSET = "postgres-init.sh";
|
|
2671
2448
|
var RELEASE_BASE = "https://github.com/sealant-sh/Mend/releases/download";
|
|
2672
2449
|
var LATEST_RELEASE_URL = "https://api.github.com/repos/sealant-sh/Mend/releases/latest";
|
|
@@ -2726,7 +2503,6 @@ var SETUP_FLAGS = /* @__PURE__ */ new Set([
|
|
|
2726
2503
|
"--port",
|
|
2727
2504
|
"--ssh-port",
|
|
2728
2505
|
"--docker-socket",
|
|
2729
|
-
"--registry-port",
|
|
2730
2506
|
"--assets-dir",
|
|
2731
2507
|
"--offline"
|
|
2732
2508
|
]);
|
|
@@ -2755,7 +2531,6 @@ var parseSetupOptions = (args) => {
|
|
|
2755
2531
|
const version = flagValue2("--version");
|
|
2756
2532
|
const appPort = flagValue2("--port");
|
|
2757
2533
|
const sshPort = flagValue2("--ssh-port");
|
|
2758
|
-
const registryPort = flagValue2("--registry-port");
|
|
2759
2534
|
const origins = values.get("--origin");
|
|
2760
2535
|
return {
|
|
2761
2536
|
context: flagValue2("--context"),
|
|
@@ -2766,7 +2541,6 @@ var parseSetupOptions = (args) => {
|
|
|
2766
2541
|
appPort: appPort === void 0 ? void 0 : parsePort(appPort, "--port"),
|
|
2767
2542
|
sshPort: sshPort === void 0 ? void 0 : parsePort(sshPort, "--ssh-port"),
|
|
2768
2543
|
dockerSocket: flagValue2("--docker-socket"),
|
|
2769
|
-
registryPort: registryPort === void 0 ? void 0 : parsePort(registryPort, "--registry-port"),
|
|
2770
2544
|
assetsDir: flagValue2("--assets-dir"),
|
|
2771
2545
|
offline: values.has("--offline")
|
|
2772
2546
|
};
|
|
@@ -2813,9 +2587,8 @@ var checkExposurePair = (bind, appUrl, requireExplicitUrl) => {
|
|
|
2813
2587
|
var validateExposure = (existing, options) => {
|
|
2814
2588
|
const appPort = options.appPort ?? existing?.appPort ?? DEFAULT_APP_PORT;
|
|
2815
2589
|
const sshPort = options.sshPort ?? existing?.sshPort ?? DEFAULT_SSH_PORT;
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
throw setupError("--port, --ssh-port and --registry-port must use different ports.");
|
|
2590
|
+
if (appPort === sshPort) {
|
|
2591
|
+
throw setupError("--port and --ssh-port must use different ports.");
|
|
2819
2592
|
}
|
|
2820
2593
|
const bind = options.bind ?? existing?.bind ?? DEFAULT_BIND;
|
|
2821
2594
|
if (net2.isIP(bind) === 0) {
|
|
@@ -2830,7 +2603,7 @@ var validateExposure = (existing, options) => {
|
|
|
2830
2603
|
const allowedOrigins = [
|
|
2831
2604
|
...new Set((requestedOrigins ?? inheritedOrigins).filter((origin) => origin !== appUrl))
|
|
2832
2605
|
];
|
|
2833
|
-
return { bind, appUrl, allowedOrigins, appPort, sshPort
|
|
2606
|
+
return { bind, appUrl, allowedOrigins, appPort, sshPort };
|
|
2834
2607
|
};
|
|
2835
2608
|
var parseServerConfig = (raw) => {
|
|
2836
2609
|
let decoded;
|
|
@@ -2871,16 +2644,17 @@ var parseServerConfig = (raw) => {
|
|
|
2871
2644
|
),
|
|
2872
2645
|
appPort: requiredInteger(fields, "appPort"),
|
|
2873
2646
|
sshPort: requiredInteger(fields, "sshPort"),
|
|
2874
|
-
|
|
2647
|
+
...fields.has("registryPort") ? { registryPort: requiredInteger(fields, "registryPort") } : {}
|
|
2875
2648
|
};
|
|
2876
|
-
if (config.schemaVersion !== CONFIG_SCHEMA_VERSION || config.assetContract !== ASSET_CONTRACT) {
|
|
2649
|
+
if (config.schemaVersion !== CONFIG_SCHEMA_VERSION || config.assetContract !== ASSET_CONTRACT && !LEGACY_ASSET_CONTRACTS.has(config.assetContract)) {
|
|
2877
2650
|
throw setupError(
|
|
2878
2651
|
`Server config uses unsupported contract ${config.schemaVersion}/${config.assetContract}. Upgrade the CLI before setup.`
|
|
2879
2652
|
);
|
|
2880
2653
|
}
|
|
2881
2654
|
parsePort(String(config.appPort), "Server config appPort");
|
|
2882
2655
|
parsePort(String(config.sshPort), "Server config sshPort");
|
|
2883
|
-
|
|
2656
|
+
if (config.registryPort !== void 0)
|
|
2657
|
+
parsePort(String(config.registryPort), "Server config registryPort");
|
|
2884
2658
|
validateExposure(null, {
|
|
2885
2659
|
context: void 0,
|
|
2886
2660
|
version: void 0,
|
|
@@ -2890,7 +2664,6 @@ var parseServerConfig = (raw) => {
|
|
|
2890
2664
|
appPort: config.appPort,
|
|
2891
2665
|
sshPort: config.sshPort,
|
|
2892
2666
|
dockerSocket: void 0,
|
|
2893
|
-
registryPort: config.registryPort,
|
|
2894
2667
|
assetsDir: void 0,
|
|
2895
2668
|
offline: false
|
|
2896
2669
|
});
|
|
@@ -2919,7 +2692,7 @@ var parseSecrets = (raw) => {
|
|
|
2919
2692
|
postgresAdminPassword: envValue(fields, "MEND_POSTGRES_ADMIN_PASSWORD"),
|
|
2920
2693
|
mendDatabasePassword: envValue(fields, "MEND_DB_PASSWORD"),
|
|
2921
2694
|
sealantDatabasePassword: envValue(fields, "SEALANT_DB_PASSWORD"),
|
|
2922
|
-
queuePassword: envValue(fields, "MEND_RABBITMQ_PASSWORD"),
|
|
2695
|
+
...fields.has("MEND_RABBITMQ_PASSWORD") ? { queuePassword: envValue(fields, "MEND_RABBITMQ_PASSWORD") } : {},
|
|
2923
2696
|
betterAuthSecret: envValue(fields, "BETTER_AUTH_SECRET"),
|
|
2924
2697
|
sealantCredentialsKey: envValue(fields, "SEALANT_CREDENTIALS_KEY"),
|
|
2925
2698
|
sealantServiceKey: envValue(fields, "SEALANT_SERVICE_KEY"),
|
|
@@ -2932,7 +2705,7 @@ var parseSecrets = (raw) => {
|
|
|
2932
2705
|
["MEND_POSTGRES_ADMIN_PASSWORD", secrets.postgresAdminPassword],
|
|
2933
2706
|
["MEND_DB_PASSWORD", secrets.mendDatabasePassword],
|
|
2934
2707
|
["SEALANT_DB_PASSWORD", secrets.sealantDatabasePassword],
|
|
2935
|
-
["MEND_RABBITMQ_PASSWORD", secrets.queuePassword],
|
|
2708
|
+
...secrets.queuePassword === void 0 ? [] : [["MEND_RABBITMQ_PASSWORD", secrets.queuePassword]],
|
|
2936
2709
|
["WORKSPACE_SSH_GATEWAY_TOKEN", secrets.workspaceSshGatewayToken]
|
|
2937
2710
|
];
|
|
2938
2711
|
for (const [name, value] of hexSecrets) {
|
|
@@ -2957,7 +2730,6 @@ var createSecrets = (runtime) => {
|
|
|
2957
2730
|
postgresAdminPassword: hex(0),
|
|
2958
2731
|
mendDatabasePassword: hex(32),
|
|
2959
2732
|
sealantDatabasePassword: hex(64),
|
|
2960
|
-
queuePassword: hex(96),
|
|
2961
2733
|
betterAuthSecret: hex(128),
|
|
2962
2734
|
sealantCredentialsKey: bytes.subarray(160, 192).toString("base64"),
|
|
2963
2735
|
sealantServiceKey: `slt_svc_${hex(192)}`,
|
|
@@ -3132,8 +2904,6 @@ var validateComposeAsset = (body) => {
|
|
|
3132
2904
|
"mend-control:",
|
|
3133
2905
|
"mend-config:",
|
|
3134
2906
|
"mend-ssh:",
|
|
3135
|
-
"mend-rabbitmq:",
|
|
3136
|
-
"mend-registry:",
|
|
3137
2907
|
"mend-postgres:",
|
|
3138
2908
|
"/var/lib/mend/store",
|
|
3139
2909
|
"/run/sealant/sockets"
|
|
@@ -3161,7 +2931,7 @@ var renderIdentity = (secrets) => [
|
|
|
3161
2931
|
`MEND_POSTGRES_ADMIN_PASSWORD=${secrets.postgresAdminPassword}`,
|
|
3162
2932
|
`MEND_DB_PASSWORD=${secrets.mendDatabasePassword}`,
|
|
3163
2933
|
`SEALANT_DB_PASSWORD=${secrets.sealantDatabasePassword}`,
|
|
3164
|
-
`MEND_RABBITMQ_PASSWORD=${secrets.queuePassword}
|
|
2934
|
+
...secrets.queuePassword === void 0 ? [] : [`MEND_RABBITMQ_PASSWORD=${secrets.queuePassword}`],
|
|
3165
2935
|
`BETTER_AUTH_SECRET=${secrets.betterAuthSecret}`,
|
|
3166
2936
|
`SEALANT_CREDENTIALS_KEY=${secrets.sealantCredentialsKey}`,
|
|
3167
2937
|
`SEALANT_SERVICE_KEY=${secrets.sealantServiceKey}`,
|
|
@@ -3180,7 +2950,7 @@ var renderSecrets = (secrets, config) => {
|
|
|
3180
2950
|
`MEND_BIND_HOST=${composeBind}`,
|
|
3181
2951
|
`MEND_PORT=${config.appPort}`,
|
|
3182
2952
|
`MEND_SSH_PORT=${config.sshPort}`,
|
|
3183
|
-
`MEND_REGISTRY_PORT=${config.registryPort}
|
|
2953
|
+
...config.registryPort === void 0 ? [] : [`MEND_REGISTRY_PORT=${config.registryPort}`],
|
|
3184
2954
|
`SEALANT_SSH_HOST=${sshHost}`,
|
|
3185
2955
|
"MEND_STORE_VOLUME_NAME=mend-store",
|
|
3186
2956
|
"MEND_CONTROL_VOLUME_NAME=mend-control",
|
|
@@ -3262,7 +3032,7 @@ var resolveServerVersion = async (runtime, options, existing) => {
|
|
|
3262
3032
|
return version;
|
|
3263
3033
|
};
|
|
3264
3034
|
var parseDockerSocketPath = (socket) => {
|
|
3265
|
-
if (!
|
|
3035
|
+
if (!path5.isAbsolute(socket) || /[\r\n$'"#:\\]/.test(socket)) {
|
|
3266
3036
|
throw setupError(
|
|
3267
3037
|
"--docker-socket must be an absolute path without line breaks or Compose interpolation characters."
|
|
3268
3038
|
);
|
|
@@ -3288,8 +3058,8 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
|
|
|
3288
3058
|
if (options.assetsDir !== void 0) {
|
|
3289
3059
|
try {
|
|
3290
3060
|
const [compose2, postgresInit2] = await Promise.all([
|
|
3291
|
-
readFile(
|
|
3292
|
-
readFile(
|
|
3061
|
+
readFile(path5.join(options.assetsDir, COMPOSE_ASSET), "utf8"),
|
|
3062
|
+
readFile(path5.join(options.assetsDir, POSTGRES_INIT_ASSET), "utf8")
|
|
3293
3063
|
]);
|
|
3294
3064
|
validateComposeAsset(compose2);
|
|
3295
3065
|
validatePostgresAsset(postgresInit2);
|
|
@@ -3297,7 +3067,7 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
|
|
|
3297
3067
|
} catch (cause) {
|
|
3298
3068
|
if (cause instanceof ServerSetupError) throw cause;
|
|
3299
3069
|
throw setupError(
|
|
3300
|
-
"Could not read release assets from --assets-dir. Supply compose.
|
|
3070
|
+
"Could not read release assets from --assets-dir. Supply compose.v2.yaml and postgres-init.sh."
|
|
3301
3071
|
);
|
|
3302
3072
|
}
|
|
3303
3073
|
}
|
|
@@ -3408,6 +3178,11 @@ var setupServer = async (args, runtime, store) => {
|
|
|
3408
3178
|
`Setup retains Mend ${existing.config.serverVersion}. Use mend server upgrade --version ${options.version} to change the server pin.`
|
|
3409
3179
|
);
|
|
3410
3180
|
}
|
|
3181
|
+
if (existing !== null && existing.config.assetContract !== ASSET_CONTRACT) {
|
|
3182
|
+
throw setupError(
|
|
3183
|
+
`Mend ${existing.config.serverVersion} was installed under the ${existing.config.assetContract} bundle contract. Use mend server upgrade --version latest to move it to ${ASSET_CONTRACT}; setup cannot repair it in place.`
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3411
3186
|
const selectedContext = await selectDockerContext(
|
|
3412
3187
|
runtime,
|
|
3413
3188
|
options.context ?? existing?.config.dockerContext
|
|
@@ -3440,7 +3215,6 @@ var setupServer = async (args, runtime, store) => {
|
|
|
3440
3215
|
storeValue(store.activate(generation));
|
|
3441
3216
|
await startCompose(runtime, config, generation);
|
|
3442
3217
|
await probeHealth(runtime, config.appUrl, config.serverVersion);
|
|
3443
|
-
await verifyRegistry(runtime, config);
|
|
3444
3218
|
runtime.writeLine(`Mend ${config.serverVersion} is reachable at ${config.appUrl}`);
|
|
3445
3219
|
runtime.writeLine(
|
|
3446
3220
|
`Open ${config.appUrl}, create the first account, then run: mend login --url ${config.appUrl}`
|
|
@@ -3504,16 +3278,6 @@ var composeCommand = async (runtime, installation, args) => {
|
|
|
3504
3278
|
var interruptionNotice = (runtime) => runtime.writeLine(
|
|
3505
3279
|
"Connections will be interrupted. Workspace containers and data are retained, but active work can lose connectivity and may need reconnection. Mend does not stop workspace containers."
|
|
3506
3280
|
);
|
|
3507
|
-
var verifyRegistry = async (runtime, config) => {
|
|
3508
|
-
const registry = await probeServerRegistry(runtime, {
|
|
3509
|
-
dockerContext: config.dockerContext,
|
|
3510
|
-
registryPort: config.registryPort,
|
|
3511
|
-
nonce: runtime.randomBytes(24).toString("hex"),
|
|
3512
|
-
temporaryDirectory: path6.resolve(runtime.configDir)
|
|
3513
|
-
});
|
|
3514
|
-
for (const warning of registry.cleanupWarnings) runtime.writeLine(`Warning: ${warning.message}`);
|
|
3515
|
-
if (registry._tag === "error") throw setupError(registry.error.message);
|
|
3516
|
-
};
|
|
3517
3281
|
var startInstallation = async (runtime, installation) => {
|
|
3518
3282
|
startingNotice(runtime, installation.config.serverVersion);
|
|
3519
3283
|
await composeCommand(runtime, installation, [
|
|
@@ -3525,7 +3289,6 @@ var startInstallation = async (runtime, installation) => {
|
|
|
3525
3289
|
"--no-build"
|
|
3526
3290
|
]);
|
|
3527
3291
|
await probeHealth(runtime, installation.config.appUrl, installation.config.serverVersion);
|
|
3528
|
-
await verifyRegistry(runtime, installation.config);
|
|
3529
3292
|
runtime.writeLine(
|
|
3530
3293
|
`Mend ${installation.config.serverVersion} is reachable at ${installation.config.appUrl}`
|
|
3531
3294
|
);
|
|
@@ -3562,7 +3325,12 @@ var upgradeServer = async (args, runtime, store, existing) => {
|
|
|
3562
3325
|
const previous = storeValue(store.readActive());
|
|
3563
3326
|
if (previous === null) throw setupError("The active server generation is missing.");
|
|
3564
3327
|
const assets = await resolveAssets(runtime, version, existing, store, options);
|
|
3565
|
-
const
|
|
3328
|
+
const { registryPort: _legacyRegistryPort, ...carried } = existing.config;
|
|
3329
|
+
const config = {
|
|
3330
|
+
...carried,
|
|
3331
|
+
assetContract: ASSET_CONTRACT,
|
|
3332
|
+
serverVersion: version
|
|
3333
|
+
};
|
|
3566
3334
|
const secrets = parseSecrets(previous.files.identity);
|
|
3567
3335
|
const files = {
|
|
3568
3336
|
identity: previous.files.identity,
|
|
@@ -3734,9 +3502,9 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
3734
3502
|
var nodeServerRuntime = () => {
|
|
3735
3503
|
const environment = { ...process.env };
|
|
3736
3504
|
const home = os3.homedir();
|
|
3737
|
-
const configHome = environment["XDG_CONFIG_HOME"] ??
|
|
3505
|
+
const configHome = environment["XDG_CONFIG_HOME"] ?? path5.join(home, ".config");
|
|
3738
3506
|
return {
|
|
3739
|
-
configDir:
|
|
3507
|
+
configDir: path5.join(configHome, "mend"),
|
|
3740
3508
|
platform: process.platform,
|
|
3741
3509
|
cliVersion: cliVersion(),
|
|
3742
3510
|
run: (command, args, options) => runServerProcess(command, args, environment, options),
|
|
@@ -3753,7 +3521,7 @@ var nodeServerRuntime = () => {
|
|
|
3753
3521
|
}
|
|
3754
3522
|
},
|
|
3755
3523
|
randomBytes,
|
|
3756
|
-
sleep: (milliseconds) => new Promise((
|
|
3524
|
+
sleep: (milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds)),
|
|
3757
3525
|
writeLine: (line) => process.stdout.write(`${line}
|
|
3758
3526
|
`)
|
|
3759
3527
|
};
|
|
@@ -3962,8 +3730,8 @@ var renderMendToml = (proposals) => {
|
|
|
3962
3730
|
};
|
|
3963
3731
|
|
|
3964
3732
|
// src/skills.ts
|
|
3965
|
-
import * as
|
|
3966
|
-
import * as
|
|
3733
|
+
import * as fs6 from "node:fs";
|
|
3734
|
+
import * as path6 from "node:path";
|
|
3967
3735
|
var DEFAULT_SKILLS_DIR = ".agents/skills";
|
|
3968
3736
|
var MAX_SKILL_FILES = 64;
|
|
3969
3737
|
var MAX_SKILL_FILE_BYTES = 512 * 1024;
|
|
@@ -3986,12 +3754,12 @@ var readFrontmatterDescription = (contents) => {
|
|
|
3986
3754
|
var walkFiles = (root) => {
|
|
3987
3755
|
const found = [];
|
|
3988
3756
|
const visit = (dir) => {
|
|
3989
|
-
for (const entry of
|
|
3757
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
3990
3758
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3991
|
-
const absolute =
|
|
3759
|
+
const absolute = path6.join(dir, entry.name);
|
|
3992
3760
|
let stat;
|
|
3993
3761
|
try {
|
|
3994
|
-
stat =
|
|
3762
|
+
stat = fs6.statSync(absolute);
|
|
3995
3763
|
} catch {
|
|
3996
3764
|
continue;
|
|
3997
3765
|
}
|
|
@@ -4005,7 +3773,7 @@ var walkFiles = (root) => {
|
|
|
4005
3773
|
var scanSkillLibrary = (root) => {
|
|
4006
3774
|
let entries;
|
|
4007
3775
|
try {
|
|
4008
|
-
entries =
|
|
3776
|
+
entries = fs6.readdirSync(root, { withFileTypes: true });
|
|
4009
3777
|
} catch {
|
|
4010
3778
|
return { error: `${root} is not a readable directory` };
|
|
4011
3779
|
}
|
|
@@ -4013,23 +3781,23 @@ var scanSkillLibrary = (root) => {
|
|
|
4013
3781
|
const notes = [];
|
|
4014
3782
|
for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
|
|
4015
3783
|
if (entry.name.startsWith(".")) continue;
|
|
4016
|
-
const skillDir =
|
|
3784
|
+
const skillDir = path6.join(root, entry.name);
|
|
4017
3785
|
let stat;
|
|
4018
3786
|
try {
|
|
4019
|
-
stat =
|
|
3787
|
+
stat = fs6.statSync(skillDir);
|
|
4020
3788
|
} catch {
|
|
4021
3789
|
continue;
|
|
4022
3790
|
}
|
|
4023
3791
|
if (!stat.isDirectory()) continue;
|
|
4024
|
-
if (!
|
|
3792
|
+
if (!fs6.existsSync(path6.join(skillDir, "SKILL.md"))) {
|
|
4025
3793
|
notes.push({ skill: entry.name, message: "no SKILL.md \u2014 skipped" });
|
|
4026
3794
|
continue;
|
|
4027
3795
|
}
|
|
4028
3796
|
const files = [];
|
|
4029
3797
|
let bytes = 0;
|
|
4030
3798
|
for (const absolute of walkFiles(skillDir)) {
|
|
4031
|
-
const relative3 =
|
|
4032
|
-
const buffer =
|
|
3799
|
+
const relative3 = path6.relative(skillDir, absolute).split(path6.sep).join("/");
|
|
3800
|
+
const buffer = fs6.readFileSync(absolute);
|
|
4033
3801
|
if (buffer.byteLength > MAX_SKILL_FILE_BYTES) {
|
|
4034
3802
|
notes.push({ skill: entry.name, message: `${relative3} is over 512KB \u2014 skipped` });
|
|
4035
3803
|
continue;
|
|
@@ -4065,15 +3833,15 @@ var scanSkillLibrary = (root) => {
|
|
|
4065
3833
|
|
|
4066
3834
|
// src/ssh-setup.ts
|
|
4067
3835
|
import * as os5 from "node:os";
|
|
4068
|
-
import * as
|
|
3836
|
+
import * as path8 from "node:path";
|
|
4069
3837
|
|
|
4070
3838
|
// ../../packages/workspace-ssh/src/index.ts
|
|
4071
3839
|
import { spawnSync } from "node:child_process";
|
|
4072
3840
|
import { createHash as createHash2 } from "node:crypto";
|
|
4073
|
-
import * as
|
|
3841
|
+
import * as fs7 from "node:fs";
|
|
4074
3842
|
import { isIP as isIP2 } from "node:net";
|
|
4075
3843
|
import * as os4 from "node:os";
|
|
4076
|
-
import * as
|
|
3844
|
+
import * as path7 from "node:path";
|
|
4077
3845
|
var LEGACY_BLOCK_BEGIN = "# >>> mend workspace ssh (managed by `mend ssh setup`) >>>";
|
|
4078
3846
|
var LEGACY_BLOCK_END = "# <<< mend workspace ssh <<<";
|
|
4079
3847
|
var WorkspaceSshError = class extends Error {
|
|
@@ -4089,7 +3857,7 @@ var WorkspaceSshError = class extends Error {
|
|
|
4089
3857
|
}
|
|
4090
3858
|
};
|
|
4091
3859
|
var success = (value) => ({ ok: true, value });
|
|
4092
|
-
var failure = (
|
|
3860
|
+
var failure = (error) => ({ ok: false, error });
|
|
4093
3861
|
var stripIpv6Brackets = (hostname6) => hostname6.startsWith("[") && hostname6.endsWith("]") ? hostname6.slice(1, -1) : hostname6;
|
|
4094
3862
|
var hashIdentity = (identity) => createHash2("sha256").update(identity).digest("hex").slice(0, 24);
|
|
4095
3863
|
var aliasLabel = (hostname6) => {
|
|
@@ -4160,8 +3928,8 @@ var blockBegin = (alias) => `# >>> mend workspace ssh ${alias} (managed) >>>`;
|
|
|
4160
3928
|
var blockEnd = (alias) => `# <<< mend workspace ssh ${alias} <<<`;
|
|
4161
3929
|
var hasControls = (value) => /[\p{Cc}\p{Zl}\p{Zp}]/u.test(value);
|
|
4162
3930
|
var parseIdentityPath = (value) => {
|
|
4163
|
-
const resolved =
|
|
4164
|
-
value.startsWith("~/") ?
|
|
3931
|
+
const resolved = path7.resolve(
|
|
3932
|
+
value.startsWith("~/") ? path7.join(os4.homedir(), value.slice(2)) : value
|
|
4165
3933
|
);
|
|
4166
3934
|
if (value === "" || hasControls(value) || hasControls(resolved) || value.includes("${") || resolved.includes("${")) {
|
|
4167
3935
|
return failure(
|
|
@@ -4297,10 +4065,10 @@ var agentKeys = () => {
|
|
|
4297
4065
|
};
|
|
4298
4066
|
var readText = (file) => {
|
|
4299
4067
|
try {
|
|
4300
|
-
return success(
|
|
4068
|
+
return success(fs7.readFileSync(file, "utf8"));
|
|
4301
4069
|
} catch (cause) {
|
|
4302
|
-
const
|
|
4303
|
-
return
|
|
4070
|
+
const error = cause instanceof Error && "code" in cause ? cause.code : void 0;
|
|
4071
|
+
return error === "ENOENT" ? success("") : failure(new WorkspaceSshError("key", `Could not read ${file}.`, cause));
|
|
4304
4072
|
}
|
|
4305
4073
|
};
|
|
4306
4074
|
var configuredWorkspaceSshIdentityFile = (config, target) => {
|
|
@@ -4355,19 +4123,19 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
4355
4123
|
var readOptionalKey = (keyPath) => {
|
|
4356
4124
|
const parsed = parseIdentityPath(keyPath);
|
|
4357
4125
|
if (!parsed.ok) return parsed;
|
|
4358
|
-
if (!
|
|
4126
|
+
if (!fs7.existsSync(parsed.value) && !fs7.existsSync(publicKeyPath(parsed.value)))
|
|
4359
4127
|
return success(null);
|
|
4360
4128
|
return readRequiredKey(parsed.value, "existing");
|
|
4361
4129
|
};
|
|
4362
4130
|
var pinAgentKey = (configHome, publicKey) => {
|
|
4363
|
-
const identityFile =
|
|
4131
|
+
const identityFile = path7.resolve(
|
|
4364
4132
|
configHome,
|
|
4365
4133
|
"ssh",
|
|
4366
4134
|
`agent-${hashIdentity(publicKeyIdentity(publicKey))}.pub`
|
|
4367
4135
|
);
|
|
4368
4136
|
try {
|
|
4369
|
-
|
|
4370
|
-
|
|
4137
|
+
fs7.mkdirSync(path7.dirname(identityFile), { recursive: true, mode: 448 });
|
|
4138
|
+
fs7.writeFileSync(identityFile, `${publicKey}
|
|
4371
4139
|
`, { mode: 384 });
|
|
4372
4140
|
return success({ publicKey, identityFile, source: "agent" });
|
|
4373
4141
|
} catch (cause) {
|
|
@@ -4378,10 +4146,10 @@ var pinAgentKey = (configHome, publicKey) => {
|
|
|
4378
4146
|
};
|
|
4379
4147
|
var generateDedicatedKey = (privatePath) => {
|
|
4380
4148
|
try {
|
|
4381
|
-
|
|
4149
|
+
fs7.mkdirSync(path7.dirname(privatePath), { recursive: true, mode: 448 });
|
|
4382
4150
|
} catch (cause) {
|
|
4383
4151
|
return failure(
|
|
4384
|
-
new WorkspaceSshError("key", `Could not create ${
|
|
4152
|
+
new WorkspaceSshError("key", `Could not create ${path7.dirname(privatePath)}.`, cause)
|
|
4385
4153
|
);
|
|
4386
4154
|
}
|
|
4387
4155
|
const generated = spawnSync(
|
|
@@ -4410,7 +4178,7 @@ var generateDedicatedKey = (privatePath) => {
|
|
|
4410
4178
|
return fresh;
|
|
4411
4179
|
};
|
|
4412
4180
|
var dedicatedKey = (configHome, create) => {
|
|
4413
|
-
const privatePath =
|
|
4181
|
+
const privatePath = path7.join(configHome, "ssh", "id_ed25519");
|
|
4414
4182
|
const existing = readOptionalKey(privatePath);
|
|
4415
4183
|
if (existing.ok === false) return failure(existing.error);
|
|
4416
4184
|
if (existing.value !== null || !create) return existing;
|
|
@@ -4458,10 +4226,10 @@ var writeWorkspaceSshConfig = (configFile, target, identityFile) => {
|
|
|
4458
4226
|
const reconciled = reconcileWorkspaceSshConfig(before.value, target, identityFile);
|
|
4459
4227
|
if (reconciled.ok === false) return failure(reconciled.error);
|
|
4460
4228
|
try {
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4229
|
+
fs7.mkdirSync(path7.dirname(configFile), { recursive: true, mode: 448 });
|
|
4230
|
+
fs7.chmodSync(path7.dirname(configFile), 448);
|
|
4231
|
+
fs7.writeFileSync(configFile, reconciled.value, { mode: 384 });
|
|
4232
|
+
fs7.chmodSync(configFile, 384);
|
|
4465
4233
|
return success(void 0);
|
|
4466
4234
|
} catch (cause) {
|
|
4467
4235
|
return failure(new WorkspaceSshError("config", `Could not write ${configFile}.`, cause));
|
|
@@ -4474,7 +4242,7 @@ var dim3 = ansi("2");
|
|
|
4474
4242
|
var green3 = ansi("32");
|
|
4475
4243
|
var warn = ansi("33");
|
|
4476
4244
|
var say3 = (line) => console.log(line);
|
|
4477
|
-
var sshConfigPath = () =>
|
|
4245
|
+
var sshConfigPath = () => path8.join(os5.homedir(), ".ssh", "config");
|
|
4478
4246
|
var flagValue = (args, flag) => {
|
|
4479
4247
|
const index = args.indexOf(flag);
|
|
4480
4248
|
return index === -1 || args[index + 1] === void 0 ? null : String(args[index + 1]);
|
|
@@ -4588,19 +4356,19 @@ var sshCommand = async (args, api2, cliHome, serverUrl) => {
|
|
|
4588
4356
|
// src/main.ts
|
|
4589
4357
|
var mendCliHome = () => {
|
|
4590
4358
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
4591
|
-
const preferred =
|
|
4592
|
-
xdg === void 0 || xdg === "" ?
|
|
4359
|
+
const preferred = path9.join(
|
|
4360
|
+
xdg === void 0 || xdg === "" ? path9.join(os6.homedir(), ".config") : xdg,
|
|
4593
4361
|
"mend"
|
|
4594
4362
|
);
|
|
4595
|
-
const legacy =
|
|
4596
|
-
return !
|
|
4363
|
+
const legacy = path9.join(os6.homedir(), ".mend");
|
|
4364
|
+
return !fs8.existsSync(preferred) && fs8.existsSync(legacy) ? legacy : preferred;
|
|
4597
4365
|
};
|
|
4598
|
-
var CONFIG_PATH =
|
|
4366
|
+
var CONFIG_PATH = path9.join(mendCliHome(), "cli.json");
|
|
4599
4367
|
var loadConfig = () => {
|
|
4600
4368
|
let fileConfig = {};
|
|
4601
|
-
if (
|
|
4369
|
+
if (fs8.existsSync(CONFIG_PATH)) {
|
|
4602
4370
|
try {
|
|
4603
|
-
fileConfig = JSON.parse(
|
|
4371
|
+
fileConfig = JSON.parse(fs8.readFileSync(CONFIG_PATH, "utf8"));
|
|
4604
4372
|
} catch {
|
|
4605
4373
|
fail4(`could not parse ${CONFIG_PATH}`);
|
|
4606
4374
|
}
|
|
@@ -4700,8 +4468,8 @@ var request = async (config, method, route, body) => {
|
|
|
4700
4468
|
var api = async (config, method, route, body) => {
|
|
4701
4469
|
try {
|
|
4702
4470
|
return await request(config, method, route, body);
|
|
4703
|
-
} catch (
|
|
4704
|
-
return fail4(
|
|
4471
|
+
} catch (error) {
|
|
4472
|
+
return fail4(error instanceof Error ? error.message : String(error));
|
|
4705
4473
|
}
|
|
4706
4474
|
};
|
|
4707
4475
|
var boundApi = (config) => (method, route, body) => api(config, method, route, body);
|
|
@@ -4737,7 +4505,7 @@ var adopt = async (config, args) => {
|
|
|
4737
4505
|
}
|
|
4738
4506
|
const sourceIssue = repositoryCloneUrlIssue(source);
|
|
4739
4507
|
if (sourceIssue !== null) return fail4(sourceIssue);
|
|
4740
|
-
const projectName = name ?? normalizeProjectName(
|
|
4508
|
+
const projectName = name ?? normalizeProjectName(path9.basename(source, ".git"));
|
|
4741
4509
|
const auth = authFlagIndex !== -1 && args[authFlagIndex + 1] !== void 0 ? String(args[authFlagIndex + 1]) : null;
|
|
4742
4510
|
if (auth !== null && auth !== "ambient" && auth !== "mend-key" && auth !== "bridge") {
|
|
4743
4511
|
return fail4(`--auth takes "ambient", "mend-key", or "bridge", not "${auth}"`);
|
|
@@ -4782,7 +4550,7 @@ var findProject = async (config, explicit, adoptCwd = false) => {
|
|
|
4782
4550
|
const sourceIssue = repositoryCloneUrlIssue(facts.originUrl);
|
|
4783
4551
|
if (sourceIssue !== null) return fail4(sourceIssue);
|
|
4784
4552
|
const adopted = await api(config, "POST", "/projects", {
|
|
4785
|
-
name: normalizeProjectName(
|
|
4553
|
+
name: normalizeProjectName(path9.basename(facts.repoRoot)),
|
|
4786
4554
|
source: facts.originUrl
|
|
4787
4555
|
});
|
|
4788
4556
|
say4(`${green4("\u2713")} adopted \xB7 ${adopted.name} \xB7 ${dim4(adopted.storePath)}`);
|
|
@@ -4927,8 +4695,8 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
4927
4695
|
const ws = new WebSocket(url);
|
|
4928
4696
|
ws.binaryType = "arraybuffer";
|
|
4929
4697
|
try {
|
|
4930
|
-
await new Promise((
|
|
4931
|
-
ws.addEventListener("open", () =>
|
|
4698
|
+
await new Promise((resolve4, reject) => {
|
|
4699
|
+
ws.addEventListener("open", () => resolve4(), { once: true });
|
|
4932
4700
|
ws.addEventListener("error", () => reject(new Error("could not connect")), { once: true });
|
|
4933
4701
|
});
|
|
4934
4702
|
} catch {
|
|
@@ -4936,8 +4704,8 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
4936
4704
|
}
|
|
4937
4705
|
const stopHerdrHint = hintHerdrAttachment(harness);
|
|
4938
4706
|
let finishAttachment;
|
|
4939
|
-
const finished = new Promise((
|
|
4940
|
-
finishAttachment =
|
|
4707
|
+
const finished = new Promise((resolve4) => {
|
|
4708
|
+
finishAttachment = resolve4;
|
|
4941
4709
|
});
|
|
4942
4710
|
const onClose = () => finishAttachment?.();
|
|
4943
4711
|
if (ws.readyState === WebSocket.CLOSED) finishAttachment?.();
|
|
@@ -5555,7 +5323,7 @@ var serviceLogs = async (config, args) => {
|
|
|
5555
5323
|
cursor = page.nextFrom;
|
|
5556
5324
|
const ended = page.status === "exited" || page.status === "failed";
|
|
5557
5325
|
if (ended && !advanced) break;
|
|
5558
|
-
if (!advanced) await new Promise((
|
|
5326
|
+
if (!advanced) await new Promise((resolve4) => setTimeout(resolve4, 250));
|
|
5559
5327
|
}
|
|
5560
5328
|
say4("");
|
|
5561
5329
|
say4(dim4(`stream ended \xB7 next sequence ${cursor}`));
|
|
@@ -5573,13 +5341,13 @@ var serviceRestart = async (config, args) => {
|
|
|
5573
5341
|
};
|
|
5574
5342
|
var serviceInit = async (args) => {
|
|
5575
5343
|
const root = gitTopLevel(process.cwd()) ?? process.cwd();
|
|
5576
|
-
const target =
|
|
5577
|
-
if (
|
|
5344
|
+
const target = path9.join(root, "mend.toml");
|
|
5345
|
+
if (fs8.existsSync(target)) {
|
|
5578
5346
|
return fail4(`${target} already exists \u2014 edit it directly (init never merges)`);
|
|
5579
5347
|
}
|
|
5580
|
-
const rootFiles =
|
|
5348
|
+
const rootFiles = fs8.readdirSync(root);
|
|
5581
5349
|
const proposals = [];
|
|
5582
|
-
const readRoot = (name) =>
|
|
5350
|
+
const readRoot = (name) => fs8.readFileSync(path9.join(root, name), "utf8");
|
|
5583
5351
|
if (rootFiles.includes("package.json")) {
|
|
5584
5352
|
proposals.push(...proposeFromPackageJson(readRoot("package.json"), rootFiles));
|
|
5585
5353
|
}
|
|
@@ -5589,13 +5357,13 @@ var serviceInit = async (args) => {
|
|
|
5589
5357
|
);
|
|
5590
5358
|
for (const glob of globs) {
|
|
5591
5359
|
const parent = glob.replace(/\/?\*+$/, "");
|
|
5592
|
-
const dirs = glob.endsWith("*") ?
|
|
5360
|
+
const dirs = glob.endsWith("*") ? fs8.existsSync(path9.join(root, parent)) ? fs8.readdirSync(path9.join(root, parent)).map((dir) => path9.join(parent, dir)) : [] : [glob];
|
|
5593
5361
|
for (const dir of dirs) {
|
|
5594
|
-
const manifest =
|
|
5595
|
-
if (!
|
|
5362
|
+
const manifest = path9.join(root, dir, "package.json");
|
|
5363
|
+
if (!fs8.existsSync(manifest)) continue;
|
|
5596
5364
|
for (const proposal of proposeFromWorkspacePackage(
|
|
5597
|
-
|
|
5598
|
-
|
|
5365
|
+
path9.basename(dir),
|
|
5366
|
+
fs8.readFileSync(manifest, "utf8"),
|
|
5599
5367
|
rootFiles
|
|
5600
5368
|
)) {
|
|
5601
5369
|
if (!proposals.some((existing) => existing.name === proposal.name)) {
|
|
@@ -5639,7 +5407,7 @@ var serviceInit = async (args) => {
|
|
|
5639
5407
|
return;
|
|
5640
5408
|
}
|
|
5641
5409
|
}
|
|
5642
|
-
|
|
5410
|
+
fs8.writeFileSync(target, toml);
|
|
5643
5411
|
say4(`${green4("\u2713")} wrote ${target} \u2014 commit it, then: mend service run ${proposals[0]?.name}`);
|
|
5644
5412
|
};
|
|
5645
5413
|
var tunnelServices = async (config, services, portOverride) => {
|
|
@@ -5670,16 +5438,16 @@ var tunnelServices = async (config, services, portOverride) => {
|
|
|
5670
5438
|
socket.on("close", () => ws.close());
|
|
5671
5439
|
socket.on("error", () => ws.close());
|
|
5672
5440
|
});
|
|
5673
|
-
await new Promise((
|
|
5674
|
-
server.once("error", (
|
|
5441
|
+
await new Promise((resolve4, reject) => {
|
|
5442
|
+
server.once("error", (error) => {
|
|
5675
5443
|
reject(
|
|
5676
5444
|
new Error(
|
|
5677
|
-
|
|
5445
|
+
error.code === "EADDRINUSE" ? `127.0.0.1:${port} is already in use here \u2014 pick one with: mend service connect ${service.label} --port <n>` : error.message
|
|
5678
5446
|
)
|
|
5679
5447
|
);
|
|
5680
5448
|
});
|
|
5681
|
-
server.listen(port, "127.0.0.1", () =>
|
|
5682
|
-
}).catch((
|
|
5449
|
+
server.listen(port, "127.0.0.1", () => resolve4());
|
|
5450
|
+
}).catch((error) => fail4(error.message));
|
|
5683
5451
|
say4(
|
|
5684
5452
|
`${green4("\u25CF")} ${service.label ?? service.id.slice(0, 8)} \u2192 127.0.0.1:${port} ${dim4(`(tunnel to ${config.url})`)}`
|
|
5685
5453
|
);
|
|
@@ -5732,18 +5500,18 @@ var serviceCommand = async (config, args) => {
|
|
|
5732
5500
|
}
|
|
5733
5501
|
};
|
|
5734
5502
|
var isProvider = (value) => value === "claude" || value === "codex" || value === "github";
|
|
5735
|
-
var readIfExists = (file) =>
|
|
5503
|
+
var readIfExists = (file) => fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : null;
|
|
5736
5504
|
var localCredential = (provider) => {
|
|
5737
5505
|
const home = os6.homedir();
|
|
5738
5506
|
switch (provider) {
|
|
5739
5507
|
case "codex":
|
|
5740
5508
|
return readIfExists(
|
|
5741
|
-
|
|
5509
|
+
path9.join(process.env["CODEX_HOME"] ?? path9.join(home, ".codex"), "auth.json")
|
|
5742
5510
|
);
|
|
5743
5511
|
case "claude":
|
|
5744
5512
|
return readIfExists(
|
|
5745
|
-
|
|
5746
|
-
process.env["CLAUDE_CONFIG_DIR"] ??
|
|
5513
|
+
path9.join(
|
|
5514
|
+
process.env["CLAUDE_CONFIG_DIR"] ?? path9.join(home, ".claude"),
|
|
5747
5515
|
".credentials.json"
|
|
5748
5516
|
)
|
|
5749
5517
|
);
|
|
@@ -5797,7 +5565,7 @@ var connectCommand = async (config, args) => {
|
|
|
5797
5565
|
}
|
|
5798
5566
|
let secret;
|
|
5799
5567
|
if (flags.includes("--from-stdin")) {
|
|
5800
|
-
secret =
|
|
5568
|
+
secret = fs8.readFileSync(0, "utf8").trim();
|
|
5801
5569
|
if (secret === "") return fail4("nothing on stdin");
|
|
5802
5570
|
} else {
|
|
5803
5571
|
secret = localCredential(provider);
|
|
@@ -5818,14 +5586,14 @@ var takeFlagValue3 = (args, flag) => {
|
|
|
5818
5586
|
return at !== -1 && args[at + 1] !== void 0 ? String(args[at + 1]) : null;
|
|
5819
5587
|
};
|
|
5820
5588
|
var saveCliConfig = (next) => {
|
|
5821
|
-
|
|
5822
|
-
|
|
5589
|
+
fs8.mkdirSync(path9.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
5590
|
+
fs8.writeFileSync(
|
|
5823
5591
|
CONFIG_PATH,
|
|
5824
5592
|
`${JSON.stringify({ url: next.url, token: next.token, deviceId: next.deviceId }, null, 2)}
|
|
5825
5593
|
`,
|
|
5826
5594
|
{ mode: 384 }
|
|
5827
5595
|
);
|
|
5828
|
-
|
|
5596
|
+
fs8.chmodSync(CONFIG_PATH, 384);
|
|
5829
5597
|
};
|
|
5830
5598
|
var login = async (config, args) => {
|
|
5831
5599
|
await loginCommand(args, {
|
|
@@ -5838,7 +5606,7 @@ var login = async (config, args) => {
|
|
|
5838
5606
|
});
|
|
5839
5607
|
};
|
|
5840
5608
|
var logout = async (config) => {
|
|
5841
|
-
if (!
|
|
5609
|
+
if (!fs8.existsSync(CONFIG_PATH) && config.token === null) {
|
|
5842
5610
|
say4(dim4("nothing saved \u2014 already signed out"));
|
|
5843
5611
|
return;
|
|
5844
5612
|
}
|
|
@@ -6027,10 +5795,10 @@ var envLoad = async (config, args) => {
|
|
|
6027
5795
|
const positional = args.filter(
|
|
6028
5796
|
(arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project" && !consumed.has(i)
|
|
6029
5797
|
);
|
|
6030
|
-
const file =
|
|
5798
|
+
const file = path9.resolve(positional[0] ?? ".env");
|
|
6031
5799
|
let contents;
|
|
6032
5800
|
try {
|
|
6033
|
-
contents =
|
|
5801
|
+
contents = fs8.readFileSync(file, "utf8");
|
|
6034
5802
|
} catch {
|
|
6035
5803
|
return fail4(`cannot read ${file} \u2014 pass a path: mend env load path/to/.env`);
|
|
6036
5804
|
}
|
|
@@ -6046,10 +5814,10 @@ var envLoad = async (config, args) => {
|
|
|
6046
5814
|
{ contents, allSecret, secretNames }
|
|
6047
5815
|
);
|
|
6048
5816
|
if (report.loaded.length === 0 && report.rejected.length === 0 && report.malformedLines.length === 0) {
|
|
6049
|
-
say4(dim4(`${
|
|
5817
|
+
say4(dim4(`${path9.basename(file)} has no variables`));
|
|
6050
5818
|
return;
|
|
6051
5819
|
}
|
|
6052
|
-
say4(`${green4("\u2713")} loaded ${
|
|
5820
|
+
say4(`${green4("\u2713")} loaded ${path9.basename(file)} into ${project2.name}`);
|
|
6053
5821
|
for (const line of formatLoadReport(report, { dim: dim4, warn: amber })) say4(line);
|
|
6054
5822
|
const plaintextUrls = report.loaded.filter(
|
|
6055
5823
|
(entry) => entry.lane === "configuration" && /_(URL|URI|DSN)$/.test(entry.name)
|
|
@@ -6238,7 +6006,7 @@ var skillsList = async (config, args) => {
|
|
|
6238
6006
|
};
|
|
6239
6007
|
var skillsPush = async (config, args) => {
|
|
6240
6008
|
const dirFlag = takeFlagValue3(args, "--dir");
|
|
6241
|
-
const root = dirFlag === null ?
|
|
6009
|
+
const root = dirFlag === null ? path9.join(os6.homedir(), DEFAULT_SKILLS_DIR) : path9.resolve(dirFlag);
|
|
6242
6010
|
const scanned = scanSkillLibrary(root);
|
|
6243
6011
|
if ("error" in scanned) return fail4(scanned.error);
|
|
6244
6012
|
for (const note of scanned.notes) say4(dim4(` ${note.skill}: ${note.message}`));
|
|
@@ -6328,7 +6096,7 @@ _mend() {
|
|
|
6328
6096
|
compadd setup status start stop restart logs upgrade
|
|
6329
6097
|
else
|
|
6330
6098
|
case $words[3] in
|
|
6331
|
-
setup) compadd -- --version --assets-dir --offline --context --port --ssh-port --
|
|
6099
|
+
setup) compadd -- --version --assets-dir --offline --context --port --ssh-port --bind --url --origin --docker-socket ;;
|
|
6332
6100
|
upgrade) compadd -- --version --assets-dir --offline ;;
|
|
6333
6101
|
start|restart) compadd -- --offline ;;
|
|
6334
6102
|
logs) compadd -- --tail ;;
|
|
@@ -6357,7 +6125,7 @@ var BASH_COMPLETIONS = `_mend() {
|
|
|
6357
6125
|
options="setup status start stop restart logs upgrade"
|
|
6358
6126
|
else
|
|
6359
6127
|
case \${COMP_WORDS[2]} in
|
|
6360
|
-
setup) options="--version --assets-dir --offline --context --port --ssh-port --
|
|
6128
|
+
setup) options="--version --assets-dir --offline --context --port --ssh-port --bind --url --origin --docker-socket" ;;
|
|
6361
6129
|
upgrade) options="--version --assets-dir --offline" ;;
|
|
6362
6130
|
start|restart) options="--offline" ;;
|
|
6363
6131
|
logs) options="--tail" ;;
|
|
@@ -6539,10 +6307,10 @@ var resumeForRejoin = async (config, sessionId, label) => {
|
|
|
6539
6307
|
request(config, "POST", `/sessions/${sessionId}/resume`, { harness: null })
|
|
6540
6308
|
);
|
|
6541
6309
|
return true;
|
|
6542
|
-
} catch (
|
|
6310
|
+
} catch (error) {
|
|
6543
6311
|
const refreshed = await api(config, "GET", `/sessions/${sessionId}`);
|
|
6544
6312
|
if (agentIsLive(refreshed.session, refreshed.currentAgent)) return false;
|
|
6545
|
-
return fail4(
|
|
6313
|
+
return fail4(error instanceof Error ? error.message : String(error));
|
|
6546
6314
|
}
|
|
6547
6315
|
};
|
|
6548
6316
|
var rejoinCommand = async (config, args) => {
|
|
@@ -6882,7 +6650,7 @@ var dashboard = async (config) => {
|
|
|
6882
6650
|
);
|
|
6883
6651
|
process.exit(rerun.status ?? 0);
|
|
6884
6652
|
}
|
|
6885
|
-
const { runDashboard } = await import("./dashboard-
|
|
6653
|
+
const { runDashboard } = await import("./dashboard-JIWYW3EM.js");
|
|
6886
6654
|
const agentShare = await startShareIfBridge(config);
|
|
6887
6655
|
try {
|
|
6888
6656
|
await runDashboard({
|
|
@@ -6952,10 +6720,10 @@ var manCommand = (words) => {
|
|
|
6952
6720
|
}
|
|
6953
6721
|
const version = cliVersion();
|
|
6954
6722
|
const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
|
|
6955
|
-
const file =
|
|
6956
|
-
|
|
6723
|
+
const file = path9.join(os6.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
|
|
6724
|
+
fs8.writeFileSync(file, page);
|
|
6957
6725
|
const result = spawnSync2("man", ["-l", file], { stdio: "inherit" });
|
|
6958
|
-
|
|
6726
|
+
fs8.rmSync(file, { force: true });
|
|
6959
6727
|
if (result.error !== void 0 || result.status !== 0) {
|
|
6960
6728
|
say4(doc === null ? renderIndex() : renderCommand(doc));
|
|
6961
6729
|
}
|