@clawops/cli 2.1.2 → 2.2.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 +122 -2
- package/dist/agent-activity-25FAWLAI.js +16 -0
- package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
- package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
- package/dist/backup-flows-NDP5JTR3.js +29 -0
- package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
- package/dist/bootstrap.sh.tmpl +4 -0
- package/dist/chunk-27335SIH.js +51 -0
- package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
- package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
- package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
- package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
- package/dist/chunk-755X5OGS.js +213 -0
- package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
- package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
- package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
- package/dist/chunk-E6BRQK67.js +289 -0
- package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
- package/dist/chunk-LV7UO5CG.js +82 -0
- package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
- package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
- package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
- package/dist/chunk-PCSX6Y3U.js +297 -0
- package/dist/chunk-POYS3EVQ.js +14 -0
- package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
- package/dist/chunk-SX75L2T3.js +46 -0
- package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
- package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
- package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
- package/dist/{chunk-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
- package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
- package/dist/cli.js +383 -925
- package/dist/conn-M2V5HBPJ.js +9 -0
- package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
- package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
- package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
- package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
- package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
- package/dist/init-HOPXDNWY.js +13 -0
- package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
- package/dist/migrate-flow-24XA4GZL.js +25 -0
- package/dist/{package-JFVGCNKU.js → package-HYGHRX35.js} +3 -1
- package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
- package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
- package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
- package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
- package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
- package/dist/server-E3YA32ND.js +53 -0
- package/dist/ssh-HOM7GF3M.js +13 -0
- package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
- package/dist/stack-delete-NTIGKWUF.js +13 -0
- package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
- package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
- package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
- package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
- package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
- package/package.json +3 -1
- package/spec/mcp-tools.yaml +282 -35
- package/dist/logs-5A7YYA53.js +0 -15
- package/dist/server-B7I6HBQH.js +0 -46
- package/dist/ssh-VAI5DU5M.js +0 -11
- package/dist/upgrade-ULD4KFYC.js +0 -88
- /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -5,38 +5,51 @@ import {
|
|
|
5
5
|
writeAppConfigs
|
|
6
6
|
} from "./chunk-OIGTOLB3.js";
|
|
7
7
|
import {
|
|
8
|
+
AUDIT_CLEAN_MESSAGE,
|
|
9
|
+
AUDIT_FIX_HINT,
|
|
10
|
+
NO_SECRETS_MESSAGE,
|
|
11
|
+
SET_SECRET_HINT,
|
|
12
|
+
auditIssuesHeadline,
|
|
13
|
+
auditSecrets,
|
|
14
|
+
deleteConfirmQuestion,
|
|
15
|
+
deletePreparedSecret,
|
|
16
|
+
dockerRunCmd,
|
|
17
|
+
formatAuditIssue,
|
|
18
|
+
gatewayStatus,
|
|
19
|
+
listSecrets,
|
|
8
20
|
monitor_default,
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
prepareSecretDelete,
|
|
22
|
+
resolveSecretPath,
|
|
23
|
+
resolveUpdateVersion,
|
|
24
|
+
runDiagnostics,
|
|
25
|
+
secretsDir,
|
|
26
|
+
updateGateway
|
|
27
|
+
} from "./chunk-OUQVKIIA.js";
|
|
11
28
|
import {
|
|
12
29
|
GATEWAY_LOGS_PROBE,
|
|
13
30
|
chooseLogSource,
|
|
14
31
|
containerLogsCommand,
|
|
15
32
|
gatewayLogsCommand
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
33
|
+
} from "./chunk-SOZ62ZXW.js";
|
|
34
|
+
import "./chunk-6ZFIFDBJ.js";
|
|
35
|
+
import "./chunk-Z3XLWZWF.js";
|
|
18
36
|
import {
|
|
19
37
|
execPrivileged,
|
|
20
|
-
execPrivilegedWithInput,
|
|
21
38
|
streamPrivileged
|
|
22
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-WPZIJONN.js";
|
|
40
|
+
import "./chunk-YPVWF7IA.js";
|
|
23
41
|
import {
|
|
24
42
|
resolveSpecDir
|
|
25
43
|
} from "./chunk-JW5A7E3X.js";
|
|
44
|
+
import "./chunk-LT4XMO5D.js";
|
|
45
|
+
import "./chunk-VUFMZEIM.js";
|
|
26
46
|
import {
|
|
27
|
-
interpretProbe,
|
|
28
|
-
probeCommand
|
|
29
|
-
} from "./chunk-LT4XMO5D.js";
|
|
30
|
-
import "./chunk-BELTQGHT.js";
|
|
31
|
-
import {
|
|
32
|
-
CONFIG_FILENAME,
|
|
33
|
-
CONTAINER_UID,
|
|
34
47
|
PUBLISH_INSPECT_CMD,
|
|
35
48
|
STATE_DIR_HOST_LINUX,
|
|
36
49
|
configPathForOS,
|
|
37
50
|
gatewayRunCommand,
|
|
38
51
|
publishForRestart
|
|
39
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-N5ZYUTZS.js";
|
|
40
53
|
import {
|
|
41
54
|
GATEWAY_PORT,
|
|
42
55
|
IMAGE_INSPECT_CMD,
|
|
@@ -58,110 +71,28 @@ import {
|
|
|
58
71
|
} from "./chunk-Q7NQY5HV.js";
|
|
59
72
|
import "./chunk-N4WZLWJ4.js";
|
|
60
73
|
import {
|
|
61
|
-
|
|
74
|
+
SUPPORTED_PROVIDERS,
|
|
75
|
+
deriveStateBucket,
|
|
76
|
+
initStack,
|
|
77
|
+
resolveScopeAccount,
|
|
78
|
+
validateStateBucket
|
|
79
|
+
} from "./chunk-755X5OGS.js";
|
|
80
|
+
import {
|
|
62
81
|
getConfigDir,
|
|
63
|
-
requireConfig
|
|
64
|
-
|
|
65
|
-
} from "./chunk-64PONSA5.js";
|
|
82
|
+
requireConfig
|
|
83
|
+
} from "./chunk-GFIR2IIJ.js";
|
|
66
84
|
import {
|
|
67
85
|
ClawopsError,
|
|
68
86
|
UsageError
|
|
69
87
|
} from "./chunk-KGXPLI7W.js";
|
|
70
|
-
import "./chunk-VUFMZEIM.js";
|
|
71
88
|
|
|
72
89
|
// src/cli/index.ts
|
|
73
90
|
import { defineCommand as defineCommand24, runMain } from "citty";
|
|
74
91
|
|
|
75
92
|
// src/cli/commands/init.ts
|
|
76
93
|
import { defineCommand } from "citty";
|
|
77
|
-
import { spawnSync } from "child_process";
|
|
78
|
-
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
79
94
|
import path from "path";
|
|
80
95
|
import process2 from "process";
|
|
81
|
-
|
|
82
|
-
// src/providers/state-bucket.ts
|
|
83
|
-
function deriveStateBucket(provider, scope) {
|
|
84
|
-
if (provider === "azure") {
|
|
85
|
-
return { ok: true, name: "clawops-state" };
|
|
86
|
-
}
|
|
87
|
-
if (!scope.account) {
|
|
88
|
-
return {
|
|
89
|
-
ok: false,
|
|
90
|
-
needs: provider === "aws" ? "an AWS account id \u2014 set AWS_PROFILE, or run `aws sso login --profile <name>`" : "a GCP project \u2014 run `gcloud config set project <id>`, or set GOOGLE_CLOUD_PROJECT"
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
if (provider === "gcp") return { ok: true, name: `clawops-state-${scope.account}` };
|
|
94
|
-
if (!scope.region) return { ok: false, needs: "a region" };
|
|
95
|
-
return { ok: true, name: `clawops-state-${scope.account}-${scope.region}` };
|
|
96
|
-
}
|
|
97
|
-
function stateUrlFor(provider, bucket) {
|
|
98
|
-
const verdict = validateStateBucket(provider, bucket);
|
|
99
|
-
if (verdict !== true) throw new Error(`Not a usable ${provider} state backend name: ${verdict}`);
|
|
100
|
-
const scheme = provider === "aws" ? "s3://" : provider === "gcp" ? "gs://" : "azblob://";
|
|
101
|
-
return provider === "azure" ? `${scheme}${bucket}` : `${scheme}${bucket}/clawops`;
|
|
102
|
-
}
|
|
103
|
-
var IPV4 = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
104
|
-
function validateStateBucket(provider, name) {
|
|
105
|
-
const n = name.trim();
|
|
106
|
-
if (n === "") return "Required";
|
|
107
|
-
if (n !== name) return "No leading or trailing spaces";
|
|
108
|
-
if (n.length < 3) return `Too short \u2014 ${labelFor(provider)} names are at least 3 characters`;
|
|
109
|
-
const dotted = provider === "gcp" && n.includes(".");
|
|
110
|
-
const max = dotted ? 222 : 63;
|
|
111
|
-
if (n.length > max) return `Too long \u2014 ${labelFor(provider)} names are at most ${max} characters`;
|
|
112
|
-
if (dotted && n.split(".").some((part) => part.length > 63)) {
|
|
113
|
-
return "Each dot-separated part of a Cloud Storage name is at most 63 characters";
|
|
114
|
-
}
|
|
115
|
-
if (n !== n.toLowerCase()) return "Lowercase only";
|
|
116
|
-
if (provider === "azure") {
|
|
117
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(n)) {
|
|
118
|
-
return "Letters, digits and single hyphens only, starting and ending with a letter or digit";
|
|
119
|
-
}
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
if (!/^[a-z0-9][a-z0-9._-]*[a-z0-9]$/.test(n)) {
|
|
123
|
-
return "Letters, digits, dots, hyphens and underscores only, starting and ending with a letter or digit";
|
|
124
|
-
}
|
|
125
|
-
if (n.includes("..")) return "No consecutive dots";
|
|
126
|
-
if (IPV4.test(n)) return "Cannot be formatted as an IP address";
|
|
127
|
-
if (provider === "aws") {
|
|
128
|
-
if (n.startsWith("xn--")) return 'Cannot start with "xn--"';
|
|
129
|
-
if (n.startsWith("sthree-")) return 'Cannot start with "sthree-"';
|
|
130
|
-
if (n.endsWith("-s3alias")) return 'Cannot end with "-s3alias"';
|
|
131
|
-
if (n.endsWith("--ol-s3")) return 'Cannot end with "--ol-s3"';
|
|
132
|
-
if (n.includes("_")) return "Underscores are not allowed in S3 bucket names";
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
if (n.startsWith("goog")) return 'Cannot start with "goog"';
|
|
136
|
-
if (n.includes("google")) return 'Cannot contain "google"';
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
function labelFor(provider) {
|
|
140
|
-
if (provider === "aws") return "S3 bucket";
|
|
141
|
-
if (provider === "gcp") return "Cloud Storage bucket";
|
|
142
|
-
return "blob container";
|
|
143
|
-
}
|
|
144
|
-
async function resolveScopeAccount(provider, signal) {
|
|
145
|
-
if (provider === "azure") return void 0;
|
|
146
|
-
try {
|
|
147
|
-
if (provider === "aws") {
|
|
148
|
-
const { callerAccount } = await import("./preflight-TFD4N5ZU.js");
|
|
149
|
-
return await callerAccount(signal);
|
|
150
|
-
}
|
|
151
|
-
const { resolveProjectId } = await import("./preflight-IE2YWBAC.js");
|
|
152
|
-
return resolveProjectId();
|
|
153
|
-
} catch {
|
|
154
|
-
return void 0;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// src/cli/commands/init.ts
|
|
159
|
-
var SUPPORTED_PROVIDERS = ["gcp", "aws", "azure", "local"];
|
|
160
|
-
var PROVIDER_DEFAULTS = {
|
|
161
|
-
gcp: { region: "us-central1", credEnv: "GOOGLE_APPLICATION_CREDENTIALS", stateScheme: "gs://" },
|
|
162
|
-
aws: { region: "us-east-1", credEnv: "AWS_PROFILE", stateScheme: "s3://" },
|
|
163
|
-
azure: { region: "eastus", credEnv: "AZURE_CLIENT_ID", stateScheme: "azblob://" }
|
|
164
|
-
};
|
|
165
96
|
var init_default = defineCommand({
|
|
166
97
|
meta: {
|
|
167
98
|
name: "init",
|
|
@@ -183,8 +114,6 @@ var init_default = defineCommand({
|
|
|
183
114
|
async run({ args }) {
|
|
184
115
|
const nonInteractive = Boolean(args["non-interactive"]);
|
|
185
116
|
const providerArg = typeof args.provider === "string" ? args.provider : null;
|
|
186
|
-
const stackName = typeof args.stack === "string" ? args.stack : "default";
|
|
187
|
-
const forceOverwrite = Boolean(args.force);
|
|
188
117
|
if (nonInteractive && !providerArg) {
|
|
189
118
|
throw new UsageError(
|
|
190
119
|
"--non-interactive requires --provider. Example: clawops init --provider gcp --non-interactive"
|
|
@@ -196,107 +125,42 @@ var init_default = defineCommand({
|
|
|
196
125
|
`Unsupported provider: ${provider}. Supported: ${SUPPORTED_PROVIDERS.join(", ")}`
|
|
197
126
|
);
|
|
198
127
|
}
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
info("Generating SSH key pair...");
|
|
215
|
-
const gen = spawnSync(
|
|
216
|
-
"ssh-keygen",
|
|
217
|
-
["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops", "-q"],
|
|
218
|
-
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
219
|
-
);
|
|
220
|
-
if (gen.error || gen.status !== 0) {
|
|
221
|
-
const reason = gen.error?.message ?? gen.stderr?.toString().trim() ?? `exit ${gen.status}`;
|
|
222
|
-
throw new UsageError(
|
|
223
|
-
`Could not generate an SSH key: ${reason}
|
|
224
|
-
Generate one yourself and point clawops at it:
|
|
225
|
-
ssh-keygen -t ed25519 -f ${keyPath} -N '' -C clawops
|
|
226
|
-
clawops init --key-path ${keyPath}`
|
|
227
|
-
);
|
|
128
|
+
const result = await initStack({
|
|
129
|
+
provider,
|
|
130
|
+
stackName: typeof args.stack === "string" ? args.stack : void 0,
|
|
131
|
+
stateUrl: typeof args.state === "string" ? args.state : void 0,
|
|
132
|
+
region: typeof args.region === "string" ? args.region : void 0,
|
|
133
|
+
force: Boolean(args.force),
|
|
134
|
+
keyPath: typeof args["key-path"] === "string" ? args["key-path"] : void 0,
|
|
135
|
+
host: typeof args.host === "string" ? args.host : void 0,
|
|
136
|
+
sshUser: typeof args["ssh-user"] === "string" ? args["ssh-user"] : void 0,
|
|
137
|
+
sshPort: typeof args["ssh-port"] === "string" ? parseInt(args["ssh-port"], 10) : void 0
|
|
138
|
+
});
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
if (result.reason.includes("already exists")) {
|
|
141
|
+
failure(result.reason.replace("Pass force", "Use --force"));
|
|
142
|
+
process2.exit(1);
|
|
228
143
|
}
|
|
229
|
-
|
|
230
|
-
} else {
|
|
231
|
-
info(`Using existing SSH key at ${keyPath}`);
|
|
232
|
-
}
|
|
233
|
-
if (!existsSync(knownHostsPath)) {
|
|
234
|
-
writeFileSync(knownHostsPath, "", "utf-8");
|
|
144
|
+
throw new UsageError(result.reason);
|
|
235
145
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (!host) {
|
|
240
|
-
throw new UsageError("--host is required for the local provider");
|
|
241
|
-
}
|
|
242
|
-
const sshUser = typeof args["ssh-user"] === "string" ? args["ssh-user"] : "root";
|
|
243
|
-
const sshPort = typeof args["ssh-port"] === "string" ? parseInt(args["ssh-port"], 10) : 22;
|
|
244
|
-
config = merge(existing, stackName, provider, keyPath, knownHostsPath, {
|
|
245
|
-
provider,
|
|
246
|
-
stateUrl: "file://~/.clawops/state",
|
|
247
|
-
credentialsRef: { source: "file", envVars: [] },
|
|
248
|
-
localOpts: { host, sshUser, sshPort, sshKeyPath: keyPath }
|
|
249
|
-
});
|
|
250
|
-
} else {
|
|
251
|
-
const defaults = PROVIDER_DEFAULTS[provider];
|
|
252
|
-
const region = typeof args.region === "string" ? args.region : defaults.region;
|
|
253
|
-
let stateUrl;
|
|
254
|
-
if (typeof args.state === "string") {
|
|
255
|
-
stateUrl = args.state;
|
|
256
|
-
} else {
|
|
257
|
-
const account = await resolveScopeAccount(provider);
|
|
258
|
-
const derived = deriveStateBucket(provider, { account, region });
|
|
259
|
-
if (!derived.ok) {
|
|
260
|
-
throw new UsageError(
|
|
261
|
-
`Could not name a state backend for ${provider}: clawops needs ${derived.needs}.
|
|
262
|
-
Authenticate and run this again, or name the backend yourself:
|
|
263
|
-
clawops init --provider ${provider} --state ${defaults.stateScheme}your-bucket/clawops`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
stateUrl = stateUrlFor(provider, derived.name);
|
|
267
|
-
}
|
|
268
|
-
config = merge(existing, stackName, provider, keyPath, knownHostsPath, {
|
|
269
|
-
provider,
|
|
270
|
-
stateUrl,
|
|
271
|
-
region,
|
|
272
|
-
credentialsRef: { source: "env", envVars: [defaults.credEnv] }
|
|
273
|
-
});
|
|
146
|
+
if (result.keyGenerated) success(`SSH key pair written to ${result.keyPath} (and ${result.keyPath}.pub)`);
|
|
147
|
+
else info(`Using existing SSH key at ${result.keyPath}`);
|
|
148
|
+
if (result.provider !== "local") {
|
|
274
149
|
process2.stdout.write("\n");
|
|
275
150
|
info(
|
|
276
|
-
`State backend: ${stateUrl}
|
|
277
|
-
clawops doctor --provider ${provider} checks it exists, and clawops setup offers to create it.`
|
|
151
|
+
`State backend: ${result.stateUrl}
|
|
152
|
+
clawops doctor --provider ${result.provider} checks it exists, and clawops setup offers to create it.`
|
|
278
153
|
);
|
|
279
154
|
process2.stdout.write("\n");
|
|
280
|
-
success(`Provider: ${provider} Region: ${region} Stack: ${stackName}`);
|
|
281
|
-
|
|
282
|
-
success(`Config written to ${path.join(configDir, "config.json")}`);
|
|
155
|
+
success(`Provider: ${result.provider} Region: ${result.region} Stack: ${result.stackName}`);
|
|
156
|
+
success(`Config written to ${path.join(getConfigDir(), "config.json")}`);
|
|
283
157
|
return;
|
|
284
158
|
}
|
|
285
|
-
|
|
286
|
-
success(`Config written to ${path.join(configDir, "config.json")}`);
|
|
159
|
+
success(`Config written to ${result.configPath}`);
|
|
287
160
|
process2.stdout.write("\n");
|
|
288
|
-
success(`Provider: ${provider} Stack: ${stackName}`);
|
|
161
|
+
success(`Provider: ${result.provider} Stack: ${result.stackName}`);
|
|
289
162
|
}
|
|
290
163
|
});
|
|
291
|
-
function merge(existing, stackName, provider, keyPath, knownHostsPath, stack) {
|
|
292
|
-
return {
|
|
293
|
-
...existing ?? {},
|
|
294
|
-
version: 1,
|
|
295
|
-
defaults: { stack: stackName, provider },
|
|
296
|
-
stacks: { ...existing?.stacks ?? {}, [stackName]: stack },
|
|
297
|
-
ssh: { keyPath, knownHostsPath }
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
164
|
|
|
301
165
|
// src/cli/commands/up.ts
|
|
302
166
|
import { defineCommand as defineCommand2 } from "citty";
|
|
@@ -339,7 +203,7 @@ var up_default = defineCommand2({
|
|
|
339
203
|
"publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' }
|
|
340
204
|
},
|
|
341
205
|
async run({ args }) {
|
|
342
|
-
const { buildContext } = await import("./context-
|
|
206
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
343
207
|
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
344
208
|
const ctx = buildContext(args);
|
|
345
209
|
const requestedVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : await defaultOpenclawVersion();
|
|
@@ -352,7 +216,7 @@ var up_default = defineCommand2({
|
|
|
352
216
|
);
|
|
353
217
|
}
|
|
354
218
|
const { localOpts } = stackConfig;
|
|
355
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
219
|
+
const { localBootstrap } = await import("./bootstrap-32TZXVDF.js");
|
|
356
220
|
const gatewayPort = parseGatewayPort(args["gateway-port"]);
|
|
357
221
|
const abortController2 = new AbortController();
|
|
358
222
|
process3.on("SIGINT", () => abortController2.abort());
|
|
@@ -392,8 +256,8 @@ var up_default = defineCommand2({
|
|
|
392
256
|
}
|
|
393
257
|
return;
|
|
394
258
|
}
|
|
395
|
-
const { generatePlan } = await import("./generate-
|
|
396
|
-
const { applyPlan } = await import("./apply-
|
|
259
|
+
const { generatePlan } = await import("./generate-4YYNW3IU.js");
|
|
260
|
+
const { applyPlan } = await import("./apply-F5SQ2ZOS.js");
|
|
397
261
|
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
398
262
|
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
399
263
|
const isDryRun = Boolean(args["dry-run"]);
|
|
@@ -473,9 +337,9 @@ function strArg(value) {
|
|
|
473
337
|
return typeof value === "string" ? value : void 0;
|
|
474
338
|
}
|
|
475
339
|
async function applyLocalConfigOverlay(opts) {
|
|
476
|
-
const { connect } = await import("./ssh-
|
|
340
|
+
const { connect } = await import("./ssh-HOM7GF3M.js");
|
|
477
341
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
478
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
342
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-OZYBMBSK.js");
|
|
479
343
|
let overlay;
|
|
480
344
|
try {
|
|
481
345
|
overlay = JSON.parse(readFileSync(opts.configPath, "utf-8"));
|
|
@@ -523,7 +387,7 @@ var down_default = defineCommand3({
|
|
|
523
387
|
"dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
|
|
524
388
|
},
|
|
525
389
|
async run({ args }) {
|
|
526
|
-
const { buildContext } = await import("./context-
|
|
390
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
527
391
|
const ctx = buildContext(args);
|
|
528
392
|
if (args["dry-run"]) {
|
|
529
393
|
info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
|
|
@@ -587,7 +451,7 @@ var status_default = defineCommand4({
|
|
|
587
451
|
json: { type: "boolean", description: "Emit JSON" }
|
|
588
452
|
},
|
|
589
453
|
async run({ args }) {
|
|
590
|
-
const { buildContext } = await import("./context-
|
|
454
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
591
455
|
const ctx = buildContext(args);
|
|
592
456
|
if (ctx.adapter.name === "local") {
|
|
593
457
|
const state = ctx.localState;
|
|
@@ -643,7 +507,7 @@ var status_default = defineCommand4({
|
|
|
643
507
|
// src/cli/commands/plan.ts
|
|
644
508
|
import { defineCommand as defineCommand5 } from "citty";
|
|
645
509
|
import process7 from "process";
|
|
646
|
-
import { writeFileSync
|
|
510
|
+
import { writeFileSync } from "fs";
|
|
647
511
|
import { isAbsolute } from "path";
|
|
648
512
|
function disruptionWarnings(diff) {
|
|
649
513
|
const out = [];
|
|
@@ -687,8 +551,8 @@ var plan_default = defineCommand5({
|
|
|
687
551
|
"private-only": { type: "boolean", description: "Close public SSH and gateway access; reach the stack over its tailnet. Needs `clawops harden --tailscale` first, and checks the tailnet answers before planning" }
|
|
688
552
|
},
|
|
689
553
|
async run({ args }) {
|
|
690
|
-
const { buildContext } = await import("./context-
|
|
691
|
-
const { generatePlan } = await import("./generate-
|
|
554
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
555
|
+
const { generatePlan } = await import("./generate-4YYNW3IU.js");
|
|
692
556
|
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
693
557
|
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
694
558
|
const ctx = buildContext(args);
|
|
@@ -713,7 +577,7 @@ var plan_default = defineCommand5({
|
|
|
713
577
|
let network = resolved;
|
|
714
578
|
if (args["private-only"]) {
|
|
715
579
|
const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
|
|
716
|
-
const { probeSsh } = await import("./tailscale-cutover-
|
|
580
|
+
const { probeSsh } = await import("./tailscale-cutover-V42HO5UM.js");
|
|
717
581
|
network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[ctx.stackName]?.tailscale, ctx.stackName);
|
|
718
582
|
await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
|
|
719
583
|
}
|
|
@@ -741,7 +605,7 @@ var plan_default = defineCommand5({
|
|
|
741
605
|
}
|
|
742
606
|
const planJson = JSON.stringify(plan, null, 2);
|
|
743
607
|
if (outPath) {
|
|
744
|
-
|
|
608
|
+
writeFileSync(outPath, planJson + "\n", "utf-8");
|
|
745
609
|
success(`Plan written to ${outPath}`);
|
|
746
610
|
} else {
|
|
747
611
|
process7.stdout.write(planJson + "\n");
|
|
@@ -803,8 +667,8 @@ var apply_default = defineCommand6({
|
|
|
803
667
|
"dry-run": { type: "boolean", description: "Validate plan and show diff without applying" }
|
|
804
668
|
},
|
|
805
669
|
async run({ args }) {
|
|
806
|
-
const { validatePlan } = await import("./validate-
|
|
807
|
-
const { applyPlan } = await import("./apply-
|
|
670
|
+
const { validatePlan } = await import("./validate-DZE6LFEB.js");
|
|
671
|
+
const { applyPlan } = await import("./apply-F5SQ2ZOS.js");
|
|
808
672
|
const planPath = args._?.[0];
|
|
809
673
|
if (!planPath) {
|
|
810
674
|
throw new UsageError("Usage: clawops apply <plan.json>");
|
|
@@ -930,7 +794,7 @@ var destroy_default = defineCommand7({
|
|
|
930
794
|
"dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
|
|
931
795
|
},
|
|
932
796
|
async run({ args }) {
|
|
933
|
-
const { buildContext } = await import("./context-
|
|
797
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
934
798
|
const ctx = buildContext(args);
|
|
935
799
|
if (ctx.adapter.name === "local") {
|
|
936
800
|
throw new UsageError(
|
|
@@ -1023,8 +887,8 @@ var ssh_default = defineCommand8({
|
|
|
1023
887
|
command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
|
|
1024
888
|
},
|
|
1025
889
|
async run({ args }) {
|
|
1026
|
-
const { buildContext } = await import("./context-
|
|
1027
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
890
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
891
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
1028
892
|
const ctx = buildContext(args);
|
|
1029
893
|
let conn;
|
|
1030
894
|
if (ctx.adapter.name === "local") {
|
|
@@ -1110,9 +974,9 @@ var tunnel_default = defineCommand9({
|
|
|
1110
974
|
"no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
|
|
1111
975
|
},
|
|
1112
976
|
async run({ args }) {
|
|
1113
|
-
const { buildContext } = await import("./context-
|
|
977
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1114
978
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1115
|
-
const { acquireSession } = await import("./pool-
|
|
979
|
+
const { acquireSession } = await import("./pool-NT4XBSUT.js");
|
|
1116
980
|
const ctx = buildContext(args);
|
|
1117
981
|
const stack = await ctx.getStack();
|
|
1118
982
|
const outputMap = await stack.outputs();
|
|
@@ -1179,25 +1043,10 @@ var logs_default = defineCommand10({
|
|
|
1179
1043
|
json: { type: "boolean", description: "Emit the gateway's structured JSON log lines" }
|
|
1180
1044
|
},
|
|
1181
1045
|
async run({ args }) {
|
|
1182
|
-
const { buildContext } = await import("./context-
|
|
1183
|
-
const {
|
|
1184
|
-
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1046
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1047
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
1185
1048
|
const ctx = buildContext(args);
|
|
1186
|
-
const
|
|
1187
|
-
const outputMap = await stack.outputs();
|
|
1188
|
-
const outputs = Object.fromEntries(
|
|
1189
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1190
|
-
);
|
|
1191
|
-
if (!outputs["publicIp"]) {
|
|
1192
|
-
failure("Stack has no outputs. Run `clawops up` first.");
|
|
1193
|
-
process12.exit(4);
|
|
1194
|
-
}
|
|
1195
|
-
const base = extractBaseOutputs(outputs);
|
|
1196
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1197
|
-
...base,
|
|
1198
|
-
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1199
|
-
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1200
|
-
});
|
|
1049
|
+
const conn = await connFor(ctx);
|
|
1201
1050
|
const tailLines = typeof args.tail === "string" ? parseInt(args.tail, 10) : 100;
|
|
1202
1051
|
const follow = Boolean(args.follow);
|
|
1203
1052
|
const since = typeof args.since === "string" ? args.since : void 0;
|
|
@@ -1239,12 +1088,33 @@ var logs_default = defineCommand10({
|
|
|
1239
1088
|
}
|
|
1240
1089
|
}
|
|
1241
1090
|
});
|
|
1091
|
+
async function connFor(ctx) {
|
|
1092
|
+
if (ctx.adapter.name === "local") {
|
|
1093
|
+
const { resolveConn } = await import("./conn-M2V5HBPJ.js");
|
|
1094
|
+
return resolveConn(ctx);
|
|
1095
|
+
}
|
|
1096
|
+
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1097
|
+
const stack = await ctx.getStack();
|
|
1098
|
+
const outputMap = await stack.outputs();
|
|
1099
|
+
const outputs = Object.fromEntries(
|
|
1100
|
+
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1101
|
+
);
|
|
1102
|
+
if (!outputs["publicIp"]) {
|
|
1103
|
+
failure("Stack has no outputs. Run `clawops up` first.");
|
|
1104
|
+
process12.exit(4);
|
|
1105
|
+
}
|
|
1106
|
+
return ctx.adapter.getConnectionInfo({
|
|
1107
|
+
...extractBaseOutputs(outputs),
|
|
1108
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1109
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1242
1112
|
|
|
1243
1113
|
// src/cli/commands/config.ts
|
|
1244
1114
|
import { defineCommand as defineCommand11 } from "citty";
|
|
1245
1115
|
import process13 from "process";
|
|
1246
1116
|
var OPENCLAW_CONFIG = configPathForOS("Linux");
|
|
1247
|
-
function
|
|
1117
|
+
function dockerRunCmd2(image) {
|
|
1248
1118
|
return gatewayRunCommand({ image, stateDir: STATE_DIR_HOST_LINUX });
|
|
1249
1119
|
}
|
|
1250
1120
|
function getPath(obj, dotKey) {
|
|
@@ -1285,9 +1155,8 @@ var config_default = defineCommand11({
|
|
|
1285
1155
|
"dry-run": { type: "boolean", description: "Show what would change without writing" }
|
|
1286
1156
|
},
|
|
1287
1157
|
async run({ args }) {
|
|
1288
|
-
const { buildContext } = await import("./context-
|
|
1289
|
-
const {
|
|
1290
|
-
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1158
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1159
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
1291
1160
|
const [action, key, value] = args._ ?? [];
|
|
1292
1161
|
if (!action || !["get", "set", "unset", "validate"].includes(action)) {
|
|
1293
1162
|
failure("Usage: clawops config <get [key] | set key value | unset key | validate>");
|
|
@@ -1302,17 +1171,8 @@ var config_default = defineCommand11({
|
|
|
1302
1171
|
process13.exit(2);
|
|
1303
1172
|
}
|
|
1304
1173
|
const ctx = buildContext(args);
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
const outputs = Object.fromEntries(
|
|
1308
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1309
|
-
);
|
|
1310
|
-
const base = extractBaseOutputs(outputs);
|
|
1311
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1312
|
-
...base,
|
|
1313
|
-
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1314
|
-
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1315
|
-
});
|
|
1174
|
+
const { resolveConn } = await import("./conn-M2V5HBPJ.js");
|
|
1175
|
+
const conn = await resolveConn(ctx);
|
|
1316
1176
|
const abortController = new AbortController();
|
|
1317
1177
|
process13.on("SIGINT", () => abortController.abort());
|
|
1318
1178
|
process13.on("SIGTERM", () => abortController.abort());
|
|
@@ -1375,7 +1235,7 @@ var config_default = defineCommand11({
|
|
|
1375
1235
|
process13.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
|
|
1376
1236
|
return;
|
|
1377
1237
|
}
|
|
1378
|
-
const { atomicWriteConfig } = await import("./remote-config-
|
|
1238
|
+
const { atomicWriteConfig } = await import("./remote-config-OZYBMBSK.js");
|
|
1379
1239
|
try {
|
|
1380
1240
|
await atomicWriteConfig(session, cfg, abortController.signal);
|
|
1381
1241
|
} catch (err) {
|
|
@@ -1391,7 +1251,7 @@ var config_default = defineCommand11({
|
|
|
1391
1251
|
abortController.signal
|
|
1392
1252
|
);
|
|
1393
1253
|
const image = imgResult.stdout.trim();
|
|
1394
|
-
const restartResult = await execPrivileged(session,
|
|
1254
|
+
const restartResult = await execPrivileged(session, dockerRunCmd2(image), abortController.signal);
|
|
1395
1255
|
if (restartResult.code !== 0) {
|
|
1396
1256
|
failure(`Restart failed: ${restartResult.stderr}`);
|
|
1397
1257
|
process13.exit(1);
|
|
@@ -1420,9 +1280,8 @@ var agents_default = defineCommand12({
|
|
|
1420
1280
|
cursor: { type: "string", description: "Continue from a previous `logs` result cursor" }
|
|
1421
1281
|
},
|
|
1422
1282
|
async run({ args }) {
|
|
1423
|
-
const { buildContext } = await import("./context-
|
|
1424
|
-
const {
|
|
1425
|
-
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1283
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1284
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
1426
1285
|
const [action, name] = args._ ?? [];
|
|
1427
1286
|
if (action === "restart") {
|
|
1428
1287
|
failure(
|
|
@@ -1439,17 +1298,8 @@ var agents_default = defineCommand12({
|
|
|
1439
1298
|
process14.exit(2);
|
|
1440
1299
|
}
|
|
1441
1300
|
const ctx = buildContext(args);
|
|
1442
|
-
const
|
|
1443
|
-
const
|
|
1444
|
-
const outputs = Object.fromEntries(
|
|
1445
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1446
|
-
);
|
|
1447
|
-
const base = extractBaseOutputs(outputs);
|
|
1448
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1449
|
-
...base,
|
|
1450
|
-
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1451
|
-
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1452
|
-
});
|
|
1301
|
+
const { resolveConn } = await import("./conn-M2V5HBPJ.js");
|
|
1302
|
+
const conn = await resolveConn(ctx);
|
|
1453
1303
|
const abortController = new AbortController();
|
|
1454
1304
|
process14.on("SIGINT", () => abortController.abort());
|
|
1455
1305
|
process14.on("SIGTERM", () => abortController.abort());
|
|
@@ -1495,22 +1345,23 @@ var agents_default = defineCommand12({
|
|
|
1495
1345
|
);
|
|
1496
1346
|
}
|
|
1497
1347
|
} else {
|
|
1498
|
-
const {
|
|
1499
|
-
const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) :
|
|
1500
|
-
const result = await
|
|
1348
|
+
const { readAgentActivityRaw, DEFAULT_AGENT_LOG_LIMIT } = await import("./agent-activity-25FAWLAI.js");
|
|
1349
|
+
const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) : DEFAULT_AGENT_LOG_LIMIT;
|
|
1350
|
+
const result = await readAgentActivityRaw(
|
|
1501
1351
|
session,
|
|
1502
|
-
|
|
1352
|
+
{ agentId: name, limit, ...typeof args.cursor === "string" ? { cursor: args.cursor } : {} },
|
|
1503
1353
|
abortController.signal
|
|
1504
1354
|
);
|
|
1505
|
-
if (result.
|
|
1506
|
-
failure(
|
|
1355
|
+
if (!result.ok) {
|
|
1356
|
+
failure(result.error);
|
|
1507
1357
|
process14.exitCode = 1;
|
|
1508
1358
|
return;
|
|
1509
1359
|
}
|
|
1510
1360
|
if (args.json) {
|
|
1511
|
-
process14.stdout.write(result.
|
|
1361
|
+
process14.stdout.write(result.value);
|
|
1512
1362
|
} else {
|
|
1513
|
-
|
|
1363
|
+
const { parseAgentActivity } = await import("./agent-activity-25FAWLAI.js");
|
|
1364
|
+
renderAgentRuns(name, parseAgentActivity(name, result.value));
|
|
1514
1365
|
}
|
|
1515
1366
|
}
|
|
1516
1367
|
} finally {
|
|
@@ -1519,16 +1370,14 @@ var agents_default = defineCommand12({
|
|
|
1519
1370
|
}
|
|
1520
1371
|
}
|
|
1521
1372
|
});
|
|
1522
|
-
function renderAgentRuns(agentId,
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
page = JSON.parse(stdout.trim() || "{}");
|
|
1526
|
-
} catch {
|
|
1527
|
-
failure(`Cannot read activity for "${agentId}": unexpected output: ${stdout.trim().slice(0, 200)}`);
|
|
1373
|
+
function renderAgentRuns(agentId, parsed) {
|
|
1374
|
+
if (!parsed.ok) {
|
|
1375
|
+
failure(parsed.error);
|
|
1528
1376
|
process14.exitCode = 1;
|
|
1529
1377
|
return;
|
|
1530
1378
|
}
|
|
1531
|
-
const
|
|
1379
|
+
const page = parsed.value;
|
|
1380
|
+
const records = page.records;
|
|
1532
1381
|
if (records.length === 0) {
|
|
1533
1382
|
info(`No recorded activity for agent "${agentId}".`);
|
|
1534
1383
|
return;
|
|
@@ -1548,99 +1397,6 @@ function renderAgentRuns(agentId, stdout) {
|
|
|
1548
1397
|
// src/cli/commands/migrate.ts
|
|
1549
1398
|
import { defineCommand as defineCommand13 } from "citty";
|
|
1550
1399
|
import process15 from "process";
|
|
1551
|
-
|
|
1552
|
-
// src/openclaw/migrate.ts
|
|
1553
|
-
async function migrate(steps) {
|
|
1554
|
-
const sourceImage = await steps.inspectSource();
|
|
1555
|
-
if (!sourceImage) {
|
|
1556
|
-
return {
|
|
1557
|
-
kind: "nothing-to-migrate",
|
|
1558
|
-
reason: "No running OpenClaw container was found, so there is no 1.x state to extract. If this deployment was restarted on a clawops release before 2.0, its state was already lost \u2014 clawops mounted none, so every container replacement discarded it. Deploy fresh with `clawops up`."
|
|
1559
|
-
};
|
|
1560
|
-
}
|
|
1561
|
-
const identityBefore = await steps.deviceId();
|
|
1562
|
-
const backup = await steps.backup();
|
|
1563
|
-
if (!backup.ok) {
|
|
1564
|
-
return {
|
|
1565
|
-
kind: "refused",
|
|
1566
|
-
reason: `Refusing to migrate without a verified backup: ${backup.detail}
|
|
1567
|
-
This replaces the running container, and the state it extracts is the only copy.`
|
|
1568
|
-
};
|
|
1569
|
-
}
|
|
1570
|
-
const extracted = await steps.extract();
|
|
1571
|
-
if (!extracted.ok) {
|
|
1572
|
-
return {
|
|
1573
|
-
kind: "failed",
|
|
1574
|
-
reason: "Could not extract state from the running container; it has not been touched.",
|
|
1575
|
-
backupDetail: backup.detail
|
|
1576
|
-
};
|
|
1577
|
-
}
|
|
1578
|
-
await steps.chown();
|
|
1579
|
-
await steps.removeSource();
|
|
1580
|
-
await steps.writeConfig();
|
|
1581
|
-
await steps.start();
|
|
1582
|
-
let restarts = 0;
|
|
1583
|
-
let gate = await steps.gate();
|
|
1584
|
-
if (!gate.ok) {
|
|
1585
|
-
await steps.start();
|
|
1586
|
-
restarts = 1;
|
|
1587
|
-
gate = await steps.gate();
|
|
1588
|
-
}
|
|
1589
|
-
if (!gate.ok) {
|
|
1590
|
-
return {
|
|
1591
|
-
kind: "failed",
|
|
1592
|
-
reason: gate.reason ?? "the gateway did not start after migration",
|
|
1593
|
-
backupDetail: backup.detail
|
|
1594
|
-
};
|
|
1595
|
-
}
|
|
1596
|
-
const identityAfter = await steps.deviceId();
|
|
1597
|
-
const identity = identityBefore === void 0 || identityAfter === void 0 ? "unknown" : identityBefore === identityAfter ? "preserved" : "changed";
|
|
1598
|
-
return { kind: "migrated", entries: extracted.entries, identity, restarts };
|
|
1599
|
-
}
|
|
1600
|
-
function describeMigration(o) {
|
|
1601
|
-
switch (o.kind) {
|
|
1602
|
-
case "migrated": {
|
|
1603
|
-
const lines = [
|
|
1604
|
-
`Migrated to the 2.0 runtime contract. Carried over: ${o.entries.join(", ") || "(nothing)"}.`
|
|
1605
|
-
];
|
|
1606
|
-
if (o.restarts > 0) {
|
|
1607
|
-
lines.push(
|
|
1608
|
-
"The gateway needed a second start to finish its state-schema migration, which is expected for a 1.x database."
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
|
-
if (o.identity === "preserved") {
|
|
1612
|
-
lines.push("Device identity preserved \u2014 paired devices do not need re-pairing.");
|
|
1613
|
-
} else if (o.identity === "changed") {
|
|
1614
|
-
lines.push(
|
|
1615
|
-
"Device identity CHANGED. Paired devices and nodes must be re-paired \u2014 check `openclaw devices list` on the host for what was paired."
|
|
1616
|
-
);
|
|
1617
|
-
} else {
|
|
1618
|
-
lines.push(
|
|
1619
|
-
"Device identity could not be compared, so pairings may or may not have carried over. Check `openclaw devices list` before relying on them."
|
|
1620
|
-
);
|
|
1621
|
-
}
|
|
1622
|
-
lines.push("Your 1.x config was NOT applied \u2014 it never took effect on 1.x either. Review it");
|
|
1623
|
-
lines.push("and set what you need with `clawops config set`.");
|
|
1624
|
-
return lines.join("\n");
|
|
1625
|
-
}
|
|
1626
|
-
case "nothing-to-migrate":
|
|
1627
|
-
return o.reason;
|
|
1628
|
-
case "refused":
|
|
1629
|
-
return o.reason;
|
|
1630
|
-
case "failed":
|
|
1631
|
-
return `Migration failed: ${o.reason}
|
|
1632
|
-
A verified backup was taken first: ${o.backupDetail}
|
|
1633
|
-
Restore it with \`clawops backup restore --file <archive>\` before retrying.`;
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
// src/cli/commands/migrate.ts
|
|
1638
|
-
var SYNTHESISED_CONFIG = JSON.stringify({
|
|
1639
|
-
meta: { lastTouchedVersion: "2026.9.2" },
|
|
1640
|
-
gateway: { mode: "local", port: GATEWAY_PORT, auth: { mode: "token" } },
|
|
1641
|
-
models: {},
|
|
1642
|
-
channels: {}
|
|
1643
|
-
});
|
|
1644
1400
|
var migrate_default = defineCommand13({
|
|
1645
1401
|
meta: {
|
|
1646
1402
|
name: "migrate",
|
|
@@ -1652,120 +1408,41 @@ var migrate_default = defineCommand13({
|
|
|
1652
1408
|
yes: { type: "boolean", description: "Skip the confirmation prompt" }
|
|
1653
1409
|
},
|
|
1654
1410
|
async run({ args }) {
|
|
1655
|
-
const {
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
const requested = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0;
|
|
1660
|
-
const target = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
|
|
1661
|
-
const targetImage = `ghcr.io/openclaw/openclaw:${target}`;
|
|
1662
|
-
const ctx = buildContext(args);
|
|
1663
|
-
const stack = await ctx.getStack();
|
|
1664
|
-
const outputs = Object.fromEntries(
|
|
1665
|
-
Object.entries(await stack.outputs()).map(([k, v]) => [k, v.value])
|
|
1666
|
-
);
|
|
1667
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1668
|
-
...extractBaseOutputs(outputs),
|
|
1669
|
-
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1670
|
-
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1411
|
+
const { prepareMigration, runMigration, migrationQuestion } = await import("./migrate-flow-24XA4GZL.js");
|
|
1412
|
+
const target = await prepareMigration({
|
|
1413
|
+
stack: typeof args.stack === "string" ? args.stack : void 0,
|
|
1414
|
+
openclawVersion: typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0
|
|
1671
1415
|
});
|
|
1416
|
+
if (!args.yes) {
|
|
1417
|
+
const inquirer = (await import("inquirer")).default;
|
|
1418
|
+
const { confirmed } = await inquirer.prompt([{
|
|
1419
|
+
type: "confirm",
|
|
1420
|
+
name: "confirmed",
|
|
1421
|
+
message: migrationQuestion(target),
|
|
1422
|
+
default: false
|
|
1423
|
+
}]);
|
|
1424
|
+
if (!confirmed) {
|
|
1425
|
+
info("Aborted. Nothing was changed.");
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1672
1429
|
const ac = new AbortController();
|
|
1673
1430
|
process15.on("SIGINT", () => ac.abort());
|
|
1674
|
-
|
|
1675
|
-
host: conn.host,
|
|
1676
|
-
port: conn.port,
|
|
1677
|
-
user: conn.user,
|
|
1678
|
-
privateKeyPath: conn.privateKeyPath,
|
|
1679
|
-
knownHostsPath: conn.knownHostsPath,
|
|
1680
|
-
signal: ac.signal
|
|
1681
|
-
});
|
|
1682
|
-
const spin = spinner("Inspecting the deployment...");
|
|
1683
|
-
const run = (cmd) => execPrivileged(session, cmd, ac.signal);
|
|
1684
|
-
const archive = "/tmp/clawops-premigration.tar.gz";
|
|
1685
|
-
const configPath = `${STATE_DIR_HOST_LINUX}/${CONFIG_FILENAME}`;
|
|
1686
|
-
const steps = {
|
|
1687
|
-
inspectSource: async () => {
|
|
1688
|
-
const r = await run(`docker inspect openclaw --format '{{.Config.Image}}'`);
|
|
1689
|
-
const image = r.stdout.trim();
|
|
1690
|
-
return r.code === 0 && image.includes(":") ? image : void 0;
|
|
1691
|
-
},
|
|
1692
|
-
backup: async () => {
|
|
1693
|
-
spin.text = "Taking a verified backup...";
|
|
1694
|
-
const r = await run(
|
|
1695
|
-
`docker exec openclaw openclaw backup create --output ${archive} --verify --json`
|
|
1696
|
-
);
|
|
1697
|
-
return r.code === 0 ? { ok: true, detail: `${archive} on the host (verified)` } : { ok: false, detail: (r.stderr || r.stdout).slice(0, 200) };
|
|
1698
|
-
},
|
|
1699
|
-
extract: async () => {
|
|
1700
|
-
spin.text = "Extracting state from the running container...";
|
|
1701
|
-
const mk = await run(`mkdir -p ${STATE_DIR_HOST_LINUX}`);
|
|
1702
|
-
if (mk.code !== 0) return { ok: false, entries: [] };
|
|
1703
|
-
const cp = await run(`docker cp openclaw:/home/node/.openclaw/. ${STATE_DIR_HOST_LINUX}/`);
|
|
1704
|
-
if (cp.code !== 0) return { ok: false, entries: [] };
|
|
1705
|
-
const ls = await run(`ls ${STATE_DIR_HOST_LINUX}`);
|
|
1706
|
-
return { ok: true, entries: ls.stdout.trim().split(/\s+/).filter(Boolean) };
|
|
1707
|
-
},
|
|
1708
|
-
chown: async () => {
|
|
1709
|
-
await run(`chown -R ${CONTAINER_UID}:${CONTAINER_UID} ${STATE_DIR_HOST_LINUX}`);
|
|
1710
|
-
},
|
|
1711
|
-
removeSource: async () => {
|
|
1712
|
-
spin.text = "Stopping the 1.x container...";
|
|
1713
|
-
await run("docker stop openclaw 2>/dev/null || true");
|
|
1714
|
-
await run("docker rm openclaw 2>/dev/null || true");
|
|
1715
|
-
},
|
|
1716
|
-
writeConfig: async () => {
|
|
1717
|
-
const b64 = Buffer.from(SYNTHESISED_CONFIG, "utf-8").toString("base64");
|
|
1718
|
-
await run(
|
|
1719
|
-
`echo '${b64}' | base64 -d > ${configPath} && chown ${CONTAINER_UID}:${CONTAINER_UID} ${configPath}`
|
|
1720
|
-
);
|
|
1721
|
-
},
|
|
1722
|
-
start: async () => {
|
|
1723
|
-
spin.text = "Starting the 2.0 gateway...";
|
|
1724
|
-
await run(gatewayRunCommand({ image: targetImage, stateDir: STATE_DIR_HOST_LINUX }));
|
|
1725
|
-
},
|
|
1726
|
-
gate: async () => {
|
|
1727
|
-
spin.text = "Waiting for the gateway to start...";
|
|
1728
|
-
let last;
|
|
1729
|
-
for (let i = 0; i < 15; i++) {
|
|
1730
|
-
if (ac.signal.aborted) return { ok: false, reason: "aborted" };
|
|
1731
|
-
const r = await run(probeCommand("started", GATEWAY_PORT));
|
|
1732
|
-
const v = interpretProbe("started", r.stdout);
|
|
1733
|
-
if (v.ok) return { ok: true };
|
|
1734
|
-
last = v.reason;
|
|
1735
|
-
await new Promise((res) => setTimeout(res, 2e3));
|
|
1736
|
-
}
|
|
1737
|
-
return { ok: false, reason: last ?? "no response" };
|
|
1738
|
-
},
|
|
1739
|
-
deviceId: async () => {
|
|
1740
|
-
const file = await run(
|
|
1741
|
-
`docker exec openclaw sh -c 'cat ~/.openclaw/identity/device.json 2>/dev/null' || cat ${STATE_DIR_HOST_LINUX}/identity/device.json 2>/dev/null || true`
|
|
1742
|
-
);
|
|
1743
|
-
try {
|
|
1744
|
-
const parsed = JSON.parse(file.stdout.trim());
|
|
1745
|
-
return parsed.deviceId;
|
|
1746
|
-
} catch {
|
|
1747
|
-
return void 0;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
};
|
|
1431
|
+
let spin;
|
|
1751
1432
|
try {
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
for (const line2 of report.split("\n").slice(1)) {
|
|
1758
|
-
if (/CHANGED|could not be compared/.test(line2)) warn(line2);
|
|
1759
|
-
else info(line2);
|
|
1433
|
+
const result = await runMigration(target, {
|
|
1434
|
+
signal: ac.signal,
|
|
1435
|
+
onStep: (text) => {
|
|
1436
|
+
if (spin) spin.text = text;
|
|
1437
|
+
else spin = spinner(text);
|
|
1760
1438
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1439
|
+
});
|
|
1440
|
+
spin?.stop();
|
|
1441
|
+
const render2 = { success, info, warn, failure };
|
|
1442
|
+
for (const line2 of result.lines) render2[line2.level](line2.text);
|
|
1443
|
+
if (result.outcome.kind !== "migrated") process15.exit(result.exitCode);
|
|
1766
1444
|
} finally {
|
|
1767
|
-
|
|
1768
|
-
drainPool();
|
|
1445
|
+
spin?.stop();
|
|
1769
1446
|
}
|
|
1770
1447
|
}
|
|
1771
1448
|
});
|
|
@@ -1773,13 +1450,6 @@ var migrate_default = defineCommand13({
|
|
|
1773
1450
|
// src/cli/commands/gateway.ts
|
|
1774
1451
|
import { defineCommand as defineCommand14 } from "citty";
|
|
1775
1452
|
import process16 from "process";
|
|
1776
|
-
function dockerRunCmd2(version, publish = "loopback") {
|
|
1777
|
-
return gatewayRunCommand({
|
|
1778
|
-
image: `ghcr.io/openclaw/openclaw:${version}`,
|
|
1779
|
-
stateDir: STATE_DIR_HOST_LINUX,
|
|
1780
|
-
publish
|
|
1781
|
-
});
|
|
1782
|
-
}
|
|
1783
1453
|
var gateway_default = defineCommand14({
|
|
1784
1454
|
meta: {
|
|
1785
1455
|
name: "gateway",
|
|
@@ -1791,26 +1461,16 @@ var gateway_default = defineCommand14({
|
|
|
1791
1461
|
json: { type: "boolean", description: "Emit JSON (for status)" }
|
|
1792
1462
|
},
|
|
1793
1463
|
async run({ args }) {
|
|
1794
|
-
const { buildContext } = await import("./context-
|
|
1795
|
-
const {
|
|
1796
|
-
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1464
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1465
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
1797
1466
|
const [action, versionArg] = args._ ?? [];
|
|
1798
1467
|
if (!action || !["status", "restart", "update"].includes(action)) {
|
|
1799
1468
|
failure("Usage: clawops gateway <status | restart | update [version]>");
|
|
1800
1469
|
process16.exit(2);
|
|
1801
1470
|
}
|
|
1802
1471
|
const ctx = buildContext(args);
|
|
1803
|
-
const
|
|
1804
|
-
const
|
|
1805
|
-
const outputs = Object.fromEntries(
|
|
1806
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1807
|
-
);
|
|
1808
|
-
const base = extractBaseOutputs(outputs);
|
|
1809
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1810
|
-
...base,
|
|
1811
|
-
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1812
|
-
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1813
|
-
});
|
|
1472
|
+
const { resolveConn } = await import("./conn-M2V5HBPJ.js");
|
|
1473
|
+
const conn = await resolveConn(ctx);
|
|
1814
1474
|
const abortController = new AbortController();
|
|
1815
1475
|
process16.on("SIGINT", () => abortController.abort());
|
|
1816
1476
|
process16.on("SIGTERM", () => abortController.abort());
|
|
@@ -1824,25 +1484,12 @@ var gateway_default = defineCommand14({
|
|
|
1824
1484
|
});
|
|
1825
1485
|
try {
|
|
1826
1486
|
if (action === "status") {
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
"openclaw",
|
|
1831
|
-
'{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}',
|
|
1832
|
-
abortController.signal
|
|
1833
|
-
);
|
|
1834
|
-
let status = { status: "unknown", started: "", image: "" };
|
|
1835
|
-
if (inspected.kind === "ok") {
|
|
1836
|
-
try {
|
|
1837
|
-
status = JSON.parse(inspected.value);
|
|
1838
|
-
} catch {
|
|
1839
|
-
}
|
|
1840
|
-
} else if (inspected.kind === "missing") {
|
|
1841
|
-
status = { status: "not running", started: "", image: "" };
|
|
1842
|
-
} else {
|
|
1843
|
-
failure(`Could not ask docker about the gateway: ${inspected.detail}`);
|
|
1487
|
+
const inspected = await gatewayStatus(session, abortController.signal);
|
|
1488
|
+
if (!inspected.ok) {
|
|
1489
|
+
failure(inspected.error);
|
|
1844
1490
|
process16.exit(1);
|
|
1845
1491
|
}
|
|
1492
|
+
const status = inspected.value;
|
|
1846
1493
|
if (args.json) {
|
|
1847
1494
|
printJson(jsonOk(status));
|
|
1848
1495
|
} else {
|
|
@@ -1870,7 +1517,7 @@ var gateway_default = defineCommand14({
|
|
|
1870
1517
|
const spin = spinner("Restarting gateway...");
|
|
1871
1518
|
const result = await execPrivileged(
|
|
1872
1519
|
session,
|
|
1873
|
-
|
|
1520
|
+
dockerRunCmd(version, publish),
|
|
1874
1521
|
abortController.signal
|
|
1875
1522
|
);
|
|
1876
1523
|
spin.stop();
|
|
@@ -1880,121 +1527,23 @@ var gateway_default = defineCommand14({
|
|
|
1880
1527
|
}
|
|
1881
1528
|
success(`Gateway restarted (${version}).`);
|
|
1882
1529
|
} else {
|
|
1883
|
-
const
|
|
1884
|
-
|
|
1885
|
-
const version =
|
|
1530
|
+
const target = await resolveUpdateVersion(versionArg ?? args.channel);
|
|
1531
|
+
if (!target.ok) throw new UsageError(target.error);
|
|
1532
|
+
const version = target.value;
|
|
1886
1533
|
const spin = spinner(`Updating gateway to ${version}...`);
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
abortController.signal
|
|
1892
|
-
);
|
|
1893
|
-
if (pullResult.code !== 0) {
|
|
1894
|
-
spin.stop();
|
|
1895
|
-
failure(`Pull failed: ${pullResult.stderr}`);
|
|
1896
|
-
process16.exit(1);
|
|
1897
|
-
}
|
|
1898
|
-
spin.text = "Checking state compatibility...";
|
|
1899
|
-
const {
|
|
1900
|
-
snapshotCommand,
|
|
1901
|
-
snapshotPathFrom,
|
|
1902
|
-
preflightCommand,
|
|
1903
|
-
parsePreflight,
|
|
1904
|
-
judgePreflight
|
|
1905
|
-
} = await import("./upgrade-ULD4KFYC.js");
|
|
1906
|
-
const cur = imageForRestart(
|
|
1907
|
-
(await execPrivileged(session, IMAGE_INSPECT_CMD, abortController.signal)).stdout
|
|
1908
|
-
);
|
|
1909
|
-
const snapRepo = `${STATE_DIR_HOST_LINUX}/snapshots`;
|
|
1910
|
-
const snapOut = await execPrivileged(
|
|
1911
|
-
session,
|
|
1912
|
-
snapshotCommand(cur.ok ? cur.value : targetImage, STATE_DIR_HOST_LINUX, snapRepo),
|
|
1913
|
-
abortController.signal
|
|
1914
|
-
);
|
|
1915
|
-
const snapPath = snapshotPathFrom(snapOut.stdout);
|
|
1916
|
-
if (!snapPath) {
|
|
1917
|
-
spin.stop();
|
|
1918
|
-
failure(
|
|
1919
|
-
"Could not snapshot the state database before upgrading, so neither the compatibility check nor a rollback point is available.\n" + (snapOut.stderr || snapOut.stdout).slice(0, 300)
|
|
1920
|
-
);
|
|
1921
|
-
process16.exit(1);
|
|
1922
|
-
}
|
|
1923
|
-
const pre = await execPrivileged(
|
|
1924
|
-
session,
|
|
1925
|
-
preflightCommand(targetImage, STATE_DIR_HOST_LINUX, `${snapPath}/database.sqlite`),
|
|
1926
|
-
abortController.signal
|
|
1927
|
-
);
|
|
1928
|
-
const report = parsePreflight(pre.stdout);
|
|
1929
|
-
const verdict = report ? judgePreflight(report) : { ok: false, reason: `preflight produced no readable report: ${pre.stderr.slice(0, 200)}` };
|
|
1930
|
-
if (!verdict.ok) {
|
|
1931
|
-
spin.stop();
|
|
1932
|
-
failure(`Refusing to upgrade to ${version}: ${verdict.reason}`);
|
|
1933
|
-
info(`A snapshot of the current state was kept at ${snapPath}.`);
|
|
1934
|
-
process16.exit(1);
|
|
1935
|
-
}
|
|
1936
|
-
if (verdict.ok && verdict.note) info(verdict.note);
|
|
1937
|
-
spin.text = `Updating gateway to ${version}...`;
|
|
1938
|
-
const pubU = await execPrivileged(session, PUBLISH_INSPECT_CMD, abortController.signal);
|
|
1939
|
-
const runResult = await execPrivileged(
|
|
1940
|
-
session,
|
|
1941
|
-
dockerRunCmd2(version, publishForRestart(pubU.stdout)),
|
|
1942
|
-
abortController.signal
|
|
1943
|
-
);
|
|
1944
|
-
if (runResult.code !== 0) {
|
|
1945
|
-
spin.stop();
|
|
1946
|
-
failure(`Start failed: ${runResult.stderr}`);
|
|
1947
|
-
info(`State snapshot from before the upgrade: ${snapPath}`);
|
|
1948
|
-
process16.exit(1);
|
|
1949
|
-
}
|
|
1950
|
-
spin.text = "Waiting for the gateway to start...";
|
|
1951
|
-
const { probeCommand: probeCommand2, interpretProbe: interpretProbe2 } = await import("./health-5CCOXYGG.js");
|
|
1952
|
-
const { repairCommand, describeOutcome } = await import("./upgrade-ULD4KFYC.js");
|
|
1953
|
-
const publish = publishForRestart(pubU.stdout);
|
|
1954
|
-
const gate = async () => {
|
|
1955
|
-
let last;
|
|
1956
|
-
for (let i = 0; i < 15; i++) {
|
|
1957
|
-
if (abortController.signal.aborted) return { ok: false, reason: "aborted" };
|
|
1958
|
-
const r = await execPrivileged(
|
|
1959
|
-
session,
|
|
1960
|
-
probeCommand2("started", GATEWAY_PORT),
|
|
1961
|
-
abortController.signal
|
|
1962
|
-
);
|
|
1963
|
-
const v = interpretProbe2("started", r.stdout);
|
|
1964
|
-
if (v.ok) return { ok: true };
|
|
1965
|
-
last = v.reason;
|
|
1966
|
-
await new Promise((res) => setTimeout(res, 2e3));
|
|
1967
|
-
}
|
|
1968
|
-
return { ok: false, reason: last ?? "no response" };
|
|
1969
|
-
};
|
|
1970
|
-
const { resolveUpgrade } = await import("./upgrade-ULD4KFYC.js");
|
|
1971
|
-
const outcome = await resolveUpgrade(
|
|
1972
|
-
{
|
|
1973
|
-
gate,
|
|
1974
|
-
repair: async () => {
|
|
1975
|
-
spin.text = "Gateway did not start; attempting one-shot repair...";
|
|
1976
|
-
await execPrivileged(
|
|
1977
|
-
session,
|
|
1978
|
-
repairCommand(targetImage, STATE_DIR_HOST_LINUX),
|
|
1979
|
-
abortController.signal
|
|
1980
|
-
);
|
|
1981
|
-
},
|
|
1982
|
-
run: async (v) => {
|
|
1983
|
-
await execPrivileged(session, dockerRunCmd2(v, publish), abortController.signal);
|
|
1984
|
-
}
|
|
1534
|
+
const result = await updateGateway(session, version, {
|
|
1535
|
+
signal: abortController.signal,
|
|
1536
|
+
onProgress: (text) => {
|
|
1537
|
+
spin.text = text;
|
|
1985
1538
|
},
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
previousVersion: cur.ok ? versionOf(cur.value) : void 0,
|
|
1989
|
-
snapshotPath: snapPath
|
|
1990
|
-
}
|
|
1991
|
-
);
|
|
1539
|
+
onNote: (text) => info(text)
|
|
1540
|
+
});
|
|
1992
1541
|
spin.stop();
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
success(message);
|
|
1542
|
+
if (result.ok) {
|
|
1543
|
+
success(result.message);
|
|
1996
1544
|
} else {
|
|
1997
|
-
failure(message);
|
|
1545
|
+
failure(result.message);
|
|
1546
|
+
if (result.hint) info(result.hint);
|
|
1998
1547
|
process16.exit(1);
|
|
1999
1548
|
}
|
|
2000
1549
|
}
|
|
@@ -2007,24 +1556,23 @@ var gateway_default = defineCommand14({
|
|
|
2007
1556
|
|
|
2008
1557
|
// src/cli/commands/backup.ts
|
|
2009
1558
|
import { defineCommand as defineCommand15 } from "citty";
|
|
2010
|
-
import { createWriteStream } from "fs";
|
|
2011
|
-
import { pipeline } from "stream/promises";
|
|
2012
1559
|
import process17 from "process";
|
|
2013
1560
|
var backup_default = defineCommand15({
|
|
2014
1561
|
meta: {
|
|
2015
1562
|
name: "backup",
|
|
2016
|
-
description: "Create an OpenClaw backup
|
|
1563
|
+
description: "Create an OpenClaw backup, or restore one into a staging directory"
|
|
2017
1564
|
},
|
|
2018
1565
|
args: {
|
|
2019
|
-
action: { type: "positional", description: "Action: create
|
|
1566
|
+
action: { type: "positional", description: "Action: create | restore", required: true },
|
|
2020
1567
|
out: { type: "string", description: "[create] Local path to write the backup archive" },
|
|
2021
|
-
file: { type: "string", description: "[restore] Local backup archive
|
|
1568
|
+
file: { type: "string", description: "[restore] Local backup archive to verify and expand on the host" },
|
|
2022
1569
|
stack: { type: "string", description: "Target stack name" },
|
|
2023
|
-
yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
|
|
1570
|
+
yes: { type: "boolean", description: "[restore] Skip confirmation prompt" },
|
|
1571
|
+
activate: { type: "boolean", description: "[restore] Put the restored state into service: stop the gateway, swap it in, restart, and roll back if it does not come up" }
|
|
2024
1572
|
},
|
|
2025
1573
|
async run({ args }) {
|
|
2026
|
-
const { buildContext } = await import("./context-
|
|
2027
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1574
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1575
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
2028
1576
|
const action = args.action;
|
|
2029
1577
|
if (action !== "create" && action !== "restore") {
|
|
2030
1578
|
throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
|
|
@@ -2074,86 +1622,91 @@ var backup_default = defineCommand15({
|
|
|
2074
1622
|
signal: abortController.signal
|
|
2075
1623
|
});
|
|
2076
1624
|
try {
|
|
1625
|
+
const flows = await import("./backup-flows-NDP5JTR3.js");
|
|
2077
1626
|
if (action === "create") {
|
|
2078
|
-
const outPath = typeof args.out === "string" ? args.out :
|
|
1627
|
+
const outPath = typeof args.out === "string" ? args.out : flows.defaultBackupFilename();
|
|
2079
1628
|
info(`Writing backup to ${outPath}...`);
|
|
2080
1629
|
const spin = spinner("Creating backup on remote host...");
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
spin.stop();
|
|
2089
|
-
throw new Error(`Backup failed on the remote host: ${createResult.stderr.slice(0, 300)}`);
|
|
2090
|
-
}
|
|
2091
|
-
const backupStream = await streamPrivileged(
|
|
2092
|
-
session,
|
|
2093
|
-
`docker exec openclaw cat ${remoteArchive}`,
|
|
2094
|
-
abortController.signal
|
|
2095
|
-
);
|
|
1630
|
+
const created = await flows.createBackup(session, {
|
|
1631
|
+
outPath,
|
|
1632
|
+
signal: abortController.signal,
|
|
1633
|
+
onPhase: (phase) => {
|
|
1634
|
+
if (phase === "downloading") spin.stop();
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
2096
1637
|
spin.stop();
|
|
2097
|
-
|
|
2098
|
-
await pipeline(backupStream, fileStream);
|
|
2099
|
-
await execPrivileged(
|
|
2100
|
-
session,
|
|
2101
|
-
`docker exec openclaw rm -f ${remoteArchive}`,
|
|
2102
|
-
abortController.signal
|
|
2103
|
-
);
|
|
1638
|
+
if (!created.ok) throw new Error(created.reason);
|
|
2104
1639
|
success(`Backup saved to ${outPath}`);
|
|
2105
|
-
|
|
2106
|
-
info("secrets and device credentials, unencrypted. Treat it as a credential.");
|
|
1640
|
+
for (const line2 of flows.ARCHIVE_IS_A_CREDENTIAL) info(line2);
|
|
2107
1641
|
} else {
|
|
2108
1642
|
const file = typeof args.file === "string" ? args.file : void 0;
|
|
2109
1643
|
if (!file) {
|
|
2110
1644
|
throw new UsageError("Usage: clawops backup restore --file <archive.tar.gz>");
|
|
2111
1645
|
}
|
|
2112
|
-
|
|
2113
|
-
const
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
);
|
|
2122
|
-
if (
|
|
2123
|
-
|
|
2124
|
-
|
|
1646
|
+
let spin;
|
|
1647
|
+
const staged = await flows.stageRestore(session, {
|
|
1648
|
+
file,
|
|
1649
|
+
signal: abortController.signal,
|
|
1650
|
+
onPhase: (phase) => {
|
|
1651
|
+
if (phase === "uploading") spin = spinner("Uploading archive...");
|
|
1652
|
+
else if (spin) spin.text = "Verifying and restoring to a staging directory...";
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
spin?.stop();
|
|
1656
|
+
if (staged.unparsedReport !== void 0) info(staged.unparsedReport);
|
|
1657
|
+
if (!staged.ok) {
|
|
1658
|
+
if (staged.kind === "refused") throw new UsageError(staged.reason);
|
|
1659
|
+
if (staged.kind === "failed") throw new Error(staged.reason);
|
|
1660
|
+
failure(staged.reason);
|
|
1661
|
+
if (staged.stagingOnHost) info(flows.nothingChanged(staged.stagingOnHost));
|
|
1662
|
+
process17.exit(1);
|
|
2125
1663
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
1664
|
+
const { stagingOnHost, stateDir } = staged;
|
|
1665
|
+
success(`Archive verified and expanded to ${stagingOnHost} on the host.`);
|
|
1666
|
+
if (staged.entryCount !== void 0) info(`${staged.entryCount} entries restored.`);
|
|
1667
|
+
for (const w of staged.warnings) warn(w);
|
|
1668
|
+
if (!args.activate) {
|
|
1669
|
+
info("");
|
|
1670
|
+
info("Nothing has been activated. Re-run with --activate to put this state into");
|
|
1671
|
+
info("service, or adopt it by hand:");
|
|
1672
|
+
info(" 1. clawops ssh --command 'sudo docker stop openclaw'");
|
|
1673
|
+
info(` 2. replace the contents of ${stateDir} with those of`);
|
|
1674
|
+
info(` ${staged.statePath}`);
|
|
1675
|
+
info(` (the archive holds ${staged.stateDirInArchive} under payload/posix, not a`);
|
|
1676
|
+
info(" drop-in state directory \u2014 moving the bundle itself stops the gateway)");
|
|
1677
|
+
info(" 3. clawops gateway restart");
|
|
1678
|
+
for (const line2 of flows.PLUGINS_NOT_CARRIED) info(line2);
|
|
1679
|
+
return;
|
|
2140
1680
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
1681
|
+
if (!args.yes) {
|
|
1682
|
+
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
1683
|
+
const rl = createInterface3({ input: process17.stdin, output: process17.stdout });
|
|
1684
|
+
const answer = await rl.question(
|
|
1685
|
+
`Stop the gateway and put this restored state into service? The state it replaces is kept alongside it, and clawops puts it back if the gateway does not come up. [y/N] `
|
|
1686
|
+
);
|
|
1687
|
+
rl.close();
|
|
1688
|
+
if (!answer.toLowerCase().startsWith("y")) {
|
|
1689
|
+
info(`Aborted. The restored state is at ${stagingOnHost}; nothing was changed.`);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
2146
1692
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
1693
|
+
const activating = spinner("Activating the restored state...");
|
|
1694
|
+
const outcome = await flows.activateStaged(session, staged, {
|
|
1695
|
+
signal: abortController.signal,
|
|
1696
|
+
onPhase: (phase) => {
|
|
1697
|
+
if (phase === "restarting") activating.text = "Restarting the gateway...";
|
|
1698
|
+
else if (phase === "waiting") activating.text = "Waiting for the gateway to answer...";
|
|
1699
|
+
else if (phase === "done") activating.stop();
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
activating.stop();
|
|
1703
|
+
if (!outcome.ok) {
|
|
1704
|
+
failure(outcome.reason);
|
|
1705
|
+
for (const line2 of flows.activationFailureDetail(outcome)) info(line2);
|
|
1706
|
+
process17.exit(1);
|
|
1707
|
+
}
|
|
1708
|
+
success(flows.ACTIVATED);
|
|
1709
|
+
for (const line2 of flows.activatedLines(outcome.preserved)) info(line2);
|
|
2157
1710
|
}
|
|
2158
1711
|
} finally {
|
|
2159
1712
|
release();
|
|
@@ -2182,16 +1735,16 @@ var stacks_default = defineCommand16({
|
|
|
2182
1735
|
process18.exit(2);
|
|
2183
1736
|
}
|
|
2184
1737
|
if (action === "list") {
|
|
2185
|
-
const
|
|
2186
|
-
const defaultStack =
|
|
2187
|
-
const rows = Object.entries(
|
|
1738
|
+
const config = requireConfig();
|
|
1739
|
+
const defaultStack = config.defaults.stack;
|
|
1740
|
+
const rows = Object.entries(config.stacks).map(([n, s]) => [
|
|
2188
1741
|
n === defaultStack ? `${n} *` : n,
|
|
2189
1742
|
s.provider,
|
|
2190
1743
|
s.region ?? "\u2014",
|
|
2191
1744
|
s.stateUrl
|
|
2192
1745
|
]);
|
|
2193
1746
|
if (args.json) {
|
|
2194
|
-
const data = Object.entries(
|
|
1747
|
+
const data = Object.entries(config.stacks).map(([n, s]) => ({
|
|
2195
1748
|
name: n,
|
|
2196
1749
|
provider: s.provider,
|
|
2197
1750
|
region: s.region ?? null,
|
|
@@ -2216,49 +1769,17 @@ var stacks_default = defineCommand16({
|
|
|
2216
1769
|
failure("Usage: clawops stacks delete <name>");
|
|
2217
1770
|
process18.exit(2);
|
|
2218
1771
|
}
|
|
2219
|
-
const
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
throw new UsageError(
|
|
2226
|
-
`Cannot delete the only remaining stack "${name}". Add another stack first or run \`clawops destroy\` to tear down resources.`
|
|
2227
|
-
);
|
|
2228
|
-
}
|
|
2229
|
-
if (name === config.defaults.stack && !args.force) {
|
|
2230
|
-
throw new UsageError(
|
|
2231
|
-
`"${name}" is the default stack. Use --force to delete it (clawops will switch the default to another stack).`
|
|
2232
|
-
);
|
|
2233
|
-
}
|
|
2234
|
-
let isDeployed = false;
|
|
2235
|
-
if (!args.force) {
|
|
2236
|
-
try {
|
|
2237
|
-
const { buildContext } = await import("./context-7VI3R6EK.js");
|
|
2238
|
-
const ctx = buildContext({ stack: name });
|
|
2239
|
-
if (ctx.adapter.name === "local") {
|
|
2240
|
-
isDeployed = !!ctx.localState;
|
|
2241
|
-
} else {
|
|
2242
|
-
const stack = await ctx.getStack();
|
|
2243
|
-
const outputMap = await stack.outputs();
|
|
2244
|
-
const outputs = Object.fromEntries(
|
|
2245
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
2246
|
-
);
|
|
2247
|
-
isDeployed = !!outputs["publicIp"];
|
|
2248
|
-
}
|
|
2249
|
-
} catch {
|
|
2250
|
-
warn(`Could not verify deployment status for "${name}" \u2014 proceeding anyway.`);
|
|
1772
|
+
const { checkStackDelete, deleteStackFromConfig, forgetNotice } = await import("./stack-delete-NTIGKWUF.js");
|
|
1773
|
+
const check = await checkStackDelete(name, { force: !!args.force });
|
|
1774
|
+
if (!check.ok) {
|
|
1775
|
+
if (check.refusal === "deployed") {
|
|
1776
|
+
failure(check.reason);
|
|
1777
|
+
process18.exit(1);
|
|
2251
1778
|
}
|
|
1779
|
+
throw new UsageError(check.reason);
|
|
2252
1780
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
`Stack "${name}" is still deployed. Run \`clawops down --stack ${name}\` first to destroy cloud resources, or pass --force to remove from registry only.`
|
|
2256
|
-
);
|
|
2257
|
-
process18.exit(1);
|
|
2258
|
-
}
|
|
2259
|
-
warn(
|
|
2260
|
-
`This removes "${name}" from clawops config only. Cloud resources are NOT destroyed. Run \`clawops destroy --stack ` + name + "` first if you want to remove cloud resources."
|
|
2261
|
-
);
|
|
1781
|
+
for (const w of check.warnings) warn(w);
|
|
1782
|
+
warn(forgetNotice(name));
|
|
2262
1783
|
if (!args.yes) {
|
|
2263
1784
|
const confirmed = await confirm(`Delete stack "${name}" from config?`);
|
|
2264
1785
|
if (!confirmed) {
|
|
@@ -2266,18 +1787,9 @@ var stacks_default = defineCommand16({
|
|
|
2266
1787
|
return;
|
|
2267
1788
|
}
|
|
2268
1789
|
}
|
|
2269
|
-
const
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
updated.stacks = newStacks;
|
|
2273
|
-
if (name === config.defaults.stack) {
|
|
2274
|
-
updated.defaults = { ...config.defaults, stack: Object.keys(newStacks)[0] };
|
|
2275
|
-
}
|
|
2276
|
-
setConfig(updated);
|
|
2277
|
-
success(`Stack "${name}" removed from config.`);
|
|
2278
|
-
if (name === config.defaults.stack) {
|
|
2279
|
-
info(`Default stack switched to "${updated.defaults.stack}".`);
|
|
2280
|
-
}
|
|
1790
|
+
const deleted = deleteStackFromConfig(name);
|
|
1791
|
+
success(deleted.message);
|
|
1792
|
+
if (deleted.newDefaultMessage) info(deleted.newDefaultMessage);
|
|
2281
1793
|
}
|
|
2282
1794
|
});
|
|
2283
1795
|
async function confirm(message) {
|
|
@@ -2375,7 +1887,7 @@ var serveCmd = defineCommand18({
|
|
|
2375
1887
|
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
2376
1888
|
},
|
|
2377
1889
|
async run({ args }) {
|
|
2378
|
-
const { serveMcp } = await import("./server-
|
|
1890
|
+
const { serveMcp } = await import("./server-E3YA32ND.js");
|
|
2379
1891
|
await serveMcp({
|
|
2380
1892
|
port: args.http ? Number(args.http) : void 0,
|
|
2381
1893
|
bind: args.bind,
|
|
@@ -2453,11 +1965,11 @@ var wireCmd = defineCommand18({
|
|
|
2453
1965
|
rewire: { type: "boolean", description: "Replace an existing clawops entry" }
|
|
2454
1966
|
},
|
|
2455
1967
|
async run({ args }) {
|
|
2456
|
-
const { buildContext } = await import("./context-
|
|
2457
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1968
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
1969
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
2458
1970
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
2459
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
2460
|
-
const { MCP_HTTP_PORT } = await import("./server-
|
|
1971
|
+
const { wireGatewayMcp } = await import("./mcp-wire-BXF4DJFT.js");
|
|
1972
|
+
const { MCP_HTTP_PORT } = await import("./server-E3YA32ND.js");
|
|
2461
1973
|
const ac = new AbortController();
|
|
2462
1974
|
process20.on("SIGINT", () => {
|
|
2463
1975
|
ac.abort();
|
|
@@ -2557,8 +2069,8 @@ import { defineCommand as defineCommand19 } from "citty";
|
|
|
2557
2069
|
import process21 from "process";
|
|
2558
2070
|
import os from "os";
|
|
2559
2071
|
import { randomBytes } from "crypto";
|
|
2560
|
-
import { readFileSync as readFileSync3, writeFileSync as
|
|
2561
|
-
import { execSync, spawnSync
|
|
2072
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync, mkdirSync } from "fs";
|
|
2073
|
+
import { execSync, spawnSync, spawn as spawn2 } from "child_process";
|
|
2562
2074
|
import path2 from "path";
|
|
2563
2075
|
var setup_default = defineCommand19({
|
|
2564
2076
|
meta: {
|
|
@@ -2617,7 +2129,7 @@ var setup_default = defineCommand19({
|
|
|
2617
2129
|
} else {
|
|
2618
2130
|
provider = "local";
|
|
2619
2131
|
info("We'll connect to your server over SSH to install and configure OpenClaw.\n");
|
|
2620
|
-
if (!
|
|
2132
|
+
if (!existsSync(localKeyPath)) {
|
|
2621
2133
|
info(`No SSH key found at ${localKeyPath}.`);
|
|
2622
2134
|
const { genKey } = await inquirer.prompt([{
|
|
2623
2135
|
type: "confirm",
|
|
@@ -2653,7 +2165,7 @@ var setup_default = defineCommand19({
|
|
|
2653
2165
|
name: "keyPath",
|
|
2654
2166
|
message: `SSH private key file: (the key file on your computer used to log in \u2014 e.g. ~/.ssh/id_ed25519)`,
|
|
2655
2167
|
default: localKeyPath,
|
|
2656
|
-
validate: (v) =>
|
|
2168
|
+
validate: (v) => existsSync(v.trim()) || `File not found: ${v.trim()}`
|
|
2657
2169
|
}
|
|
2658
2170
|
]);
|
|
2659
2171
|
localHost = localAnswers.host;
|
|
@@ -2875,10 +2387,10 @@ var setup_default = defineCommand19({
|
|
|
2875
2387
|
}
|
|
2876
2388
|
const gatewayToken = randomBytes(24).toString("hex");
|
|
2877
2389
|
const secretsDir2 = path2.join(os.homedir(), ".clawops", "secrets");
|
|
2878
|
-
|
|
2879
|
-
|
|
2390
|
+
mkdirSync(secretsDir2, { recursive: true });
|
|
2391
|
+
spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
|
|
2880
2392
|
const tokenPath = path2.join(secretsDir2, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
|
|
2881
|
-
|
|
2393
|
+
writeFileSync2(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
|
|
2882
2394
|
const openclawConfigOverlay = {
|
|
2883
2395
|
models: builtModelConfig,
|
|
2884
2396
|
gateway: { auth: { mode: "token", token: gatewayToken } }
|
|
@@ -2889,7 +2401,7 @@ var setup_default = defineCommand19({
|
|
|
2889
2401
|
let outputPath;
|
|
2890
2402
|
if (provider === "local") {
|
|
2891
2403
|
outputPath = path2.join(outDir, `openclaw-${stackAnswers.stackName}.json`);
|
|
2892
|
-
|
|
2404
|
+
writeFileSync2(outputPath, JSON.stringify(openclawConfigOverlay, null, 2), "utf-8");
|
|
2893
2405
|
process21.stdout.write("\n");
|
|
2894
2406
|
success(`Config file saved to ${outputPath}`);
|
|
2895
2407
|
} else {
|
|
@@ -2935,7 +2447,7 @@ var setup_default = defineCommand19({
|
|
|
2935
2447
|
}
|
|
2936
2448
|
};
|
|
2937
2449
|
outputPath = path2.join(outDir, `clawops-${stackAnswers.stackName}-plan.json`);
|
|
2938
|
-
|
|
2450
|
+
writeFileSync2(outputPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
2939
2451
|
process21.stdout.write("\n");
|
|
2940
2452
|
success(`Deployment plan saved to ${outputPath}`);
|
|
2941
2453
|
}
|
|
@@ -3040,7 +2552,7 @@ var setup_default = defineCommand19({
|
|
|
3040
2552
|
inquirer
|
|
3041
2553
|
});
|
|
3042
2554
|
} else {
|
|
3043
|
-
const { getConfigDir: getConfigDir2 } = await import("./store-
|
|
2555
|
+
const { getConfigDir: getConfigDir2 } = await import("./store-YOLQYXMB.js");
|
|
3044
2556
|
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
3045
2557
|
process21.stdout.write("\n");
|
|
3046
2558
|
info("To deploy later, run these two commands:");
|
|
@@ -3057,7 +2569,7 @@ var setup_default = defineCommand19({
|
|
|
3057
2569
|
default: false
|
|
3058
2570
|
}]);
|
|
3059
2571
|
if (applyNow) {
|
|
3060
|
-
const { applyPlan } = await import("./apply-
|
|
2572
|
+
const { applyPlan } = await import("./apply-F5SQ2ZOS.js");
|
|
3061
2573
|
const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
|
|
3062
2574
|
try {
|
|
3063
2575
|
await applyPlan(plan, {
|
|
@@ -3085,13 +2597,13 @@ To deploy later: clawops apply ${outputPath}`);
|
|
|
3085
2597
|
}
|
|
3086
2598
|
});
|
|
3087
2599
|
async function runLocalDeploy(opts) {
|
|
3088
|
-
const { setConfig
|
|
3089
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
3090
|
-
const { connect } = await import("./ssh-
|
|
3091
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
2600
|
+
const { setConfig, getConfig, getConfigDir: getConfigDir2 } = await import("./store-YOLQYXMB.js");
|
|
2601
|
+
const { localBootstrap } = await import("./bootstrap-32TZXVDF.js");
|
|
2602
|
+
const { connect } = await import("./ssh-HOM7GF3M.js");
|
|
2603
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-OZYBMBSK.js");
|
|
3092
2604
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
3093
2605
|
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
3094
|
-
const existing =
|
|
2606
|
+
const existing = getConfig();
|
|
3095
2607
|
const stackEntry = {
|
|
3096
2608
|
provider: "local",
|
|
3097
2609
|
stateUrl: "file://~/.clawops/state",
|
|
@@ -3104,7 +2616,7 @@ async function runLocalDeploy(opts) {
|
|
|
3104
2616
|
stacks: { [opts.stackName]: stackEntry },
|
|
3105
2617
|
ssh: { keyPath: opts.keyPath, knownHostsPath }
|
|
3106
2618
|
};
|
|
3107
|
-
|
|
2619
|
+
setConfig(newConfig);
|
|
3108
2620
|
success(`Deployment "${opts.stackName}" registered (~/.clawops/config.json)`);
|
|
3109
2621
|
await checkDockerPreflight({ host: opts.host, port: opts.port, user: opts.user, keyPath: opts.keyPath, knownHostsPath, signal: opts.signal, inquirer: opts.inquirer });
|
|
3110
2622
|
const STAGES = [
|
|
@@ -3169,7 +2681,7 @@ async function runLocalDeploy(opts) {
|
|
|
3169
2681
|
spin2.fail("Configuration failed");
|
|
3170
2682
|
throw err;
|
|
3171
2683
|
}
|
|
3172
|
-
const { drainPool } = await import("./pool-
|
|
2684
|
+
const { drainPool } = await import("./pool-NT4XBSUT.js");
|
|
3173
2685
|
drainPool();
|
|
3174
2686
|
const tokenPath = path2.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
|
|
3175
2687
|
const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
|
|
@@ -3229,7 +2741,7 @@ ${cfg.loginHint}`);
|
|
|
3229
2741
|
}]);
|
|
3230
2742
|
if (runNow) {
|
|
3231
2743
|
process21.stdout.write("\n");
|
|
3232
|
-
const result =
|
|
2744
|
+
const result = spawnSync(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
|
|
3233
2745
|
if (result.error) {
|
|
3234
2746
|
failure(`Could not launch ${cliName} \u2014 is it installed? (${result.error.message})`);
|
|
3235
2747
|
info(`Install guide: ${cliInstallUrl(provider)}`);
|
|
@@ -3273,12 +2785,12 @@ async function promptSecret(name, label, envDefault, inquirer) {
|
|
|
3273
2785
|
message: `Paste your ${label}: (input is hidden)`,
|
|
3274
2786
|
validate: (v) => v.trim() !== "" || "Value cannot be empty"
|
|
3275
2787
|
}]);
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
success(`Secret saved to ${
|
|
3281
|
-
return { name, source: "file", ref:
|
|
2788
|
+
mkdirSync(secretsDir2, { recursive: true });
|
|
2789
|
+
spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
|
|
2790
|
+
const secretPath = path2.join(secretsDir2, name);
|
|
2791
|
+
writeFileSync2(secretPath, secretValue.trim(), { encoding: "utf-8", mode: 384 });
|
|
2792
|
+
success(`Secret saved to ${secretPath} (chmod 600)`);
|
|
2793
|
+
return { name, source: "file", ref: secretPath };
|
|
3282
2794
|
}
|
|
3283
2795
|
if (secretSource === "env") {
|
|
3284
2796
|
const { envVar } = await inquirer.prompt([{
|
|
@@ -3293,7 +2805,7 @@ async function promptSecret(name, label, envDefault, inquirer) {
|
|
|
3293
2805
|
type: "input",
|
|
3294
2806
|
name: "filePath",
|
|
3295
2807
|
message: `File path: (full path to the file containing your ${label})`,
|
|
3296
|
-
validate: (v) =>
|
|
2808
|
+
validate: (v) => existsSync(v.trim()) || `File not found: ${v.trim()}`
|
|
3297
2809
|
}]);
|
|
3298
2810
|
return { name, source: "file", ref: filePath };
|
|
3299
2811
|
}
|
|
@@ -3307,7 +2819,7 @@ var DOCKER_CHECK_CMD = [
|
|
|
3307
2819
|
"echo NOT_RUNNING"
|
|
3308
2820
|
].join("; ");
|
|
3309
2821
|
async function checkDockerPreflight(opts) {
|
|
3310
|
-
const { acquireSession } = await import("./pool-
|
|
2822
|
+
const { acquireSession } = await import("./pool-NT4XBSUT.js");
|
|
3311
2823
|
const { ProviderError } = await import("./errors-OK47MQFD.js");
|
|
3312
2824
|
const spin = spinner("Checking Docker on target host...");
|
|
3313
2825
|
const timeoutSignal = AbortSignal.timeout(DOCKER_PREFLIGHT_TIMEOUT_MS);
|
|
@@ -3354,7 +2866,7 @@ async function checkDockerPreflight(opts) {
|
|
|
3354
2866
|
var DOCKER_POLL_MS = 3e3;
|
|
3355
2867
|
var DOCKER_TIMEOUT_MS = 9e4;
|
|
3356
2868
|
async function startRemoteDocker(opts, spin) {
|
|
3357
|
-
const { acquireSession } = await import("./pool-
|
|
2869
|
+
const { acquireSession } = await import("./pool-NT4XBSUT.js");
|
|
3358
2870
|
const { ProviderError } = await import("./errors-OK47MQFD.js");
|
|
3359
2871
|
spin.text = "Starting Docker on the remote server...";
|
|
3360
2872
|
const { session, release } = await acquireSession({
|
|
@@ -3451,7 +2963,7 @@ async function startDockerAndWait(inquirer) {
|
|
|
3451
2963
|
await pollUntilDockerReady("Waiting for Docker daemon...");
|
|
3452
2964
|
} else if (choice === "systemctl") {
|
|
3453
2965
|
const spin = spinner("Running: sudo systemctl start docker...");
|
|
3454
|
-
const res =
|
|
2966
|
+
const res = spawnSync("sudo", ["systemctl", "start", "docker"], { stdio: "pipe" });
|
|
3455
2967
|
if (res.status !== 0) {
|
|
3456
2968
|
spin.fail("systemctl start docker failed.");
|
|
3457
2969
|
throw new Error("systemctl start docker failed");
|
|
@@ -3459,18 +2971,18 @@ async function startDockerAndWait(inquirer) {
|
|
|
3459
2971
|
spin.succeed("Docker service started.");
|
|
3460
2972
|
await pollUntilDockerReady("Waiting for Docker daemon to be reachable...");
|
|
3461
2973
|
} else if (choice === "desktop") {
|
|
3462
|
-
const backendRunning =
|
|
2974
|
+
const backendRunning = spawnSync("pgrep", ["-f", "com.docker.backend"], { stdio: "ignore" }).status === 0;
|
|
3463
2975
|
if (backendRunning) {
|
|
3464
2976
|
const killSpin = spinner("Docker Desktop is stuck \u2014 force-restarting...");
|
|
3465
2977
|
for (const pat of ["com.docker.backend", "com.docker.virtualization", "com.docker.build", "Docker Desktop"]) {
|
|
3466
|
-
|
|
2978
|
+
spawnSync("pkill", ["-9", "-f", pat], { stdio: "ignore" });
|
|
3467
2979
|
}
|
|
3468
2980
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
3469
2981
|
killSpin.succeed("Old Docker processes cleared.");
|
|
3470
2982
|
}
|
|
3471
2983
|
const openSpin = spinner("Launching Docker Desktop...");
|
|
3472
|
-
let opened =
|
|
3473
|
-
if (opened.status !== 0) opened =
|
|
2984
|
+
let opened = spawnSync("open", ["-a", "Docker"], { stdio: "ignore" });
|
|
2985
|
+
if (opened.status !== 0) opened = spawnSync("open", ["/Applications/Docker.app"], { stdio: "ignore" });
|
|
3474
2986
|
if (opened.status !== 0) {
|
|
3475
2987
|
openSpin.fail("Could not open Docker Desktop \u2014 is it installed in /Applications?");
|
|
3476
2988
|
throw new Error("Failed to open Docker Desktop");
|
|
@@ -3479,7 +2991,7 @@ async function startDockerAndWait(inquirer) {
|
|
|
3479
2991
|
await pollUntilDockerReady("Waiting for Docker engine to start...");
|
|
3480
2992
|
} else if (choice === "colima") {
|
|
3481
2993
|
const spin = spinner("Running: colima start...");
|
|
3482
|
-
const res =
|
|
2994
|
+
const res = spawnSync("colima", ["start"], { stdio: "pipe" });
|
|
3483
2995
|
if (res.status !== 0) {
|
|
3484
2996
|
spin.fail("colima start failed \u2014 is Colima installed? (brew install colima docker)");
|
|
3485
2997
|
throw new Error("colima start failed");
|
|
@@ -3491,12 +3003,12 @@ async function startDockerAndWait(inquirer) {
|
|
|
3491
3003
|
var LOCALHOST_ALIASES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
3492
3004
|
async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
3493
3005
|
const pubKeyPath = `${keyPath}.pub`;
|
|
3494
|
-
if (!
|
|
3006
|
+
if (!existsSync(pubKeyPath)) return;
|
|
3495
3007
|
const pubKey = readFileSync3(pubKeyPath, "utf-8").trim();
|
|
3496
3008
|
const isLocal = LOCALHOST_ALIASES.has(host.toLowerCase());
|
|
3497
3009
|
const authorizedKeysPath = path2.join(os.homedir(), ".ssh", "authorized_keys");
|
|
3498
3010
|
if (isLocal) {
|
|
3499
|
-
const existing =
|
|
3011
|
+
const existing = existsSync(authorizedKeysPath) ? readFileSync3(authorizedKeysPath, "utf-8") : "";
|
|
3500
3012
|
if (existing.includes(pubKey)) {
|
|
3501
3013
|
success("SSH public key is already in authorized_keys.");
|
|
3502
3014
|
return;
|
|
@@ -3509,10 +3021,10 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
3509
3021
|
default: true
|
|
3510
3022
|
}]);
|
|
3511
3023
|
if (addKey) {
|
|
3512
|
-
|
|
3024
|
+
mkdirSync(path2.join(os.homedir(), ".ssh"), { recursive: true });
|
|
3513
3025
|
const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
|
|
3514
|
-
|
|
3515
|
-
|
|
3026
|
+
writeFileSync2(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
|
|
3027
|
+
spawnSync("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
|
|
3516
3028
|
success("Public key added to ~/.ssh/authorized_keys.");
|
|
3517
3029
|
} else {
|
|
3518
3030
|
info("SSH will likely fail. Add this line to ~/.ssh/authorized_keys manually:");
|
|
@@ -3527,8 +3039,8 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
3527
3039
|
}
|
|
3528
3040
|
async function generateSshKey() {
|
|
3529
3041
|
const keyPath = path2.join(os.homedir(), ".ssh", "id_ed25519");
|
|
3530
|
-
|
|
3531
|
-
const result =
|
|
3042
|
+
mkdirSync(path2.join(os.homedir(), ".ssh"), { recursive: true });
|
|
3043
|
+
const result = spawnSync("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
|
|
3532
3044
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3533
3045
|
});
|
|
3534
3046
|
if (result.status !== 0) {
|
|
@@ -3543,7 +3055,7 @@ function detectSshKey() {
|
|
|
3543
3055
|
const candidates = ["id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"];
|
|
3544
3056
|
for (const name of candidates) {
|
|
3545
3057
|
const p = path2.join(os.homedir(), ".ssh", name);
|
|
3546
|
-
if (
|
|
3058
|
+
if (existsSync(p)) return p;
|
|
3547
3059
|
}
|
|
3548
3060
|
return path2.join(os.homedir(), ".ssh", "id_ed25519");
|
|
3549
3061
|
}
|
|
@@ -3559,9 +3071,9 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3559
3071
|
process21.stdout.write("\n");
|
|
3560
3072
|
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
3561
3073
|
if (!wireGateway) return;
|
|
3562
|
-
const { connect } = await import("./ssh-
|
|
3563
|
-
const { getConfigDir: getConfigDir2 } = await import("./store-
|
|
3564
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
3074
|
+
const { connect } = await import("./ssh-HOM7GF3M.js");
|
|
3075
|
+
const { getConfigDir: getConfigDir2 } = await import("./store-YOLQYXMB.js");
|
|
3076
|
+
const { wireGatewayMcp } = await import("./mcp-wire-BXF4DJFT.js");
|
|
3565
3077
|
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
3566
3078
|
const spin = spinner("Wiring gateway MCP client...");
|
|
3567
3079
|
const session = await connect({
|
|
@@ -3680,9 +3192,9 @@ function instanceChoices(provider) {
|
|
|
3680
3192
|
return table[provider] ?? [];
|
|
3681
3193
|
}
|
|
3682
3194
|
async function maybeHarden(opts) {
|
|
3683
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-
|
|
3684
|
-
const { getConfig
|
|
3685
|
-
const config =
|
|
3195
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-UHAOVQMZ.js");
|
|
3196
|
+
const { getConfig } = await import("./store-YOLQYXMB.js");
|
|
3197
|
+
const config = getConfig();
|
|
3686
3198
|
if (!config) return;
|
|
3687
3199
|
const availableModules = resolveModules(MODULE_CATALOG, void 0, opts.provider);
|
|
3688
3200
|
if (availableModules.length === 0) return;
|
|
@@ -3704,7 +3216,7 @@ async function maybeHarden(opts) {
|
|
|
3704
3216
|
const modules = availableModules.filter((m) => selectedIds.includes(m.id));
|
|
3705
3217
|
process21.stdout.write("\nApplying hardening modules...\n\n");
|
|
3706
3218
|
try {
|
|
3707
|
-
const { buildContext } = await import("./context-
|
|
3219
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
3708
3220
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
3709
3221
|
const ctx = buildContext({ stack: opts.stackName });
|
|
3710
3222
|
const stackObj = await ctx.getStack();
|
|
@@ -3827,21 +3339,16 @@ async function runAccountPreflight(opts) {
|
|
|
3827
3339
|
|
|
3828
3340
|
// src/cli/commands/secret.ts
|
|
3829
3341
|
import { defineCommand as defineCommand20 } from "citty";
|
|
3830
|
-
import {
|
|
3831
|
-
import { spawnSync as
|
|
3832
|
-
import path3 from "path";
|
|
3833
|
-
import os2 from "os";
|
|
3342
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
3343
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3834
3344
|
import process22 from "process";
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
function listSecretNames() {
|
|
3843
|
-
if (!existsSync3(secretsDir())) return [];
|
|
3844
|
-
return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
|
|
3345
|
+
function secretPathOrExit(name) {
|
|
3346
|
+
const resolved = resolveSecretPath(name);
|
|
3347
|
+
if (!resolved.ok) {
|
|
3348
|
+
failure(resolved.reason);
|
|
3349
|
+
process22.exit(2);
|
|
3350
|
+
}
|
|
3351
|
+
return resolved.path;
|
|
3845
3352
|
}
|
|
3846
3353
|
var listCmd = defineCommand20({
|
|
3847
3354
|
meta: { name: "list", description: "List all stored secrets and their status" },
|
|
@@ -3849,24 +3356,12 @@ var listCmd = defineCommand20({
|
|
|
3849
3356
|
json: { type: "boolean", description: "Emit JSON" }
|
|
3850
3357
|
},
|
|
3851
3358
|
run({ args }) {
|
|
3852
|
-
const
|
|
3853
|
-
if (
|
|
3854
|
-
info(
|
|
3855
|
-
info(
|
|
3359
|
+
const rows = listSecrets();
|
|
3360
|
+
if (rows.length === 0) {
|
|
3361
|
+
info(NO_SECRETS_MESSAGE);
|
|
3362
|
+
info(SET_SECRET_HINT);
|
|
3856
3363
|
return;
|
|
3857
3364
|
}
|
|
3858
|
-
const rows = names.map((name) => {
|
|
3859
|
-
const p = secretPath(name);
|
|
3860
|
-
try {
|
|
3861
|
-
const stat = statSync(p);
|
|
3862
|
-
const content = readFileSync4(p, "utf-8").trim();
|
|
3863
|
-
const status = content.length > 0 ? "ok" : "empty";
|
|
3864
|
-
const modified = stat.mtime.toISOString().slice(0, 10);
|
|
3865
|
-
return { name, status, source: "file", path: p, modified, resolvable: content.length > 0 };
|
|
3866
|
-
} catch {
|
|
3867
|
-
return { name, status: "unreadable", source: "file", path: p, modified: "\u2014", resolvable: false };
|
|
3868
|
-
}
|
|
3869
|
-
});
|
|
3870
3365
|
if (args.json) {
|
|
3871
3366
|
process22.stdout.write(JSON.stringify(rows, null, 2) + "\n");
|
|
3872
3367
|
return;
|
|
@@ -3891,6 +3386,7 @@ var setCmd = defineCommand20({
|
|
|
3891
3386
|
failure("Usage: clawops secret set <name>");
|
|
3892
3387
|
process22.exit(2);
|
|
3893
3388
|
}
|
|
3389
|
+
const target = secretPathOrExit(name);
|
|
3894
3390
|
const inquirer = (await import("inquirer")).default;
|
|
3895
3391
|
let value;
|
|
3896
3392
|
if (args.value) {
|
|
@@ -3904,10 +3400,10 @@ var setCmd = defineCommand20({
|
|
|
3904
3400
|
}]);
|
|
3905
3401
|
value = secretValue;
|
|
3906
3402
|
}
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
success(`Secret "${name}" saved to ${
|
|
3403
|
+
mkdirSync2(secretsDir(), { recursive: true });
|
|
3404
|
+
spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
|
|
3405
|
+
writeFileSync3(target, value.trim(), { encoding: "utf-8", mode: 384 });
|
|
3406
|
+
success(`Secret "${name}" saved to ${target} (chmod 600)`);
|
|
3911
3407
|
info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
|
|
3912
3408
|
}
|
|
3913
3409
|
});
|
|
@@ -3922,23 +3418,18 @@ var deleteCmd = defineCommand20({
|
|
|
3922
3418
|
failure("Usage: clawops secret delete <name>");
|
|
3923
3419
|
process22.exit(2);
|
|
3924
3420
|
}
|
|
3925
|
-
const
|
|
3926
|
-
if (!
|
|
3927
|
-
failure(
|
|
3928
|
-
process22.exit(1);
|
|
3929
|
-
}
|
|
3930
|
-
const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
|
|
3931
|
-
const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
|
|
3932
|
-
if (referencingStacks.length > 0) {
|
|
3933
|
-
warn(`Secret "${name}" is referenced by stack(s): ${referencingStacks.join(", ")}`);
|
|
3934
|
-
warn("Deleting it will leave those stacks with an unresolvable $secret: ref.");
|
|
3421
|
+
const prepared = prepareSecretDelete(name);
|
|
3422
|
+
if (!prepared.ok) {
|
|
3423
|
+
failure(prepared.reason);
|
|
3424
|
+
process22.exit(prepared.kind === "invalid-name" ? 2 : 1);
|
|
3935
3425
|
}
|
|
3426
|
+
for (const line2 of prepared.warnings) warn(line2);
|
|
3936
3427
|
if (!args.yes) {
|
|
3937
3428
|
const inquirer = (await import("inquirer")).default;
|
|
3938
3429
|
const { confirmed } = await inquirer.prompt([{
|
|
3939
3430
|
type: "confirm",
|
|
3940
3431
|
name: "confirmed",
|
|
3941
|
-
message:
|
|
3432
|
+
message: deleteConfirmQuestion(name),
|
|
3942
3433
|
default: false
|
|
3943
3434
|
}]);
|
|
3944
3435
|
if (!confirmed) {
|
|
@@ -3946,8 +3437,7 @@ var deleteCmd = defineCommand20({
|
|
|
3946
3437
|
return;
|
|
3947
3438
|
}
|
|
3948
3439
|
}
|
|
3949
|
-
|
|
3950
|
-
success(`Secret "${name}" deleted.`);
|
|
3440
|
+
success(deletePreparedSecret(prepared));
|
|
3951
3441
|
}
|
|
3952
3442
|
});
|
|
3953
3443
|
var rotateCmd = defineCommand20({
|
|
@@ -3962,6 +3452,7 @@ var rotateCmd = defineCommand20({
|
|
|
3962
3452
|
failure("Usage: clawops secret rotate <name> [--stack <name>]");
|
|
3963
3453
|
process22.exit(2);
|
|
3964
3454
|
}
|
|
3455
|
+
const target = secretPathOrExit(name);
|
|
3965
3456
|
const inquirer = (await import("inquirer")).default;
|
|
3966
3457
|
let value;
|
|
3967
3458
|
if (args.value) {
|
|
@@ -3975,12 +3466,12 @@ var rotateCmd = defineCommand20({
|
|
|
3975
3466
|
}]);
|
|
3976
3467
|
value = secretValue;
|
|
3977
3468
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3469
|
+
mkdirSync2(secretsDir(), { recursive: true });
|
|
3470
|
+
spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
|
|
3471
|
+
writeFileSync3(target, value.trim(), { encoding: "utf-8", mode: 384 });
|
|
3981
3472
|
success(`Secret "${name}" updated.`);
|
|
3982
|
-
const { getConfig
|
|
3983
|
-
const cfg =
|
|
3473
|
+
const { getConfig } = await import("./store-YOLQYXMB.js");
|
|
3474
|
+
const cfg = getConfig();
|
|
3984
3475
|
const targetStack = args.stack ?? cfg?.defaults?.stack;
|
|
3985
3476
|
if (!targetStack) {
|
|
3986
3477
|
warn("No stack specified and no default stack in config \u2014 skipping re-apply.");
|
|
@@ -3996,11 +3487,11 @@ var rotateCmd = defineCommand20({
|
|
|
3996
3487
|
}
|
|
3997
3488
|
info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
|
|
3998
3489
|
try {
|
|
3999
|
-
const { buildContext } = await import("./context-
|
|
4000
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
3490
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
3491
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-OZYBMBSK.js");
|
|
4001
3492
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
4002
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
4003
|
-
const { localStateToConnectionInfo } = await import("./state-
|
|
3493
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
3494
|
+
const { localStateToConnectionInfo } = await import("./state-AKZP2S4T.js");
|
|
4004
3495
|
const ctx = buildContext({ stack: targetStack });
|
|
4005
3496
|
let conn;
|
|
4006
3497
|
if (ctx.adapter.name === "local") {
|
|
@@ -4040,53 +3531,20 @@ var auditCmd = defineCommand20({
|
|
|
4040
3531
|
json: { type: "boolean", description: "Emit JSON" }
|
|
4041
3532
|
},
|
|
4042
3533
|
async run({ args }) {
|
|
4043
|
-
const {
|
|
4044
|
-
const overlays = listOverlays();
|
|
4045
|
-
const issues = [];
|
|
4046
|
-
for (const name of listSecretNames()) {
|
|
4047
|
-
const p = secretPath(name);
|
|
4048
|
-
try {
|
|
4049
|
-
const content = readFileSync4(p, "utf-8").trim();
|
|
4050
|
-
if (content.length === 0) {
|
|
4051
|
-
issues.push({ kind: "empty-secret", secret: name, detail: `File exists but is empty: ${p}` });
|
|
4052
|
-
}
|
|
4053
|
-
} catch {
|
|
4054
|
-
issues.push({ kind: "unreadable-secret", secret: name, detail: `Cannot read: ${p}` });
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4057
|
-
for (const overlay of overlays) {
|
|
4058
|
-
for (const s of overlay.secrets) {
|
|
4059
|
-
if (s.source === "file") {
|
|
4060
|
-
const ref = s.ref ?? secretPath(s.name);
|
|
4061
|
-
if (!existsSync3(ref)) {
|
|
4062
|
-
issues.push({ kind: "missing-file", stack: overlay.stackName, secret: s.name, detail: `File not found: ${ref}` });
|
|
4063
|
-
}
|
|
4064
|
-
} else if (s.source === "env") {
|
|
4065
|
-
const envVar = s.ref ?? s.name;
|
|
4066
|
-
if (!process22.env[envVar]) {
|
|
4067
|
-
issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
|
|
4068
|
-
}
|
|
4069
|
-
} else {
|
|
4070
|
-
issues.push({ kind: "cloud-sm-unresolved", stack: overlay.stackName, secret: s.name, detail: `Source "${s.source}" is not auto-resolved \u2014 set manually` });
|
|
4071
|
-
}
|
|
4072
|
-
}
|
|
4073
|
-
}
|
|
3534
|
+
const { issues } = auditSecrets();
|
|
4074
3535
|
if (args.json) {
|
|
4075
3536
|
process22.stdout.write(JSON.stringify({ issues, ok: issues.length === 0 }, null, 2) + "\n");
|
|
4076
3537
|
return;
|
|
4077
3538
|
}
|
|
4078
3539
|
if (issues.length === 0) {
|
|
4079
|
-
success(
|
|
3540
|
+
success(AUDIT_CLEAN_MESSAGE);
|
|
4080
3541
|
return;
|
|
4081
3542
|
}
|
|
4082
|
-
failure(`${issues.length}
|
|
3543
|
+
failure(`${auditIssuesHeadline(issues.length)}
|
|
4083
3544
|
`);
|
|
4084
|
-
for (const issue of issues)
|
|
4085
|
-
const prefix = issue.stack ? `[${issue.stack}] ` : "";
|
|
4086
|
-
warn(`${prefix}${issue.secret}: ${issue.detail}`);
|
|
4087
|
-
}
|
|
3545
|
+
for (const issue of issues) warn(formatAuditIssue(issue));
|
|
4088
3546
|
process22.stdout.write("\n");
|
|
4089
|
-
info(
|
|
3547
|
+
info(AUDIT_FIX_HINT);
|
|
4090
3548
|
}
|
|
4091
3549
|
});
|
|
4092
3550
|
var secret_default = defineCommand20({
|
|
@@ -4183,16 +3641,16 @@ import { accessSync, constants } from "fs";
|
|
|
4183
3641
|
import { execSync as execSync2 } from "child_process";
|
|
4184
3642
|
var ISSUES_URL = `${REPO_URL}/issues/new`;
|
|
4185
3643
|
var BODY_CAP = 2e3;
|
|
4186
|
-
var VERSION = true ? "2.
|
|
3644
|
+
var VERSION = true ? "2.2.0" : "dev";
|
|
4187
3645
|
async function collectContext() {
|
|
4188
3646
|
const nodeVersion = process23.version;
|
|
4189
|
-
const
|
|
3647
|
+
const os2 = `${process23.platform} ${process23.arch}`;
|
|
4190
3648
|
let provider = "unknown";
|
|
4191
3649
|
let stackCount = 0;
|
|
4192
3650
|
let sshKey = "unknown";
|
|
4193
3651
|
try {
|
|
4194
|
-
const { getConfig
|
|
4195
|
-
const config =
|
|
3652
|
+
const { getConfig } = await import("./store-YOLQYXMB.js");
|
|
3653
|
+
const config = getConfig();
|
|
4196
3654
|
if (config) {
|
|
4197
3655
|
const stacks = Object.values(config.stacks);
|
|
4198
3656
|
stackCount = stacks.length;
|
|
@@ -4213,7 +3671,7 @@ async function collectContext() {
|
|
|
4213
3671
|
return [
|
|
4214
3672
|
`**clawops version:** ${VERSION}`,
|
|
4215
3673
|
`**Node:** ${nodeVersion}`,
|
|
4216
|
-
`**OS:** ${
|
|
3674
|
+
`**OS:** ${os2}`,
|
|
4217
3675
|
`**Provider:** ${provider}`,
|
|
4218
3676
|
`**Stacks:** ${stackCount}`,
|
|
4219
3677
|
`**SSH key:** ${sshKey}`
|
|
@@ -4302,9 +3760,9 @@ var harden_default = defineCommand23({
|
|
|
4302
3760
|
"tailscale-revert": { type: "boolean", description: "Take the host off its tailnet and go back to its public address. A private-only stack must be reopened with a plan first" }
|
|
4303
3761
|
},
|
|
4304
3762
|
async run({ args }) {
|
|
4305
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-
|
|
4306
|
-
const { buildContext } = await import("./context-
|
|
4307
|
-
const { getConfig
|
|
3763
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-UHAOVQMZ.js");
|
|
3764
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
3765
|
+
const { getConfig } = await import("./store-YOLQYXMB.js");
|
|
4308
3766
|
if (args.list) {
|
|
4309
3767
|
process24.stdout.write("\nAvailable hardening modules:\n\n");
|
|
4310
3768
|
for (const mod of MODULE_CATALOG) {
|
|
@@ -4318,7 +3776,7 @@ var harden_default = defineCommand23({
|
|
|
4318
3776
|
process24.stdout.write("\n");
|
|
4319
3777
|
return;
|
|
4320
3778
|
}
|
|
4321
|
-
const config =
|
|
3779
|
+
const config = getConfig();
|
|
4322
3780
|
if (!config) {
|
|
4323
3781
|
failure("No clawops config found. Run `clawops init` first.");
|
|
4324
3782
|
process24.exit(1);
|
|
@@ -4326,7 +3784,7 @@ var harden_default = defineCommand23({
|
|
|
4326
3784
|
const ctx = buildContext({ stack: args.stack });
|
|
4327
3785
|
const provider = ctx.adapter.name;
|
|
4328
3786
|
if (args["tailscale-revert"]) {
|
|
4329
|
-
const { revertTailnet } = await import("./flows-
|
|
3787
|
+
const { revertTailnet } = await import("./flows-6B2I6TRW.js");
|
|
4330
3788
|
const override = config.stacks[ctx.stackName]?.tailscale;
|
|
4331
3789
|
if (override && args.yes !== true) {
|
|
4332
3790
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
@@ -4349,7 +3807,7 @@ var harden_default = defineCommand23({
|
|
|
4349
3807
|
else success(outcome.message);
|
|
4350
3808
|
return;
|
|
4351
3809
|
}
|
|
4352
|
-
const { makeTailscaleModule } = await import("./harden-
|
|
3810
|
+
const { makeTailscaleModule } = await import("./harden-UHAOVQMZ.js");
|
|
4353
3811
|
const forStack = makeTailscaleModule(ctx.stackName);
|
|
4354
3812
|
const resolved = resolveModules(MODULE_CATALOG, args.options, provider).map(
|
|
4355
3813
|
(m) => m.id === "tailscale" ? forStack : m
|
|
@@ -4433,7 +3891,7 @@ var harden_default = defineCommand23({
|
|
|
4433
3891
|
success("Hardening complete.");
|
|
4434
3892
|
}
|
|
4435
3893
|
if (args.tailscale && !args["dry-run"]) {
|
|
4436
|
-
const { cutOverToTailnet } = await import("./flows-
|
|
3894
|
+
const { cutOverToTailnet } = await import("./flows-6B2I6TRW.js");
|
|
4437
3895
|
process24.stdout.write("\nVerifying the tailnet address before using it...\n");
|
|
4438
3896
|
const outcome = await cutOverToTailnet(ctx, conn, config);
|
|
4439
3897
|
if (!outcome.ok) {
|
|
@@ -4470,7 +3928,7 @@ function resolveArgv(argv2, stdinIsTty) {
|
|
|
4470
3928
|
}
|
|
4471
3929
|
|
|
4472
3930
|
// src/cli/index.ts
|
|
4473
|
-
var CLI_VERSION = true ? "2.
|
|
3931
|
+
var CLI_VERSION = true ? "2.2.0" : "dev";
|
|
4474
3932
|
var main = defineCommand24({
|
|
4475
3933
|
meta: {
|
|
4476
3934
|
name: "clawops",
|