cloudflared-manager 0.1.1 → 0.1.3
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 +21 -1
- package/cloudflared_manager.mjs +241 -17
- package/cloudflared_manager.sh +25 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,14 @@ node ./cloudflared_manager.mjs --profile-dir /opt/cloudflare-prod interactive
|
|
|
58
58
|
npx cloudflared-manager
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
首次进入交互模式时,如果当前机器还没有安装 `cloudflared`,会先主动提示安装。对 `login`、`tunnels`、`add`、`adopt`、`modify`、`start`、`restart`、`delete --delete-tunnel` 这类依赖 `cloudflared` 的动作,交互层也会在执行前自动拦一下,避免你先报错再回头安装。
|
|
62
|
+
|
|
63
|
+
在 macOS 上,交互层会先调用脚本内置的 `install`:
|
|
64
|
+
|
|
65
|
+
- 可以选择直接 `sudo` 安装官方 `pkg`
|
|
66
|
+
- 如果不想立刻提权,也可以只打开官方安装器
|
|
67
|
+
- 如果 GitHub 官方安装包下载失败,会自动回退到 `brew install cloudflared`
|
|
68
|
+
|
|
61
69
|
交互模式里提供了常用菜单;如果某些高级参数菜单里还没覆盖,可以用“执行自定义命令”直接透传到底层 shell 脚本。
|
|
62
70
|
主菜单同时支持两种选择方式:
|
|
63
71
|
|
|
@@ -135,6 +143,18 @@ npx cloudflared-manager
|
|
|
135
143
|
|
|
136
144
|
## 快速开始
|
|
137
145
|
|
|
146
|
+
如果你要从零开始初始化一台新机器,最短路径通常就是这一条:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
./cloudflared_manager.sh --profile-dir /opt/cloudflare-prod init --install --login
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
如果你走的是 npm 入口,也可以直接:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx cloudflared-manager init --install --login
|
|
156
|
+
```
|
|
157
|
+
|
|
138
158
|
### 1. 保存默认 profile
|
|
139
159
|
|
|
140
160
|
```bash
|
|
@@ -213,5 +233,5 @@ npx cloudflared-manager
|
|
|
213
233
|
- `modify` 既支持旧的单条写法,也支持批量 `--set N:hostname=service`
|
|
214
234
|
- 如果你只是同一台机器上暴露多个域名/服务,通常优先考虑“1 个 Tunnel + 多 ingress”
|
|
215
235
|
- `modify` 和 `delete --delete-tunnel` 不会自动清理旧 DNS 记录,这仍需要你到 Cloudflare 侧手工处理
|
|
216
|
-
- `install` 目前按 macOS
|
|
236
|
+
- `install` 目前按 macOS 设计,优先官方 `pkg`,下载失败时会回退到 Homebrew
|
|
217
237
|
- 仓库只保留 shell 版,不再维护 Python 入口
|
package/cloudflared_manager.mjs
CHANGED
|
@@ -285,6 +285,99 @@ function runShell(baseArgs, commandArgs, options = {}) {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
// 返回当前可用的 cloudflared 可执行文件路径。
|
|
289
|
+
function resolveCloudflaredBinary() {
|
|
290
|
+
const explicitBin = (process.env.CLOUDFLARED_BIN ?? "").trim();
|
|
291
|
+
if (explicitBin.length > 0) {
|
|
292
|
+
const explicitResult = spawnSync("bash", ["-lc", '[[ -x "$CLOUDFLARED_BIN" ]]'], {
|
|
293
|
+
env: process.env,
|
|
294
|
+
});
|
|
295
|
+
if ((explicitResult.status ?? 1) === 0) {
|
|
296
|
+
return explicitBin;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const result = spawnSync("bash", ["-lc", "command -v cloudflared || true"], {
|
|
301
|
+
encoding: "utf8",
|
|
302
|
+
});
|
|
303
|
+
if (result.error) {
|
|
304
|
+
return "";
|
|
305
|
+
}
|
|
306
|
+
return (result.stdout ?? "").trim();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 判断当前机器是否已经安装 cloudflared。
|
|
310
|
+
function isCloudflaredInstalled() {
|
|
311
|
+
return resolveCloudflaredBinary().length > 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 在交互模式中引导安装 cloudflared,成功后返回 true。
|
|
315
|
+
async function installCloudflaredInteractive(rl, baseArgs, options = {}) {
|
|
316
|
+
if (isCloudflaredInstalled()) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (options.confirm !== false) {
|
|
321
|
+
const prompt = options.prompt ?? "是否现在安装 cloudflared";
|
|
322
|
+
if (!(await askYesNo(rl, prompt, true))) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const installArgs = ["install"];
|
|
328
|
+
if (process.platform === "darwin") {
|
|
329
|
+
const useSudoInstall = await askYesNo(
|
|
330
|
+
rl,
|
|
331
|
+
"是否直接使用 sudo 安装(否则只打开官方 pkg 安装器)",
|
|
332
|
+
true,
|
|
333
|
+
);
|
|
334
|
+
if (useSudoInstall) {
|
|
335
|
+
installArgs.push("--sudo-install");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
runShell(baseArgs, installArgs);
|
|
340
|
+
|
|
341
|
+
if (isCloudflaredInstalled()) {
|
|
342
|
+
console.log("已检测到 cloudflared,可继续后续操作。");
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (process.platform === "darwin" && !installArgs.includes("--sudo-install")) {
|
|
347
|
+
console.log("已打开 cloudflared 官方安装器。完成安装后,回到这里继续即可。");
|
|
348
|
+
} else {
|
|
349
|
+
console.log("安装命令已执行,但当前仍未检测到 cloudflared。请完成安装后再继续。");
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 在首次进入交互模式时,主动提示安装 cloudflared。
|
|
355
|
+
async function maybeOfferCloudflaredInstall(rl, baseArgs) {
|
|
356
|
+
if (isCloudflaredInstalled()) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log("检测到当前机器尚未安装 cloudflared。");
|
|
361
|
+
console.log("登录 Cloudflare、查看远端 Tunnel、创建或启动 Tunnel 之前,需要先安装它。");
|
|
362
|
+
if (await askYesNo(rl, "是否现在安装 cloudflared", true)) {
|
|
363
|
+
await installCloudflaredInteractive(rl, baseArgs, { confirm: false });
|
|
364
|
+
} else {
|
|
365
|
+
console.log("已跳过安装。你仍可先执行 doctor 或 init,后续需要时再安装。");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 某些交互动作依赖 cloudflared,执行前先确保已安装。
|
|
370
|
+
async function ensureCloudflaredForAction(rl, baseArgs, label) {
|
|
371
|
+
if (isCloudflaredInstalled()) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
console.log(`${label}前需要先安装 cloudflared。`);
|
|
376
|
+
return installCloudflaredInteractive(rl, baseArgs, {
|
|
377
|
+
prompt: "是否现在安装 cloudflared",
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
288
381
|
// 读取一行输入,可选提供默认值。
|
|
289
382
|
async function ask(rl, label, options = {}) {
|
|
290
383
|
const suffix =
|
|
@@ -909,13 +1002,21 @@ function buildSetupReviewMenu(type, baseArgs, state) {
|
|
|
909
1002
|
];
|
|
910
1003
|
|
|
911
1004
|
if (isAdd) {
|
|
1005
|
+
prelude.push(colorize(`覆盖已有 DNS 记录: ${formatEnabled(state.overwriteDns)}`, COLORS.dim, COLORS.gray));
|
|
1006
|
+
prelude.push(colorize(`跳过本地连通性检查: ${formatEnabled(state.skipCheck)}`, COLORS.dim, COLORS.gray));
|
|
912
1007
|
prelude.push(colorize(`立即启动: ${formatEnabled(state.start)}`, COLORS.dim, COLORS.gray));
|
|
913
1008
|
prelude.push(colorize(`激活为默认配置: ${formatEnabled(state.activate)}`, COLORS.dim, COLORS.gray));
|
|
1009
|
+
if (state.overwriteDns) commandArgs.push("--overwrite-dns");
|
|
1010
|
+
if (state.skipCheck) commandArgs.push("--skip-check");
|
|
914
1011
|
if (state.start) commandArgs.push("--start");
|
|
915
1012
|
} else {
|
|
916
1013
|
prelude.push(colorize(`同时确保 DNS 路由存在: ${formatEnabled(state.ensureDns)}`, COLORS.dim, COLORS.gray));
|
|
1014
|
+
prelude.push(colorize(`覆盖已有 DNS 记录: ${formatEnabled(state.overwriteDns)}`, COLORS.dim, COLORS.gray));
|
|
1015
|
+
prelude.push(colorize(`跳过本地连通性检查: ${formatEnabled(state.skipCheck)}`, COLORS.dim, COLORS.gray));
|
|
917
1016
|
prelude.push(colorize(`激活为默认配置: ${formatEnabled(state.activate)}`, COLORS.dim, COLORS.gray));
|
|
918
1017
|
if (state.ensureDns) commandArgs.push("--ensure-dns");
|
|
1018
|
+
if (state.ensureDns && state.overwriteDns) commandArgs.push("--overwrite-dns");
|
|
1019
|
+
if (state.skipCheck) commandArgs.push("--skip-check");
|
|
919
1020
|
}
|
|
920
1021
|
if (state.activate) commandArgs.push("--activate");
|
|
921
1022
|
|
|
@@ -929,12 +1030,24 @@ function buildSetupReviewMenu(type, baseArgs, state) {
|
|
|
929
1030
|
{ value: "4", label: "重新录入 ingress 规则", hint: `当前共 ${state.specs.length} 条`, kind: "secondary" },
|
|
930
1031
|
{
|
|
931
1032
|
value: "5",
|
|
932
|
-
label: isAdd ? "
|
|
933
|
-
hint: `当前: ${isAdd ? formatEnabled(state.
|
|
1033
|
+
label: isAdd ? "切换“覆盖已有 DNS”" : "切换“确保 DNS 路由存在”",
|
|
1034
|
+
hint: `当前: ${isAdd ? formatEnabled(state.overwriteDns) : formatEnabled(state.ensureDns)}`,
|
|
934
1035
|
kind: "assist",
|
|
935
1036
|
},
|
|
936
1037
|
{
|
|
937
1038
|
value: "6",
|
|
1039
|
+
label: isAdd ? "切换“跳过连通性检查”" : "切换“覆盖已有 DNS”",
|
|
1040
|
+
hint: `当前: ${isAdd ? formatEnabled(state.skipCheck) : formatEnabled(state.overwriteDns)}`,
|
|
1041
|
+
kind: "assist",
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
value: "7",
|
|
1045
|
+
label: isAdd ? "切换“立即启动”" : "切换“跳过连通性检查”",
|
|
1046
|
+
hint: `当前: ${isAdd ? formatEnabled(state.start) : formatEnabled(state.skipCheck)}`,
|
|
1047
|
+
kind: "assist",
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
value: "8",
|
|
938
1051
|
label: "切换“激活为默认配置”",
|
|
939
1052
|
hint: `当前: ${formatEnabled(state.activate)}`,
|
|
940
1053
|
kind: "assist",
|
|
@@ -1011,7 +1124,14 @@ function buildIngressChangeReviewMenu(type, baseArgs, state, preview) {
|
|
|
1011
1124
|
|
|
1012
1125
|
prelude.push(colorize("变更后:", COLORS.bold, COLORS.white));
|
|
1013
1126
|
prelude.push(...renderRuleObjects(preview.nextRules).map((line) => colorize(line, COLORS.dim, COLORS.gray)));
|
|
1014
|
-
|
|
1127
|
+
if (type === "add") {
|
|
1128
|
+
prelude.push(colorize(`覆盖已有 DNS 记录: ${formatEnabled(state.overwriteDns)}`, COLORS.dim, COLORS.gray));
|
|
1129
|
+
prelude.push(colorize(`跳过本地连通性检查: ${formatEnabled(state.skipCheck)}`, COLORS.dim, COLORS.gray));
|
|
1130
|
+
prelude.push(colorize(`暂不重启: ${formatEnabled(state.noRestart)}`, COLORS.dim, COLORS.gray));
|
|
1131
|
+
prelude.push(colorize(`激活为默认配置: ${formatEnabled(state.activate)}`, COLORS.dim, COLORS.gray));
|
|
1132
|
+
} else {
|
|
1133
|
+
prelude.push(colorize(`暂不重启: ${formatEnabled(state.noRestart)}`, COLORS.dim, COLORS.gray));
|
|
1134
|
+
}
|
|
1015
1135
|
|
|
1016
1136
|
let commandArgs;
|
|
1017
1137
|
if (type === "modify") {
|
|
@@ -1024,24 +1144,41 @@ function buildIngressChangeReviewMenu(type, baseArgs, state, preview) {
|
|
|
1024
1144
|
} else {
|
|
1025
1145
|
commandArgs = ["ingress-remove", state.name, ...buildRemoveArgs(state.specs)];
|
|
1026
1146
|
}
|
|
1147
|
+
if (type === "add") {
|
|
1148
|
+
if (state.overwriteDns) commandArgs.push("--overwrite-dns");
|
|
1149
|
+
if (state.skipCheck) commandArgs.push("--skip-check");
|
|
1150
|
+
}
|
|
1027
1151
|
if (state.noRestart) {
|
|
1028
1152
|
commandArgs.push("--no-restart");
|
|
1029
1153
|
}
|
|
1154
|
+
if (type === "add" && state.activate) {
|
|
1155
|
+
commandArgs.push("--activate");
|
|
1156
|
+
}
|
|
1030
1157
|
|
|
1031
1158
|
prelude.push(colorize("命令预览:", COLORS.bold, COLORS.cyan));
|
|
1032
1159
|
prelude.push(...renderCommandPreview(baseArgs, commandArgs).map((line) => colorize(line, COLORS.dim, COLORS.gray)));
|
|
1033
1160
|
|
|
1034
|
-
const items =
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1161
|
+
const items = type === "add"
|
|
1162
|
+
? [
|
|
1163
|
+
{ value: "1", label: "执行变更", hint: "调用底层 shell 脚本真正落地", kind: "primary" },
|
|
1164
|
+
{ value: "2", label: "重新录入新增规则", hint: `当前共 ${state.specs.length} 条`, kind: "secondary" },
|
|
1165
|
+
{ value: "3", label: "切换“覆盖已有 DNS”", hint: `当前: ${formatEnabled(state.overwriteDns)}`, kind: "assist" },
|
|
1166
|
+
{ value: "4", label: "切换“跳过连通性检查”", hint: `当前: ${formatEnabled(state.skipCheck)}`, kind: "assist" },
|
|
1167
|
+
{ value: "5", label: "切换“暂不重启”", hint: `当前: ${formatEnabled(state.noRestart)}`, kind: "assist" },
|
|
1168
|
+
{ value: "6", label: "切换“激活为默认配置”", hint: `当前: ${formatEnabled(state.activate)}`, kind: "assist" },
|
|
1169
|
+
{ value: "0", label: "取消当前流程", hint: "不执行这次变更", kind: "danger", back: true },
|
|
1170
|
+
]
|
|
1171
|
+
: [
|
|
1172
|
+
{ value: "1", label: "执行变更", hint: "调用底层 shell 脚本真正落地", kind: "primary" },
|
|
1173
|
+
{
|
|
1174
|
+
value: "2",
|
|
1175
|
+
label: type === "modify" ? "重新录入修改规则" : "重新录入删除规则",
|
|
1176
|
+
hint: `当前共 ${state.specs.length} 条`,
|
|
1177
|
+
kind: "secondary",
|
|
1178
|
+
},
|
|
1179
|
+
{ value: "3", label: "切换“暂不重启”", hint: `当前: ${formatEnabled(state.noRestart)}`, kind: "assist" },
|
|
1180
|
+
{ value: "0", label: "取消当前流程", hint: "不执行这次变更", kind: "danger", back: true },
|
|
1181
|
+
];
|
|
1045
1182
|
|
|
1046
1183
|
return {
|
|
1047
1184
|
key: `${type}_ingress_review`,
|
|
@@ -1105,7 +1242,10 @@ async function runIngressAddWizard(rl, baseArgs, name) {
|
|
|
1105
1242
|
name,
|
|
1106
1243
|
currentRules,
|
|
1107
1244
|
specs: await collectRequiredIngressSpecs(rl),
|
|
1245
|
+
overwriteDns: false,
|
|
1246
|
+
skipCheck: false,
|
|
1108
1247
|
noRestart: true,
|
|
1248
|
+
activate: false,
|
|
1109
1249
|
};
|
|
1110
1250
|
|
|
1111
1251
|
while (true) {
|
|
@@ -1124,8 +1264,17 @@ async function runIngressAddWizard(rl, baseArgs, name) {
|
|
|
1124
1264
|
state.specs = await collectRequiredIngressSpecs(rl);
|
|
1125
1265
|
break;
|
|
1126
1266
|
case "3":
|
|
1267
|
+
state.overwriteDns = !state.overwriteDns;
|
|
1268
|
+
break;
|
|
1269
|
+
case "4":
|
|
1270
|
+
state.skipCheck = !state.skipCheck;
|
|
1271
|
+
break;
|
|
1272
|
+
case "5":
|
|
1127
1273
|
state.noRestart = !state.noRestart;
|
|
1128
1274
|
break;
|
|
1275
|
+
case "6":
|
|
1276
|
+
state.activate = !state.activate;
|
|
1277
|
+
break;
|
|
1129
1278
|
case "0":
|
|
1130
1279
|
return null;
|
|
1131
1280
|
default:
|
|
@@ -1245,6 +1394,8 @@ async function runAddWizard(rl, baseArgs) {
|
|
|
1245
1394
|
name: "",
|
|
1246
1395
|
tunnelName: "",
|
|
1247
1396
|
specs: [],
|
|
1397
|
+
overwriteDns: false,
|
|
1398
|
+
skipCheck: false,
|
|
1248
1399
|
start: false,
|
|
1249
1400
|
activate: false,
|
|
1250
1401
|
};
|
|
@@ -1279,9 +1430,15 @@ async function runAddWizard(rl, baseArgs) {
|
|
|
1279
1430
|
state.specs = await collectRequiredIngressSpecs(rl);
|
|
1280
1431
|
break;
|
|
1281
1432
|
case "5":
|
|
1282
|
-
state.
|
|
1433
|
+
state.overwriteDns = !state.overwriteDns;
|
|
1283
1434
|
break;
|
|
1284
1435
|
case "6":
|
|
1436
|
+
state.skipCheck = !state.skipCheck;
|
|
1437
|
+
break;
|
|
1438
|
+
case "7":
|
|
1439
|
+
state.start = !state.start;
|
|
1440
|
+
break;
|
|
1441
|
+
case "8":
|
|
1285
1442
|
state.activate = !state.activate;
|
|
1286
1443
|
break;
|
|
1287
1444
|
case "0":
|
|
@@ -1300,6 +1457,8 @@ async function runAdoptWizard(rl, baseArgs) {
|
|
|
1300
1457
|
tunnelName: "",
|
|
1301
1458
|
specs: [],
|
|
1302
1459
|
ensureDns: false,
|
|
1460
|
+
overwriteDns: false,
|
|
1461
|
+
skipCheck: false,
|
|
1303
1462
|
activate: false,
|
|
1304
1463
|
};
|
|
1305
1464
|
|
|
@@ -1340,8 +1499,20 @@ async function runAdoptWizard(rl, baseArgs) {
|
|
|
1340
1499
|
break;
|
|
1341
1500
|
case "5":
|
|
1342
1501
|
state.ensureDns = !state.ensureDns;
|
|
1502
|
+
if (!state.ensureDns) {
|
|
1503
|
+
state.overwriteDns = false;
|
|
1504
|
+
}
|
|
1343
1505
|
break;
|
|
1344
1506
|
case "6":
|
|
1507
|
+
state.overwriteDns = !state.overwriteDns;
|
|
1508
|
+
if (state.overwriteDns) {
|
|
1509
|
+
state.ensureDns = true;
|
|
1510
|
+
}
|
|
1511
|
+
break;
|
|
1512
|
+
case "7":
|
|
1513
|
+
state.skipCheck = !state.skipCheck;
|
|
1514
|
+
break;
|
|
1515
|
+
case "8":
|
|
1345
1516
|
state.activate = !state.activate;
|
|
1346
1517
|
break;
|
|
1347
1518
|
case "0":
|
|
@@ -1751,6 +1922,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1751
1922
|
runShell(baseArgs, ["list"]);
|
|
1752
1923
|
return { keepRunning: true };
|
|
1753
1924
|
case "3":
|
|
1925
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "查看远端 Tunnel"))) {
|
|
1926
|
+
return { keepRunning: true };
|
|
1927
|
+
}
|
|
1754
1928
|
runShell(baseArgs, ["tunnels"]);
|
|
1755
1929
|
return { keepRunning: true };
|
|
1756
1930
|
case "4":
|
|
@@ -1766,12 +1940,17 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1766
1940
|
return { keepRunning: true };
|
|
1767
1941
|
case "5":
|
|
1768
1942
|
{
|
|
1943
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "创建新 Tunnel"))) {
|
|
1944
|
+
return { keepRunning: true };
|
|
1945
|
+
}
|
|
1769
1946
|
const addState = await runAddWizard(rl, baseArgs);
|
|
1770
1947
|
if (!addState) {
|
|
1771
1948
|
console.log("已取消创建新 tunnel。");
|
|
1772
1949
|
return { keepRunning: true };
|
|
1773
1950
|
}
|
|
1774
1951
|
args = ["add", addState.name, "--tunnel-name", addState.tunnelName, ...buildIngressArgs(addState.specs)];
|
|
1952
|
+
if (addState.overwriteDns) args.push("--overwrite-dns");
|
|
1953
|
+
if (addState.skipCheck) args.push("--skip-check");
|
|
1775
1954
|
if (addState.start) args.push("--start");
|
|
1776
1955
|
if (addState.activate) args.push("--activate");
|
|
1777
1956
|
runShell(baseArgs, args);
|
|
@@ -1779,6 +1958,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1779
1958
|
}
|
|
1780
1959
|
case "6":
|
|
1781
1960
|
{
|
|
1961
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "接管已有 Tunnel"))) {
|
|
1962
|
+
return { keepRunning: true };
|
|
1963
|
+
}
|
|
1782
1964
|
const adoptState = await runAdoptWizard(rl, baseArgs);
|
|
1783
1965
|
if (!adoptState) {
|
|
1784
1966
|
console.log("已取消接管已有 tunnel。");
|
|
@@ -1786,6 +1968,8 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1786
1968
|
}
|
|
1787
1969
|
args = ["adopt", adoptState.name, "--tunnel-name", adoptState.tunnelName, ...buildIngressArgs(adoptState.specs)];
|
|
1788
1970
|
if (adoptState.ensureDns) args.push("--ensure-dns");
|
|
1971
|
+
if (adoptState.ensureDns && adoptState.overwriteDns) args.push("--overwrite-dns");
|
|
1972
|
+
if (adoptState.skipCheck) args.push("--skip-check");
|
|
1789
1973
|
if (adoptState.activate) args.push("--activate");
|
|
1790
1974
|
runShell(baseArgs, args);
|
|
1791
1975
|
return { keepRunning: true };
|
|
@@ -1800,6 +1984,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1800
1984
|
}
|
|
1801
1985
|
if (!name) return { keepRunning: true };
|
|
1802
1986
|
{
|
|
1987
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "修改 ingress"))) {
|
|
1988
|
+
return { keepRunning: true };
|
|
1989
|
+
}
|
|
1803
1990
|
const modifyState = await runModifyWizard(rl, baseArgs, name);
|
|
1804
1991
|
if (!modifyState) {
|
|
1805
1992
|
console.log("已取消修改 ingress。");
|
|
@@ -1823,13 +2010,19 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1823
2010
|
}
|
|
1824
2011
|
if (!name) return { keepRunning: true };
|
|
1825
2012
|
{
|
|
2013
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "新增 ingress"))) {
|
|
2014
|
+
return { keepRunning: true };
|
|
2015
|
+
}
|
|
1826
2016
|
const addState = await runIngressAddWizard(rl, baseArgs, name);
|
|
1827
2017
|
if (!addState) {
|
|
1828
2018
|
console.log("已取消新增 ingress。");
|
|
1829
2019
|
return { keepRunning: true };
|
|
1830
2020
|
}
|
|
1831
2021
|
args = ["ingress-add", name, ...buildIngressArgs(addState.specs)];
|
|
2022
|
+
if (addState.overwriteDns) args.push("--overwrite-dns");
|
|
2023
|
+
if (addState.skipCheck) args.push("--skip-check");
|
|
1832
2024
|
if (addState.noRestart) args.push("--no-restart");
|
|
2025
|
+
if (addState.activate) args.push("--activate");
|
|
1833
2026
|
runShell(baseArgs, args);
|
|
1834
2027
|
return { keepRunning: true };
|
|
1835
2028
|
}
|
|
@@ -1862,6 +2055,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1862
2055
|
});
|
|
1863
2056
|
}
|
|
1864
2057
|
if (!name) return { keepRunning: true };
|
|
2058
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "启动应用"))) {
|
|
2059
|
+
return { keepRunning: true };
|
|
2060
|
+
}
|
|
1865
2061
|
runShell(baseArgs, ["start", name]);
|
|
1866
2062
|
return { keepRunning: true };
|
|
1867
2063
|
case "11":
|
|
@@ -1884,6 +2080,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1884
2080
|
});
|
|
1885
2081
|
}
|
|
1886
2082
|
if (!name) return { keepRunning: true };
|
|
2083
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "重启应用"))) {
|
|
2084
|
+
return { keepRunning: true };
|
|
2085
|
+
}
|
|
1887
2086
|
runShell(baseArgs, ["restart", name]);
|
|
1888
2087
|
return { keepRunning: true };
|
|
1889
2088
|
case "13":
|
|
@@ -1929,18 +2128,42 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1929
2128
|
}
|
|
1930
2129
|
if (!name) return { keepRunning: true };
|
|
1931
2130
|
args = ["delete", name];
|
|
1932
|
-
if (await askYesNo(rl, "是否同时删除远端 tunnel", false))
|
|
2131
|
+
if (await askYesNo(rl, "是否同时删除远端 tunnel", false)) {
|
|
2132
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "删除远端 Tunnel"))) {
|
|
2133
|
+
return { keepRunning: true };
|
|
2134
|
+
}
|
|
2135
|
+
args.push("--delete-tunnel");
|
|
2136
|
+
}
|
|
1933
2137
|
runShell(baseArgs, args);
|
|
1934
2138
|
return { keepRunning: true, nextMenuKey: "app_picker", clearAppName: true };
|
|
1935
2139
|
case "17":
|
|
1936
2140
|
args = ["init"];
|
|
1937
|
-
if (
|
|
2141
|
+
if (!isCloudflaredInstalled()) {
|
|
2142
|
+
if (await askYesNo(rl, "检测到未安装 cloudflared,是否在初始化时一并安装", true)) {
|
|
2143
|
+
args.push("--install");
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
if (await askYesNo(rl, "是否同时执行 login", false)) {
|
|
2147
|
+
if (!isCloudflaredInstalled() && !args.includes("--install")) {
|
|
2148
|
+
if (await askYesNo(rl, "login 需要先安装 cloudflared,是否在初始化时一并安装", true)) {
|
|
2149
|
+
args.push("--install");
|
|
2150
|
+
} else {
|
|
2151
|
+
console.log("已跳过 login。完成安装后可在“环境与登录 -> 登录 Cloudflare”中继续。");
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
if (isCloudflaredInstalled() || args.includes("--install")) {
|
|
2155
|
+
args.push("--login");
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
1938
2158
|
runShell(baseArgs, args);
|
|
1939
2159
|
return { keepRunning: true };
|
|
1940
2160
|
case "18":
|
|
1941
2161
|
runShell(baseArgs, ["use"]);
|
|
1942
2162
|
return { keepRunning: true };
|
|
1943
2163
|
case "19":
|
|
2164
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "登录 Cloudflare"))) {
|
|
2165
|
+
return { keepRunning: true };
|
|
2166
|
+
}
|
|
1944
2167
|
runShell(baseArgs, ["login"]);
|
|
1945
2168
|
return { keepRunning: true };
|
|
1946
2169
|
case "20": {
|
|
@@ -1996,6 +2219,7 @@ async function runInteractive(baseArgs) {
|
|
|
1996
2219
|
});
|
|
1997
2220
|
|
|
1998
2221
|
try {
|
|
2222
|
+
await maybeOfferCloudflaredInstall(rl, baseArgs);
|
|
1999
2223
|
let keepRunning = true;
|
|
2000
2224
|
let currentMenuKey = MAIN_MENU_KEY;
|
|
2001
2225
|
let currentAppName = "";
|
package/cloudflared_manager.sh
CHANGED
|
@@ -339,7 +339,7 @@ ensure_manager_dirs() {
|
|
|
339
339
|
# 确保系统已经安装 cloudflared。
|
|
340
340
|
ensure_cloudflared() {
|
|
341
341
|
if [[ -z "$CLOUDFLARED_BIN" ]]; then
|
|
342
|
-
die "cloudflared
|
|
342
|
+
die "cloudflared 未安装。请先执行 install,或使用 init --install --login 完成首次初始化。"
|
|
343
343
|
fi
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -1636,6 +1636,7 @@ EOF
|
|
|
1636
1636
|
# 在 macOS 上下载安装 cloudflared。
|
|
1637
1637
|
cmd_install() {
|
|
1638
1638
|
local sudo_install=0
|
|
1639
|
+
local arch pkg_name url pkg_path
|
|
1639
1640
|
while [[ $# -gt 0 ]]; do
|
|
1640
1641
|
case "$1" in
|
|
1641
1642
|
--sudo-install) sudo_install=1; shift ;;
|
|
@@ -1660,7 +1661,6 @@ EOF
|
|
|
1660
1661
|
die "自动安装目前只支持 macOS。"
|
|
1661
1662
|
fi
|
|
1662
1663
|
|
|
1663
|
-
local arch pkg_name url pkg_path
|
|
1664
1664
|
arch="$(uname -m)"
|
|
1665
1665
|
case "$arch" in
|
|
1666
1666
|
x86_64) pkg_name="cloudflared-amd64.pkg" ;;
|
|
@@ -1669,13 +1669,32 @@ EOF
|
|
|
1669
1669
|
esac
|
|
1670
1670
|
url="https://github.com/cloudflare/cloudflared/releases/latest/download/$pkg_name"
|
|
1671
1671
|
pkg_path="/tmp/$pkg_name"
|
|
1672
|
-
curl -L --fail -o "$pkg_path" "$url"
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1672
|
+
if curl -L --fail -o "$pkg_path" "$url"; then
|
|
1673
|
+
if [[ "$sudo_install" == "1" ]]; then
|
|
1674
|
+
sudo installer -pkg "$pkg_path" -target /
|
|
1675
|
+
CLOUDFLARED_BIN="${CLOUDFLARED_BIN:-$(command -v cloudflared || true)}"
|
|
1676
|
+
if [[ -n "$CLOUDFLARED_BIN" ]]; then
|
|
1677
|
+
info "cloudflared 已安装:$("$CLOUDFLARED_BIN" --version)"
|
|
1678
|
+
fi
|
|
1679
|
+
return 0
|
|
1680
|
+
fi
|
|
1676
1681
|
open "$pkg_path"
|
|
1677
1682
|
info "已打开安装包: $pkg_path"
|
|
1683
|
+
return 0
|
|
1684
|
+
fi
|
|
1685
|
+
|
|
1686
|
+
warn "官方下载失败,尝试改用 Homebrew 安装。"
|
|
1687
|
+
if ! command_exists brew; then
|
|
1688
|
+
die "未找到 Homebrew,且 cloudflared 官方安装包下载失败。请稍后重试,或先安装 Homebrew。"
|
|
1689
|
+
fi
|
|
1690
|
+
|
|
1691
|
+
HOMEBREW_NO_AUTO_UPDATE=1 brew install cloudflared
|
|
1692
|
+
CLOUDFLARED_BIN="${CLOUDFLARED_BIN:-$(command -v cloudflared || true)}"
|
|
1693
|
+
if [[ -n "$CLOUDFLARED_BIN" ]]; then
|
|
1694
|
+
info "cloudflared 已安装:$("$CLOUDFLARED_BIN" --version)"
|
|
1695
|
+
return 0
|
|
1678
1696
|
fi
|
|
1697
|
+
die "Homebrew 安装 cloudflared 后仍未检测到可执行文件。"
|
|
1679
1698
|
}
|
|
1680
1699
|
|
|
1681
1700
|
# 执行 cloudflared tunnel login,并同步默认证书到目标位置。
|