@clawops/cli 1.7.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +504 -76
- package/dist/apply-KYFPWI5U.js +27 -0
- package/dist/automation-RMAIBNVC.js +44 -0
- package/dist/bedrock-XVASJX2K.js +61 -0
- package/dist/{bootstrap-HL4DNUNG.js → bootstrap-HNGKY6WW.js} +31 -19
- package/dist/bootstrap.sh.tmpl +51 -31
- package/dist/channels-KBUR42IU.js +53 -0
- package/dist/chunk-2I7KCMXZ.js +142 -0
- package/dist/{chunk-DQDAWNGB.js → chunk-2Z7J3ERQ.js} +1 -1
- package/dist/chunk-3OLUJPCB.js +36 -0
- package/dist/chunk-5XT5IAJK.js +69 -0
- package/dist/chunk-AEEMRFZV.js +24 -0
- package/dist/chunk-BSLIYB5Q.js +106 -0
- package/dist/{chunk-3XK66DKK.js → chunk-E4NJSJ54.js} +57 -93
- package/dist/chunk-FEK7VJAY.js +16 -0
- package/dist/{chunk-UKTP332Z.js → chunk-GAZPPSKU.js} +6 -7
- package/dist/chunk-GIEVAGEK.js +126 -0
- package/dist/chunk-HSPHXG6W.js +67 -0
- package/dist/chunk-IVWP4QUJ.js +901 -0
- package/dist/{chunk-PVUVI35Y.js → chunk-JW5A7E3X.js} +5 -1
- package/dist/{chunk-GZN7PKP4.js → chunk-KC3GC3PE.js} +328 -121
- package/dist/chunk-LBE6COC4.js +244 -0
- package/dist/chunk-LEVIW2PV.js +890 -0
- package/dist/chunk-LT4XMO5D.js +50 -0
- package/dist/chunk-MC5ATCXS.js +108 -0
- package/dist/chunk-N4WZLWJ4.js +16 -0
- package/dist/chunk-NUDIHZEH.js +55 -0
- package/dist/chunk-NUL5F6YV.js +56 -0
- package/dist/chunk-RHOWYIWT.js +52 -0
- package/dist/chunk-STLRHLLV.js +193 -0
- package/dist/chunk-V25QRZRB.js +45 -0
- package/dist/chunk-VOEJYRIK.js +33 -0
- package/dist/{providers-2OABPW2E.js → chunk-VUFMZEIM.js} +3 -2
- package/dist/chunk-W3OLNNJW.js +51 -0
- package/dist/cli-WLD3EBEM.js +11 -0
- package/dist/cli-auth-GD5SK7JU.js +13 -0
- package/dist/cli.js +1296 -695
- package/dist/config-validate-K2N6XLDX.js +76 -0
- package/dist/context-7EVC6W45.js +20 -0
- package/dist/docker-3PDO4TS7.js +12 -0
- package/dist/{firewall-XQGO7HWK.js → firewall-5VKZ4WBH.js} +32 -1
- package/dist/generate-I6YQ5V2T.js +29 -0
- package/dist/{harden-URPXLSVF.js → harden-72JMRT4E.js} +51 -11
- package/dist/health-5CCOXYGG.js +11 -0
- package/dist/index.d.ts +56 -0
- package/dist/known-hosts-file-I3Q3NBYQ.js +22 -0
- package/dist/logs-5A7YYA53.js +15 -0
- package/dist/{mcp-wire-7E5JQY47.js → mcp-wire-3SWBM7D6.js} +32 -16
- package/dist/models-AGTQWYDL.js +32 -0
- package/dist/network-args-PYJRJBYO.js +64 -0
- package/dist/{package-5AR4ZPK4.js → package-5BCMEBTE.js} +11 -4
- package/dist/passphrase-MJBE45N7.js +15 -0
- package/dist/plugins-DTST3OER.js +49 -0
- package/dist/{pool-QGFQRMGU.js → pool-PIIKJ4L2.js} +3 -2
- package/dist/preflight-EF52S3WW.js +162 -0
- package/dist/preflight-MWWM5JT3.js +181 -0
- package/dist/preflight-TFD4N5ZU.js +141 -0
- package/dist/privileged-JFA6LLTV.js +17 -0
- package/dist/providers-FW6H6OPF.js +11 -0
- package/dist/ready-KDNRBNSU.js +95 -0
- package/dist/{remote-config-QDLN4UMG.js → remote-config-3HZLUWEA.js} +4 -3
- package/dist/runtime-X3KW5TCP.js +44 -0
- package/dist/server-GBA7BJGK.js +46 -0
- package/dist/spec-path-KQOOIQCA.js +9 -0
- package/dist/ssh-VAI5DU5M.js +11 -0
- package/dist/ssh-key-DUO6OKOI.js +7 -0
- package/dist/stack-config-R4SPSUQZ.js +12 -0
- package/dist/upgrade-ULD4KFYC.js +88 -0
- package/dist/validate-YVI3BJ55.js +14 -0
- package/dist/{version-guard-VUCNZXQS.js → version-guard-XVYW5WFJ.js} +2 -2
- package/dist/{versions-2UVAT6PQ.js → versions-W5LRQCAJ.js} +2 -2
- package/dist/wait-WO72TRRQ.js +79 -0
- package/package.json +11 -4
- package/spec/deploy-plan.schema.json +114 -25
- package/spec/integrations.yaml +135 -28
- package/spec/mcp-tools.yaml +89 -37
- package/spec/models.yaml +23 -3
- package/spec/openclaw-2.0.config.schema.json +1 -0
- package/spec/openclaw-versions.yaml +116 -27
- package/spec/providers.schema.json +163 -28
- package/dist/apply-F3KY2HTB.js +0 -17
- package/dist/automation-RULUSJBW.js +0 -30
- package/dist/aws-L4RR2LSE.js +0 -254
- package/dist/azure-7OXQENQL.js +0 -262
- package/dist/chunk-35WVQY3X.js +0 -122
- package/dist/chunk-6X45DDGH.js +0 -42
- package/dist/chunk-A4QR4TXW.js +0 -120
- package/dist/chunk-C5X375RO.js +0 -85
- package/dist/chunk-FNS56UQY.js +0 -741
- package/dist/chunk-P5LKWRPL.js +0 -114
- package/dist/chunk-SEZMVVAS.js +0 -156
- package/dist/context-5LCUG2QR.js +0 -10
- package/dist/gcp-UG6AWPIZ.js +0 -193
- package/dist/generate-OAGYT3VH.js +0 -15
- package/dist/local-DXBEVZ5C.js +0 -47
- package/dist/server-FIZCOLHL.js +0 -32
- package/dist/ssh-CNJNLM3E.js +0 -8
- package/dist/validate-CFQ4FH2T.js +0 -10
package/dist/cli.js
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
monitor_default,
|
|
4
|
+
runDiagnostics
|
|
5
|
+
} from "./chunk-IVWP4QUJ.js";
|
|
6
|
+
import {
|
|
7
|
+
GATEWAY_LOGS_PROBE,
|
|
8
|
+
chooseLogSource,
|
|
9
|
+
containerLogsCommand,
|
|
10
|
+
gatewayLogsCommand
|
|
11
|
+
} from "./chunk-NUL5F6YV.js";
|
|
2
12
|
import {
|
|
3
13
|
MCP_APPS,
|
|
4
14
|
buildMcpEntry,
|
|
5
15
|
writeAppConfigs
|
|
6
16
|
} from "./chunk-OIGTOLB3.js";
|
|
17
|
+
import "./chunk-3OLUJPCB.js";
|
|
7
18
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
execPrivileged,
|
|
20
|
+
execPrivilegedWithInput,
|
|
21
|
+
streamPrivileged
|
|
22
|
+
} from "./chunk-NUDIHZEH.js";
|
|
12
23
|
import {
|
|
13
|
-
|
|
24
|
+
resolveSpecDir
|
|
25
|
+
} from "./chunk-JW5A7E3X.js";
|
|
26
|
+
import {
|
|
27
|
+
interpretProbe,
|
|
28
|
+
probeCommand
|
|
29
|
+
} from "./chunk-LT4XMO5D.js";
|
|
30
|
+
import "./chunk-LEVIW2PV.js";
|
|
31
|
+
import {
|
|
32
|
+
CONFIG_FILENAME,
|
|
33
|
+
CONTAINER_UID,
|
|
34
|
+
PUBLISH_INSPECT_CMD,
|
|
35
|
+
STATE_DIR_HOST_LINUX,
|
|
36
|
+
configPathForOS,
|
|
14
37
|
gatewayRunCommand,
|
|
38
|
+
publishForRestart
|
|
39
|
+
} from "./chunk-GIEVAGEK.js";
|
|
40
|
+
import {
|
|
41
|
+
GATEWAY_PORT,
|
|
42
|
+
IMAGE_INSPECT_CMD,
|
|
15
43
|
imageForRestart,
|
|
16
44
|
versionOf
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-P5LKWRPL.js";
|
|
45
|
+
} from "./chunk-VOEJYRIK.js";
|
|
19
46
|
import "./chunk-EI7CQQ7D.js";
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
} from "./chunk-PVUVI35Y.js";
|
|
23
|
-
import "./chunk-3XK66DKK.js";
|
|
47
|
+
import "./chunk-FEK7VJAY.js";
|
|
48
|
+
import "./chunk-RHOWYIWT.js";
|
|
24
49
|
import {
|
|
25
50
|
REPO_URL,
|
|
26
51
|
chalk,
|
|
@@ -31,7 +56,7 @@ import {
|
|
|
31
56
|
success,
|
|
32
57
|
warn
|
|
33
58
|
} from "./chunk-Q7NQY5HV.js";
|
|
34
|
-
import "./chunk-
|
|
59
|
+
import "./chunk-N4WZLWJ4.js";
|
|
35
60
|
import {
|
|
36
61
|
getConfig,
|
|
37
62
|
getConfigDir,
|
|
@@ -42,16 +67,95 @@ import {
|
|
|
42
67
|
ClawopsError,
|
|
43
68
|
UsageError
|
|
44
69
|
} from "./chunk-KGXPLI7W.js";
|
|
70
|
+
import "./chunk-VUFMZEIM.js";
|
|
45
71
|
|
|
46
72
|
// src/cli/index.ts
|
|
47
|
-
import { defineCommand as
|
|
73
|
+
import { defineCommand as defineCommand24, runMain } from "citty";
|
|
48
74
|
|
|
49
75
|
// src/cli/commands/init.ts
|
|
50
76
|
import { defineCommand } from "citty";
|
|
51
|
-
import {
|
|
77
|
+
import { spawnSync } from "child_process";
|
|
52
78
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
53
79
|
import path from "path";
|
|
54
80
|
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-EF52S3WW.js");
|
|
152
|
+
return resolveProjectId();
|
|
153
|
+
} catch {
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/cli/commands/init.ts
|
|
55
159
|
var SUPPORTED_PROVIDERS = ["gcp", "aws", "azure", "local"];
|
|
56
160
|
var PROVIDER_DEFAULTS = {
|
|
57
161
|
gcp: { region: "us-central1", credEnv: "GOOGLE_APPLICATION_CREDENTIALS", stateScheme: "gs://" },
|
|
@@ -93,9 +197,9 @@ var init_default = defineCommand({
|
|
|
93
197
|
);
|
|
94
198
|
}
|
|
95
199
|
const existing = getConfig();
|
|
96
|
-
if (existing && !forceOverwrite
|
|
200
|
+
if (existing?.stacks[stackName] && !forceOverwrite) {
|
|
97
201
|
failure(
|
|
98
|
-
`
|
|
202
|
+
`Stack "${stackName}" already exists in ${path.join(getConfigDir(), "config.json")} (${existing.stacks[stackName].stateUrl}). Use --force to overwrite its settings, or pass a different --stack name to add another one.`
|
|
99
203
|
);
|
|
100
204
|
process2.exit(1);
|
|
101
205
|
}
|
|
@@ -108,12 +212,21 @@ var init_default = defineCommand({
|
|
|
108
212
|
throw new UsageError(`SSH key not found at ${keyPath}`);
|
|
109
213
|
}
|
|
110
214
|
info("Generating SSH key pair...");
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
);
|
|
228
|
+
}
|
|
229
|
+
success(`SSH key pair written to ${keyPath} (and ${keyPath}.pub)`);
|
|
117
230
|
} else {
|
|
118
231
|
info(`Using existing SSH key at ${keyPath}`);
|
|
119
232
|
}
|
|
@@ -128,43 +241,41 @@ var init_default = defineCommand({
|
|
|
128
241
|
}
|
|
129
242
|
const sshUser = typeof args["ssh-user"] === "string" ? args["ssh-user"] : "root";
|
|
130
243
|
const sshPort = typeof args["ssh-port"] === "string" ? parseInt(args["ssh-port"], 10) : 22;
|
|
131
|
-
config = {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
stateUrl: "file://~/.clawops/state",
|
|
138
|
-
credentialsRef: { source: "file", envVars: [] },
|
|
139
|
-
localOpts: { host, sshUser, sshPort, sshKeyPath: keyPath }
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
ssh: { keyPath, knownHostsPath }
|
|
143
|
-
};
|
|
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
|
+
});
|
|
144
250
|
} else {
|
|
145
251
|
const defaults = PROVIDER_DEFAULTS[provider];
|
|
146
252
|
const region = typeof args.region === "string" ? args.region : defaults.region;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (stateUrl.includes("CHANGEME")) {
|
|
162
|
-
process2.stdout.write("\n");
|
|
163
|
-
info(
|
|
164
|
-
`Update stateUrl in the config to a real state backend before running \`clawops up\`.
|
|
165
|
-
Example: clawops init --provider ${provider} --state ${defaults.stateScheme}your-bucket/clawops`
|
|
166
|
-
);
|
|
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);
|
|
167
267
|
}
|
|
268
|
+
config = merge(existing, stackName, provider, keyPath, knownHostsPath, {
|
|
269
|
+
provider,
|
|
270
|
+
stateUrl,
|
|
271
|
+
region,
|
|
272
|
+
credentialsRef: { source: "env", envVars: [defaults.credEnv] }
|
|
273
|
+
});
|
|
274
|
+
process2.stdout.write("\n");
|
|
275
|
+
info(
|
|
276
|
+
`State backend: ${stateUrl}
|
|
277
|
+
clawops doctor --provider ${provider} checks it exists, and clawops setup offers to create it.`
|
|
278
|
+
);
|
|
168
279
|
process2.stdout.write("\n");
|
|
169
280
|
success(`Provider: ${provider} Region: ${region} Stack: ${stackName}`);
|
|
170
281
|
setConfig(config);
|
|
@@ -177,6 +288,15 @@ var init_default = defineCommand({
|
|
|
177
288
|
success(`Provider: ${provider} Stack: ${stackName}`);
|
|
178
289
|
}
|
|
179
290
|
});
|
|
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
|
+
}
|
|
180
300
|
|
|
181
301
|
// src/cli/commands/up.ts
|
|
182
302
|
import { defineCommand as defineCommand2 } from "citty";
|
|
@@ -199,7 +319,6 @@ function renderTable(headers, rows) {
|
|
|
199
319
|
}
|
|
200
320
|
|
|
201
321
|
// src/cli/commands/up.ts
|
|
202
|
-
var VALID_INSTANCE_TYPES = ["micro", "small", "medium", "large", "gpu"];
|
|
203
322
|
var up_default = defineCommand2({
|
|
204
323
|
meta: {
|
|
205
324
|
name: "up",
|
|
@@ -208,16 +327,20 @@ var up_default = defineCommand2({
|
|
|
208
327
|
args: {
|
|
209
328
|
provider: { type: "string", description: "Cloud provider (gcp|aws|azure|local)" },
|
|
210
329
|
region: { type: "string", description: "Cloud region" },
|
|
211
|
-
"instance-type": { type: "string", description: "Instance size alias (micro|small|medium|large|gpu)" },
|
|
330
|
+
"instance-type": { type: "string", description: "Instance size: a clawops alias (micro|small|medium|large|gpu) or a type your cloud names itself, e.g. t3.small" },
|
|
212
331
|
"dry-run": { type: "boolean", description: "Preview without applying" },
|
|
213
332
|
"no-wait": { type: "boolean", description: "Return immediately without waiting for healthy state" },
|
|
214
333
|
"openclaw-version": { type: "string", description: "OpenClaw release (e.g. 2026.7.1-2). Moving tags are resolved and range-checked" },
|
|
215
334
|
stack: { type: "string", description: "Target stack name" },
|
|
216
|
-
config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" }
|
|
335
|
+
config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" },
|
|
336
|
+
"gateway-port": { type: "string", description: `Host port to publish the gateway on (default ${GATEWAY_PORT})` },
|
|
337
|
+
"ssh-cidr": { type: "string", description: "CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine's IP. Omitted = none, and nothing will be able to connect" },
|
|
338
|
+
"gateway-cidr": { type: "string", description: "CIDR(s) allowed to reach the gateway port, or 'auto'. Requires --publish-gateway all" },
|
|
339
|
+
"publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' }
|
|
217
340
|
},
|
|
218
341
|
async run({ args }) {
|
|
219
|
-
const { buildContext } = await import("./context-
|
|
220
|
-
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-
|
|
342
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
343
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
221
344
|
const ctx = buildContext(args);
|
|
222
345
|
const requestedVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : await defaultOpenclawVersion();
|
|
223
346
|
const openclawVersion = await guardOpenclawVersion(requestedVersion);
|
|
@@ -229,10 +352,11 @@ var up_default = defineCommand2({
|
|
|
229
352
|
);
|
|
230
353
|
}
|
|
231
354
|
const { localOpts } = stackConfig;
|
|
232
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
process3.on("
|
|
355
|
+
const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
|
|
356
|
+
const gatewayPort = parseGatewayPort(args["gateway-port"]);
|
|
357
|
+
const abortController2 = new AbortController();
|
|
358
|
+
process3.on("SIGINT", () => abortController2.abort());
|
|
359
|
+
process3.on("SIGTERM", () => abortController2.abort());
|
|
236
360
|
const spin2 = spinner(`Bootstrapping local host "${localOpts.host}"...`);
|
|
237
361
|
try {
|
|
238
362
|
const state = await localBootstrap({
|
|
@@ -243,8 +367,9 @@ var up_default = defineCommand2({
|
|
|
243
367
|
knownHostsPath: ctx.config.ssh.knownHostsPath,
|
|
244
368
|
openclawVersion,
|
|
245
369
|
stackName: ctx.stackName,
|
|
370
|
+
gatewayPort,
|
|
246
371
|
noWait: Boolean(args["no-wait"]),
|
|
247
|
-
signal:
|
|
372
|
+
signal: abortController2.signal
|
|
248
373
|
});
|
|
249
374
|
spin2.succeed(`Host "${localOpts.host}" bootstrapped`);
|
|
250
375
|
info(`Gateway URL: ${state.gatewayUrl}`);
|
|
@@ -257,7 +382,7 @@ var up_default = defineCommand2({
|
|
|
257
382
|
user: localOpts.sshUser,
|
|
258
383
|
privateKeyPath: localOpts.sshKeyPath,
|
|
259
384
|
knownHostsPath: ctx.config.ssh.knownHostsPath,
|
|
260
|
-
signal:
|
|
385
|
+
signal: abortController2.signal
|
|
261
386
|
});
|
|
262
387
|
info("Config overlay applied and gateway restarted.");
|
|
263
388
|
}
|
|
@@ -267,64 +392,90 @@ var up_default = defineCommand2({
|
|
|
267
392
|
}
|
|
268
393
|
return;
|
|
269
394
|
}
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
);
|
|
275
|
-
}
|
|
395
|
+
const { generatePlan } = await import("./generate-I6YQ5V2T.js");
|
|
396
|
+
const { applyPlan } = await import("./apply-KYFPWI5U.js");
|
|
397
|
+
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
398
|
+
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
276
399
|
const isDryRun = Boolean(args["dry-run"]);
|
|
277
400
|
const validation = await ctx.adapter.validateConfig();
|
|
278
401
|
if (!validation.ok) {
|
|
279
402
|
for (const e of validation.errors) failure(e);
|
|
280
403
|
process3.exit(3);
|
|
281
404
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const instanceType = ctx.adapter.normalizeInstanceType(
|
|
285
|
-
instanceAlias
|
|
286
|
-
);
|
|
287
|
-
await stack.setConfig("region", { value: region });
|
|
288
|
-
await stack.setConfig("instanceType", { value: instanceType });
|
|
289
|
-
await stack.setConfig("openclawVersion", { value: openclawVersion });
|
|
290
|
-
if (isDryRun) {
|
|
291
|
-
info("Previewing changes (--dry-run)...");
|
|
292
|
-
const preview = await stack.preview({ onOutput: (out) => process3.stdout.write(out) });
|
|
293
|
-
process3.stdout.write("\n");
|
|
294
|
-
if (preview.changeSummary) {
|
|
295
|
-
const rows = Object.entries(preview.changeSummary).filter(([, count]) => count > 0).map(([op, count]) => [op, String(count)]);
|
|
296
|
-
if (rows.length > 0) {
|
|
297
|
-
process3.stdout.write(renderTable(["Operation", "Count"], rows) + "\n");
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
success("Preview complete (no resources changed)");
|
|
301
|
-
return;
|
|
405
|
+
if (typeof args.config === "string") {
|
|
406
|
+
info("--config applies to local stacks only; put the overlay in the plan for a cloud stack.");
|
|
302
407
|
}
|
|
408
|
+
const abortController = new AbortController();
|
|
409
|
+
process3.on("SIGINT", () => abortController.abort());
|
|
410
|
+
process3.on("SIGTERM", () => abortController.abort());
|
|
411
|
+
const network = await resolveNetworkFlags(
|
|
412
|
+
{
|
|
413
|
+
sshCidr: strArg(args["ssh-cidr"]),
|
|
414
|
+
gatewayCidr: strArg(args["gateway-cidr"]),
|
|
415
|
+
publishGateway: strArg(args["publish-gateway"])
|
|
416
|
+
},
|
|
417
|
+
{ detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip") }
|
|
418
|
+
);
|
|
419
|
+
const cloudGatewayPort = strArg(args["gateway-port"]);
|
|
420
|
+
if (cloudGatewayPort) network.gatewayPort = parseGatewayPort(cloudGatewayPort);
|
|
303
421
|
const spin = spinner(`Deploying stack "${ctx.stackName}"...`);
|
|
304
422
|
try {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
423
|
+
const plan = await generatePlan(
|
|
424
|
+
{
|
|
425
|
+
stackName: ctx.stackName,
|
|
426
|
+
provider: ctx.adapter.name,
|
|
427
|
+
...strArg(args.region) ? { region: strArg(args.region) } : {},
|
|
428
|
+
...strArg(args["instance-type"]) ? { instanceType: strArg(args["instance-type"]) } : {},
|
|
429
|
+
openclawVersion,
|
|
430
|
+
network
|
|
431
|
+
},
|
|
432
|
+
{ signal: abortController.signal }
|
|
433
|
+
);
|
|
434
|
+
if (isDryRun) {
|
|
435
|
+
spin.stop();
|
|
436
|
+
info("Previewing changes (--dry-run)...");
|
|
437
|
+
const diff = plan.diff;
|
|
438
|
+
if (diff && diff.totalChanges > 0) {
|
|
439
|
+
const rows = [
|
|
440
|
+
["create", String(diff.create.length)],
|
|
441
|
+
["update", String(diff.update.length)],
|
|
442
|
+
["delete", String(diff.delete.length)]
|
|
443
|
+
].filter(([, count]) => count !== "0");
|
|
444
|
+
process3.stdout.write(renderTable(["Operation", "Count"], rows) + "\n");
|
|
308
445
|
}
|
|
446
|
+
success("Preview complete (no resources changed)");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const result = await applyPlan(plan, {
|
|
450
|
+
onOutput: (line2) => {
|
|
451
|
+
spin.text = line2.trim() || spin.text;
|
|
452
|
+
},
|
|
453
|
+
onProgress: (line2) => {
|
|
454
|
+
const text = line2.trim();
|
|
455
|
+
if (!text) return;
|
|
456
|
+
spin.text = text;
|
|
457
|
+
if (!process3.stderr.isTTY) process3.stderr.write(`${text}
|
|
458
|
+
`);
|
|
459
|
+
},
|
|
460
|
+
signal: abortController.signal,
|
|
461
|
+
skipReadiness: Boolean(args["no-wait"])
|
|
309
462
|
});
|
|
310
463
|
spin.succeed(`Stack "${ctx.stackName}" deployed`);
|
|
311
|
-
|
|
312
|
-
if (outputs["
|
|
313
|
-
info(`Public IP: ${outputs["publicIp"].value}`);
|
|
314
|
-
}
|
|
315
|
-
if (outputs["gatewayUrl"]) {
|
|
316
|
-
info(`Gateway URL: ${outputs["gatewayUrl"].value}`);
|
|
317
|
-
}
|
|
464
|
+
if (result.outputs["publicIp"]) info(`Public IP: ${String(result.outputs["publicIp"])}`);
|
|
465
|
+
if (result.outputs["gatewayUrl"]) info(`Gateway URL: ${String(result.outputs["gatewayUrl"])}`);
|
|
318
466
|
} catch (err) {
|
|
319
467
|
spin.fail("Deployment failed");
|
|
320
468
|
throw err;
|
|
321
469
|
}
|
|
322
470
|
}
|
|
323
471
|
});
|
|
472
|
+
function strArg(value) {
|
|
473
|
+
return typeof value === "string" ? value : void 0;
|
|
474
|
+
}
|
|
324
475
|
async function applyLocalConfigOverlay(opts) {
|
|
325
|
-
const { connect } = await import("./ssh-
|
|
476
|
+
const { connect } = await import("./ssh-VAI5DU5M.js");
|
|
326
477
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
327
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
478
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
|
|
328
479
|
let overlay;
|
|
329
480
|
try {
|
|
330
481
|
overlay = JSON.parse(readFileSync(opts.configPath, "utf-8"));
|
|
@@ -349,6 +500,14 @@ async function applyLocalConfigOverlay(opts) {
|
|
|
349
500
|
session.close();
|
|
350
501
|
}
|
|
351
502
|
}
|
|
503
|
+
function parseGatewayPort(raw) {
|
|
504
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
505
|
+
const port = Number(raw);
|
|
506
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
507
|
+
throw new UsageError(`--gateway-port must be a port number between 1 and 65535, got "${raw}"`);
|
|
508
|
+
}
|
|
509
|
+
return port;
|
|
510
|
+
}
|
|
352
511
|
|
|
353
512
|
// src/cli/commands/down.ts
|
|
354
513
|
import { defineCommand as defineCommand3 } from "citty";
|
|
@@ -364,7 +523,7 @@ var down_default = defineCommand3({
|
|
|
364
523
|
"dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
|
|
365
524
|
},
|
|
366
525
|
async run({ args }) {
|
|
367
|
-
const { buildContext } = await import("./context-
|
|
526
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
368
527
|
const ctx = buildContext(args);
|
|
369
528
|
if (args["dry-run"]) {
|
|
370
529
|
info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
|
|
@@ -428,7 +587,7 @@ var status_default = defineCommand4({
|
|
|
428
587
|
json: { type: "boolean", description: "Emit JSON" }
|
|
429
588
|
},
|
|
430
589
|
async run({ args }) {
|
|
431
|
-
const { buildContext } = await import("./context-
|
|
590
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
432
591
|
const ctx = buildContext(args);
|
|
433
592
|
if (ctx.adapter.name === "local") {
|
|
434
593
|
const state = ctx.localState;
|
|
@@ -495,13 +654,18 @@ var plan_default = defineCommand5({
|
|
|
495
654
|
provider: { type: "string", description: "Cloud provider (aws|gcp|azure)" },
|
|
496
655
|
stack: { type: "string", description: "Target stack name" },
|
|
497
656
|
region: { type: "string", description: "Cloud region" },
|
|
498
|
-
"instance-type": { type: "string", description: "Instance size alias (micro|small|medium|large|gpu)" },
|
|
657
|
+
"instance-type": { type: "string", description: "Instance size: a clawops alias (micro|small|medium|large|gpu) or a type your cloud names itself, e.g. t3.small" },
|
|
499
658
|
"openclaw-version": { type: "string", description: "semver or 'stable'/'dev'" },
|
|
659
|
+
"ssh-cidr": { type: "string", description: "CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine's IP. Omitted = none, and nothing will be able to connect" },
|
|
660
|
+
"gateway-cidr": { type: "string", description: "CIDR(s) allowed to reach the gateway port, comma-separated, or 'auto'. Requires --publish-gateway all" },
|
|
661
|
+
"publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' },
|
|
500
662
|
out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
|
|
501
663
|
},
|
|
502
664
|
async run({ args }) {
|
|
503
|
-
const { buildContext } = await import("./context-
|
|
504
|
-
const { generatePlan } = await import("./generate-
|
|
665
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
666
|
+
const { generatePlan } = await import("./generate-I6YQ5V2T.js");
|
|
667
|
+
const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
|
|
668
|
+
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
505
669
|
const ctx = buildContext(args);
|
|
506
670
|
if (ctx.adapter.name === "local") {
|
|
507
671
|
throw new UsageError(
|
|
@@ -513,6 +677,14 @@ var plan_default = defineCommand5({
|
|
|
513
677
|
if (outPath && !isAbsolute(outPath)) {
|
|
514
678
|
throw new UsageError("--out path must be absolute (R7). Use an absolute path like /tmp/plan.json.");
|
|
515
679
|
}
|
|
680
|
+
const network = await resolveNetworkFlags(
|
|
681
|
+
{
|
|
682
|
+
sshCidr: strArg2(args["ssh-cidr"]),
|
|
683
|
+
gatewayCidr: strArg2(args["gateway-cidr"]),
|
|
684
|
+
publishGateway: strArg2(args["publish-gateway"])
|
|
685
|
+
},
|
|
686
|
+
{ detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip") }
|
|
687
|
+
);
|
|
516
688
|
const abortController = new AbortController();
|
|
517
689
|
process7.on("SIGINT", () => abortController.abort());
|
|
518
690
|
process7.on("SIGTERM", () => abortController.abort());
|
|
@@ -525,7 +697,8 @@ var plan_default = defineCommand5({
|
|
|
525
697
|
provider,
|
|
526
698
|
region: typeof args.region === "string" ? args.region : void 0,
|
|
527
699
|
instanceType: typeof args["instance-type"] === "string" ? args["instance-type"] : void 0,
|
|
528
|
-
openclawVersion: typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0
|
|
700
|
+
openclawVersion: typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0,
|
|
701
|
+
network
|
|
529
702
|
},
|
|
530
703
|
{ signal: abortController.signal }
|
|
531
704
|
);
|
|
@@ -544,6 +717,7 @@ var plan_default = defineCommand5({
|
|
|
544
717
|
const { spec, metadata } = plan;
|
|
545
718
|
const sshCidrs = spec.network.allowedSshCidrs.join(", ") || "(none)";
|
|
546
719
|
const gatewayCidrs = spec.network.allowedGatewayCidrs.join(", ") || "(none)";
|
|
720
|
+
const publish = spec.network.publishGateway ?? "loopback";
|
|
547
721
|
process7.stderr.write(
|
|
548
722
|
`
|
|
549
723
|
Plan: ${metadata.name} (${spec.provider}${spec.region ? ` / ${spec.region}` : ""})
|
|
@@ -551,6 +725,7 @@ Plan: ${metadata.name} (${spec.provider}${spec.region ? ` / ${spec.region}` : "
|
|
|
551
725
|
OpenClaw: ${spec.openclaw.version}
|
|
552
726
|
SSH CIDRs: ${sshCidrs}
|
|
553
727
|
Gateway: ${gatewayCidrs}
|
|
728
|
+
Published: ${publish === "all" ? "0.0.0.0 \u2014 reachable from the network; plaintext HTTP unless you add TLS" : "127.0.0.1 only \u2014 use `clawops tunnel` or a proxy on the host"}
|
|
554
729
|
`
|
|
555
730
|
);
|
|
556
731
|
if (plan.diff) {
|
|
@@ -573,6 +748,9 @@ Changes: ${create.length} to create, ${update.length} to update, ${del.length} t
|
|
|
573
748
|
}
|
|
574
749
|
}
|
|
575
750
|
});
|
|
751
|
+
function strArg2(value) {
|
|
752
|
+
return typeof value === "string" ? value : void 0;
|
|
753
|
+
}
|
|
576
754
|
|
|
577
755
|
// src/cli/commands/apply.ts
|
|
578
756
|
import { defineCommand as defineCommand6 } from "citty";
|
|
@@ -590,8 +768,8 @@ var apply_default = defineCommand6({
|
|
|
590
768
|
"dry-run": { type: "boolean", description: "Validate plan and show diff without applying" }
|
|
591
769
|
},
|
|
592
770
|
async run({ args }) {
|
|
593
|
-
const { validatePlan } = await import("./validate-
|
|
594
|
-
const { applyPlan } = await import("./apply-
|
|
771
|
+
const { validatePlan } = await import("./validate-YVI3BJ55.js");
|
|
772
|
+
const { applyPlan } = await import("./apply-KYFPWI5U.js");
|
|
595
773
|
const planPath = args._?.[0];
|
|
596
774
|
if (!planPath) {
|
|
597
775
|
throw new UsageError("Usage: clawops apply <plan.json>");
|
|
@@ -657,8 +835,18 @@ ${validation.errors.join("\n")}`);
|
|
|
657
835
|
const spin = spinner(`Applying plan for stack "${typedPlan.spec.stackName}"\u2026`);
|
|
658
836
|
try {
|
|
659
837
|
const result = await applyPlan(typedPlan, {
|
|
660
|
-
onOutput: (
|
|
661
|
-
spin.text =
|
|
838
|
+
onOutput: (line2) => {
|
|
839
|
+
spin.text = line2.trim() || spin.text;
|
|
840
|
+
},
|
|
841
|
+
// A spinner renders nothing without a TTY, so a scripted or CI deploy sat silent
|
|
842
|
+
// through the minutes an image pull takes. These lines are rare — one per half
|
|
843
|
+
// minute at most — so they are cheap to print outright where nothing else will.
|
|
844
|
+
onProgress: (line2) => {
|
|
845
|
+
const text = line2.trim();
|
|
846
|
+
if (!text) return;
|
|
847
|
+
spin.text = text;
|
|
848
|
+
if (!process8.stderr.isTTY) process8.stderr.write(`${text}
|
|
849
|
+
`);
|
|
662
850
|
},
|
|
663
851
|
signal: abortController.signal,
|
|
664
852
|
confirmDrift: args.yes ? void 0 : async () => {
|
|
@@ -707,7 +895,7 @@ var destroy_default = defineCommand7({
|
|
|
707
895
|
"dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
|
|
708
896
|
},
|
|
709
897
|
async run({ args }) {
|
|
710
|
-
const { buildContext } = await import("./context-
|
|
898
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
711
899
|
const ctx = buildContext(args);
|
|
712
900
|
if (ctx.adapter.name === "local") {
|
|
713
901
|
throw new UsageError(
|
|
@@ -745,6 +933,7 @@ Pass --yes to proceed with destruction.
|
|
|
745
933
|
const abortController = new AbortController();
|
|
746
934
|
process9.on("SIGINT", () => abortController.abort());
|
|
747
935
|
process9.on("SIGTERM", () => abortController.abort());
|
|
936
|
+
const doomedHost = await hostOf(stack, ctx);
|
|
748
937
|
const spin = spinner(`Destroying stack "${ctx.stackName}"\u2026`);
|
|
749
938
|
try {
|
|
750
939
|
await stack.destroy({
|
|
@@ -758,8 +947,35 @@ Pass --yes to proceed with destruction.
|
|
|
758
947
|
spin.fail("Destroy failed");
|
|
759
948
|
throw err;
|
|
760
949
|
}
|
|
950
|
+
if (doomedHost) {
|
|
951
|
+
const { forgetHost } = await import("./known-hosts-file-I3Q3NBYQ.js");
|
|
952
|
+
const removed = forgetHost(
|
|
953
|
+
expandHome(ctx.config.ssh.knownHostsPath),
|
|
954
|
+
doomedHost.host,
|
|
955
|
+
doomedHost.port
|
|
956
|
+
);
|
|
957
|
+
if (removed) info(`Forgot the host key for ${doomedHost.host} \u2014 its instance is gone.`);
|
|
958
|
+
}
|
|
761
959
|
}
|
|
762
960
|
});
|
|
961
|
+
async function hostOf(stack, ctx) {
|
|
962
|
+
try {
|
|
963
|
+
const outputMap = await stack.outputs();
|
|
964
|
+
const raw = Object.fromEntries(Object.entries(outputMap).map(([k, v]) => [k, v.value]));
|
|
965
|
+
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
966
|
+
const conn = ctx.adapter.getConnectionInfo({
|
|
967
|
+
...extractBaseOutputs(raw),
|
|
968
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
969
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
970
|
+
});
|
|
971
|
+
return conn.host ? { host: conn.host, port: conn.port } : void 0;
|
|
972
|
+
} catch {
|
|
973
|
+
return void 0;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function expandHome(p) {
|
|
977
|
+
return p.replace(/^~/, process9.env["HOME"] ?? "~");
|
|
978
|
+
}
|
|
763
979
|
|
|
764
980
|
// src/cli/commands/ssh.ts
|
|
765
981
|
import { defineCommand as defineCommand8 } from "citty";
|
|
@@ -774,8 +990,8 @@ var ssh_default = defineCommand8({
|
|
|
774
990
|
command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
|
|
775
991
|
},
|
|
776
992
|
async run({ args }) {
|
|
777
|
-
const { buildContext } = await import("./context-
|
|
778
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
993
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
994
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
779
995
|
const ctx = buildContext(args);
|
|
780
996
|
let conn;
|
|
781
997
|
if (ctx.adapter.name === "local") {
|
|
@@ -861,9 +1077,9 @@ var tunnel_default = defineCommand9({
|
|
|
861
1077
|
"no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
|
|
862
1078
|
},
|
|
863
1079
|
async run({ args }) {
|
|
864
|
-
const { buildContext } = await import("./context-
|
|
1080
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
865
1081
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
866
|
-
const { acquireSession } = await import("./pool-
|
|
1082
|
+
const { acquireSession } = await import("./pool-PIIKJ4L2.js");
|
|
867
1083
|
const ctx = buildContext(args);
|
|
868
1084
|
const stack = await ctx.getStack();
|
|
869
1085
|
const outputMap = await stack.outputs();
|
|
@@ -926,12 +1142,13 @@ var logs_default = defineCommand10({
|
|
|
926
1142
|
stack: { type: "string", description: "Target stack name" },
|
|
927
1143
|
follow: { type: "boolean", alias: "f", description: "Follow log output" },
|
|
928
1144
|
tail: { type: "string", description: "Number of lines to show from end (default: 100)" },
|
|
929
|
-
since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h)" }
|
|
1145
|
+
since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h). Reads container output \u2014 the gateway log command has no time filter" },
|
|
1146
|
+
json: { type: "boolean", description: "Emit the gateway's structured JSON log lines" }
|
|
930
1147
|
},
|
|
931
1148
|
async run({ args }) {
|
|
932
|
-
const { buildContext } = await import("./context-
|
|
1149
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
933
1150
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
934
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1151
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
935
1152
|
const ctx = buildContext(args);
|
|
936
1153
|
const stack = await ctx.getStack();
|
|
937
1154
|
const outputMap = await stack.outputs();
|
|
@@ -950,18 +1167,7 @@ var logs_default = defineCommand10({
|
|
|
950
1167
|
});
|
|
951
1168
|
const tailLines = typeof args.tail === "string" ? parseInt(args.tail, 10) : 100;
|
|
952
1169
|
const follow = Boolean(args.follow);
|
|
953
|
-
const
|
|
954
|
-
const followFlag = follow ? "-f" : "";
|
|
955
|
-
const command = [
|
|
956
|
-
"journalctl -u openclaw",
|
|
957
|
-
`-n ${tailLines}`,
|
|
958
|
-
followFlag,
|
|
959
|
-
sinceFlag,
|
|
960
|
-
"2>/dev/null",
|
|
961
|
-
"|| docker logs openclaw",
|
|
962
|
-
`-n ${tailLines}`,
|
|
963
|
-
follow ? "-f" : ""
|
|
964
|
-
].filter(Boolean).join(" ");
|
|
1170
|
+
const since = typeof args.since === "string" ? args.since : void 0;
|
|
965
1171
|
const abortController = new AbortController();
|
|
966
1172
|
process12.on("SIGINT", () => abortController.abort());
|
|
967
1173
|
process12.on("SIGTERM", () => abortController.abort());
|
|
@@ -976,8 +1182,13 @@ var logs_default = defineCommand10({
|
|
|
976
1182
|
});
|
|
977
1183
|
spin.stop();
|
|
978
1184
|
try {
|
|
1185
|
+
const probe = since ? { code: 1, stderr: "" } : await execPrivileged(session, GATEWAY_LOGS_PROBE, abortController.signal);
|
|
1186
|
+
const choice = chooseLogSource({ since, gatewayReachable: probe.code === 0 });
|
|
1187
|
+
const opts = { tail: tailLines, follow, since, json: Boolean(args.json) };
|
|
1188
|
+
const command = choice.source === "gateway" ? gatewayLogsCommand(opts) : containerLogsCommand(opts);
|
|
1189
|
+
info(`Logs: ${choice.source} \u2014 ${choice.reason}`);
|
|
979
1190
|
if (follow) {
|
|
980
|
-
const logStream = await session
|
|
1191
|
+
const logStream = await streamPrivileged(session, command, abortController.signal);
|
|
981
1192
|
logStream.pipe(process12.stdout);
|
|
982
1193
|
await new Promise((resolve) => {
|
|
983
1194
|
logStream.on("end", resolve);
|
|
@@ -985,7 +1196,7 @@ var logs_default = defineCommand10({
|
|
|
985
1196
|
abortController.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
986
1197
|
});
|
|
987
1198
|
} else {
|
|
988
|
-
const result = await session
|
|
1199
|
+
const result = await execPrivileged(session, command, abortController.signal);
|
|
989
1200
|
process12.stdout.write(result.stdout);
|
|
990
1201
|
if (result.stderr) process12.stderr.write(result.stderr);
|
|
991
1202
|
}
|
|
@@ -999,10 +1210,9 @@ var logs_default = defineCommand10({
|
|
|
999
1210
|
// src/cli/commands/config.ts
|
|
1000
1211
|
import { defineCommand as defineCommand11 } from "citty";
|
|
1001
1212
|
import process13 from "process";
|
|
1002
|
-
var OPENCLAW_CONFIG = "
|
|
1003
|
-
var OPENCLAW_TMP = "/tmp/clawops-config.json.tmp";
|
|
1213
|
+
var OPENCLAW_CONFIG = configPathForOS("Linux");
|
|
1004
1214
|
function dockerRunCmd(image) {
|
|
1005
|
-
return gatewayRunCommand({ image,
|
|
1215
|
+
return gatewayRunCommand({ image, stateDir: STATE_DIR_HOST_LINUX });
|
|
1006
1216
|
}
|
|
1007
1217
|
function getPath(obj, dotKey) {
|
|
1008
1218
|
return dotKey.split(".").reduce((cur, k) => {
|
|
@@ -1042,9 +1252,9 @@ var config_default = defineCommand11({
|
|
|
1042
1252
|
"dry-run": { type: "boolean", description: "Show what would change without writing" }
|
|
1043
1253
|
},
|
|
1044
1254
|
async run({ args }) {
|
|
1045
|
-
const { buildContext } = await import("./context-
|
|
1255
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
1046
1256
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1047
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1257
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1048
1258
|
const [action, key, value] = args._ ?? [];
|
|
1049
1259
|
if (!action || !["get", "set", "unset", "validate"].includes(action)) {
|
|
1050
1260
|
failure("Usage: clawops config <get [key] | set key value | unset key | validate>");
|
|
@@ -1100,12 +1310,19 @@ var config_default = defineCommand11({
|
|
|
1100
1310
|
return;
|
|
1101
1311
|
}
|
|
1102
1312
|
if (action === "validate") {
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1313
|
+
const { validateConfig } = await import("./config-validate-K2N6XLDX.js");
|
|
1314
|
+
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
1315
|
+
const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
|
|
1316
|
+
const spec = loadVersionSpec(yaml);
|
|
1317
|
+
const { errors, warnings } = await validateConfig(cfg, {
|
|
1318
|
+
schemaCapturedFrom: spec.runtime?.configSchemaCapturedFrom
|
|
1319
|
+
});
|
|
1320
|
+
for (const w of warnings) warn(w);
|
|
1321
|
+
if (errors.length === 0) {
|
|
1322
|
+
success(warnings.length === 0 ? "Config is valid." : "Config is valid, with notes.");
|
|
1106
1323
|
} else {
|
|
1107
|
-
for (const
|
|
1108
|
-
failure(`Config has ${
|
|
1324
|
+
for (const e of errors) failure(e);
|
|
1325
|
+
failure(`Config has ${errors.length} error(s).`);
|
|
1109
1326
|
process13.exit(1);
|
|
1110
1327
|
}
|
|
1111
1328
|
return;
|
|
@@ -1125,23 +1342,23 @@ var config_default = defineCommand11({
|
|
|
1125
1342
|
process13.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
|
|
1126
1343
|
return;
|
|
1127
1344
|
}
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
failure(`Failed to write config: ${writeResult.stderr}`);
|
|
1345
|
+
const { atomicWriteConfig } = await import("./remote-config-3HZLUWEA.js");
|
|
1346
|
+
try {
|
|
1347
|
+
await atomicWriteConfig(session, cfg, abortController.signal);
|
|
1348
|
+
} catch (err) {
|
|
1349
|
+
failure(err.message);
|
|
1134
1350
|
process13.exit(1);
|
|
1135
1351
|
}
|
|
1136
1352
|
success(`config ${action}: ${key ?? "(all)"}`);
|
|
1137
1353
|
if (args.restart) {
|
|
1138
1354
|
info("Restarting gateway...");
|
|
1139
|
-
const imgResult = await
|
|
1355
|
+
const imgResult = await execPrivileged(
|
|
1356
|
+
session,
|
|
1140
1357
|
`docker inspect openclaw --format '{{.Config.Image}}' 2>/dev/null || echo 'ghcr.io/openclaw/openclaw:stable'`,
|
|
1141
1358
|
abortController.signal
|
|
1142
1359
|
);
|
|
1143
1360
|
const image = imgResult.stdout.trim();
|
|
1144
|
-
const restartResult = await session
|
|
1361
|
+
const restartResult = await execPrivileged(session, dockerRunCmd(image), abortController.signal);
|
|
1145
1362
|
if (restartResult.code !== 0) {
|
|
1146
1363
|
failure(`Restart failed: ${restartResult.stderr}`);
|
|
1147
1364
|
process13.exit(1);
|
|
@@ -1161,19 +1378,27 @@ import process14 from "process";
|
|
|
1161
1378
|
var agents_default = defineCommand12({
|
|
1162
1379
|
meta: {
|
|
1163
1380
|
name: "agents",
|
|
1164
|
-
description: "Manage OpenClaw agents (list |
|
|
1381
|
+
description: "Manage OpenClaw agents (list | logs <name>)"
|
|
1165
1382
|
},
|
|
1166
1383
|
args: {
|
|
1167
1384
|
stack: { type: "string", description: "Target stack name" },
|
|
1168
|
-
json: { type: "boolean", description: "Emit JSON (for list)" }
|
|
1385
|
+
json: { type: "boolean", description: "Emit JSON (for list and logs)" },
|
|
1386
|
+
limit: { type: "string", description: "Max activity records for `logs` (default 50)" },
|
|
1387
|
+
cursor: { type: "string", description: "Continue from a previous `logs` result cursor" }
|
|
1169
1388
|
},
|
|
1170
1389
|
async run({ args }) {
|
|
1171
|
-
const { buildContext } = await import("./context-
|
|
1390
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
1172
1391
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1173
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1392
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1174
1393
|
const [action, name] = args._ ?? [];
|
|
1175
|
-
if (
|
|
1176
|
-
failure(
|
|
1394
|
+
if (action === "restart") {
|
|
1395
|
+
failure(
|
|
1396
|
+
"clawops agents restart was removed in clawops 2.0.\n OpenClaw 2.0 has no per-agent restart; the only restart it offers is\n gateway-wide and interrupts every agent on the host.\n Run `clawops gateway restart` if that is what you want."
|
|
1397
|
+
);
|
|
1398
|
+
process14.exit(2);
|
|
1399
|
+
}
|
|
1400
|
+
if (!action || !["list", "logs"].includes(action)) {
|
|
1401
|
+
failure("Usage: clawops agents <list | logs <name>>");
|
|
1177
1402
|
process14.exit(2);
|
|
1178
1403
|
}
|
|
1179
1404
|
if (action === "logs" && !name) {
|
|
@@ -1205,15 +1430,24 @@ var agents_default = defineCommand12({
|
|
|
1205
1430
|
});
|
|
1206
1431
|
try {
|
|
1207
1432
|
if (action === "list") {
|
|
1208
|
-
const result = await
|
|
1209
|
-
|
|
1433
|
+
const result = await execPrivileged(
|
|
1434
|
+
session,
|
|
1435
|
+
"docker exec openclaw openclaw agents list --json",
|
|
1210
1436
|
abortController.signal
|
|
1211
1437
|
);
|
|
1212
|
-
|
|
1438
|
+
if (result.code !== 0) {
|
|
1439
|
+
const why = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
|
|
1440
|
+
failure(`Cannot list agents: ${why}`);
|
|
1441
|
+
process14.exitCode = 1;
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
let agents;
|
|
1213
1445
|
try {
|
|
1214
|
-
agents = JSON.parse(result.stdout.trim());
|
|
1446
|
+
agents = JSON.parse(result.stdout.trim() || "[]");
|
|
1215
1447
|
} catch {
|
|
1216
|
-
agents
|
|
1448
|
+
failure(`Cannot list agents: unexpected output from OpenClaw: ${result.stdout.trim().slice(0, 200)}`);
|
|
1449
|
+
process14.exitCode = 1;
|
|
1450
|
+
return;
|
|
1217
1451
|
}
|
|
1218
1452
|
if (args.json) {
|
|
1219
1453
|
printJson(jsonOk(agents));
|
|
@@ -1227,25 +1461,24 @@ var agents_default = defineCommand12({
|
|
|
1227
1461
|
) + "\n\n"
|
|
1228
1462
|
);
|
|
1229
1463
|
}
|
|
1230
|
-
} else if (action === "restart") {
|
|
1231
|
-
const cmd = name ? `docker exec openclaw openclaw agents restart ${name}` : "docker exec openclaw openclaw agents restart";
|
|
1232
|
-
const result = await session.exec(cmd, abortController.signal);
|
|
1233
|
-
if (result.code !== 0) {
|
|
1234
|
-
failure(`Restart failed: ${result.stderr}`);
|
|
1235
|
-
process14.exit(1);
|
|
1236
|
-
}
|
|
1237
|
-
success(name ? `Agent '${name}' restarted.` : "All agents restarted.");
|
|
1238
1464
|
} else {
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1465
|
+
const { agentAuditCommand } = await import("./logs-5A7YYA53.js");
|
|
1466
|
+
const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) : 50;
|
|
1467
|
+
const result = await execPrivileged(
|
|
1468
|
+
session,
|
|
1469
|
+
agentAuditCommand({ agentId: name, limit, cursor: typeof args.cursor === "string" ? args.cursor : void 0 }),
|
|
1241
1470
|
abortController.signal
|
|
1242
1471
|
);
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1472
|
+
if (result.code !== 0) {
|
|
1473
|
+
failure(`Cannot read activity for "${name}": ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`);
|
|
1474
|
+
process14.exitCode = 1;
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (args.json) {
|
|
1478
|
+
process14.stdout.write(result.stdout);
|
|
1479
|
+
} else {
|
|
1480
|
+
renderAgentRuns(name, result.stdout);
|
|
1481
|
+
}
|
|
1249
1482
|
}
|
|
1250
1483
|
} finally {
|
|
1251
1484
|
release();
|
|
@@ -1253,35 +1486,285 @@ var agents_default = defineCommand12({
|
|
|
1253
1486
|
}
|
|
1254
1487
|
}
|
|
1255
1488
|
});
|
|
1489
|
+
function renderAgentRuns(agentId, stdout) {
|
|
1490
|
+
let page;
|
|
1491
|
+
try {
|
|
1492
|
+
page = JSON.parse(stdout.trim() || "{}");
|
|
1493
|
+
} catch {
|
|
1494
|
+
failure(`Cannot read activity for "${agentId}": unexpected output: ${stdout.trim().slice(0, 200)}`);
|
|
1495
|
+
process14.exitCode = 1;
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
const records = page.records ?? [];
|
|
1499
|
+
if (records.length === 0) {
|
|
1500
|
+
info(`No recorded activity for agent "${agentId}".`);
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
process14.stdout.write(
|
|
1504
|
+
"\n" + renderTable(
|
|
1505
|
+
["When", "Status", "Detail"],
|
|
1506
|
+
records.map((r) => [r.at ?? "\u2014", r.status ?? "\u2014", r.summary ?? r.message ?? "\u2014"])
|
|
1507
|
+
) + "\n"
|
|
1508
|
+
);
|
|
1509
|
+
if (page.cursor) {
|
|
1510
|
+
info(`More records: clawops agents logs ${agentId} --cursor ${page.cursor}`);
|
|
1511
|
+
}
|
|
1512
|
+
process14.stdout.write("\n");
|
|
1513
|
+
}
|
|
1256
1514
|
|
|
1257
|
-
// src/cli/commands/
|
|
1515
|
+
// src/cli/commands/migrate.ts
|
|
1258
1516
|
import { defineCommand as defineCommand13 } from "citty";
|
|
1259
1517
|
import process15 from "process";
|
|
1260
|
-
|
|
1261
|
-
|
|
1518
|
+
|
|
1519
|
+
// src/openclaw/migrate.ts
|
|
1520
|
+
async function migrate(steps) {
|
|
1521
|
+
const sourceImage = await steps.inspectSource();
|
|
1522
|
+
if (!sourceImage) {
|
|
1523
|
+
return {
|
|
1524
|
+
kind: "nothing-to-migrate",
|
|
1525
|
+
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`."
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
const identityBefore = await steps.deviceId();
|
|
1529
|
+
const backup = await steps.backup();
|
|
1530
|
+
if (!backup.ok) {
|
|
1531
|
+
return {
|
|
1532
|
+
kind: "refused",
|
|
1533
|
+
reason: `Refusing to migrate without a verified backup: ${backup.detail}
|
|
1534
|
+
This replaces the running container, and the state it extracts is the only copy.`
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const extracted = await steps.extract();
|
|
1538
|
+
if (!extracted.ok) {
|
|
1539
|
+
return {
|
|
1540
|
+
kind: "failed",
|
|
1541
|
+
reason: "Could not extract state from the running container; it has not been touched.",
|
|
1542
|
+
backupDetail: backup.detail
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
await steps.chown();
|
|
1546
|
+
await steps.removeSource();
|
|
1547
|
+
await steps.writeConfig();
|
|
1548
|
+
await steps.start();
|
|
1549
|
+
let restarts = 0;
|
|
1550
|
+
let gate = await steps.gate();
|
|
1551
|
+
if (!gate.ok) {
|
|
1552
|
+
await steps.start();
|
|
1553
|
+
restarts = 1;
|
|
1554
|
+
gate = await steps.gate();
|
|
1555
|
+
}
|
|
1556
|
+
if (!gate.ok) {
|
|
1557
|
+
return {
|
|
1558
|
+
kind: "failed",
|
|
1559
|
+
reason: gate.reason ?? "the gateway did not start after migration",
|
|
1560
|
+
backupDetail: backup.detail
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
const identityAfter = await steps.deviceId();
|
|
1564
|
+
const identity = identityBefore === void 0 || identityAfter === void 0 ? "unknown" : identityBefore === identityAfter ? "preserved" : "changed";
|
|
1565
|
+
return { kind: "migrated", entries: extracted.entries, identity, restarts };
|
|
1566
|
+
}
|
|
1567
|
+
function describeMigration(o) {
|
|
1568
|
+
switch (o.kind) {
|
|
1569
|
+
case "migrated": {
|
|
1570
|
+
const lines = [
|
|
1571
|
+
`Migrated to the 2.0 runtime contract. Carried over: ${o.entries.join(", ") || "(nothing)"}.`
|
|
1572
|
+
];
|
|
1573
|
+
if (o.restarts > 0) {
|
|
1574
|
+
lines.push(
|
|
1575
|
+
"The gateway needed a second start to finish its state-schema migration, which is expected for a 1.x database."
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
if (o.identity === "preserved") {
|
|
1579
|
+
lines.push("Device identity preserved \u2014 paired devices do not need re-pairing.");
|
|
1580
|
+
} else if (o.identity === "changed") {
|
|
1581
|
+
lines.push(
|
|
1582
|
+
"Device identity CHANGED. Paired devices and nodes must be re-paired \u2014 check `openclaw devices list` on the host for what was paired."
|
|
1583
|
+
);
|
|
1584
|
+
} else {
|
|
1585
|
+
lines.push(
|
|
1586
|
+
"Device identity could not be compared, so pairings may or may not have carried over. Check `openclaw devices list` before relying on them."
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
lines.push("Your 1.x config was NOT applied \u2014 it never took effect on 1.x either. Review it");
|
|
1590
|
+
lines.push("and set what you need with `clawops config set`.");
|
|
1591
|
+
return lines.join("\n");
|
|
1592
|
+
}
|
|
1593
|
+
case "nothing-to-migrate":
|
|
1594
|
+
return o.reason;
|
|
1595
|
+
case "refused":
|
|
1596
|
+
return o.reason;
|
|
1597
|
+
case "failed":
|
|
1598
|
+
return `Migration failed: ${o.reason}
|
|
1599
|
+
A verified backup was taken first: ${o.backupDetail}
|
|
1600
|
+
Restore it with \`clawops backup restore --file <archive>\` before retrying.`;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// src/cli/commands/migrate.ts
|
|
1605
|
+
var SYNTHESISED_CONFIG = JSON.stringify({
|
|
1606
|
+
meta: { lastTouchedVersion: "2026.9.2" },
|
|
1607
|
+
gateway: { mode: "local", port: GATEWAY_PORT, auth: { mode: "token" } },
|
|
1608
|
+
models: {},
|
|
1609
|
+
channels: {}
|
|
1610
|
+
});
|
|
1611
|
+
var migrate_default = defineCommand13({
|
|
1612
|
+
meta: {
|
|
1613
|
+
name: "migrate",
|
|
1614
|
+
description: "Move a 1.x OpenClaw deployment onto the 2.0 runtime contract"
|
|
1615
|
+
},
|
|
1616
|
+
args: {
|
|
1617
|
+
stack: { type: "string", description: "Target stack name" },
|
|
1618
|
+
"openclaw-version": { type: "string", description: "OpenClaw 2.x version to migrate to" },
|
|
1619
|
+
yes: { type: "boolean", description: "Skip the confirmation prompt" }
|
|
1620
|
+
},
|
|
1621
|
+
async run({ args }) {
|
|
1622
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
1623
|
+
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1624
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1625
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
1626
|
+
const requested = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0;
|
|
1627
|
+
const target = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
|
|
1628
|
+
const targetImage = `ghcr.io/openclaw/openclaw:${target}`;
|
|
1629
|
+
const ctx = buildContext(args);
|
|
1630
|
+
const stack = await ctx.getStack();
|
|
1631
|
+
const outputs = Object.fromEntries(
|
|
1632
|
+
Object.entries(await stack.outputs()).map(([k, v]) => [k, v.value])
|
|
1633
|
+
);
|
|
1634
|
+
const conn = ctx.adapter.getConnectionInfo({
|
|
1635
|
+
...extractBaseOutputs(outputs),
|
|
1636
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1637
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1638
|
+
});
|
|
1639
|
+
const ac = new AbortController();
|
|
1640
|
+
process15.on("SIGINT", () => ac.abort());
|
|
1641
|
+
const { session, release } = await acquireSession({
|
|
1642
|
+
host: conn.host,
|
|
1643
|
+
port: conn.port,
|
|
1644
|
+
user: conn.user,
|
|
1645
|
+
privateKeyPath: conn.privateKeyPath,
|
|
1646
|
+
knownHostsPath: conn.knownHostsPath,
|
|
1647
|
+
signal: ac.signal
|
|
1648
|
+
});
|
|
1649
|
+
const spin = spinner("Inspecting the deployment...");
|
|
1650
|
+
const run = (cmd) => execPrivileged(session, cmd, ac.signal);
|
|
1651
|
+
const archive = "/tmp/clawops-premigration.tar.gz";
|
|
1652
|
+
const configPath = `${STATE_DIR_HOST_LINUX}/${CONFIG_FILENAME}`;
|
|
1653
|
+
const steps = {
|
|
1654
|
+
inspectSource: async () => {
|
|
1655
|
+
const r = await run(`docker inspect openclaw --format '{{.Config.Image}}'`);
|
|
1656
|
+
const image = r.stdout.trim();
|
|
1657
|
+
return r.code === 0 && image.includes(":") ? image : void 0;
|
|
1658
|
+
},
|
|
1659
|
+
backup: async () => {
|
|
1660
|
+
spin.text = "Taking a verified backup...";
|
|
1661
|
+
const r = await run(
|
|
1662
|
+
`docker exec openclaw openclaw backup create --output ${archive} --verify --json`
|
|
1663
|
+
);
|
|
1664
|
+
return r.code === 0 ? { ok: true, detail: `${archive} on the host (verified)` } : { ok: false, detail: (r.stderr || r.stdout).slice(0, 200) };
|
|
1665
|
+
},
|
|
1666
|
+
extract: async () => {
|
|
1667
|
+
spin.text = "Extracting state from the running container...";
|
|
1668
|
+
const mk = await run(`mkdir -p ${STATE_DIR_HOST_LINUX}`);
|
|
1669
|
+
if (mk.code !== 0) return { ok: false, entries: [] };
|
|
1670
|
+
const cp = await run(`docker cp openclaw:/home/node/.openclaw/. ${STATE_DIR_HOST_LINUX}/`);
|
|
1671
|
+
if (cp.code !== 0) return { ok: false, entries: [] };
|
|
1672
|
+
const ls = await run(`ls ${STATE_DIR_HOST_LINUX}`);
|
|
1673
|
+
return { ok: true, entries: ls.stdout.trim().split(/\s+/).filter(Boolean) };
|
|
1674
|
+
},
|
|
1675
|
+
chown: async () => {
|
|
1676
|
+
await run(`chown -R ${CONTAINER_UID}:${CONTAINER_UID} ${STATE_DIR_HOST_LINUX}`);
|
|
1677
|
+
},
|
|
1678
|
+
removeSource: async () => {
|
|
1679
|
+
spin.text = "Stopping the 1.x container...";
|
|
1680
|
+
await run("docker stop openclaw 2>/dev/null || true");
|
|
1681
|
+
await run("docker rm openclaw 2>/dev/null || true");
|
|
1682
|
+
},
|
|
1683
|
+
writeConfig: async () => {
|
|
1684
|
+
const b64 = Buffer.from(SYNTHESISED_CONFIG, "utf-8").toString("base64");
|
|
1685
|
+
await run(
|
|
1686
|
+
`echo '${b64}' | base64 -d > ${configPath} && chown ${CONTAINER_UID}:${CONTAINER_UID} ${configPath}`
|
|
1687
|
+
);
|
|
1688
|
+
},
|
|
1689
|
+
start: async () => {
|
|
1690
|
+
spin.text = "Starting the 2.0 gateway...";
|
|
1691
|
+
await run(gatewayRunCommand({ image: targetImage, stateDir: STATE_DIR_HOST_LINUX }));
|
|
1692
|
+
},
|
|
1693
|
+
gate: async () => {
|
|
1694
|
+
spin.text = "Waiting for the gateway to start...";
|
|
1695
|
+
let last;
|
|
1696
|
+
for (let i = 0; i < 15; i++) {
|
|
1697
|
+
if (ac.signal.aborted) return { ok: false, reason: "aborted" };
|
|
1698
|
+
const r = await run(probeCommand("started", GATEWAY_PORT));
|
|
1699
|
+
const v = interpretProbe("started", r.stdout);
|
|
1700
|
+
if (v.ok) return { ok: true };
|
|
1701
|
+
last = v.reason;
|
|
1702
|
+
await new Promise((res) => setTimeout(res, 2e3));
|
|
1703
|
+
}
|
|
1704
|
+
return { ok: false, reason: last ?? "no response" };
|
|
1705
|
+
},
|
|
1706
|
+
deviceId: async () => {
|
|
1707
|
+
const file = await run(
|
|
1708
|
+
`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`
|
|
1709
|
+
);
|
|
1710
|
+
try {
|
|
1711
|
+
const parsed = JSON.parse(file.stdout.trim());
|
|
1712
|
+
return parsed.deviceId;
|
|
1713
|
+
} catch {
|
|
1714
|
+
return void 0;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
try {
|
|
1719
|
+
const outcome = await migrate(steps);
|
|
1720
|
+
spin.stop();
|
|
1721
|
+
const report = describeMigration(outcome);
|
|
1722
|
+
if (outcome.kind === "migrated") {
|
|
1723
|
+
success(report.split("\n")[0]);
|
|
1724
|
+
for (const line2 of report.split("\n").slice(1)) {
|
|
1725
|
+
if (/CHANGED|could not be compared/.test(line2)) warn(line2);
|
|
1726
|
+
else info(line2);
|
|
1727
|
+
}
|
|
1728
|
+
info(`Pre-migration backup: ${archive}`);
|
|
1729
|
+
} else {
|
|
1730
|
+
failure(report);
|
|
1731
|
+
process15.exit(outcome.kind === "nothing-to-migrate" ? 0 : 1);
|
|
1732
|
+
}
|
|
1733
|
+
} finally {
|
|
1734
|
+
release();
|
|
1735
|
+
drainPool();
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1739
|
+
|
|
1740
|
+
// src/cli/commands/gateway.ts
|
|
1741
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
1742
|
+
import process16 from "process";
|
|
1743
|
+
function dockerRunCmd2(version, publish = "loopback") {
|
|
1262
1744
|
return gatewayRunCommand({
|
|
1263
1745
|
image: `ghcr.io/openclaw/openclaw:${version}`,
|
|
1264
|
-
|
|
1746
|
+
stateDir: STATE_DIR_HOST_LINUX,
|
|
1747
|
+
publish
|
|
1265
1748
|
});
|
|
1266
1749
|
}
|
|
1267
|
-
var gateway_default =
|
|
1750
|
+
var gateway_default = defineCommand14({
|
|
1268
1751
|
meta: {
|
|
1269
1752
|
name: "gateway",
|
|
1270
1753
|
description: "Manage the OpenClaw gateway daemon (status | restart | update [version])"
|
|
1271
1754
|
},
|
|
1272
1755
|
args: {
|
|
1273
1756
|
stack: { type: "string", description: "Target stack name" },
|
|
1274
|
-
channel: { type: "string", description: "
|
|
1757
|
+
channel: { type: "string", description: "Version for update (a concrete pin; moving tags are refused)" },
|
|
1275
1758
|
json: { type: "boolean", description: "Emit JSON (for status)" }
|
|
1276
1759
|
},
|
|
1277
1760
|
async run({ args }) {
|
|
1278
|
-
const { buildContext } = await import("./context-
|
|
1761
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
1279
1762
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1280
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1763
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1281
1764
|
const [action, versionArg] = args._ ?? [];
|
|
1282
1765
|
if (!action || !["status", "restart", "update"].includes(action)) {
|
|
1283
1766
|
failure("Usage: clawops gateway <status | restart | update [version]>");
|
|
1284
|
-
|
|
1767
|
+
process16.exit(2);
|
|
1285
1768
|
}
|
|
1286
1769
|
const ctx = buildContext(args);
|
|
1287
1770
|
const stack = await ctx.getStack();
|
|
@@ -1296,8 +1779,8 @@ var gateway_default = defineCommand13({
|
|
|
1296
1779
|
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1297
1780
|
});
|
|
1298
1781
|
const abortController = new AbortController();
|
|
1299
|
-
|
|
1300
|
-
|
|
1782
|
+
process16.on("SIGINT", () => abortController.abort());
|
|
1783
|
+
process16.on("SIGTERM", () => abortController.abort());
|
|
1301
1784
|
const { session, release } = await acquireSession({
|
|
1302
1785
|
host: conn.host,
|
|
1303
1786
|
port: conn.port,
|
|
@@ -1308,17 +1791,29 @@ var gateway_default = defineCommand13({
|
|
|
1308
1791
|
});
|
|
1309
1792
|
try {
|
|
1310
1793
|
if (action === "status") {
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1794
|
+
const { inspectContainer } = await import("./docker-3PDO4TS7.js");
|
|
1795
|
+
const inspected = await inspectContainer(
|
|
1796
|
+
session,
|
|
1797
|
+
"openclaw",
|
|
1798
|
+
'{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}',
|
|
1799
|
+
abortController.signal
|
|
1800
|
+
);
|
|
1313
1801
|
let status = { status: "unknown", started: "", image: "" };
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1802
|
+
if (inspected.kind === "ok") {
|
|
1803
|
+
try {
|
|
1804
|
+
status = JSON.parse(inspected.value);
|
|
1805
|
+
} catch {
|
|
1806
|
+
}
|
|
1807
|
+
} else if (inspected.kind === "missing") {
|
|
1808
|
+
status = { status: "not running", started: "", image: "" };
|
|
1809
|
+
} else {
|
|
1810
|
+
failure(`Could not ask docker about the gateway: ${inspected.detail}`);
|
|
1811
|
+
process16.exit(1);
|
|
1317
1812
|
}
|
|
1318
1813
|
if (args.json) {
|
|
1319
1814
|
printJson(jsonOk(status));
|
|
1320
1815
|
} else {
|
|
1321
|
-
|
|
1816
|
+
process16.stdout.write(
|
|
1322
1817
|
"\n" + renderTable(
|
|
1323
1818
|
["Field", "Value"],
|
|
1324
1819
|
[
|
|
@@ -1330,40 +1825,145 @@ var gateway_default = defineCommand13({
|
|
|
1330
1825
|
);
|
|
1331
1826
|
}
|
|
1332
1827
|
} else if (action === "restart") {
|
|
1333
|
-
const imgResult = await session
|
|
1828
|
+
const imgResult = await execPrivileged(session, IMAGE_INSPECT_CMD, abortController.signal);
|
|
1334
1829
|
const image = imageForRestart(imgResult.stdout);
|
|
1335
1830
|
if (!image.ok) {
|
|
1336
1831
|
failure(image.error);
|
|
1337
|
-
|
|
1832
|
+
process16.exit(1);
|
|
1338
1833
|
}
|
|
1339
1834
|
const version = versionOf(image.value);
|
|
1835
|
+
const pub = await execPrivileged(session, PUBLISH_INSPECT_CMD, abortController.signal);
|
|
1836
|
+
const publish = publishForRestart(pub.stdout);
|
|
1340
1837
|
const spin = spinner("Restarting gateway...");
|
|
1341
|
-
const result = await
|
|
1838
|
+
const result = await execPrivileged(
|
|
1839
|
+
session,
|
|
1840
|
+
dockerRunCmd2(version, publish),
|
|
1841
|
+
abortController.signal
|
|
1842
|
+
);
|
|
1342
1843
|
spin.stop();
|
|
1343
1844
|
if (result.code !== 0) {
|
|
1344
1845
|
failure(`Restart failed: ${result.stderr}`);
|
|
1345
|
-
|
|
1846
|
+
process16.exit(1);
|
|
1346
1847
|
}
|
|
1347
1848
|
success(`Gateway restarted (${version}).`);
|
|
1348
1849
|
} else {
|
|
1349
|
-
const
|
|
1850
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
1851
|
+
const requested = versionArg ?? args.channel;
|
|
1852
|
+
const version = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
|
|
1350
1853
|
const spin = spinner(`Updating gateway to ${version}...`);
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1854
|
+
const targetImage = `ghcr.io/openclaw/openclaw:${version}`;
|
|
1855
|
+
const pullResult = await execPrivileged(
|
|
1856
|
+
session,
|
|
1857
|
+
`docker pull ${targetImage}`,
|
|
1353
1858
|
abortController.signal
|
|
1354
1859
|
);
|
|
1355
1860
|
if (pullResult.code !== 0) {
|
|
1356
1861
|
spin.stop();
|
|
1357
1862
|
failure(`Pull failed: ${pullResult.stderr}`);
|
|
1358
|
-
|
|
1863
|
+
process16.exit(1);
|
|
1359
1864
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1865
|
+
spin.text = "Checking state compatibility...";
|
|
1866
|
+
const {
|
|
1867
|
+
snapshotCommand,
|
|
1868
|
+
snapshotPathFrom,
|
|
1869
|
+
preflightCommand,
|
|
1870
|
+
parsePreflight,
|
|
1871
|
+
judgePreflight
|
|
1872
|
+
} = await import("./upgrade-ULD4KFYC.js");
|
|
1873
|
+
const cur = imageForRestart(
|
|
1874
|
+
(await execPrivileged(session, IMAGE_INSPECT_CMD, abortController.signal)).stdout
|
|
1875
|
+
);
|
|
1876
|
+
const snapRepo = `${STATE_DIR_HOST_LINUX}/snapshots`;
|
|
1877
|
+
const snapOut = await execPrivileged(
|
|
1878
|
+
session,
|
|
1879
|
+
snapshotCommand(cur.ok ? cur.value : targetImage, STATE_DIR_HOST_LINUX, snapRepo),
|
|
1880
|
+
abortController.signal
|
|
1881
|
+
);
|
|
1882
|
+
const snapPath = snapshotPathFrom(snapOut.stdout);
|
|
1883
|
+
if (!snapPath) {
|
|
1884
|
+
spin.stop();
|
|
1885
|
+
failure(
|
|
1886
|
+
"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)
|
|
1887
|
+
);
|
|
1888
|
+
process16.exit(1);
|
|
1889
|
+
}
|
|
1890
|
+
const pre = await execPrivileged(
|
|
1891
|
+
session,
|
|
1892
|
+
preflightCommand(targetImage, STATE_DIR_HOST_LINUX, `${snapPath}/database.sqlite`),
|
|
1893
|
+
abortController.signal
|
|
1894
|
+
);
|
|
1895
|
+
const report = parsePreflight(pre.stdout);
|
|
1896
|
+
const verdict = report ? judgePreflight(report) : { ok: false, reason: `preflight produced no readable report: ${pre.stderr.slice(0, 200)}` };
|
|
1897
|
+
if (!verdict.ok) {
|
|
1898
|
+
spin.stop();
|
|
1899
|
+
failure(`Refusing to upgrade to ${version}: ${verdict.reason}`);
|
|
1900
|
+
info(`A snapshot of the current state was kept at ${snapPath}.`);
|
|
1901
|
+
process16.exit(1);
|
|
1902
|
+
}
|
|
1903
|
+
if (verdict.ok && verdict.note) info(verdict.note);
|
|
1904
|
+
spin.text = `Updating gateway to ${version}...`;
|
|
1905
|
+
const pubU = await execPrivileged(session, PUBLISH_INSPECT_CMD, abortController.signal);
|
|
1906
|
+
const runResult = await execPrivileged(
|
|
1907
|
+
session,
|
|
1908
|
+
dockerRunCmd2(version, publishForRestart(pubU.stdout)),
|
|
1909
|
+
abortController.signal
|
|
1910
|
+
);
|
|
1362
1911
|
if (runResult.code !== 0) {
|
|
1912
|
+
spin.stop();
|
|
1363
1913
|
failure(`Start failed: ${runResult.stderr}`);
|
|
1364
|
-
|
|
1914
|
+
info(`State snapshot from before the upgrade: ${snapPath}`);
|
|
1915
|
+
process16.exit(1);
|
|
1916
|
+
}
|
|
1917
|
+
spin.text = "Waiting for the gateway to start...";
|
|
1918
|
+
const { probeCommand: probeCommand2, interpretProbe: interpretProbe2 } = await import("./health-5CCOXYGG.js");
|
|
1919
|
+
const { repairCommand, describeOutcome } = await import("./upgrade-ULD4KFYC.js");
|
|
1920
|
+
const publish = publishForRestart(pubU.stdout);
|
|
1921
|
+
const gate = async () => {
|
|
1922
|
+
let last;
|
|
1923
|
+
for (let i = 0; i < 15; i++) {
|
|
1924
|
+
if (abortController.signal.aborted) return { ok: false, reason: "aborted" };
|
|
1925
|
+
const r = await execPrivileged(
|
|
1926
|
+
session,
|
|
1927
|
+
probeCommand2("started", GATEWAY_PORT),
|
|
1928
|
+
abortController.signal
|
|
1929
|
+
);
|
|
1930
|
+
const v = interpretProbe2("started", r.stdout);
|
|
1931
|
+
if (v.ok) return { ok: true };
|
|
1932
|
+
last = v.reason;
|
|
1933
|
+
await new Promise((res) => setTimeout(res, 2e3));
|
|
1934
|
+
}
|
|
1935
|
+
return { ok: false, reason: last ?? "no response" };
|
|
1936
|
+
};
|
|
1937
|
+
const { resolveUpgrade } = await import("./upgrade-ULD4KFYC.js");
|
|
1938
|
+
const outcome = await resolveUpgrade(
|
|
1939
|
+
{
|
|
1940
|
+
gate,
|
|
1941
|
+
repair: async () => {
|
|
1942
|
+
spin.text = "Gateway did not start; attempting one-shot repair...";
|
|
1943
|
+
await execPrivileged(
|
|
1944
|
+
session,
|
|
1945
|
+
repairCommand(targetImage, STATE_DIR_HOST_LINUX),
|
|
1946
|
+
abortController.signal
|
|
1947
|
+
);
|
|
1948
|
+
},
|
|
1949
|
+
run: async (v) => {
|
|
1950
|
+
await execPrivileged(session, dockerRunCmd2(v, publish), abortController.signal);
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
{
|
|
1954
|
+
version,
|
|
1955
|
+
previousVersion: cur.ok ? versionOf(cur.value) : void 0,
|
|
1956
|
+
snapshotPath: snapPath
|
|
1957
|
+
}
|
|
1958
|
+
);
|
|
1959
|
+
spin.stop();
|
|
1960
|
+
const message = describeOutcome(outcome, version);
|
|
1961
|
+
if (outcome.kind === "started" || outcome.kind === "repaired") {
|
|
1962
|
+
success(message);
|
|
1963
|
+
} else {
|
|
1964
|
+
failure(message);
|
|
1965
|
+
process16.exit(1);
|
|
1365
1966
|
}
|
|
1366
|
-
success(`Gateway updated to ${version}.`);
|
|
1367
1967
|
}
|
|
1368
1968
|
} finally {
|
|
1369
1969
|
release();
|
|
@@ -1373,11 +1973,11 @@ var gateway_default = defineCommand13({
|
|
|
1373
1973
|
});
|
|
1374
1974
|
|
|
1375
1975
|
// src/cli/commands/backup.ts
|
|
1376
|
-
import { defineCommand as
|
|
1976
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
1377
1977
|
import { createWriteStream } from "fs";
|
|
1378
1978
|
import { pipeline } from "stream/promises";
|
|
1379
|
-
import
|
|
1380
|
-
var backup_default =
|
|
1979
|
+
import process17 from "process";
|
|
1980
|
+
var backup_default = defineCommand15({
|
|
1381
1981
|
meta: {
|
|
1382
1982
|
name: "backup",
|
|
1383
1983
|
description: "Create an OpenClaw backup (restore is unavailable on this release line)"
|
|
@@ -1390,8 +1990,8 @@ var backup_default = defineCommand14({
|
|
|
1390
1990
|
yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
|
|
1391
1991
|
},
|
|
1392
1992
|
async run({ args }) {
|
|
1393
|
-
const { buildContext } = await import("./context-
|
|
1394
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1993
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
1994
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1395
1995
|
const action = args.action;
|
|
1396
1996
|
if (action !== "create" && action !== "restore") {
|
|
1397
1997
|
throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
|
|
@@ -1402,7 +2002,7 @@ var backup_default = defineCommand14({
|
|
|
1402
2002
|
const state = ctx.localState;
|
|
1403
2003
|
if (!state) {
|
|
1404
2004
|
failure("Stack has no state. Run `clawops up` first.");
|
|
1405
|
-
|
|
2005
|
+
process17.exit(4);
|
|
1406
2006
|
}
|
|
1407
2007
|
conn = {
|
|
1408
2008
|
host: state.sshHost,
|
|
@@ -1420,7 +2020,7 @@ var backup_default = defineCommand14({
|
|
|
1420
2020
|
);
|
|
1421
2021
|
if (!outputs["publicIp"]) {
|
|
1422
2022
|
failure("Stack has no outputs. Run `clawops up` first.");
|
|
1423
|
-
|
|
2023
|
+
process17.exit(4);
|
|
1424
2024
|
}
|
|
1425
2025
|
const base = extractBaseOutputs(outputs);
|
|
1426
2026
|
conn = ctx.adapter.getConnectionInfo({
|
|
@@ -1430,8 +2030,8 @@ var backup_default = defineCommand14({
|
|
|
1430
2030
|
});
|
|
1431
2031
|
}
|
|
1432
2032
|
const abortController = new AbortController();
|
|
1433
|
-
|
|
1434
|
-
|
|
2033
|
+
process17.on("SIGINT", () => abortController.abort());
|
|
2034
|
+
process17.on("SIGTERM", () => abortController.abort());
|
|
1435
2035
|
const { session, release } = await acquireSession({
|
|
1436
2036
|
host: conn.host,
|
|
1437
2037
|
port: conn.port,
|
|
@@ -1446,7 +2046,8 @@ var backup_default = defineCommand14({
|
|
|
1446
2046
|
info(`Writing backup to ${outPath}...`);
|
|
1447
2047
|
const spin = spinner("Creating backup on remote host...");
|
|
1448
2048
|
const remoteArchive = "/tmp/clawops-backup.tar.gz";
|
|
1449
|
-
const createResult = await
|
|
2049
|
+
const createResult = await execPrivileged(
|
|
2050
|
+
session,
|
|
1450
2051
|
`docker exec openclaw sh -lc 'rm -f ${remoteArchive} && openclaw backup create --output ${remoteArchive} --verify --json'`,
|
|
1451
2052
|
abortController.signal
|
|
1452
2053
|
);
|
|
@@ -1454,23 +2055,72 @@ var backup_default = defineCommand14({
|
|
|
1454
2055
|
spin.stop();
|
|
1455
2056
|
throw new Error(`Backup failed on the remote host: ${createResult.stderr.slice(0, 300)}`);
|
|
1456
2057
|
}
|
|
1457
|
-
const backupStream = await
|
|
2058
|
+
const backupStream = await streamPrivileged(
|
|
2059
|
+
session,
|
|
1458
2060
|
`docker exec openclaw cat ${remoteArchive}`,
|
|
1459
2061
|
abortController.signal
|
|
1460
2062
|
);
|
|
1461
2063
|
spin.stop();
|
|
1462
|
-
const fileStream = createWriteStream(outPath);
|
|
2064
|
+
const fileStream = createWriteStream(outPath, { mode: 384 });
|
|
1463
2065
|
await pipeline(backupStream, fileStream);
|
|
1464
|
-
await
|
|
2066
|
+
await execPrivileged(
|
|
2067
|
+
session,
|
|
1465
2068
|
`docker exec openclaw rm -f ${remoteArchive}`,
|
|
1466
2069
|
abortController.signal
|
|
1467
2070
|
);
|
|
1468
2071
|
success(`Backup saved to ${outPath}`);
|
|
1469
|
-
info("The archive contains
|
|
2072
|
+
info("Saved with mode 0600. The archive contains the state database \u2014 OAuth tokens,");
|
|
2073
|
+
info("secrets and device credentials, unencrypted. Treat it as a credential.");
|
|
1470
2074
|
} else {
|
|
1471
|
-
|
|
1472
|
-
|
|
2075
|
+
const file = typeof args.file === "string" ? args.file : void 0;
|
|
2076
|
+
if (!file) {
|
|
2077
|
+
throw new UsageError("Usage: clawops backup restore --file <archive.tar.gz>");
|
|
2078
|
+
}
|
|
2079
|
+
const { createReadStream } = await import("fs");
|
|
2080
|
+
const remoteArchive = "/tmp/clawops-restore.tar.gz";
|
|
2081
|
+
const staging = `/tmp/clawops-restored-${Date.now()}`;
|
|
2082
|
+
const spin = spinner("Uploading archive...");
|
|
2083
|
+
const uploadResult = await execPrivilegedWithInput(
|
|
2084
|
+
session,
|
|
2085
|
+
`docker exec -i openclaw sh -c 'cat > ${remoteArchive}'`,
|
|
2086
|
+
createReadStream(file),
|
|
2087
|
+
abortController.signal
|
|
2088
|
+
);
|
|
2089
|
+
if (uploadResult.code !== 0) {
|
|
2090
|
+
spin.stop();
|
|
2091
|
+
throw new Error(`Could not upload the archive: ${uploadResult.stderr.slice(0, 300)}`);
|
|
2092
|
+
}
|
|
2093
|
+
spin.text = "Verifying and restoring to a staging directory...";
|
|
2094
|
+
const restore = await execPrivileged(
|
|
2095
|
+
session,
|
|
2096
|
+
`docker exec openclaw openclaw backup restore ${remoteArchive} --target ${staging} --json`,
|
|
2097
|
+
abortController.signal
|
|
1473
2098
|
);
|
|
2099
|
+
await execPrivileged(
|
|
2100
|
+
session,
|
|
2101
|
+
`docker exec openclaw rm -f ${remoteArchive}`,
|
|
2102
|
+
abortController.signal
|
|
2103
|
+
);
|
|
2104
|
+
spin.stop();
|
|
2105
|
+
if (restore.code !== 0) {
|
|
2106
|
+
throw new Error(`Restore failed: ${(restore.stderr || restore.stdout).slice(0, 400)}`);
|
|
2107
|
+
}
|
|
2108
|
+
let report = {};
|
|
2109
|
+
try {
|
|
2110
|
+
report = JSON.parse(restore.stdout.trim());
|
|
2111
|
+
} catch {
|
|
2112
|
+
info(restore.stdout);
|
|
2113
|
+
}
|
|
2114
|
+
success(`Archive verified and restored to ${staging} on the host.`);
|
|
2115
|
+
if (report.entryCount !== void 0) info(`${report.entryCount} entries restored.`);
|
|
2116
|
+
for (const w of report.warnings ?? []) warn(w);
|
|
2117
|
+
info("");
|
|
2118
|
+
info("Nothing has been activated. To adopt the restored state:");
|
|
2119
|
+
info(" 1. clawops ssh --command 'sudo docker stop openclaw'");
|
|
2120
|
+
info(` 2. replace the state directory contents with ${staging}`);
|
|
2121
|
+
info(" 3. clawops gateway restart");
|
|
2122
|
+
info("Provider plugins are not carried in the archive; re-run `clawops apply` to");
|
|
2123
|
+
info("reinstall them, or the gateway starts without its model providers.");
|
|
1474
2124
|
}
|
|
1475
2125
|
} finally {
|
|
1476
2126
|
release();
|
|
@@ -1480,9 +2130,9 @@ var backup_default = defineCommand14({
|
|
|
1480
2130
|
});
|
|
1481
2131
|
|
|
1482
2132
|
// src/cli/commands/stacks.ts
|
|
1483
|
-
import { defineCommand as
|
|
1484
|
-
import
|
|
1485
|
-
var stacks_default =
|
|
2133
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
2134
|
+
import process18 from "process";
|
|
2135
|
+
var stacks_default = defineCommand16({
|
|
1486
2136
|
meta: {
|
|
1487
2137
|
name: "stacks",
|
|
1488
2138
|
description: "Manage clawops stacks (list | delete <name>)"
|
|
@@ -1496,7 +2146,7 @@ var stacks_default = defineCommand15({
|
|
|
1496
2146
|
const [action, name] = args._ ?? [];
|
|
1497
2147
|
if (!action || !["list", "delete"].includes(action)) {
|
|
1498
2148
|
failure("Usage: clawops stacks <list | delete <name>>");
|
|
1499
|
-
|
|
2149
|
+
process18.exit(2);
|
|
1500
2150
|
}
|
|
1501
2151
|
if (action === "list") {
|
|
1502
2152
|
const config2 = requireConfig();
|
|
@@ -1519,7 +2169,7 @@ var stacks_default = defineCommand15({
|
|
|
1519
2169
|
} else if (rows.length === 0) {
|
|
1520
2170
|
info("No stacks configured.");
|
|
1521
2171
|
} else {
|
|
1522
|
-
|
|
2172
|
+
process18.stdout.write(
|
|
1523
2173
|
"\n" + renderTable(
|
|
1524
2174
|
["Name", "Provider", "Region", "State URL"],
|
|
1525
2175
|
rows
|
|
@@ -1531,7 +2181,7 @@ var stacks_default = defineCommand15({
|
|
|
1531
2181
|
}
|
|
1532
2182
|
if (!name) {
|
|
1533
2183
|
failure("Usage: clawops stacks delete <name>");
|
|
1534
|
-
|
|
2184
|
+
process18.exit(2);
|
|
1535
2185
|
}
|
|
1536
2186
|
const config = requireConfig();
|
|
1537
2187
|
if (!(name in config.stacks)) {
|
|
@@ -1551,7 +2201,7 @@ var stacks_default = defineCommand15({
|
|
|
1551
2201
|
let isDeployed = false;
|
|
1552
2202
|
if (!args.force) {
|
|
1553
2203
|
try {
|
|
1554
|
-
const { buildContext } = await import("./context-
|
|
2204
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
1555
2205
|
const ctx = buildContext({ stack: name });
|
|
1556
2206
|
if (ctx.adapter.name === "local") {
|
|
1557
2207
|
isDeployed = !!ctx.localState;
|
|
@@ -1571,7 +2221,7 @@ var stacks_default = defineCommand15({
|
|
|
1571
2221
|
failure(
|
|
1572
2222
|
`Stack "${name}" is still deployed. Run \`clawops down --stack ${name}\` first to destroy cloud resources, or pass --force to remove from registry only.`
|
|
1573
2223
|
);
|
|
1574
|
-
|
|
2224
|
+
process18.exit(1);
|
|
1575
2225
|
}
|
|
1576
2226
|
warn(
|
|
1577
2227
|
`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."
|
|
@@ -1599,7 +2249,7 @@ var stacks_default = defineCommand15({
|
|
|
1599
2249
|
});
|
|
1600
2250
|
async function confirm(message) {
|
|
1601
2251
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
1602
|
-
const rl = createInterface3({ input:
|
|
2252
|
+
const rl = createInterface3({ input: process18.stdin, output: process18.stdout });
|
|
1603
2253
|
try {
|
|
1604
2254
|
const answer = await rl.question(`${message} (y/N) `);
|
|
1605
2255
|
return answer.trim().toLowerCase() === "y";
|
|
@@ -1609,286 +2259,75 @@ async function confirm(message) {
|
|
|
1609
2259
|
}
|
|
1610
2260
|
|
|
1611
2261
|
// src/cli/commands/doctor.ts
|
|
1612
|
-
import { defineCommand as
|
|
1613
|
-
import
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
var doctor_default = defineCommand16({
|
|
2262
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
2263
|
+
import process19 from "process";
|
|
2264
|
+
var NAME_COLUMN = 13;
|
|
2265
|
+
var doctor_default = defineCommand17({
|
|
1617
2266
|
meta: {
|
|
1618
2267
|
name: "doctor",
|
|
1619
2268
|
description: "Check system prerequisites, config, SSH keys, and cloud credentials"
|
|
1620
2269
|
},
|
|
1621
2270
|
args: {
|
|
1622
|
-
stack: { type: "string", description: "Stack name to include remote health checks" }
|
|
2271
|
+
stack: { type: "string", description: "Stack name to include remote health checks" },
|
|
2272
|
+
provider: { type: "string", description: "Check this provider's credentials and account setup, with or without a stack" },
|
|
2273
|
+
"instance-type": { type: "string", description: "Ask account checks about this machine size rather than the provider default" },
|
|
2274
|
+
json: { type: "boolean", description: "Emit the report as JSON" }
|
|
1623
2275
|
},
|
|
1624
2276
|
async run({ args }) {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
const nodeOk = nodeMajor >= 22;
|
|
1631
|
-
if (nodeOk) {
|
|
1632
|
-
success(`Node.js ${nodeVersion}`);
|
|
1633
|
-
} else {
|
|
1634
|
-
failure(`Node.js ${nodeVersion} (requires >=22)`);
|
|
1635
|
-
}
|
|
1636
|
-
const configDir = getConfigDir2();
|
|
1637
|
-
const pulumiHome = path2.join(configDir, ".pulumi");
|
|
2277
|
+
const ac = new AbortController();
|
|
2278
|
+
const abort = () => ac.abort();
|
|
2279
|
+
process19.on("SIGINT", abort);
|
|
2280
|
+
process19.on("SIGTERM", abort);
|
|
2281
|
+
let report;
|
|
1638
2282
|
try {
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
} else {
|
|
1649
|
-
success(`Config file ${path2.join(configDir, "config.json")}`);
|
|
1650
|
-
}
|
|
1651
|
-
process18.stdout.write("\nSSH\n");
|
|
1652
|
-
if (!config) {
|
|
1653
|
-
info("SSH checks skipped \u2014 no config");
|
|
1654
|
-
} else {
|
|
1655
|
-
const keyPath = config.ssh.keyPath.replace(/^~/, process18.env["HOME"] ?? "~");
|
|
1656
|
-
try {
|
|
1657
|
-
accessSync(keyPath, constants.R_OK);
|
|
1658
|
-
success(`SSH key ${keyPath}`);
|
|
1659
|
-
} catch {
|
|
1660
|
-
failure(`SSH key ${keyPath} (not found or not readable)`);
|
|
1661
|
-
}
|
|
1662
|
-
const knownHostsPath = config.ssh.knownHostsPath.replace(/^~/, process18.env["HOME"] ?? "~");
|
|
1663
|
-
try {
|
|
1664
|
-
accessSync(knownHostsPath, constants.F_OK);
|
|
1665
|
-
success(`known_hosts ${knownHostsPath}`);
|
|
1666
|
-
} catch {
|
|
1667
|
-
warn(`known_hosts ${knownHostsPath} (does not exist \u2014 will be created on first connect)`);
|
|
1668
|
-
}
|
|
2283
|
+
report = await runDiagnostics({
|
|
2284
|
+
stack: args.stack,
|
|
2285
|
+
provider: typeof args.provider === "string" ? args.provider : void 0,
|
|
2286
|
+
instanceType: typeof args["instance-type"] === "string" ? args["instance-type"] : void 0,
|
|
2287
|
+
signal: ac.signal
|
|
2288
|
+
});
|
|
2289
|
+
} finally {
|
|
2290
|
+
process19.off("SIGINT", abort);
|
|
2291
|
+
process19.off("SIGTERM", abort);
|
|
1669
2292
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
info("Credential checks skipped \u2014 no config");
|
|
2293
|
+
if (args.json) {
|
|
2294
|
+
printJson(jsonOk(report));
|
|
1673
2295
|
} else {
|
|
1674
|
-
|
|
1675
|
-
await import("./aws-L4RR2LSE.js");
|
|
1676
|
-
await import("./gcp-UG6AWPIZ.js");
|
|
1677
|
-
await import("./azure-7OXQENQL.js");
|
|
1678
|
-
await import("./local-DXBEVZ5C.js");
|
|
1679
|
-
const checkedProviders = /* @__PURE__ */ new Set();
|
|
1680
|
-
for (const [stackName, stackCfg] of Object.entries(config.stacks)) {
|
|
1681
|
-
const providerName = stackCfg.provider;
|
|
1682
|
-
if (checkedProviders.has(providerName)) continue;
|
|
1683
|
-
checkedProviders.add(providerName);
|
|
1684
|
-
if (providerName === "local") {
|
|
1685
|
-
success(`local stack "${stackName}" (SSH-only, no cloud credentials required)`);
|
|
1686
|
-
continue;
|
|
1687
|
-
}
|
|
1688
|
-
try {
|
|
1689
|
-
const adapter = getProvider(providerName);
|
|
1690
|
-
const result = await adapter.validateConfig();
|
|
1691
|
-
if (result.ok) {
|
|
1692
|
-
success(`${providerName} stack "${stackName}"`);
|
|
1693
|
-
} else {
|
|
1694
|
-
for (const err of result.errors) {
|
|
1695
|
-
failure(`${providerName} stack "${stackName}" \u2014 ${err}`);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
} catch (err) {
|
|
1699
|
-
failure(`${providerName} stack "${stackName}" \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
if (checkedProviders.size === 0) {
|
|
1703
|
-
warn("No stacks configured \u2014 run `clawops init`");
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
process18.stdout.write("\nOpenClaw\n");
|
|
1707
|
-
try {
|
|
1708
|
-
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
1709
|
-
const { loadVersionSpec, describeRange } = await import("./versions-2UVAT6PQ.js");
|
|
1710
|
-
const support = loadVersionSpec(yaml).support;
|
|
1711
|
-
success(`Supported range ${describeRange(support)} (recommended ${support.recommended})`);
|
|
1712
|
-
if (!support.max) {
|
|
1713
|
-
warn("No upper bound declared \u2014 this line would accept any OpenClaw release");
|
|
1714
|
-
}
|
|
1715
|
-
} catch (e) {
|
|
1716
|
-
failure(`Version matrix ${e.message}`);
|
|
1717
|
-
}
|
|
1718
|
-
if (args.stack) {
|
|
1719
|
-
process18.stdout.write("\nRemote health\n");
|
|
1720
|
-
if (!config) {
|
|
1721
|
-
info("Remote health skipped \u2014 no config");
|
|
1722
|
-
} else {
|
|
1723
|
-
try {
|
|
1724
|
-
const { buildContext } = await import("./context-5LCUG2QR.js");
|
|
1725
|
-
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1726
|
-
const { acquireSession, drainPool } = await import("./pool-QGFQRMGU.js");
|
|
1727
|
-
const ctx = buildContext({ stack: args.stack });
|
|
1728
|
-
const stack = await ctx.getStack();
|
|
1729
|
-
const outputMap = await stack.outputs();
|
|
1730
|
-
const outputs = Object.fromEntries(
|
|
1731
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1732
|
-
);
|
|
1733
|
-
const base = extractBaseOutputs(outputs);
|
|
1734
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1735
|
-
...base,
|
|
1736
|
-
privateKeyPath: config.ssh.keyPath,
|
|
1737
|
-
knownHostsPath: config.ssh.knownHostsPath
|
|
1738
|
-
});
|
|
1739
|
-
const ac = new AbortController();
|
|
1740
|
-
process18.on("SIGINT", () => ac.abort());
|
|
1741
|
-
process18.on("SIGTERM", () => ac.abort());
|
|
1742
|
-
const { session, release } = await acquireSession({
|
|
1743
|
-
host: conn.host,
|
|
1744
|
-
port: conn.port,
|
|
1745
|
-
user: conn.user,
|
|
1746
|
-
privateKeyPath: conn.privateKeyPath,
|
|
1747
|
-
knownHostsPath: conn.knownHostsPath,
|
|
1748
|
-
signal: ac.signal
|
|
1749
|
-
});
|
|
1750
|
-
try {
|
|
1751
|
-
const containerResult = await session.exec(
|
|
1752
|
-
`docker inspect openclaw --format '{{.State.Status}}' 2>/dev/null || echo 'not found'`,
|
|
1753
|
-
ac.signal
|
|
1754
|
-
);
|
|
1755
|
-
const containerStatus = containerResult.stdout.trim();
|
|
1756
|
-
if (containerStatus === "running") {
|
|
1757
|
-
success(`Container running`);
|
|
1758
|
-
} else {
|
|
1759
|
-
failure(`Container ${containerStatus || "unknown"}`);
|
|
1760
|
-
}
|
|
1761
|
-
const imageResult = await session.exec(
|
|
1762
|
-
`docker inspect openclaw --format '{{.Config.Image}}' 2>/dev/null || echo ''`,
|
|
1763
|
-
ac.signal
|
|
1764
|
-
);
|
|
1765
|
-
const image = imageResult.stdout.trim();
|
|
1766
|
-
const deployedVersion = image.includes(":") ? image.slice(image.lastIndexOf(":") + 1) : "";
|
|
1767
|
-
if (deployedVersion) {
|
|
1768
|
-
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
1769
|
-
const { loadVersionSpec, checkVersion, isMovingTag } = await import("./versions-2UVAT6PQ.js");
|
|
1770
|
-
const support = loadVersionSpec(yaml).support;
|
|
1771
|
-
if (isMovingTag(deployedVersion)) {
|
|
1772
|
-
warn(
|
|
1773
|
-
`Deployed ${image} (moving tag \u2014 pin an explicit version; "${deployedVersion}" now resolves to OpenClaw 2.0)`
|
|
1774
|
-
);
|
|
1775
|
-
} else {
|
|
1776
|
-
const check = checkVersion(deployedVersion, support);
|
|
1777
|
-
if (check.ok) {
|
|
1778
|
-
success(`Deployed OpenClaw ${deployedVersion}`);
|
|
1779
|
-
} else {
|
|
1780
|
-
failure(`Deployed OpenClaw ${deployedVersion} is not supported by this clawops line`);
|
|
1781
|
-
if (check.error.reason === "too-new") {
|
|
1782
|
-
warn(
|
|
1783
|
-
" This gateway is running OpenClaw 2.0 or later. Session state may already have been lost: 2.0 stores sessions and credentials in SQLite under a directory this clawops line does not mount, so every container restart discards them. Upgrade to clawops 2.x before making changes."
|
|
1784
|
-
);
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
const healthResult = await session.exec(
|
|
1790
|
-
`docker inspect openclaw --format '{{.State.Health.Status}}' 2>/dev/null || echo 'none'`,
|
|
1791
|
-
ac.signal
|
|
1792
|
-
);
|
|
1793
|
-
const healthStatus = healthResult.stdout.trim();
|
|
1794
|
-
if (healthStatus === "healthy") {
|
|
1795
|
-
success(`Healthcheck healthy`);
|
|
1796
|
-
} else if (healthStatus === "none" || healthStatus === "<no value>") {
|
|
1797
|
-
info(`Healthcheck no healthcheck configured`);
|
|
1798
|
-
} else {
|
|
1799
|
-
warn(`Healthcheck ${healthStatus}`);
|
|
1800
|
-
}
|
|
1801
|
-
const diskResult = await session.exec(
|
|
1802
|
-
`df -h /home/clawops 2>/dev/null | awk 'NR==2{print $5" used ("$3" of "$2")"}'`,
|
|
1803
|
-
ac.signal
|
|
1804
|
-
);
|
|
1805
|
-
const diskUsage = diskResult.stdout.trim();
|
|
1806
|
-
if (diskUsage) {
|
|
1807
|
-
const pctMatch = diskUsage.match(/^(\d+)%/);
|
|
1808
|
-
const pct = pctMatch ? parseInt(pctMatch[1], 10) : 0;
|
|
1809
|
-
if (pct >= 90) {
|
|
1810
|
-
failure(`Disk ${diskUsage}`);
|
|
1811
|
-
} else if (pct >= 75) {
|
|
1812
|
-
warn(`Disk ${diskUsage}`);
|
|
1813
|
-
} else {
|
|
1814
|
-
success(`Disk ${diskUsage}`);
|
|
1815
|
-
}
|
|
1816
|
-
} else {
|
|
1817
|
-
warn("Disk unable to determine disk usage");
|
|
1818
|
-
}
|
|
1819
|
-
const logrotateResult = await session.exec(
|
|
1820
|
-
`test -f /etc/logrotate.d/openclaw && echo 'configured' || echo 'not configured'`,
|
|
1821
|
-
ac.signal
|
|
1822
|
-
);
|
|
1823
|
-
const logrotate = logrotateResult.stdout.trim();
|
|
1824
|
-
if (logrotate === "configured") {
|
|
1825
|
-
success(`Log rotation configured`);
|
|
1826
|
-
} else {
|
|
1827
|
-
warn(`Log rotation not configured \u2014 logs may grow unbounded`);
|
|
1828
|
-
}
|
|
1829
|
-
} finally {
|
|
1830
|
-
release();
|
|
1831
|
-
drainPool();
|
|
1832
|
-
}
|
|
1833
|
-
} catch (err) {
|
|
1834
|
-
failure(`Remote health checks failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
if (args.stack && config) {
|
|
1839
|
-
process18.stdout.write("\nHardening\n");
|
|
1840
|
-
try {
|
|
1841
|
-
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-URPXLSVF.js");
|
|
1842
|
-
const { buildContext } = await import("./context-5LCUG2QR.js");
|
|
1843
|
-
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1844
|
-
const ctx = buildContext({ stack: args.stack });
|
|
1845
|
-
const stackObj = await ctx.getStack();
|
|
1846
|
-
const outputMap = await stackObj.outputs();
|
|
1847
|
-
const outputs = Object.fromEntries(
|
|
1848
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1849
|
-
);
|
|
1850
|
-
const base = extractBaseOutputs(outputs);
|
|
1851
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1852
|
-
...base,
|
|
1853
|
-
privateKeyPath: config.ssh.keyPath,
|
|
1854
|
-
knownHostsPath: config.ssh.knownHostsPath
|
|
1855
|
-
});
|
|
1856
|
-
const hardenAc = new AbortController();
|
|
1857
|
-
process18.on("SIGINT", () => hardenAc.abort());
|
|
1858
|
-
const provider = ctx.adapter.name;
|
|
1859
|
-
const modules = resolveModules(MODULE_CATALOG, void 0, provider);
|
|
1860
|
-
await withRemoteExec(conn, hardenAc.signal, async (exec) => {
|
|
1861
|
-
for (const mod of modules) {
|
|
1862
|
-
const result = await mod.check(exec);
|
|
1863
|
-
if (result.status === "applied") {
|
|
1864
|
-
success(`${mod.label.padEnd(32)} applied`);
|
|
1865
|
-
} else if (result.status === "drifted") {
|
|
1866
|
-
warn(`${mod.label.padEnd(32)} drifted \u2014 ${result.detail}`);
|
|
1867
|
-
} else if (result.status === "missing") {
|
|
1868
|
-
info(`${mod.label.padEnd(32)} not applied \u2014 run \`clawops harden\``);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
});
|
|
1872
|
-
} catch (err) {
|
|
1873
|
-
failure(`Hardening checks failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1874
|
-
}
|
|
2296
|
+
render(report);
|
|
1875
2297
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2298
|
+
if (!report.ok) {
|
|
2299
|
+
if (!args.json) {
|
|
2300
|
+
process19.stdout.write(
|
|
2301
|
+
`Run \`clawops bug\` to open a pre-filled issue at ${REPO_URL}/issues
|
|
1880
2302
|
|
|
1881
2303
|
`
|
|
1882
|
-
|
|
1883
|
-
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
process19.exit(1);
|
|
1884
2307
|
}
|
|
1885
2308
|
}
|
|
1886
2309
|
});
|
|
2310
|
+
function render(report) {
|
|
2311
|
+
process19.stdout.write("\nclawops doctor\n");
|
|
2312
|
+
for (const section of report.sections) {
|
|
2313
|
+
process19.stdout.write(`
|
|
2314
|
+
${section.title}
|
|
2315
|
+
`);
|
|
2316
|
+
for (const check of section.checks) line(check);
|
|
2317
|
+
}
|
|
2318
|
+
process19.stdout.write("\n");
|
|
2319
|
+
}
|
|
2320
|
+
function line(check) {
|
|
2321
|
+
const label = check.detail ? `${check.name.padEnd(NAME_COLUMN)}${check.name.length >= NAME_COLUMN ? " " : ""}${check.detail}` : check.name;
|
|
2322
|
+
const emit = { pass: success, fail: failure, warn, info }[check.status];
|
|
2323
|
+
emit(label);
|
|
2324
|
+
if (check.remedy) info(` ${check.remedy}`);
|
|
2325
|
+
}
|
|
1887
2326
|
|
|
1888
2327
|
// src/cli/commands/mcp.ts
|
|
1889
|
-
import { defineCommand as
|
|
1890
|
-
import
|
|
1891
|
-
var serveCmd =
|
|
2328
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
2329
|
+
import process20 from "process";
|
|
2330
|
+
var serveCmd = defineCommand18({
|
|
1892
2331
|
meta: {
|
|
1893
2332
|
name: "serve",
|
|
1894
2333
|
description: "Start the clawops MCP server"
|
|
@@ -1903,7 +2342,7 @@ var serveCmd = defineCommand17({
|
|
|
1903
2342
|
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
1904
2343
|
},
|
|
1905
2344
|
async run({ args }) {
|
|
1906
|
-
const { serveMcp } = await import("./server-
|
|
2345
|
+
const { serveMcp } = await import("./server-GBA7BJGK.js");
|
|
1907
2346
|
await serveMcp({
|
|
1908
2347
|
port: args.http ? Number(args.http) : void 0,
|
|
1909
2348
|
bind: args.bind,
|
|
@@ -1915,7 +2354,7 @@ var serveCmd = defineCommand17({
|
|
|
1915
2354
|
});
|
|
1916
2355
|
}
|
|
1917
2356
|
});
|
|
1918
|
-
var installCmd =
|
|
2357
|
+
var installCmd = defineCommand18({
|
|
1919
2358
|
meta: {
|
|
1920
2359
|
name: "install",
|
|
1921
2360
|
description: "Interactively wire clawops into AI editors (Claude Desktop, Claude Code, Cursor, \u2026)"
|
|
@@ -1946,7 +2385,7 @@ var installCmd = defineCommand17({
|
|
|
1946
2385
|
const selected = MCP_APPS2.filter((app) => selectedIds.includes(app.id));
|
|
1947
2386
|
if (selected.length === 0) {
|
|
1948
2387
|
info("No apps selected. Add this to an app's MCP config manually:");
|
|
1949
|
-
|
|
2388
|
+
process20.stdout.write(JSON.stringify({
|
|
1950
2389
|
mcpServers: { clawops: { command: mcpEntry.command, args: mcpEntry.args } }
|
|
1951
2390
|
}, null, 2) + "\n\n");
|
|
1952
2391
|
info("Config file locations:");
|
|
@@ -1969,7 +2408,7 @@ var installCmd = defineCommand17({
|
|
|
1969
2408
|
}
|
|
1970
2409
|
}
|
|
1971
2410
|
});
|
|
1972
|
-
var wireCmd =
|
|
2411
|
+
var wireCmd = defineCommand18({
|
|
1973
2412
|
meta: {
|
|
1974
2413
|
name: "wire",
|
|
1975
2414
|
description: "Wire the gateway AI as an MCP client of clawops (WO-28)"
|
|
@@ -1981,26 +2420,26 @@ var wireCmd = defineCommand17({
|
|
|
1981
2420
|
rewire: { type: "boolean", description: "Replace an existing clawops entry" }
|
|
1982
2421
|
},
|
|
1983
2422
|
async run({ args }) {
|
|
1984
|
-
const { buildContext } = await import("./context-
|
|
1985
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
2423
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
2424
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
1986
2425
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1987
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
1988
|
-
const { MCP_HTTP_PORT } = await import("./server-
|
|
2426
|
+
const { wireGatewayMcp } = await import("./mcp-wire-3SWBM7D6.js");
|
|
2427
|
+
const { MCP_HTTP_PORT } = await import("./server-GBA7BJGK.js");
|
|
1989
2428
|
const ac = new AbortController();
|
|
1990
|
-
|
|
2429
|
+
process20.on("SIGINT", () => {
|
|
1991
2430
|
ac.abort();
|
|
1992
|
-
|
|
2431
|
+
process20.exit(130);
|
|
1993
2432
|
});
|
|
1994
|
-
|
|
2433
|
+
process20.on("SIGTERM", () => {
|
|
1995
2434
|
ac.abort();
|
|
1996
|
-
|
|
2435
|
+
process20.exit(143);
|
|
1997
2436
|
});
|
|
1998
2437
|
const ctx = buildContext({ stack: args.stack });
|
|
1999
2438
|
let conn;
|
|
2000
2439
|
if (ctx.adapter.name === "local") {
|
|
2001
2440
|
if (!ctx.localState) {
|
|
2002
2441
|
failure("Stack is not deployed. Run `clawops up` first.");
|
|
2003
|
-
|
|
2442
|
+
process20.exit(1);
|
|
2004
2443
|
}
|
|
2005
2444
|
const ls = ctx.localState;
|
|
2006
2445
|
conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
|
|
@@ -2012,7 +2451,7 @@ var wireCmd = defineCommand17({
|
|
|
2012
2451
|
);
|
|
2013
2452
|
if (!outputs["publicIp"]) {
|
|
2014
2453
|
failure("Stack has no outputs. Run `clawops up` first.");
|
|
2015
|
-
|
|
2454
|
+
process20.exit(1);
|
|
2016
2455
|
}
|
|
2017
2456
|
const base = extractBaseOutputs(outputs);
|
|
2018
2457
|
conn = {
|
|
@@ -2036,7 +2475,7 @@ var wireCmd = defineCommand17({
|
|
|
2036
2475
|
spin.fail("This gateway has no `openclaw mcp add`, so clawops cannot wire it.");
|
|
2037
2476
|
info("OpenClaw 2026.4.5 ships `openclaw mcp` with only `list` and `serve`.");
|
|
2038
2477
|
info("Upgrade the gateway to 2026.7.1-2 or later, then re-run this command.");
|
|
2039
|
-
|
|
2478
|
+
process20.exit(1);
|
|
2040
2479
|
}
|
|
2041
2480
|
if (result.status === "exists") {
|
|
2042
2481
|
spin.info("The gateway already has a clawops MCP server configured.");
|
|
@@ -2049,7 +2488,7 @@ var wireCmd = defineCommand17({
|
|
|
2049
2488
|
warn("clawops does not run on the gateway host. Start it where the gateway can reach it:");
|
|
2050
2489
|
info(` clawops mcp serve --http ${MCP_HTTP_PORT} --bind 0.0.0.0 --token <token>`);
|
|
2051
2490
|
info(`Then re-run: clawops mcp wire --url ${result.url} --token <token>`);
|
|
2052
|
-
|
|
2491
|
+
process20.exit(1);
|
|
2053
2492
|
}
|
|
2054
2493
|
spin.succeed(
|
|
2055
2494
|
result.rewired ? "Re-wired the gateway to clawops \u2014 previous entry replaced." : "Gateway wired to clawops, and the connection was verified."
|
|
@@ -2060,14 +2499,14 @@ var wireCmd = defineCommand17({
|
|
|
2060
2499
|
} catch (err) {
|
|
2061
2500
|
spin.fail("Failed to wire gateway MCP client.");
|
|
2062
2501
|
failure(err instanceof Error ? err.message : String(err));
|
|
2063
|
-
|
|
2502
|
+
process20.exit(1);
|
|
2064
2503
|
} finally {
|
|
2065
2504
|
release();
|
|
2066
2505
|
drainPool();
|
|
2067
2506
|
}
|
|
2068
2507
|
}
|
|
2069
2508
|
});
|
|
2070
|
-
var mcp_default =
|
|
2509
|
+
var mcp_default = defineCommand18({
|
|
2071
2510
|
meta: {
|
|
2072
2511
|
name: "mcp",
|
|
2073
2512
|
description: "MCP server operations"
|
|
@@ -2081,14 +2520,14 @@ var mcp_default = defineCommand17({
|
|
|
2081
2520
|
});
|
|
2082
2521
|
|
|
2083
2522
|
// src/cli/commands/setup.ts
|
|
2084
|
-
import { defineCommand as
|
|
2085
|
-
import
|
|
2523
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
2524
|
+
import process21 from "process";
|
|
2086
2525
|
import os from "os";
|
|
2087
2526
|
import { randomBytes } from "crypto";
|
|
2088
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as
|
|
2089
|
-
import { execSync, spawnSync, spawn as spawn2 } from "child_process";
|
|
2090
|
-
import
|
|
2091
|
-
var setup_default =
|
|
2527
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
2528
|
+
import { execSync, spawnSync as spawnSync2, spawn as spawn2 } from "child_process";
|
|
2529
|
+
import path2 from "path";
|
|
2530
|
+
var setup_default = defineCommand19({
|
|
2092
2531
|
meta: {
|
|
2093
2532
|
name: "setup",
|
|
2094
2533
|
description: "Interactive wizard: configure and deploy an OpenClaw stack"
|
|
@@ -2103,16 +2542,16 @@ var setup_default = defineCommand18({
|
|
|
2103
2542
|
const catalogs = loadCatalogs(yaml);
|
|
2104
2543
|
const dryRun = Boolean(args["dry-run"]);
|
|
2105
2544
|
const ac = new AbortController();
|
|
2106
|
-
|
|
2545
|
+
process21.on("SIGINT", () => {
|
|
2107
2546
|
ac.abort();
|
|
2108
|
-
|
|
2547
|
+
process21.exit(130);
|
|
2109
2548
|
});
|
|
2110
|
-
|
|
2549
|
+
process21.on("SIGTERM", () => {
|
|
2111
2550
|
ac.abort();
|
|
2112
|
-
|
|
2551
|
+
process21.exit(143);
|
|
2113
2552
|
});
|
|
2114
|
-
|
|
2115
|
-
|
|
2553
|
+
process21.stdout.write("\nWelcome to clawops setup. This wizard will help you deploy OpenClaw\n");
|
|
2554
|
+
process21.stdout.write("and connect it to your AI tools. It takes about 2 minutes.\n\n");
|
|
2116
2555
|
const { deploymentType } = await inquirer.prompt([{
|
|
2117
2556
|
type: "list",
|
|
2118
2557
|
name: "deploymentType",
|
|
@@ -2140,7 +2579,7 @@ var setup_default = defineCommand18({
|
|
|
2140
2579
|
]
|
|
2141
2580
|
}]);
|
|
2142
2581
|
provider = answer.provider;
|
|
2143
|
-
|
|
2582
|
+
process21.stdout.write("\n");
|
|
2144
2583
|
await ensureCloudAuth(provider, inquirer);
|
|
2145
2584
|
} else {
|
|
2146
2585
|
provider = "local";
|
|
@@ -2196,7 +2635,16 @@ var setup_default = defineCommand18({
|
|
|
2196
2635
|
}]);
|
|
2197
2636
|
localSudoPassword = sudoPass;
|
|
2198
2637
|
}
|
|
2199
|
-
|
|
2638
|
+
process21.stdout.write("\n");
|
|
2639
|
+
const sshCidrDefault = provider === "local" ? void 0 : await detectSshCidrDefault();
|
|
2640
|
+
const scopeAccount = provider === "local" ? void 0 : await resolveScopeAccount(provider);
|
|
2641
|
+
if (provider !== "local" && provider !== "azure" && !scopeAccount) {
|
|
2642
|
+
const needed = deriveStateBucket(provider, {});
|
|
2643
|
+
if (!needed.ok) {
|
|
2644
|
+
warn(`clawops cannot suggest a name for the state backend: it needs ${needed.needs}.`);
|
|
2645
|
+
info("Type the name of a bucket you already have, or quit, authenticate, and start again.\n");
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2200
2648
|
const stackAnswers = await inquirer.prompt([
|
|
2201
2649
|
{
|
|
2202
2650
|
type: "input",
|
|
@@ -2218,12 +2666,7 @@ var setup_default = defineCommand18({
|
|
|
2218
2666
|
message: "Server size: (bigger = faster, but costs more per month)",
|
|
2219
2667
|
choices: instanceChoices(provider)
|
|
2220
2668
|
},
|
|
2221
|
-
|
|
2222
|
-
type: "input",
|
|
2223
|
-
name: "stateBucket",
|
|
2224
|
-
message: `${stateLabel(provider)} bucket name: (a storage bucket that tracks what's deployed \u2014 create one first if you haven't)`,
|
|
2225
|
-
validate: (v) => v.trim() !== "" || "Required \u2014 create a bucket in your cloud console first"
|
|
2226
|
-
},
|
|
2669
|
+
stateBucketQuestion(provider, scopeAccount),
|
|
2227
2670
|
{
|
|
2228
2671
|
type: "input",
|
|
2229
2672
|
name: "sshKeyPath",
|
|
@@ -2233,8 +2676,13 @@ var setup_default = defineCommand18({
|
|
|
2233
2676
|
{
|
|
2234
2677
|
type: "input",
|
|
2235
2678
|
name: "sshCidr",
|
|
2236
|
-
message: "Restrict SSH access to: (your IP
|
|
2237
|
-
|
|
2679
|
+
message: sshCidrDefault ? "Restrict SSH access to: (detected your current IP \u2014 press Enter to accept, or enter another CIDR)" : "Restrict SSH access to: (a CIDR such as 203.0.113.4/32 \u2014 your own IP is the usual answer)",
|
|
2680
|
+
// Defaulted to 0.0.0.0/0 until 2.0, so pressing Enter opened SSH to the whole
|
|
2681
|
+
// internet on the path most first-time users take. N10 forbids exactly that
|
|
2682
|
+
// default. When the egress IP cannot be detected there is no default at all and
|
|
2683
|
+
// the answer is required — an unanswerable prompt is better than a wide one.
|
|
2684
|
+
...sshCidrDefault ? { default: sshCidrDefault } : {},
|
|
2685
|
+
validate: validateCidrAnswer
|
|
2238
2686
|
}
|
|
2239
2687
|
] : [],
|
|
2240
2688
|
{
|
|
@@ -2244,7 +2692,24 @@ var setup_default = defineCommand18({
|
|
|
2244
2692
|
default: "latest"
|
|
2245
2693
|
}
|
|
2246
2694
|
]);
|
|
2247
|
-
|
|
2695
|
+
if (provider !== "local") {
|
|
2696
|
+
await runAccountPreflight({
|
|
2697
|
+
provider,
|
|
2698
|
+
region: typeof stackAnswers.region === "string" ? stackAnswers.region : void 0,
|
|
2699
|
+
bucket: stackAnswers.stateBucket,
|
|
2700
|
+
// The size was chosen three questions ago. Azure offers SKU families per subscription,
|
|
2701
|
+
// so asking about the provider default here would clear a size the operator is not
|
|
2702
|
+
// deploying and say nothing about the one they are.
|
|
2703
|
+
instanceType: typeof stackAnswers.instanceSize === "string" ? stackAnswers.instanceSize : void 0,
|
|
2704
|
+
inquirer
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
if (provider !== "local" && admitsInternet(stackAnswers.sshCidr)) {
|
|
2708
|
+
warn(
|
|
2709
|
+
"SSH will be reachable from anywhere on the internet. `clawops harden` reports this as a finding; narrowing it to your own IP is the usual fix."
|
|
2710
|
+
);
|
|
2711
|
+
}
|
|
2712
|
+
process21.stdout.write("\n");
|
|
2248
2713
|
info("Choose the AI model that your OpenClaw agent will use.");
|
|
2249
2714
|
info("You'll need an API key from your chosen provider (except Bedrock and Ollama).\n");
|
|
2250
2715
|
const { modelProviderId } = await inquirer.prompt([{
|
|
@@ -2256,6 +2721,8 @@ var setup_default = defineCommand18({
|
|
|
2256
2721
|
const modelProvider = catalogs.models.find((p) => p.id === modelProviderId);
|
|
2257
2722
|
const modelConfig = {};
|
|
2258
2723
|
const secrets = [];
|
|
2724
|
+
let resolvedModelId;
|
|
2725
|
+
let bedrockRegion;
|
|
2259
2726
|
const { selectedModelId } = await inquirer.prompt([{
|
|
2260
2727
|
type: "list",
|
|
2261
2728
|
name: "selectedModelId",
|
|
@@ -2273,9 +2740,24 @@ var setup_default = defineCommand18({
|
|
|
2273
2740
|
secrets.push(entry);
|
|
2274
2741
|
modelConfig["apiKey"] = `$secret:${secretName}`;
|
|
2275
2742
|
} else if (modelProvider.credentialSource === "aws-profile") {
|
|
2276
|
-
|
|
2743
|
+
process21.stdout.write("\n");
|
|
2277
2744
|
info(modelProvider.iamNote ?? "Bedrock uses your AWS server's IAM role \u2014 no API key needed.");
|
|
2278
|
-
|
|
2745
|
+
bedrockRegion = typeof stackAnswers.region === "string" && stackAnswers.region ? stackAnswers.region : defaultRegion("aws");
|
|
2746
|
+
const { fetchInferenceProfiles, resolveInferenceProfile } = await import("./bedrock-XVASJX2K.js");
|
|
2747
|
+
const listed = await fetchInferenceProfiles(bedrockRegion);
|
|
2748
|
+
if (!listed.ok) {
|
|
2749
|
+
warn(listed.error);
|
|
2750
|
+
info("Continuing with the foundation-model id; Bedrock will refuse it until this is fixed.");
|
|
2751
|
+
} else {
|
|
2752
|
+
const wanted = selectedModel.modelId ?? selectedModel.id;
|
|
2753
|
+
const resolution = resolveInferenceProfile(wanted, listed.profiles, bedrockRegion);
|
|
2754
|
+
if (!resolution.ok) {
|
|
2755
|
+
const { UsageError: UsageError2 } = await import("./errors-OK47MQFD.js");
|
|
2756
|
+
throw new UsageError2(resolution.error);
|
|
2757
|
+
}
|
|
2758
|
+
resolvedModelId = resolution.profileId;
|
|
2759
|
+
info(`Using inference profile ${resolution.profileId} (${resolution.why}).`);
|
|
2760
|
+
}
|
|
2279
2761
|
} else if (modelProvider.id === "ollama") {
|
|
2280
2762
|
const { baseUrl } = await inquirer.prompt([{
|
|
2281
2763
|
type: "input",
|
|
@@ -2289,18 +2771,22 @@ var setup_default = defineCommand18({
|
|
|
2289
2771
|
}
|
|
2290
2772
|
modelConfig["baseUrl"] = rewritten;
|
|
2291
2773
|
}
|
|
2292
|
-
const
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2774
|
+
const { buildModelsBlock } = await import("./models-AGTQWYDL.js");
|
|
2775
|
+
const builtModelConfig = buildModelsBlock({
|
|
2776
|
+
provider: modelProvider,
|
|
2777
|
+
model: { id: selectedModel.id, modelId: selectedModel.modelId, displayName: selectedModel.displayName },
|
|
2778
|
+
resolvedModelId,
|
|
2779
|
+
apiKeyRef: typeof modelConfig["apiKey"] === "string" ? modelConfig["apiKey"] : void 0,
|
|
2780
|
+
baseUrl: typeof modelConfig["baseUrl"] === "string" ? modelConfig["baseUrl"] : void 0,
|
|
2781
|
+
region: modelProvider.credentialSource === "aws-profile" ? bedrockRegion : void 0
|
|
2782
|
+
});
|
|
2783
|
+
process21.stdout.write("\n");
|
|
2298
2784
|
info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
|
|
2299
2785
|
const { selectedIntegrationIds } = await inquirer.prompt([{
|
|
2300
2786
|
type: "checkbox",
|
|
2301
2787
|
name: "selectedIntegrationIds",
|
|
2302
2788
|
message: "Which chat integrations would you like to enable?",
|
|
2303
|
-
choices: catalogs.integrations.map((integ) => ({
|
|
2789
|
+
choices: wizardChannels(catalogs.integrations).map((integ) => ({
|
|
2304
2790
|
name: `${integ.displayName} \u2014 ${integ.description}`,
|
|
2305
2791
|
value: integ.id,
|
|
2306
2792
|
checked: false
|
|
@@ -2310,9 +2796,9 @@ var setup_default = defineCommand18({
|
|
|
2310
2796
|
const channelsConfig = {};
|
|
2311
2797
|
const infraRequired = [];
|
|
2312
2798
|
for (const integ of catalogs.integrations.filter((i) => selectedIntegrationIds.includes(i.id))) {
|
|
2313
|
-
|
|
2799
|
+
process21.stdout.write("\n");
|
|
2314
2800
|
info(`Setting up ${integ.displayName}:`);
|
|
2315
|
-
const channelConfig =
|
|
2801
|
+
const channelConfig = startChannelConfig(integ);
|
|
2316
2802
|
for (const field of integ.fields) {
|
|
2317
2803
|
if (field.sensitive && field.envDefault) {
|
|
2318
2804
|
const entry = await promptSecret(field.envDefault, `${integ.displayName} ${field.label}`, field.envDefault, inquirer);
|
|
@@ -2328,6 +2814,15 @@ var setup_default = defineCommand18({
|
|
|
2328
2814
|
channelConfig[field.name] = value;
|
|
2329
2815
|
}
|
|
2330
2816
|
}
|
|
2817
|
+
if (integ.useEnvSupported === false) {
|
|
2818
|
+
warn(`${integ.displayName} has no non-interactive setup in OpenClaw.`);
|
|
2819
|
+
info(` Run \`openclaw channels add --channel ${integ.channelKey}\` on the host, interactively.`);
|
|
2820
|
+
} else if (integ.plugin?.source === "bundled") {
|
|
2821
|
+
info(`${integ.displayName} ships in the image. Activate it on the host with:`);
|
|
2822
|
+
info(` openclaw channels add --channel ${integ.channelKey} --use-env`);
|
|
2823
|
+
} else if (integ.plugin?.package) {
|
|
2824
|
+
info(`${integ.displayName} needs ${integ.plugin.package}; clawops installs it during apply.`);
|
|
2825
|
+
}
|
|
2331
2826
|
channelsConfig[integ.channelKey] = channelConfig;
|
|
2332
2827
|
if (integ.infraRequired) infraRequired.push(integ);
|
|
2333
2828
|
}
|
|
@@ -2346,10 +2841,10 @@ var setup_default = defineCommand18({
|
|
|
2346
2841
|
}
|
|
2347
2842
|
}
|
|
2348
2843
|
const gatewayToken = randomBytes(24).toString("hex");
|
|
2349
|
-
const secretsDir2 =
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
const tokenPath =
|
|
2844
|
+
const secretsDir2 = path2.join(os.homedir(), ".clawops", "secrets");
|
|
2845
|
+
mkdirSync2(secretsDir2, { recursive: true });
|
|
2846
|
+
spawnSync2("chmod", ["700", secretsDir2], { stdio: "ignore" });
|
|
2847
|
+
const tokenPath = path2.join(secretsDir2, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
|
|
2353
2848
|
writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
|
|
2354
2849
|
const openclawConfigOverlay = {
|
|
2355
2850
|
models: builtModelConfig,
|
|
@@ -2360,9 +2855,9 @@ var setup_default = defineCommand18({
|
|
|
2360
2855
|
}
|
|
2361
2856
|
let outputPath;
|
|
2362
2857
|
if (provider === "local") {
|
|
2363
|
-
outputPath =
|
|
2858
|
+
outputPath = path2.join(outDir, `openclaw-${stackAnswers.stackName}.json`);
|
|
2364
2859
|
writeFileSync3(outputPath, JSON.stringify(openclawConfigOverlay, null, 2), "utf-8");
|
|
2365
|
-
|
|
2860
|
+
process21.stdout.write("\n");
|
|
2366
2861
|
success(`Config file saved to ${outputPath}`);
|
|
2367
2862
|
} else {
|
|
2368
2863
|
let sshPublicKey = "";
|
|
@@ -2370,7 +2865,7 @@ var setup_default = defineCommand18({
|
|
|
2370
2865
|
sshPublicKey = readFileSync3(stackAnswers.sshKeyPath ?? "", "utf-8").trim();
|
|
2371
2866
|
} catch {
|
|
2372
2867
|
failure(`Cannot read SSH public key at ${stackAnswers.sshKeyPath ?? ""}`);
|
|
2373
|
-
|
|
2868
|
+
process21.exit(1);
|
|
2374
2869
|
}
|
|
2375
2870
|
const plan = {
|
|
2376
2871
|
apiVersion: "clawops.dev/v1",
|
|
@@ -2392,19 +2887,26 @@ var setup_default = defineCommand18({
|
|
|
2392
2887
|
},
|
|
2393
2888
|
secrets,
|
|
2394
2889
|
network: {
|
|
2395
|
-
|
|
2396
|
-
|
|
2890
|
+
// No `?? '0.0.0.0/0'`. The prompt requires an answer now, and a fallback that
|
|
2891
|
+
// opened the internet would reintroduce the default it replaced.
|
|
2892
|
+
allowedSshCidrs: [stackAnswers.sshCidr.trim()],
|
|
2893
|
+
// Empty, not the SSH CIDR. Reusing it opened a plaintext HTTP dashboard to
|
|
2894
|
+
// whatever network the operator picked for shell access — two very different
|
|
2895
|
+
// risks answered by one question. The gateway is reached with
|
|
2896
|
+
// `clawops tunnel`; exposing it is an explicit choice (network.publishGateway).
|
|
2897
|
+
allowedGatewayCidrs: [],
|
|
2898
|
+
publishGateway: "loopback"
|
|
2397
2899
|
},
|
|
2398
2900
|
ssh: { publicKey: sshPublicKey },
|
|
2399
2901
|
...stackAnswers.stateBucket ? { stateBucket: stackAnswers.stateBucket } : {}
|
|
2400
2902
|
}
|
|
2401
2903
|
};
|
|
2402
|
-
outputPath =
|
|
2904
|
+
outputPath = path2.join(outDir, `clawops-${stackAnswers.stackName}-plan.json`);
|
|
2403
2905
|
writeFileSync3(outputPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
2404
|
-
|
|
2906
|
+
process21.stdout.write("\n");
|
|
2405
2907
|
success(`Deployment plan saved to ${outputPath}`);
|
|
2406
2908
|
}
|
|
2407
|
-
|
|
2909
|
+
process21.stdout.write("\n");
|
|
2408
2910
|
const mcpEntry = buildMcpEntry();
|
|
2409
2911
|
if (!mcpEntry.resolved) {
|
|
2410
2912
|
info("Note: clawops is not installed globally \u2014 AI apps will not be able to start");
|
|
@@ -2450,7 +2952,7 @@ var setup_default = defineCommand18({
|
|
|
2450
2952
|
}
|
|
2451
2953
|
}
|
|
2452
2954
|
if (infraRequired.length > 0) {
|
|
2453
|
-
|
|
2955
|
+
process21.stdout.write("\n");
|
|
2454
2956
|
info("\u2500\u2500 Action required: webhook registration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2455
2957
|
info("These integrations need a webhook URL set up in their developer portal:");
|
|
2456
2958
|
for (const integ of infraRequired) {
|
|
@@ -2461,7 +2963,7 @@ var setup_default = defineCommand18({
|
|
|
2461
2963
|
}
|
|
2462
2964
|
}
|
|
2463
2965
|
if (modelProvider.id === "ollama" && selectedModel.pullSuffix !== void 0) {
|
|
2464
|
-
|
|
2966
|
+
process21.stdout.write("\n");
|
|
2465
2967
|
info("\u2500\u2500 Action required: download the Ollama model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2466
2968
|
info("After deployment, run this command on the server (or locally if Ollama is local):");
|
|
2467
2969
|
info(` ollama pull ${selectedModel.id}${selectedModel.pullSuffix}`);
|
|
@@ -2506,8 +3008,8 @@ var setup_default = defineCommand18({
|
|
|
2506
3008
|
});
|
|
2507
3009
|
} else {
|
|
2508
3010
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2509
|
-
const knownHostsPath =
|
|
2510
|
-
|
|
3011
|
+
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
3012
|
+
process21.stdout.write("\n");
|
|
2511
3013
|
info("To deploy later, run these two commands:");
|
|
2512
3014
|
info(` clawops init --provider local --host ${localHost} --port ${localPort} --user ${localUser} --key ${localKeyPath} --stack ${stackAnswers.stackName}`);
|
|
2513
3015
|
info(` clawops up --stack ${stackAnswers.stackName} --config ${outputPath}`);
|
|
@@ -2522,18 +3024,18 @@ var setup_default = defineCommand18({
|
|
|
2522
3024
|
default: false
|
|
2523
3025
|
}]);
|
|
2524
3026
|
if (applyNow) {
|
|
2525
|
-
const { applyPlan } = await import("./apply-
|
|
3027
|
+
const { applyPlan } = await import("./apply-KYFPWI5U.js");
|
|
2526
3028
|
const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
|
|
2527
3029
|
try {
|
|
2528
3030
|
await applyPlan(plan, {
|
|
2529
|
-
onOutput: (
|
|
3031
|
+
onOutput: (line2) => process21.stdout.write(line2),
|
|
2530
3032
|
signal: ac.signal
|
|
2531
3033
|
});
|
|
2532
3034
|
success("Server provisioned and OpenClaw installed.");
|
|
2533
3035
|
info(`Run: clawops doctor --stack ${stackAnswers.stackName} to verify everything is healthy`);
|
|
2534
3036
|
} catch (err) {
|
|
2535
3037
|
failure(err instanceof Error ? err.message : String(err));
|
|
2536
|
-
|
|
3038
|
+
process21.exit(1);
|
|
2537
3039
|
}
|
|
2538
3040
|
await maybeWireGatewayMcpCloud({ stackName: stackAnswers.stackName, signal: ac.signal, inquirer });
|
|
2539
3041
|
await maybeHarden({
|
|
@@ -2551,11 +3053,11 @@ To deploy later: clawops apply ${outputPath}`);
|
|
|
2551
3053
|
});
|
|
2552
3054
|
async function runLocalDeploy(opts) {
|
|
2553
3055
|
const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2554
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
2555
|
-
const { connect } = await import("./ssh-
|
|
2556
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
3056
|
+
const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
|
|
3057
|
+
const { connect } = await import("./ssh-VAI5DU5M.js");
|
|
3058
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
|
|
2557
3059
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
2558
|
-
const knownHostsPath =
|
|
3060
|
+
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
2559
3061
|
const existing = getConfig2();
|
|
2560
3062
|
const stackEntry = {
|
|
2561
3063
|
provider: "local",
|
|
@@ -2593,9 +3095,9 @@ async function runLocalDeploy(opts) {
|
|
|
2593
3095
|
stackName: opts.stackName,
|
|
2594
3096
|
sudoPassword: opts.sudoPassword,
|
|
2595
3097
|
noWait: false,
|
|
2596
|
-
onOutput: (
|
|
3098
|
+
onOutput: (line2) => {
|
|
2597
3099
|
for (const [pattern, label] of STAGES) {
|
|
2598
|
-
if (pattern.test(
|
|
3100
|
+
if (pattern.test(line2)) {
|
|
2599
3101
|
spin.text = label;
|
|
2600
3102
|
break;
|
|
2601
3103
|
}
|
|
@@ -2634,11 +3136,11 @@ async function runLocalDeploy(opts) {
|
|
|
2634
3136
|
spin2.fail("Configuration failed");
|
|
2635
3137
|
throw err;
|
|
2636
3138
|
}
|
|
2637
|
-
const { drainPool } = await import("./pool-
|
|
3139
|
+
const { drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
2638
3140
|
drainPool();
|
|
2639
|
-
const tokenPath =
|
|
3141
|
+
const tokenPath = path2.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
|
|
2640
3142
|
const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
|
|
2641
|
-
|
|
3143
|
+
process21.stdout.write("\n");
|
|
2642
3144
|
success("All done! OpenClaw is running.");
|
|
2643
3145
|
info(`Open dashboard: ${dashboardUrl}`);
|
|
2644
3146
|
info(` (or enter the token manually in the "Gateway Token" field: ${opts.gatewayToken})`);
|
|
@@ -2693,8 +3195,8 @@ ${cfg.loginHint}`);
|
|
|
2693
3195
|
default: true
|
|
2694
3196
|
}]);
|
|
2695
3197
|
if (runNow) {
|
|
2696
|
-
|
|
2697
|
-
const result =
|
|
3198
|
+
process21.stdout.write("\n");
|
|
3199
|
+
const result = spawnSync2(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
|
|
2698
3200
|
if (result.error) {
|
|
2699
3201
|
failure(`Could not launch ${cliName} \u2014 is it installed? (${result.error.message})`);
|
|
2700
3202
|
info(`Install guide: ${cliInstallUrl(provider)}`);
|
|
@@ -2703,10 +3205,10 @@ ${cfg.loginHint}`);
|
|
|
2703
3205
|
}
|
|
2704
3206
|
try {
|
|
2705
3207
|
const identity = cfg.check();
|
|
2706
|
-
|
|
3208
|
+
process21.stdout.write("\n");
|
|
2707
3209
|
success(`Authenticated: ${cfg.identity(identity)}`);
|
|
2708
3210
|
} catch {
|
|
2709
|
-
|
|
3211
|
+
process21.stdout.write("\n");
|
|
2710
3212
|
failure(`Still not authenticated \u2014 the sign-in may have been cancelled or failed.`);
|
|
2711
3213
|
info("Re-run `clawops setup` after signing in, or continue and authenticate before running `clawops up`.\n");
|
|
2712
3214
|
}
|
|
@@ -2720,7 +3222,7 @@ function cliInstallUrl(provider) {
|
|
|
2720
3222
|
return "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli";
|
|
2721
3223
|
}
|
|
2722
3224
|
async function promptSecret(name, label, envDefault, inquirer) {
|
|
2723
|
-
const secretsDir2 =
|
|
3225
|
+
const secretsDir2 = path2.join(os.homedir(), ".clawops", "secrets");
|
|
2724
3226
|
const { secretSource } = await inquirer.prompt([{
|
|
2725
3227
|
type: "list",
|
|
2726
3228
|
name: "secretSource",
|
|
@@ -2738,9 +3240,9 @@ async function promptSecret(name, label, envDefault, inquirer) {
|
|
|
2738
3240
|
message: `Paste your ${label}: (input is hidden)`,
|
|
2739
3241
|
validate: (v) => v.trim() !== "" || "Value cannot be empty"
|
|
2740
3242
|
}]);
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
const secretPath2 =
|
|
3243
|
+
mkdirSync2(secretsDir2, { recursive: true });
|
|
3244
|
+
spawnSync2("chmod", ["700", secretsDir2], { stdio: "ignore" });
|
|
3245
|
+
const secretPath2 = path2.join(secretsDir2, name);
|
|
2744
3246
|
writeFileSync3(secretPath2, secretValue.trim(), { encoding: "utf-8", mode: 384 });
|
|
2745
3247
|
success(`Secret saved to ${secretPath2} (chmod 600)`);
|
|
2746
3248
|
return { name, source: "file", ref: secretPath2 };
|
|
@@ -2772,7 +3274,7 @@ var DOCKER_CHECK_CMD = [
|
|
|
2772
3274
|
"echo NOT_RUNNING"
|
|
2773
3275
|
].join("; ");
|
|
2774
3276
|
async function checkDockerPreflight(opts) {
|
|
2775
|
-
const { acquireSession } = await import("./pool-
|
|
3277
|
+
const { acquireSession } = await import("./pool-PIIKJ4L2.js");
|
|
2776
3278
|
const { ProviderError } = await import("./errors-OK47MQFD.js");
|
|
2777
3279
|
const spin = spinner("Checking Docker on target host...");
|
|
2778
3280
|
const timeoutSignal = AbortSignal.timeout(DOCKER_PREFLIGHT_TIMEOUT_MS);
|
|
@@ -2787,7 +3289,7 @@ async function checkDockerPreflight(opts) {
|
|
|
2787
3289
|
});
|
|
2788
3290
|
let out = "";
|
|
2789
3291
|
try {
|
|
2790
|
-
const result = await session
|
|
3292
|
+
const result = await execPrivileged(session, DOCKER_CHECK_CMD, execSignal);
|
|
2791
3293
|
out = result.stdout.trim();
|
|
2792
3294
|
} catch {
|
|
2793
3295
|
spin.warn("Docker check timed out \u2014 proceeding anyway. Bootstrap will verify.");
|
|
@@ -2819,7 +3321,7 @@ async function checkDockerPreflight(opts) {
|
|
|
2819
3321
|
var DOCKER_POLL_MS = 3e3;
|
|
2820
3322
|
var DOCKER_TIMEOUT_MS = 9e4;
|
|
2821
3323
|
async function startRemoteDocker(opts, spin) {
|
|
2822
|
-
const { acquireSession } = await import("./pool-
|
|
3324
|
+
const { acquireSession } = await import("./pool-PIIKJ4L2.js");
|
|
2823
3325
|
const { ProviderError } = await import("./errors-OK47MQFD.js");
|
|
2824
3326
|
spin.text = "Starting Docker on the remote server...";
|
|
2825
3327
|
const { session, release } = await acquireSession({
|
|
@@ -2845,7 +3347,8 @@ async function startRemoteDocker(opts, spin) {
|
|
|
2845
3347
|
while (Date.now() < deadline) {
|
|
2846
3348
|
await new Promise((r) => setTimeout(r, DOCKER_POLL_MS));
|
|
2847
3349
|
spin.text = `Waiting for Docker to start on ${opts.host}...`;
|
|
2848
|
-
const check = await
|
|
3350
|
+
const check = await execPrivileged(
|
|
3351
|
+
session,
|
|
2849
3352
|
"docker version >/dev/null 2>&1 && echo OK || echo NOT_RUNNING",
|
|
2850
3353
|
opts.signal
|
|
2851
3354
|
);
|
|
@@ -2891,7 +3394,7 @@ async function pollUntilDockerReady(label) {
|
|
|
2891
3394
|
throw new Error("Docker daemon did not become ready within timeout.");
|
|
2892
3395
|
}
|
|
2893
3396
|
async function startDockerAndWait(inquirer) {
|
|
2894
|
-
const isLinux =
|
|
3397
|
+
const isLinux = process21.platform === "linux";
|
|
2895
3398
|
const { choice } = await inquirer.prompt([{
|
|
2896
3399
|
type: "list",
|
|
2897
3400
|
name: "choice",
|
|
@@ -2915,7 +3418,7 @@ async function startDockerAndWait(inquirer) {
|
|
|
2915
3418
|
await pollUntilDockerReady("Waiting for Docker daemon...");
|
|
2916
3419
|
} else if (choice === "systemctl") {
|
|
2917
3420
|
const spin = spinner("Running: sudo systemctl start docker...");
|
|
2918
|
-
const res =
|
|
3421
|
+
const res = spawnSync2("sudo", ["systemctl", "start", "docker"], { stdio: "pipe" });
|
|
2919
3422
|
if (res.status !== 0) {
|
|
2920
3423
|
spin.fail("systemctl start docker failed.");
|
|
2921
3424
|
throw new Error("systemctl start docker failed");
|
|
@@ -2923,18 +3426,18 @@ async function startDockerAndWait(inquirer) {
|
|
|
2923
3426
|
spin.succeed("Docker service started.");
|
|
2924
3427
|
await pollUntilDockerReady("Waiting for Docker daemon to be reachable...");
|
|
2925
3428
|
} else if (choice === "desktop") {
|
|
2926
|
-
const backendRunning =
|
|
3429
|
+
const backendRunning = spawnSync2("pgrep", ["-f", "com.docker.backend"], { stdio: "ignore" }).status === 0;
|
|
2927
3430
|
if (backendRunning) {
|
|
2928
3431
|
const killSpin = spinner("Docker Desktop is stuck \u2014 force-restarting...");
|
|
2929
3432
|
for (const pat of ["com.docker.backend", "com.docker.virtualization", "com.docker.build", "Docker Desktop"]) {
|
|
2930
|
-
|
|
3433
|
+
spawnSync2("pkill", ["-9", "-f", pat], { stdio: "ignore" });
|
|
2931
3434
|
}
|
|
2932
3435
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
2933
3436
|
killSpin.succeed("Old Docker processes cleared.");
|
|
2934
3437
|
}
|
|
2935
3438
|
const openSpin = spinner("Launching Docker Desktop...");
|
|
2936
|
-
let opened =
|
|
2937
|
-
if (opened.status !== 0) opened =
|
|
3439
|
+
let opened = spawnSync2("open", ["-a", "Docker"], { stdio: "ignore" });
|
|
3440
|
+
if (opened.status !== 0) opened = spawnSync2("open", ["/Applications/Docker.app"], { stdio: "ignore" });
|
|
2938
3441
|
if (opened.status !== 0) {
|
|
2939
3442
|
openSpin.fail("Could not open Docker Desktop \u2014 is it installed in /Applications?");
|
|
2940
3443
|
throw new Error("Failed to open Docker Desktop");
|
|
@@ -2943,7 +3446,7 @@ async function startDockerAndWait(inquirer) {
|
|
|
2943
3446
|
await pollUntilDockerReady("Waiting for Docker engine to start...");
|
|
2944
3447
|
} else if (choice === "colima") {
|
|
2945
3448
|
const spin = spinner("Running: colima start...");
|
|
2946
|
-
const res =
|
|
3449
|
+
const res = spawnSync2("colima", ["start"], { stdio: "pipe" });
|
|
2947
3450
|
if (res.status !== 0) {
|
|
2948
3451
|
spin.fail("colima start failed \u2014 is Colima installed? (brew install colima docker)");
|
|
2949
3452
|
throw new Error("colima start failed");
|
|
@@ -2958,7 +3461,7 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2958
3461
|
if (!existsSync2(pubKeyPath)) return;
|
|
2959
3462
|
const pubKey = readFileSync3(pubKeyPath, "utf-8").trim();
|
|
2960
3463
|
const isLocal = LOCALHOST_ALIASES.has(host.toLowerCase());
|
|
2961
|
-
const authorizedKeysPath =
|
|
3464
|
+
const authorizedKeysPath = path2.join(os.homedir(), ".ssh", "authorized_keys");
|
|
2962
3465
|
if (isLocal) {
|
|
2963
3466
|
const existing = existsSync2(authorizedKeysPath) ? readFileSync3(authorizedKeysPath, "utf-8") : "";
|
|
2964
3467
|
if (existing.includes(pubKey)) {
|
|
@@ -2973,26 +3476,26 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2973
3476
|
default: true
|
|
2974
3477
|
}]);
|
|
2975
3478
|
if (addKey) {
|
|
2976
|
-
|
|
3479
|
+
mkdirSync2(path2.join(os.homedir(), ".ssh"), { recursive: true });
|
|
2977
3480
|
const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
|
|
2978
3481
|
writeFileSync3(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
|
|
2979
|
-
|
|
3482
|
+
spawnSync2("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
|
|
2980
3483
|
success("Public key added to ~/.ssh/authorized_keys.");
|
|
2981
3484
|
} else {
|
|
2982
3485
|
info("SSH will likely fail. Add this line to ~/.ssh/authorized_keys manually:");
|
|
2983
3486
|
info(` ${pubKey}`);
|
|
2984
3487
|
}
|
|
2985
3488
|
} else {
|
|
2986
|
-
|
|
3489
|
+
process21.stdout.write("\n");
|
|
2987
3490
|
info("Make sure this public key is in ~/.ssh/authorized_keys on your server:");
|
|
2988
3491
|
info(` ${pubKey}`);
|
|
2989
3492
|
info("(If you just created the server, paste the line above into the server's ~/.ssh/authorized_keys file.)\n");
|
|
2990
3493
|
}
|
|
2991
3494
|
}
|
|
2992
3495
|
async function generateSshKey() {
|
|
2993
|
-
const keyPath =
|
|
2994
|
-
|
|
2995
|
-
const result =
|
|
3496
|
+
const keyPath = path2.join(os.homedir(), ".ssh", "id_ed25519");
|
|
3497
|
+
mkdirSync2(path2.join(os.homedir(), ".ssh"), { recursive: true });
|
|
3498
|
+
const result = spawnSync2("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
|
|
2996
3499
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2997
3500
|
});
|
|
2998
3501
|
if (result.status !== 0) {
|
|
@@ -3006,27 +3509,27 @@ async function generateSshKey() {
|
|
|
3006
3509
|
function detectSshKey() {
|
|
3007
3510
|
const candidates = ["id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"];
|
|
3008
3511
|
for (const name of candidates) {
|
|
3009
|
-
const p =
|
|
3512
|
+
const p = path2.join(os.homedir(), ".ssh", name);
|
|
3010
3513
|
if (existsSync2(p)) return p;
|
|
3011
3514
|
}
|
|
3012
|
-
return
|
|
3515
|
+
return path2.join(os.homedir(), ".ssh", "id_ed25519");
|
|
3013
3516
|
}
|
|
3014
3517
|
function printMcpSnippet() {
|
|
3015
|
-
|
|
3016
|
-
|
|
3518
|
+
process21.stdout.write("\n");
|
|
3519
|
+
process21.stdout.write(JSON.stringify({
|
|
3017
3520
|
mcpServers: {
|
|
3018
3521
|
clawops: { command: "clawops", args: ["mcp", "serve", "--read-only"] }
|
|
3019
3522
|
}
|
|
3020
3523
|
}, null, 2) + "\n\n");
|
|
3021
3524
|
}
|
|
3022
3525
|
async function maybeWireGatewayMcp(opts) {
|
|
3023
|
-
|
|
3526
|
+
process21.stdout.write("\n");
|
|
3024
3527
|
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
3025
3528
|
if (!wireGateway) return;
|
|
3026
|
-
const { connect } = await import("./ssh-
|
|
3529
|
+
const { connect } = await import("./ssh-VAI5DU5M.js");
|
|
3027
3530
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
3028
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
3029
|
-
const knownHostsPath =
|
|
3531
|
+
const { wireGatewayMcp } = await import("./mcp-wire-3SWBM7D6.js");
|
|
3532
|
+
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
3030
3533
|
const spin = spinner("Wiring gateway MCP client...");
|
|
3031
3534
|
const session = await connect({
|
|
3032
3535
|
host: opts.host,
|
|
@@ -3040,8 +3543,7 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3040
3543
|
const result = await wireGatewayMcp(session, opts.signal ?? AbortSignal.timeout(3e4));
|
|
3041
3544
|
if (result.status === "unsupported") {
|
|
3042
3545
|
spin.warn("This gateway has no `openclaw mcp add`, so nothing was wired.");
|
|
3043
|
-
info("OpenClaw 2026.
|
|
3044
|
-
info("Upgrade to 2026.7.1-2 and run: clawops mcp wire --stack " + opts.stackName);
|
|
3546
|
+
info("Upgrade OpenClaw to 2026.7.1-2 or later and run: clawops mcp wire --stack " + opts.stackName);
|
|
3045
3547
|
} else if (result.status === "probe-failed") {
|
|
3046
3548
|
spin.warn("The gateway could not reach clawops, so nothing was wired.");
|
|
3047
3549
|
info("clawops does not run on the gateway host. Start it where the gateway can reach it,");
|
|
@@ -3062,7 +3564,7 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3062
3564
|
}
|
|
3063
3565
|
}
|
|
3064
3566
|
async function maybeWireGatewayMcpCloud(opts) {
|
|
3065
|
-
|
|
3567
|
+
process21.stdout.write("\n");
|
|
3066
3568
|
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
3067
3569
|
if (!wireGateway) return;
|
|
3068
3570
|
info(`Run this after the server is ready: clawops mcp wire --stack ${opts.stackName}`);
|
|
@@ -3078,8 +3580,8 @@ async function inquirerPromptWire(inquirer) {
|
|
|
3078
3580
|
function loadCatalogs(yaml) {
|
|
3079
3581
|
const specDir = resolveSpecDir();
|
|
3080
3582
|
try {
|
|
3081
|
-
const modelsRaw = yaml.load(readFileSync3(
|
|
3082
|
-
const integrationsRaw = yaml.load(readFileSync3(
|
|
3583
|
+
const modelsRaw = yaml.load(readFileSync3(path2.join(specDir, "models.yaml"), "utf-8"));
|
|
3584
|
+
const integrationsRaw = yaml.load(readFileSync3(path2.join(specDir, "integrations.yaml"), "utf-8"));
|
|
3083
3585
|
return {
|
|
3084
3586
|
models: modelsRaw.providers,
|
|
3085
3587
|
integrations: integrationsRaw.integrations
|
|
@@ -3100,6 +3602,21 @@ function defaultRegion(provider) {
|
|
|
3100
3602
|
if (provider === "azure") return "eastus";
|
|
3101
3603
|
return "";
|
|
3102
3604
|
}
|
|
3605
|
+
function stateBucketQuestion(provider, account) {
|
|
3606
|
+
return {
|
|
3607
|
+
type: "input",
|
|
3608
|
+
name: "stateBucket",
|
|
3609
|
+
message: `${stateLabel(provider)} name: (where clawops records what it deployed \u2014 press Enter to accept)`,
|
|
3610
|
+
default: (answers) => {
|
|
3611
|
+
const derived = deriveStateBucket(provider, {
|
|
3612
|
+
account,
|
|
3613
|
+
region: answers.region ?? defaultRegion(provider)
|
|
3614
|
+
});
|
|
3615
|
+
return derived.ok ? derived.name : void 0;
|
|
3616
|
+
},
|
|
3617
|
+
validate: (v) => validateStateBucket(provider, v)
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3103
3620
|
function stateLabel(provider) {
|
|
3104
3621
|
if (provider === "aws") return "S3";
|
|
3105
3622
|
if (provider === "gcp") return "GCS";
|
|
@@ -3130,13 +3647,13 @@ function instanceChoices(provider) {
|
|
|
3130
3647
|
return table[provider] ?? [];
|
|
3131
3648
|
}
|
|
3132
3649
|
async function maybeHarden(opts) {
|
|
3133
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-
|
|
3650
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-72JMRT4E.js");
|
|
3134
3651
|
const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
|
|
3135
3652
|
const config = getConfig2();
|
|
3136
3653
|
if (!config) return;
|
|
3137
3654
|
const availableModules = resolveModules(MODULE_CATALOG, void 0, opts.provider);
|
|
3138
3655
|
if (availableModules.length === 0) return;
|
|
3139
|
-
|
|
3656
|
+
process21.stdout.write("\n");
|
|
3140
3657
|
const { selectedIds } = await opts.inquirer.prompt([{
|
|
3141
3658
|
type: "checkbox",
|
|
3142
3659
|
name: "selectedIds",
|
|
@@ -3152,9 +3669,9 @@ async function maybeHarden(opts) {
|
|
|
3152
3669
|
return;
|
|
3153
3670
|
}
|
|
3154
3671
|
const modules = availableModules.filter((m) => selectedIds.includes(m.id));
|
|
3155
|
-
|
|
3672
|
+
process21.stdout.write("\nApplying hardening modules...\n\n");
|
|
3156
3673
|
try {
|
|
3157
|
-
const { buildContext } = await import("./context-
|
|
3674
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
3158
3675
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
3159
3676
|
const ctx = buildContext({ stack: opts.stackName });
|
|
3160
3677
|
const stackObj = await ctx.getStack();
|
|
@@ -3173,11 +3690,11 @@ async function maybeHarden(opts) {
|
|
|
3173
3690
|
modules,
|
|
3174
3691
|
signal: opts.signal,
|
|
3175
3692
|
onProgress: (r) => {
|
|
3176
|
-
|
|
3693
|
+
process21.stdout.write(` \xB7 ${r.module.label}
|
|
3177
3694
|
`);
|
|
3178
3695
|
}
|
|
3179
3696
|
});
|
|
3180
|
-
|
|
3697
|
+
process21.stdout.write("\n" + formatHardenSummary(results));
|
|
3181
3698
|
const errors = results.filter((r) => r.error);
|
|
3182
3699
|
if (errors.length > 0) {
|
|
3183
3700
|
for (const r of errors) fail(`${r.module.label}: ${r.error}`);
|
|
@@ -3191,26 +3708,109 @@ async function maybeHarden(opts) {
|
|
|
3191
3708
|
info("Run `clawops harden --stack " + opts.stackName + "` to retry.");
|
|
3192
3709
|
}
|
|
3193
3710
|
}
|
|
3711
|
+
async function detectSshCidrDefault() {
|
|
3712
|
+
const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
|
|
3713
|
+
const result = await detectEgressIp("https://ifconfig.me/ip");
|
|
3714
|
+
if (!result.ok) return void 0;
|
|
3715
|
+
const ip = result.ip.trim();
|
|
3716
|
+
return ip === "" ? void 0 : ip.includes("/") ? ip : `${ip}/32`;
|
|
3717
|
+
}
|
|
3718
|
+
function validateCidrAnswer(value) {
|
|
3719
|
+
const v = value.trim();
|
|
3720
|
+
if (v === "") return "Required \u2014 enter a CIDR such as 203.0.113.4/32";
|
|
3721
|
+
const looksIpv6 = v.includes(":");
|
|
3722
|
+
if (!looksIpv6 && !/^(\d{1,3}\.){3}\d{1,3}\/(3[0-2]|[12]?\d)$/.test(v)) {
|
|
3723
|
+
return `"${v}" is not a CIDR. Use an address and a prefix, e.g. 203.0.113.4/32`;
|
|
3724
|
+
}
|
|
3725
|
+
return true;
|
|
3726
|
+
}
|
|
3727
|
+
function admitsInternet(cidr) {
|
|
3728
|
+
const v = cidr.trim();
|
|
3729
|
+
return v === "0.0.0.0/0" || v === "::/0";
|
|
3730
|
+
}
|
|
3731
|
+
function wizardChannels(integrations) {
|
|
3732
|
+
return integrations.filter((i) => i.wizardSupported !== false);
|
|
3733
|
+
}
|
|
3734
|
+
function startChannelConfig(integ) {
|
|
3735
|
+
return { ...integ.defaults ?? {} };
|
|
3736
|
+
}
|
|
3737
|
+
async function runAccountPreflight(opts) {
|
|
3738
|
+
const { getProvider } = await import("./providers-FW6H6OPF.js");
|
|
3739
|
+
let checks;
|
|
3740
|
+
try {
|
|
3741
|
+
const adapter = getProvider(opts.provider);
|
|
3742
|
+
if (!adapter.preflight) return;
|
|
3743
|
+
checks = await adapter.preflight({
|
|
3744
|
+
region: opts.region,
|
|
3745
|
+
bucket: opts.bucket,
|
|
3746
|
+
instanceType: opts.instanceType
|
|
3747
|
+
});
|
|
3748
|
+
} catch (err) {
|
|
3749
|
+
warn(`Could not check the ${opts.provider} account: ${err instanceof Error ? err.message : String(err)}`);
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
const unanswered = checks.filter((c) => c.unknown);
|
|
3753
|
+
const failed = checks.filter((c) => !c.ok && !c.unknown);
|
|
3754
|
+
if (unanswered.length > 0) {
|
|
3755
|
+
process21.stdout.write("\n");
|
|
3756
|
+
for (const check of unanswered) {
|
|
3757
|
+
warn(`Could not check: ${check.label}`);
|
|
3758
|
+
if (check.detail) info(` ${check.detail}`);
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
if (failed.length === 0) {
|
|
3762
|
+
success(
|
|
3763
|
+
unanswered.length > 0 ? `${opts.provider} account is ready, as far as clawops could tell.` : `${opts.provider} account is ready.`
|
|
3764
|
+
);
|
|
3765
|
+
process21.stdout.write("\n");
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
process21.stdout.write("\n");
|
|
3769
|
+
for (const check of failed) {
|
|
3770
|
+
warn(check.label);
|
|
3771
|
+
if (check.detail) info(` ${check.detail}`);
|
|
3772
|
+
if (!check.fix) {
|
|
3773
|
+
continue;
|
|
3774
|
+
}
|
|
3775
|
+
const { apply } = await opts.inquirer.prompt([{
|
|
3776
|
+
type: "confirm",
|
|
3777
|
+
name: "apply",
|
|
3778
|
+
message: `Fix this now? ${check.mutates}`,
|
|
3779
|
+
default: true
|
|
3780
|
+
}]);
|
|
3781
|
+
if (!apply) {
|
|
3782
|
+
info(" Left as is. The deploy will fail until this is resolved.");
|
|
3783
|
+
continue;
|
|
3784
|
+
}
|
|
3785
|
+
try {
|
|
3786
|
+
await check.fix();
|
|
3787
|
+
success(` Done \u2014 ${check.mutates}`);
|
|
3788
|
+
} catch (err) {
|
|
3789
|
+
failure(` Could not fix it: ${err instanceof Error ? err.message : String(err)}`);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
process21.stdout.write("\n");
|
|
3793
|
+
}
|
|
3194
3794
|
|
|
3195
3795
|
// src/cli/commands/secret.ts
|
|
3196
|
-
import { defineCommand as
|
|
3197
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as
|
|
3198
|
-
import { spawnSync as
|
|
3199
|
-
import
|
|
3796
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
3797
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync3, statSync } from "fs";
|
|
3798
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3799
|
+
import path3 from "path";
|
|
3200
3800
|
import os2 from "os";
|
|
3201
|
-
import
|
|
3202
|
-
var SECRETS_DIR =
|
|
3801
|
+
import process22 from "process";
|
|
3802
|
+
var SECRETS_DIR = path3.join(os2.homedir(), ".clawops", "secrets");
|
|
3203
3803
|
function secretsDir() {
|
|
3204
3804
|
return SECRETS_DIR;
|
|
3205
3805
|
}
|
|
3206
3806
|
function secretPath(name) {
|
|
3207
|
-
return
|
|
3807
|
+
return path3.join(secretsDir(), name);
|
|
3208
3808
|
}
|
|
3209
3809
|
function listSecretNames() {
|
|
3210
3810
|
if (!existsSync3(secretsDir())) return [];
|
|
3211
3811
|
return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
|
|
3212
3812
|
}
|
|
3213
|
-
var listCmd =
|
|
3813
|
+
var listCmd = defineCommand20({
|
|
3214
3814
|
meta: { name: "list", description: "List all stored secrets and their status" },
|
|
3215
3815
|
args: {
|
|
3216
3816
|
json: { type: "boolean", description: "Emit JSON" }
|
|
@@ -3235,7 +3835,7 @@ var listCmd = defineCommand19({
|
|
|
3235
3835
|
}
|
|
3236
3836
|
});
|
|
3237
3837
|
if (args.json) {
|
|
3238
|
-
|
|
3838
|
+
process22.stdout.write(JSON.stringify(rows, null, 2) + "\n");
|
|
3239
3839
|
return;
|
|
3240
3840
|
}
|
|
3241
3841
|
const tableRows = rows.map((r) => [
|
|
@@ -3244,10 +3844,10 @@ var listCmd = defineCommand19({
|
|
|
3244
3844
|
r.modified,
|
|
3245
3845
|
r.path
|
|
3246
3846
|
]);
|
|
3247
|
-
|
|
3847
|
+
process22.stdout.write("\n" + renderTable(["Name", "Status", "Modified", "Path"], tableRows) + "\n\n");
|
|
3248
3848
|
}
|
|
3249
3849
|
});
|
|
3250
|
-
var setCmd =
|
|
3850
|
+
var setCmd = defineCommand20({
|
|
3251
3851
|
meta: { name: "set", description: "Create or update a secret" },
|
|
3252
3852
|
args: {
|
|
3253
3853
|
value: { type: "string", description: "Secret value (skips interactive prompt)" }
|
|
@@ -3256,7 +3856,7 @@ var setCmd = defineCommand19({
|
|
|
3256
3856
|
const [name] = args._ ?? [];
|
|
3257
3857
|
if (!name) {
|
|
3258
3858
|
failure("Usage: clawops secret set <name>");
|
|
3259
|
-
|
|
3859
|
+
process22.exit(2);
|
|
3260
3860
|
}
|
|
3261
3861
|
const inquirer = (await import("inquirer")).default;
|
|
3262
3862
|
let value;
|
|
@@ -3271,14 +3871,14 @@ var setCmd = defineCommand19({
|
|
|
3271
3871
|
}]);
|
|
3272
3872
|
value = secretValue;
|
|
3273
3873
|
}
|
|
3274
|
-
|
|
3275
|
-
|
|
3874
|
+
mkdirSync3(secretsDir(), { recursive: true });
|
|
3875
|
+
spawnSync3("chmod", ["700", secretsDir()], { stdio: "ignore" });
|
|
3276
3876
|
writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
|
|
3277
3877
|
success(`Secret "${name}" saved to ${secretPath(name)} (chmod 600)`);
|
|
3278
3878
|
info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
|
|
3279
3879
|
}
|
|
3280
3880
|
});
|
|
3281
|
-
var deleteCmd =
|
|
3881
|
+
var deleteCmd = defineCommand20({
|
|
3282
3882
|
meta: { name: "delete", description: "Remove a stored secret" },
|
|
3283
3883
|
args: {
|
|
3284
3884
|
yes: { type: "boolean", description: "Skip confirmation prompt" }
|
|
@@ -3287,12 +3887,12 @@ var deleteCmd = defineCommand19({
|
|
|
3287
3887
|
const [name] = args._ ?? [];
|
|
3288
3888
|
if (!name) {
|
|
3289
3889
|
failure("Usage: clawops secret delete <name>");
|
|
3290
|
-
|
|
3890
|
+
process22.exit(2);
|
|
3291
3891
|
}
|
|
3292
3892
|
const p = secretPath(name);
|
|
3293
3893
|
if (!existsSync3(p)) {
|
|
3294
3894
|
failure(`Secret "${name}" not found at ${p}`);
|
|
3295
|
-
|
|
3895
|
+
process22.exit(1);
|
|
3296
3896
|
}
|
|
3297
3897
|
const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
|
|
3298
3898
|
const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
|
|
@@ -3317,7 +3917,7 @@ var deleteCmd = defineCommand19({
|
|
|
3317
3917
|
success(`Secret "${name}" deleted.`);
|
|
3318
3918
|
}
|
|
3319
3919
|
});
|
|
3320
|
-
var rotateCmd =
|
|
3920
|
+
var rotateCmd = defineCommand20({
|
|
3321
3921
|
meta: { name: "rotate", description: "Update a secret and re-apply config to a running stack" },
|
|
3322
3922
|
args: {
|
|
3323
3923
|
stack: { type: "string", description: "Stack name to re-apply (defaults to config default)" },
|
|
@@ -3327,7 +3927,7 @@ var rotateCmd = defineCommand19({
|
|
|
3327
3927
|
const [name] = args._ ?? [];
|
|
3328
3928
|
if (!name) {
|
|
3329
3929
|
failure("Usage: clawops secret rotate <name> [--stack <name>]");
|
|
3330
|
-
|
|
3930
|
+
process22.exit(2);
|
|
3331
3931
|
}
|
|
3332
3932
|
const inquirer = (await import("inquirer")).default;
|
|
3333
3933
|
let value;
|
|
@@ -3342,8 +3942,8 @@ var rotateCmd = defineCommand19({
|
|
|
3342
3942
|
}]);
|
|
3343
3943
|
value = secretValue;
|
|
3344
3944
|
}
|
|
3345
|
-
|
|
3346
|
-
|
|
3945
|
+
mkdirSync3(secretsDir(), { recursive: true });
|
|
3946
|
+
spawnSync3("chmod", ["700", secretsDir()], { stdio: "ignore" });
|
|
3347
3947
|
writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
|
|
3348
3948
|
success(`Secret "${name}" updated.`);
|
|
3349
3949
|
const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
|
|
@@ -3363,10 +3963,10 @@ var rotateCmd = defineCommand19({
|
|
|
3363
3963
|
}
|
|
3364
3964
|
info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
|
|
3365
3965
|
try {
|
|
3366
|
-
const { buildContext } = await import("./context-
|
|
3367
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
3966
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
3967
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
|
|
3368
3968
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
3369
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
3969
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
3370
3970
|
const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
|
|
3371
3971
|
const ctx = buildContext({ stack: targetStack });
|
|
3372
3972
|
let conn;
|
|
@@ -3401,7 +4001,7 @@ var rotateCmd = defineCommand19({
|
|
|
3401
4001
|
}
|
|
3402
4002
|
}
|
|
3403
4003
|
});
|
|
3404
|
-
var auditCmd =
|
|
4004
|
+
var auditCmd = defineCommand20({
|
|
3405
4005
|
meta: { name: "audit", description: "Report missing secrets and unresolvable $secret: refs" },
|
|
3406
4006
|
args: {
|
|
3407
4007
|
json: { type: "boolean", description: "Emit JSON" }
|
|
@@ -3430,7 +4030,7 @@ var auditCmd = defineCommand19({
|
|
|
3430
4030
|
}
|
|
3431
4031
|
} else if (s.source === "env") {
|
|
3432
4032
|
const envVar = s.ref ?? s.name;
|
|
3433
|
-
if (!
|
|
4033
|
+
if (!process22.env[envVar]) {
|
|
3434
4034
|
issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
|
|
3435
4035
|
}
|
|
3436
4036
|
} else {
|
|
@@ -3439,7 +4039,7 @@ var auditCmd = defineCommand19({
|
|
|
3439
4039
|
}
|
|
3440
4040
|
}
|
|
3441
4041
|
if (args.json) {
|
|
3442
|
-
|
|
4042
|
+
process22.stdout.write(JSON.stringify({ issues, ok: issues.length === 0 }, null, 2) + "\n");
|
|
3443
4043
|
return;
|
|
3444
4044
|
}
|
|
3445
4045
|
if (issues.length === 0) {
|
|
@@ -3452,11 +4052,11 @@ var auditCmd = defineCommand19({
|
|
|
3452
4052
|
const prefix = issue.stack ? `[${issue.stack}] ` : "";
|
|
3453
4053
|
warn(`${prefix}${issue.secret}: ${issue.detail}`);
|
|
3454
4054
|
}
|
|
3455
|
-
|
|
4055
|
+
process22.stdout.write("\n");
|
|
3456
4056
|
info("Run `clawops secret set <name>` to update a missing or empty secret.");
|
|
3457
4057
|
}
|
|
3458
4058
|
});
|
|
3459
|
-
var secret_default =
|
|
4059
|
+
var secret_default = defineCommand20({
|
|
3460
4060
|
meta: {
|
|
3461
4061
|
name: "secret",
|
|
3462
4062
|
description: "Manage clawops secrets (list | set | delete | rotate | audit)"
|
|
@@ -3472,7 +4072,7 @@ var secret_default = defineCommand19({
|
|
|
3472
4072
|
});
|
|
3473
4073
|
|
|
3474
4074
|
// src/cli/commands/help.ts
|
|
3475
|
-
import { defineCommand as
|
|
4075
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
3476
4076
|
var COMMANDS = [
|
|
3477
4077
|
["setup", "Interactive first-run wizard \u2014 configure, deploy, and wire AI apps"],
|
|
3478
4078
|
["init", "Write config and generate SSH key pair (non-interactive)"],
|
|
@@ -3504,7 +4104,7 @@ var GLOBAL_FLAGS = [
|
|
|
3504
4104
|
["--dry-run", "Preview without applying (mutating commands)"],
|
|
3505
4105
|
["--yes", "Skip interactive confirmations (CI mode)"]
|
|
3506
4106
|
];
|
|
3507
|
-
var help_default =
|
|
4107
|
+
var help_default = defineCommand21({
|
|
3508
4108
|
meta: {
|
|
3509
4109
|
name: "help",
|
|
3510
4110
|
description: "Show available commands and global flags"
|
|
@@ -3544,16 +4144,16 @@ ${dim("Run `clawops <command> --help` for command-specific flags.")}
|
|
|
3544
4144
|
});
|
|
3545
4145
|
|
|
3546
4146
|
// src/cli/commands/bug.ts
|
|
3547
|
-
import { defineCommand as
|
|
3548
|
-
import
|
|
3549
|
-
import { accessSync
|
|
4147
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
4148
|
+
import process23 from "process";
|
|
4149
|
+
import { accessSync, constants } from "fs";
|
|
3550
4150
|
import { execSync as execSync2 } from "child_process";
|
|
3551
4151
|
var ISSUES_URL = `${REPO_URL}/issues/new`;
|
|
3552
4152
|
var BODY_CAP = 2e3;
|
|
3553
|
-
var VERSION = true ? "
|
|
4153
|
+
var VERSION = true ? "2.0.1" : "dev";
|
|
3554
4154
|
async function collectContext() {
|
|
3555
|
-
const nodeVersion =
|
|
3556
|
-
const os3 = `${
|
|
4155
|
+
const nodeVersion = process23.version;
|
|
4156
|
+
const os3 = `${process23.platform} ${process23.arch}`;
|
|
3557
4157
|
let provider = "unknown";
|
|
3558
4158
|
let stackCount = 0;
|
|
3559
4159
|
let sshKey = "unknown";
|
|
@@ -3564,9 +4164,9 @@ async function collectContext() {
|
|
|
3564
4164
|
const stacks = Object.values(config.stacks);
|
|
3565
4165
|
stackCount = stacks.length;
|
|
3566
4166
|
provider = stacks[0]?.provider ?? "none";
|
|
3567
|
-
const keyPath = config.ssh.keyPath.replace(/^~/,
|
|
4167
|
+
const keyPath = config.ssh.keyPath.replace(/^~/, process23.env["HOME"] ?? "~");
|
|
3568
4168
|
try {
|
|
3569
|
-
|
|
4169
|
+
accessSync(keyPath, constants.R_OK);
|
|
3570
4170
|
sshKey = "present";
|
|
3571
4171
|
} catch {
|
|
3572
4172
|
sshKey = "missing";
|
|
@@ -3604,14 +4204,14 @@ function buildIssueUrl(title, command, context) {
|
|
|
3604
4204
|
}
|
|
3605
4205
|
function openBrowser2(url) {
|
|
3606
4206
|
try {
|
|
3607
|
-
const cmd =
|
|
4207
|
+
const cmd = process23.platform === "darwin" ? "open" : "xdg-open";
|
|
3608
4208
|
execSync2(`${cmd} "${url}"`, { stdio: "ignore" });
|
|
3609
4209
|
return true;
|
|
3610
4210
|
} catch {
|
|
3611
4211
|
return false;
|
|
3612
4212
|
}
|
|
3613
4213
|
}
|
|
3614
|
-
var bug_default =
|
|
4214
|
+
var bug_default = defineCommand22({
|
|
3615
4215
|
meta: {
|
|
3616
4216
|
name: "bug",
|
|
3617
4217
|
description: "Open a pre-filled GitHub issue with system context from clawops doctor"
|
|
@@ -3625,7 +4225,7 @@ var bug_default = defineCommand21({
|
|
|
3625
4225
|
let command = "";
|
|
3626
4226
|
if (!args.json) {
|
|
3627
4227
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
3628
|
-
const rl = createInterface3({ input:
|
|
4228
|
+
const rl = createInterface3({ input: process23.stdin, output: process23.stdout });
|
|
3629
4229
|
try {
|
|
3630
4230
|
title = (await rl.question("Describe the issue in one line: ")).trim();
|
|
3631
4231
|
command = (await rl.question("Which command triggered it? (optional): ")).trim();
|
|
@@ -3638,12 +4238,12 @@ var bug_default = defineCommand21({
|
|
|
3638
4238
|
printJson(jsonOk({ url }));
|
|
3639
4239
|
return;
|
|
3640
4240
|
}
|
|
3641
|
-
|
|
4241
|
+
process23.stdout.write("\n");
|
|
3642
4242
|
info(`Opening: ${url}`);
|
|
3643
|
-
|
|
4243
|
+
process23.stdout.write("\n");
|
|
3644
4244
|
const opened = openBrowser2(url);
|
|
3645
4245
|
if (!opened) {
|
|
3646
|
-
|
|
4246
|
+
process23.stdout.write(`Open this URL to file the issue:
|
|
3647
4247
|
${url}
|
|
3648
4248
|
`);
|
|
3649
4249
|
}
|
|
@@ -3651,9 +4251,9 @@ ${url}
|
|
|
3651
4251
|
});
|
|
3652
4252
|
|
|
3653
4253
|
// src/cli/commands/harden.ts
|
|
3654
|
-
import { defineCommand as
|
|
3655
|
-
import
|
|
3656
|
-
var harden_default =
|
|
4254
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
4255
|
+
import process24 from "process";
|
|
4256
|
+
var harden_default = defineCommand23({
|
|
3657
4257
|
meta: {
|
|
3658
4258
|
name: "harden",
|
|
3659
4259
|
description: "Apply security hardening modules to a deployed stack"
|
|
@@ -3667,26 +4267,26 @@ var harden_default = defineCommand22({
|
|
|
3667
4267
|
json: { type: "boolean", description: "Emit structured JSON output" }
|
|
3668
4268
|
},
|
|
3669
4269
|
async run({ args }) {
|
|
3670
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-
|
|
3671
|
-
const { buildContext } = await import("./context-
|
|
4270
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-72JMRT4E.js");
|
|
4271
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
3672
4272
|
const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
|
|
3673
4273
|
if (args.list) {
|
|
3674
|
-
|
|
4274
|
+
process24.stdout.write("\nAvailable hardening modules:\n\n");
|
|
3675
4275
|
for (const mod of MODULE_CATALOG) {
|
|
3676
4276
|
const on = mod.defaultOn ? chalk.green("ON ") : chalk.dim("off");
|
|
3677
4277
|
const providers = mod.providers === "all" ? "all" : mod.providers.join(", ");
|
|
3678
|
-
|
|
4278
|
+
process24.stdout.write(
|
|
3679
4279
|
` ${on} ${mod.id.padEnd(28)} ${mod.label} [${providers}]
|
|
3680
4280
|
`
|
|
3681
4281
|
);
|
|
3682
4282
|
}
|
|
3683
|
-
|
|
4283
|
+
process24.stdout.write("\n");
|
|
3684
4284
|
return;
|
|
3685
4285
|
}
|
|
3686
4286
|
const config = getConfig2();
|
|
3687
4287
|
if (!config) {
|
|
3688
4288
|
failure("No clawops config found. Run `clawops init` first.");
|
|
3689
|
-
|
|
4289
|
+
process24.exit(1);
|
|
3690
4290
|
}
|
|
3691
4291
|
const ctx = buildContext({ stack: args.stack });
|
|
3692
4292
|
const provider = ctx.adapter.name;
|
|
@@ -3696,17 +4296,17 @@ var harden_default = defineCommand22({
|
|
|
3696
4296
|
return;
|
|
3697
4297
|
}
|
|
3698
4298
|
if (!args.json) {
|
|
3699
|
-
|
|
4299
|
+
process24.stdout.write("\nModules to run:\n");
|
|
3700
4300
|
for (const mod of modules) {
|
|
3701
4301
|
const flag = mod.defaultOn ? "" : chalk.yellow(" [opt-in]");
|
|
3702
|
-
|
|
4302
|
+
process24.stdout.write(` \xB7 ${mod.label}${flag}
|
|
3703
4303
|
`);
|
|
3704
4304
|
}
|
|
3705
|
-
|
|
4305
|
+
process24.stdout.write("\n");
|
|
3706
4306
|
}
|
|
3707
4307
|
if (!args["dry-run"] && !args.yes && !args.json) {
|
|
3708
4308
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
3709
|
-
const rl = createInterface3({ input:
|
|
4309
|
+
const rl = createInterface3({ input: process24.stdin, output: process24.stdout });
|
|
3710
4310
|
const answer = await rl.question("Apply these hardening modules? [y/N] ");
|
|
3711
4311
|
rl.close();
|
|
3712
4312
|
if (!answer.toLowerCase().startsWith("y")) {
|
|
@@ -3715,8 +4315,8 @@ var harden_default = defineCommand22({
|
|
|
3715
4315
|
}
|
|
3716
4316
|
}
|
|
3717
4317
|
const ac = new AbortController();
|
|
3718
|
-
|
|
3719
|
-
|
|
4318
|
+
process24.on("SIGINT", () => ac.abort());
|
|
4319
|
+
process24.on("SIGTERM", () => ac.abort());
|
|
3720
4320
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
3721
4321
|
const stackObj = await ctx.getStack();
|
|
3722
4322
|
const outputMap = await stackObj.outputs();
|
|
@@ -3730,7 +4330,7 @@ var harden_default = defineCommand22({
|
|
|
3730
4330
|
knownHostsPath: config.ssh.knownHostsPath
|
|
3731
4331
|
});
|
|
3732
4332
|
if (!args.json) {
|
|
3733
|
-
|
|
4333
|
+
process24.stdout.write(args["dry-run"] ? "Checking hardening state...\n\n" : "Applying hardening...\n\n");
|
|
3734
4334
|
}
|
|
3735
4335
|
const results = await runHardening(conn, {
|
|
3736
4336
|
modules,
|
|
@@ -3738,7 +4338,7 @@ var harden_default = defineCommand22({
|
|
|
3738
4338
|
signal: ac.signal,
|
|
3739
4339
|
onProgress: args.json ? void 0 : (r) => {
|
|
3740
4340
|
const icon = r.error ? chalk.red("\u2717") : r.applyResult?.changed ? chalk.green("\u2713") : chalk.dim("\xB7");
|
|
3741
|
-
|
|
4341
|
+
process24.stdout.write(` ${icon} ${r.module.label}
|
|
3742
4342
|
`);
|
|
3743
4343
|
}
|
|
3744
4344
|
});
|
|
@@ -3756,14 +4356,14 @@ var harden_default = defineCommand22({
|
|
|
3756
4356
|
}));
|
|
3757
4357
|
return;
|
|
3758
4358
|
}
|
|
3759
|
-
|
|
4359
|
+
process24.stdout.write("\n" + formatHardenSummary(results));
|
|
3760
4360
|
const errors = results.filter((r) => r.error);
|
|
3761
4361
|
if (errors.length > 0) {
|
|
3762
|
-
|
|
4362
|
+
process24.stdout.write("\n");
|
|
3763
4363
|
for (const r of errors) {
|
|
3764
4364
|
failure(`${r.module.label}: ${r.error}`);
|
|
3765
4365
|
}
|
|
3766
|
-
|
|
4366
|
+
process24.exit(1);
|
|
3767
4367
|
}
|
|
3768
4368
|
if (!args["dry-run"]) {
|
|
3769
4369
|
success("Hardening complete.");
|
|
@@ -3772,26 +4372,26 @@ var harden_default = defineCommand22({
|
|
|
3772
4372
|
});
|
|
3773
4373
|
|
|
3774
4374
|
// src/cli/error-handler.ts
|
|
3775
|
-
import
|
|
4375
|
+
import process25 from "process";
|
|
3776
4376
|
function handleError(err) {
|
|
3777
4377
|
if (err instanceof ClawopsError) {
|
|
3778
4378
|
failure(err.message);
|
|
3779
|
-
|
|
4379
|
+
process25.exit(err.exitCode);
|
|
3780
4380
|
}
|
|
3781
4381
|
if (err instanceof Error) {
|
|
3782
4382
|
failure(`Unexpected error: ${err.message}`);
|
|
3783
|
-
if (
|
|
3784
|
-
|
|
4383
|
+
if (process25.env["DEBUG"]) {
|
|
4384
|
+
process25.stderr.write(err.stack ?? "\n");
|
|
3785
4385
|
}
|
|
3786
|
-
|
|
4386
|
+
process25.exit(1);
|
|
3787
4387
|
}
|
|
3788
4388
|
failure(`Unknown error: ${String(err)}`);
|
|
3789
|
-
|
|
4389
|
+
process25.exit(1);
|
|
3790
4390
|
}
|
|
3791
4391
|
|
|
3792
4392
|
// src/cli/index.ts
|
|
3793
|
-
var CLI_VERSION = true ? "
|
|
3794
|
-
var main =
|
|
4393
|
+
var CLI_VERSION = true ? "2.0.1" : "dev";
|
|
4394
|
+
var main = defineCommand24({
|
|
3795
4395
|
meta: {
|
|
3796
4396
|
name: "clawops",
|
|
3797
4397
|
description: "Deploy and manage self-hosted OpenClaw instances across clouds",
|
|
@@ -3819,6 +4419,7 @@ var main = defineCommand23({
|
|
|
3819
4419
|
logs: logs_default,
|
|
3820
4420
|
config: config_default,
|
|
3821
4421
|
agents: agents_default,
|
|
4422
|
+
migrate: migrate_default,
|
|
3822
4423
|
gateway: gateway_default,
|
|
3823
4424
|
backup: backup_default,
|
|
3824
4425
|
stacks: stacks_default,
|