cloudflared-manager 0.1.0 → 0.1.2
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 +141 -2
- package/cloudflared_manager.sh +109 -37
- 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 =
|
|
@@ -1751,6 +1844,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1751
1844
|
runShell(baseArgs, ["list"]);
|
|
1752
1845
|
return { keepRunning: true };
|
|
1753
1846
|
case "3":
|
|
1847
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "查看远端 Tunnel"))) {
|
|
1848
|
+
return { keepRunning: true };
|
|
1849
|
+
}
|
|
1754
1850
|
runShell(baseArgs, ["tunnels"]);
|
|
1755
1851
|
return { keepRunning: true };
|
|
1756
1852
|
case "4":
|
|
@@ -1766,6 +1862,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1766
1862
|
return { keepRunning: true };
|
|
1767
1863
|
case "5":
|
|
1768
1864
|
{
|
|
1865
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "创建新 Tunnel"))) {
|
|
1866
|
+
return { keepRunning: true };
|
|
1867
|
+
}
|
|
1769
1868
|
const addState = await runAddWizard(rl, baseArgs);
|
|
1770
1869
|
if (!addState) {
|
|
1771
1870
|
console.log("已取消创建新 tunnel。");
|
|
@@ -1779,6 +1878,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1779
1878
|
}
|
|
1780
1879
|
case "6":
|
|
1781
1880
|
{
|
|
1881
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "接管已有 Tunnel"))) {
|
|
1882
|
+
return { keepRunning: true };
|
|
1883
|
+
}
|
|
1782
1884
|
const adoptState = await runAdoptWizard(rl, baseArgs);
|
|
1783
1885
|
if (!adoptState) {
|
|
1784
1886
|
console.log("已取消接管已有 tunnel。");
|
|
@@ -1800,6 +1902,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1800
1902
|
}
|
|
1801
1903
|
if (!name) return { keepRunning: true };
|
|
1802
1904
|
{
|
|
1905
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "修改 ingress"))) {
|
|
1906
|
+
return { keepRunning: true };
|
|
1907
|
+
}
|
|
1803
1908
|
const modifyState = await runModifyWizard(rl, baseArgs, name);
|
|
1804
1909
|
if (!modifyState) {
|
|
1805
1910
|
console.log("已取消修改 ingress。");
|
|
@@ -1823,6 +1928,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1823
1928
|
}
|
|
1824
1929
|
if (!name) return { keepRunning: true };
|
|
1825
1930
|
{
|
|
1931
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "新增 ingress"))) {
|
|
1932
|
+
return { keepRunning: true };
|
|
1933
|
+
}
|
|
1826
1934
|
const addState = await runIngressAddWizard(rl, baseArgs, name);
|
|
1827
1935
|
if (!addState) {
|
|
1828
1936
|
console.log("已取消新增 ingress。");
|
|
@@ -1862,6 +1970,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1862
1970
|
});
|
|
1863
1971
|
}
|
|
1864
1972
|
if (!name) return { keepRunning: true };
|
|
1973
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "启动应用"))) {
|
|
1974
|
+
return { keepRunning: true };
|
|
1975
|
+
}
|
|
1865
1976
|
runShell(baseArgs, ["start", name]);
|
|
1866
1977
|
return { keepRunning: true };
|
|
1867
1978
|
case "11":
|
|
@@ -1884,6 +1995,9 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1884
1995
|
});
|
|
1885
1996
|
}
|
|
1886
1997
|
if (!name) return { keepRunning: true };
|
|
1998
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "重启应用"))) {
|
|
1999
|
+
return { keepRunning: true };
|
|
2000
|
+
}
|
|
1887
2001
|
runShell(baseArgs, ["restart", name]);
|
|
1888
2002
|
return { keepRunning: true };
|
|
1889
2003
|
case "13":
|
|
@@ -1929,18 +2043,42 @@ async function handleAction(rl, baseArgs, action, context = {}) {
|
|
|
1929
2043
|
}
|
|
1930
2044
|
if (!name) return { keepRunning: true };
|
|
1931
2045
|
args = ["delete", name];
|
|
1932
|
-
if (await askYesNo(rl, "是否同时删除远端 tunnel", false))
|
|
2046
|
+
if (await askYesNo(rl, "是否同时删除远端 tunnel", false)) {
|
|
2047
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "删除远端 Tunnel"))) {
|
|
2048
|
+
return { keepRunning: true };
|
|
2049
|
+
}
|
|
2050
|
+
args.push("--delete-tunnel");
|
|
2051
|
+
}
|
|
1933
2052
|
runShell(baseArgs, args);
|
|
1934
2053
|
return { keepRunning: true, nextMenuKey: "app_picker", clearAppName: true };
|
|
1935
2054
|
case "17":
|
|
1936
2055
|
args = ["init"];
|
|
1937
|
-
if (
|
|
2056
|
+
if (!isCloudflaredInstalled()) {
|
|
2057
|
+
if (await askYesNo(rl, "检测到未安装 cloudflared,是否在初始化时一并安装", true)) {
|
|
2058
|
+
args.push("--install");
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (await askYesNo(rl, "是否同时执行 login", false)) {
|
|
2062
|
+
if (!isCloudflaredInstalled() && !args.includes("--install")) {
|
|
2063
|
+
if (await askYesNo(rl, "login 需要先安装 cloudflared,是否在初始化时一并安装", true)) {
|
|
2064
|
+
args.push("--install");
|
|
2065
|
+
} else {
|
|
2066
|
+
console.log("已跳过 login。完成安装后可在“环境与登录 -> 登录 Cloudflare”中继续。");
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
if (isCloudflaredInstalled() || args.includes("--install")) {
|
|
2070
|
+
args.push("--login");
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
1938
2073
|
runShell(baseArgs, args);
|
|
1939
2074
|
return { keepRunning: true };
|
|
1940
2075
|
case "18":
|
|
1941
2076
|
runShell(baseArgs, ["use"]);
|
|
1942
2077
|
return { keepRunning: true };
|
|
1943
2078
|
case "19":
|
|
2079
|
+
if (!(await ensureCloudflaredForAction(rl, baseArgs, "登录 Cloudflare"))) {
|
|
2080
|
+
return { keepRunning: true };
|
|
2081
|
+
}
|
|
1944
2082
|
runShell(baseArgs, ["login"]);
|
|
1945
2083
|
return { keepRunning: true };
|
|
1946
2084
|
case "20": {
|
|
@@ -1996,6 +2134,7 @@ async function runInteractive(baseArgs) {
|
|
|
1996
2134
|
});
|
|
1997
2135
|
|
|
1998
2136
|
try {
|
|
2137
|
+
await maybeOfferCloudflaredInstall(rl, baseArgs);
|
|
1999
2138
|
let keepRunning = true;
|
|
2000
2139
|
let currentMenuKey = MAIN_MENU_KEY;
|
|
2001
2140
|
let currentAppName = "";
|
package/cloudflared_manager.sh
CHANGED
|
@@ -160,6 +160,22 @@ dir_exists() {
|
|
|
160
160
|
[[ -d "$1" ]]
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
# 判断数组是否至少包含一个元素,兼容 macOS Bash 3 在 set -u 下的空数组行为。
|
|
164
|
+
array_has_items() {
|
|
165
|
+
local array_name="$1"
|
|
166
|
+
eval "[[ \${${array_name}[0]+_} == _ ]]"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# 返回数组长度,兼容 macOS Bash 3 在 set -u 下的空数组行为。
|
|
170
|
+
array_length() {
|
|
171
|
+
local array_name="$1"
|
|
172
|
+
if array_has_items "$array_name"; then
|
|
173
|
+
eval "printf '%s\n' \"\${#${array_name}[@]}\""
|
|
174
|
+
else
|
|
175
|
+
printf '0\n'
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
163
179
|
# 判断命令是否可用。
|
|
164
180
|
command_exists() {
|
|
165
181
|
command -v "$1" >/dev/null 2>&1
|
|
@@ -323,7 +339,7 @@ ensure_manager_dirs() {
|
|
|
323
339
|
# 确保系统已经安装 cloudflared。
|
|
324
340
|
ensure_cloudflared() {
|
|
325
341
|
if [[ -z "$CLOUDFLARED_BIN" ]]; then
|
|
326
|
-
die "cloudflared
|
|
342
|
+
die "cloudflared 未安装。请先执行 install,或使用 init --install --login 完成首次初始化。"
|
|
327
343
|
fi
|
|
328
344
|
}
|
|
329
345
|
|
|
@@ -1620,6 +1636,7 @@ EOF
|
|
|
1620
1636
|
# 在 macOS 上下载安装 cloudflared。
|
|
1621
1637
|
cmd_install() {
|
|
1622
1638
|
local sudo_install=0
|
|
1639
|
+
local arch pkg_name url pkg_path
|
|
1623
1640
|
while [[ $# -gt 0 ]]; do
|
|
1624
1641
|
case "$1" in
|
|
1625
1642
|
--sudo-install) sudo_install=1; shift ;;
|
|
@@ -1644,7 +1661,6 @@ EOF
|
|
|
1644
1661
|
die "自动安装目前只支持 macOS。"
|
|
1645
1662
|
fi
|
|
1646
1663
|
|
|
1647
|
-
local arch pkg_name url pkg_path
|
|
1648
1664
|
arch="$(uname -m)"
|
|
1649
1665
|
case "$arch" in
|
|
1650
1666
|
x86_64) pkg_name="cloudflared-amd64.pkg" ;;
|
|
@@ -1653,13 +1669,32 @@ EOF
|
|
|
1653
1669
|
esac
|
|
1654
1670
|
url="https://github.com/cloudflare/cloudflared/releases/latest/download/$pkg_name"
|
|
1655
1671
|
pkg_path="/tmp/$pkg_name"
|
|
1656
|
-
curl -L --fail -o "$pkg_path" "$url"
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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
|
|
1660
1681
|
open "$pkg_path"
|
|
1661
1682
|
info "已打开安装包: $pkg_path"
|
|
1683
|
+
return 0
|
|
1684
|
+
fi
|
|
1685
|
+
|
|
1686
|
+
warn "官方下载失败,尝试改用 Homebrew 安装。"
|
|
1687
|
+
if ! command_exists brew; then
|
|
1688
|
+
die "未找到 Homebrew,且 cloudflared 官方安装包下载失败。请稍后重试,或先安装 Homebrew。"
|
|
1662
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
|
|
1696
|
+
fi
|
|
1697
|
+
die "Homebrew 安装 cloudflared 后仍未检测到可执行文件。"
|
|
1663
1698
|
}
|
|
1664
1699
|
|
|
1665
1700
|
# 执行 cloudflared tunnel login,并同步默认证书到目标位置。
|
|
@@ -1777,6 +1812,18 @@ cmd_add() {
|
|
|
1777
1812
|
local foreground=0
|
|
1778
1813
|
local activate=0
|
|
1779
1814
|
|
|
1815
|
+
if [[ "$name" == "-h" || "$name" == "--help" ]]; then
|
|
1816
|
+
cat <<EOF
|
|
1817
|
+
用法:
|
|
1818
|
+
$SCRIPT_NAME add NAME [--hostname HOST --service URL] [--ingress HOST=URL ...] [--tunnel-name NAME] [--use-existing] [--overwrite-dns] [--skip-check] [--start] [--foreground] [--activate]
|
|
1819
|
+
|
|
1820
|
+
说明:
|
|
1821
|
+
- 兼容旧写法:使用一组 --hostname 和 --service
|
|
1822
|
+
- 多 ingress 写法:重复传 --ingress hostname=service
|
|
1823
|
+
- 可以混用,首条规则会作为“主入口”展示
|
|
1824
|
+
EOF
|
|
1825
|
+
return 0
|
|
1826
|
+
fi
|
|
1780
1827
|
[[ -z "$name" ]] && die "add 需要 NAME"
|
|
1781
1828
|
shift
|
|
1782
1829
|
|
|
@@ -1818,7 +1865,11 @@ EOF
|
|
|
1818
1865
|
die "受管应用已存在: $name"
|
|
1819
1866
|
fi
|
|
1820
1867
|
|
|
1821
|
-
|
|
1868
|
+
if array_has_items ingress_specs; then
|
|
1869
|
+
build_requested_ingress_rules "$hostname" "$service" "${ingress_specs[@]}"
|
|
1870
|
+
else
|
|
1871
|
+
build_requested_ingress_rules "$hostname" "$service"
|
|
1872
|
+
fi
|
|
1822
1873
|
if [[ "$skip_check" == "0" ]]; then
|
|
1823
1874
|
check_all_ingress_targets
|
|
1824
1875
|
fi
|
|
@@ -1863,6 +1914,17 @@ cmd_adopt() {
|
|
|
1863
1914
|
local skip_check=0
|
|
1864
1915
|
local activate=0
|
|
1865
1916
|
|
|
1917
|
+
if [[ "$name" == "-h" || "$name" == "--help" ]]; then
|
|
1918
|
+
cat <<EOF
|
|
1919
|
+
用法:
|
|
1920
|
+
$SCRIPT_NAME adopt NAME --tunnel-name NAME [--hostname HOST --service URL] [--ingress HOST=URL ...] [--ensure-dns] [--overwrite-dns] [--skip-check] [--activate]
|
|
1921
|
+
|
|
1922
|
+
说明:
|
|
1923
|
+
- 至少要提供一条 ingress 规则
|
|
1924
|
+
- 如果远端 DNS 已经配好,可不传 --ensure-dns
|
|
1925
|
+
EOF
|
|
1926
|
+
return 0
|
|
1927
|
+
fi
|
|
1866
1928
|
[[ -z "$name" ]] && die "adopt 需要 NAME"
|
|
1867
1929
|
shift
|
|
1868
1930
|
|
|
@@ -1903,7 +1965,11 @@ EOF
|
|
|
1903
1965
|
die "受管应用已存在: $name"
|
|
1904
1966
|
fi
|
|
1905
1967
|
|
|
1906
|
-
|
|
1968
|
+
if array_has_items ingress_specs; then
|
|
1969
|
+
build_requested_ingress_rules "$hostname" "$service" "${ingress_specs[@]}"
|
|
1970
|
+
else
|
|
1971
|
+
build_requested_ingress_rules "$hostname" "$service"
|
|
1972
|
+
fi
|
|
1907
1973
|
if [[ "$skip_check" == "0" ]]; then
|
|
1908
1974
|
check_all_ingress_targets
|
|
1909
1975
|
fi
|
|
@@ -2002,7 +2068,7 @@ EOF
|
|
|
2002
2068
|
[[ -n "$pid" ]] && was_running=1
|
|
2003
2069
|
fi
|
|
2004
2070
|
|
|
2005
|
-
if
|
|
2071
|
+
if array_has_items modify_specs; then
|
|
2006
2072
|
if [[ -n "$new_hostname" || -n "$new_service" || "$index" != "1" ]]; then
|
|
2007
2073
|
die "使用 --set 批量修改时,不要再混用 --index / --hostname / --service"
|
|
2008
2074
|
fi
|
|
@@ -2052,7 +2118,7 @@ EOF
|
|
|
2052
2118
|
i=$((i + 1))
|
|
2053
2119
|
done
|
|
2054
2120
|
|
|
2055
|
-
changed_count="$
|
|
2121
|
+
changed_count="$(array_length changed_indices)"
|
|
2056
2122
|
if [[ "$changed_count" == "0" ]]; then
|
|
2057
2123
|
info "没有变更。"
|
|
2058
2124
|
return 0
|
|
@@ -2132,13 +2198,13 @@ EOF
|
|
|
2132
2198
|
if [[ "$was_running" == "1" && "$no_restart" == "0" ]]; then
|
|
2133
2199
|
stop_app 0
|
|
2134
2200
|
start_app 0 "$skip_check"
|
|
2135
|
-
if
|
|
2201
|
+
if array_has_items modify_specs; then
|
|
2136
2202
|
info "已批量修改 ${changed_count} 条 ingress 并重启: $META_NAME"
|
|
2137
2203
|
else
|
|
2138
2204
|
info "已修改并重启: $META_NAME"
|
|
2139
2205
|
fi
|
|
2140
2206
|
else
|
|
2141
|
-
if
|
|
2207
|
+
if array_has_items modify_specs; then
|
|
2142
2208
|
info "已批量修改 ingress 规则数量: ${changed_count}"
|
|
2143
2209
|
else
|
|
2144
2210
|
info "已修改: $META_NAME"
|
|
@@ -2226,18 +2292,20 @@ EOF
|
|
|
2226
2292
|
requested_services+=("$(normalize_service "$service")")
|
|
2227
2293
|
fi
|
|
2228
2294
|
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2295
|
+
if array_has_items ingress_specs; then
|
|
2296
|
+
for candidate_service in "${ingress_specs[@]}"; do
|
|
2297
|
+
parse_ingress_spec "$candidate_service"
|
|
2298
|
+
requested_hosts+=("$PARSED_INGRESS_HOSTNAME")
|
|
2299
|
+
requested_services+=("$PARSED_INGRESS_SERVICE")
|
|
2300
|
+
done
|
|
2301
|
+
fi
|
|
2234
2302
|
|
|
2235
|
-
if [[ "$
|
|
2303
|
+
if [[ "$(array_length requested_hosts)" == "0" ]]; then
|
|
2236
2304
|
die "至少需要提供一条 ingress 规则。可使用 --hostname/--service,或重复传 --ingress hostname=service"
|
|
2237
2305
|
fi
|
|
2238
2306
|
|
|
2239
2307
|
i=0
|
|
2240
|
-
while [[ "$i" -lt "$
|
|
2308
|
+
while [[ "$i" -lt "$(array_length requested_hosts)" ]]; do
|
|
2241
2309
|
candidate_hostname="${requested_hosts[$i]}"
|
|
2242
2310
|
candidate_service="${requested_services[$i]}"
|
|
2243
2311
|
|
|
@@ -2340,7 +2408,7 @@ EOF
|
|
|
2340
2408
|
esac
|
|
2341
2409
|
done
|
|
2342
2410
|
|
|
2343
|
-
if [[ "$
|
|
2411
|
+
if [[ "$(array_length hostnames)" == "0" && "$(array_length indices)" == "0" ]]; then
|
|
2344
2412
|
die "ingress-remove 至少需要一个 --hostname 或 --index"
|
|
2345
2413
|
fi
|
|
2346
2414
|
|
|
@@ -2359,21 +2427,25 @@ EOF
|
|
|
2359
2427
|
i=$((i + 1))
|
|
2360
2428
|
done
|
|
2361
2429
|
|
|
2362
|
-
|
|
2363
|
-
hostname
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2430
|
+
if array_has_items hostnames; then
|
|
2431
|
+
for hostname in "${hostnames[@]}"; do
|
|
2432
|
+
hostname="$(normalize_hostname "$hostname")"
|
|
2433
|
+
current_index="$(find_ingress_rule_index_by_hostname "$hostname" 2>/dev/null || true)"
|
|
2434
|
+
[[ -n "$current_index" ]] || die "未找到要删除的 ingress 主机名: $hostname"
|
|
2435
|
+
remove_flags[$current_index]=1
|
|
2436
|
+
done
|
|
2437
|
+
fi
|
|
2368
2438
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2439
|
+
if array_has_items indices; then
|
|
2440
|
+
for index in "${indices[@]}"; do
|
|
2441
|
+
[[ "$index" =~ ^[1-9][0-9]*$ ]] || die "--index 必须是大于等于 1 的整数"
|
|
2442
|
+
current_index=$((index - 1))
|
|
2443
|
+
if [[ "$current_index" -lt 0 || "$current_index" -ge "$total_rules" ]]; then
|
|
2444
|
+
die "要删除的 ingress 序号超出范围: $index"
|
|
2445
|
+
fi
|
|
2446
|
+
remove_flags[$current_index]=1
|
|
2447
|
+
done
|
|
2448
|
+
fi
|
|
2377
2449
|
|
|
2378
2450
|
i=0
|
|
2379
2451
|
while [[ "$i" -lt "$total_rules" ]]; do
|
|
@@ -2390,7 +2462,7 @@ EOF
|
|
|
2390
2462
|
if [[ "$remove_count" == "0" ]]; then
|
|
2391
2463
|
die "未找到要删除的 ingress 规则。"
|
|
2392
2464
|
fi
|
|
2393
|
-
if [[ "$
|
|
2465
|
+
if [[ "$(array_length remaining_hosts)" == "0" ]]; then
|
|
2394
2466
|
die "至少需要保留一条 ingress 规则。当前删除范围会把全部规则删空。"
|
|
2395
2467
|
fi
|
|
2396
2468
|
|
|
@@ -2402,7 +2474,7 @@ EOF
|
|
|
2402
2474
|
save_ingress_rules
|
|
2403
2475
|
write_config 0
|
|
2404
2476
|
save_meta
|
|
2405
|
-
warn "已从配置中移除 $
|
|
2477
|
+
warn "已从配置中移除 $(array_length removed_hosts) 条 ingress。Cloudflare 上旧 DNS 记录不会自动删除,如已弃用请手工清理。"
|
|
2406
2478
|
|
|
2407
2479
|
if [[ "$activate" == "1" ]]; then
|
|
2408
2480
|
activate_default_config
|
|
@@ -2412,7 +2484,7 @@ EOF
|
|
|
2412
2484
|
start_app 0 0
|
|
2413
2485
|
info "已移除 ingress 并重启: $META_NAME"
|
|
2414
2486
|
else
|
|
2415
|
-
info "已移除 ingress 规则数量: $
|
|
2487
|
+
info "已移除 ingress 规则数量: $(array_length removed_hosts)"
|
|
2416
2488
|
fi
|
|
2417
2489
|
}
|
|
2418
2490
|
|