forked from fscarmen/sba
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtunnel.sh
More file actions
292 lines (240 loc) · 9.79 KB
/
Copy pathtunnel.sh
File metadata and controls
292 lines (240 loc) · 9.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/bin/bash
# Cloudflare Tunnel 管理脚本
# 用法: ./tunnel.sh <CLOUDFLARE_API_TOKEN> <DOMAIN_NAME> [SERVICE_URL]
#
# API Token 最小权限要求:
# 1. Account - Cloudflare Tunnel:Edit
# 2. Zone - DNS:Edit
# 3. Zone - Zone:Read
#
# 创建 API Token 步骤:
# 1. 访问 https://dash.cloudflare.com/profile/api-tokens
# 2. 点击 "Create Token"
# 3. 选择 "Create Custom Token"
# 4. 添加以下权限:
# - Account > Cloudflare Tunnel > Edit
# - Zone > DNS > Edit
# - Zone > Zone > Read
# 5. Account Resources: Include > 所需账户
# 6. Zone Resources: Include > 特定Zone > 所需Zone
#
set -e
# 验证 32位 Hex 字符串 (Zone ID, Account ID)
validate_hex32() {
if [[ ! "$1" =~ ^[a-f0-9]{32}$ ]]; then
echo "✗ 错误: ID 格式校验失败 ($1),应为 32 位 Hex 字符串。"
exit 1
fi
}
# 验证 UUID (Tunnel ID)
validate_uuid() {
if [[ ! "$1" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$ ]]; then
echo "✗ 错误: Tunnel ID 格式校验失败 ($1),应为 UUID 格式。"
exit 1
fi
}
# JSON 提取 用法: echo "$JSON" | get_json_value "key"
get_json_value() {
local key=$1
# 1. grep -o 匹配 "key": 后面的内容,直到遇到逗号或右大括号 (不假设值有引号)
# 2. head -n 1 取第一个匹配项
# 3. cut 取冒号后的值部分
# 4. sed 去除可能存在的首尾空白和双引号 (支持 "value", true, 123 等)
grep -o "\"$key\":[^,}]*" | head -n 1 | cut -d':' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/^"//;s/"$//'
}
# 参数检查与初始化
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "用法: $0 <CLOUDFLARE_API_TOKEN> <DOMAIN_NAME> [SERVICE_URL]"
echo ""
echo "参数说明:"
echo " CLOUDFLARE_API_TOKEN: Cloudflare API Token"
echo " DOMAIN_NAME: 完整域名,例如 app.example.com(必须包含子域名)"
echo " SERVICE_URL: 可选,默认为 http://localhost:3010"
echo ""
echo "示例:"
echo " $0 your_token app.example.com"
echo " $0 your_token app.example.com https://localhost:8443"
exit 1
fi
CLOUDFLARE_API_TOKEN="$1"
DOMAIN_NAME="$2"
SERVICE_URL="${3:-http://localhost:3010}"
# 检查必要依赖
for cmd in wget openssl grep sed awk; do
if ! command -v $cmd &> /dev/null; then
echo "✗ 错误: 未找到命令 '$cmd',请先安装。"
exit 1
fi
done
# 提取域名信息
DOT_COUNT=$(echo "$DOMAIN_NAME" | grep -o "\." | wc -l)
if [ "$DOT_COUNT" -lt 2 ]; then
echo "✗ 错误: DOMAIN_NAME ($DOMAIN_NAME) 必须包含子域名 (例如: app.example.com)"
exit 1
fi
TUNNEL_NAME=$(echo "$DOMAIN_NAME" | cut -d'.' -f1)
ROOT_DOMAIN=$(echo "$DOMAIN_NAME" | cut -d'.' -f2-)
API_BASE="https://api.cloudflare.com/client/v4"
# wget header
WGET_ARGS=(--no-check-certificate -qO- --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" --header "Content-Type: application/json")
echo "=========================================="
echo "Cloudflare Tunnel 配置脚本"
echo "=========================================="
echo "输入域名: $DOMAIN_NAME"
echo "=========================================="
echo "推导出Tunnel名称: $TUNNEL_NAME"
echo "推导出根域名: $ROOT_DOMAIN"
echo "Service URL: $SERVICE_URL"
echo "=========================================="
# 步骤 1: 获取 Zone ID 和 Account ID
echo ""
echo "[步骤 1] 获取 Zone ID 和 Account ID..."
ZONE_RESPONSE=$(wget "${WGET_ARGS[@]}" "${API_BASE}/zones?name=${ROOT_DOMAIN}")
# 提取 Zone ID
ZONE_ID=$(echo "$ZONE_RESPONSE" | grep -o '"id":"[a-f0-9]\{32\}"' | head -n 1 | cut -d'"' -f4)
# 提取 Account ID
ACCOUNT_ID=$(echo "$ZONE_RESPONSE" | sed -n 's/.*"account":{"id":"\([a-f0-9]\{32\}\)".*/\1/p' | head -n 1)
if [ -z "$ZONE_ID" ]; then
echo "✗ 无法获取 Zone ID,请检查域名是否正确或 Token 权限 (Zone:Read)。"
exit 1
fi
if [ -z "$ACCOUNT_ID" ]; then
echo "✗ 无法获取 Account ID,请检查 Token 权限。"
exit 1
fi
validate_hex32 "$ZONE_ID"
validate_hex32 "$ACCOUNT_ID"
echo "✓ Zone ID: $ZONE_ID"
echo "✓ Account ID: $ACCOUNT_ID"
# 步骤 2: 查询并处理现有 Tunnel
echo ""
echo "[步骤 2] 检查现有 Tunnel..."
# 查询未删除的同名隧道
TUNNEL_LIST=$(wget "${WGET_ARGS[@]}" "${API_BASE}/accounts/${ACCOUNT_ID}/cfd_tunnel?name=${TUNNEL_NAME}&is_deleted=false")
# 尝试提取现有 ID
EXISTING_TUNNEL_ID=$(echo "$TUNNEL_LIST" | grep -o '"id":"[a-f0-9]\{8\}-[a-f0-9]\{4\}-[a-f0-9]\{4\}-[a-f0-9]\{4\}-[a-f0-9]\{12\}"' | head -n 1 | cut -d'"' -f4)
TUNNEL_ID=""
TUNNEL_TOKEN=""
TUNNEL_SECRET=""
if [ -n "$EXISTING_TUNNEL_ID" ]; then
validate_uuid "$EXISTING_TUNNEL_ID"
echo "✓ 发现现有隧道,准备复用。"
TUNNEL_ID="$EXISTING_TUNNEL_ID"
echo "✓ Tunnel ID: $TUNNEL_ID"
# 获取 Tunnel Token (复用时需要单独获取 Token)
TOKEN_RESPONSE=$(wget "${WGET_ARGS[@]}" "${API_BASE}/accounts/${ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}/token")
TUNNEL_TOKEN=$(echo "$TOKEN_RESPONSE" | get_json_value "result")
if [ -z "$TUNNEL_TOKEN" ]; then
echo "✗ 错误: 无法获取 Tunnel Token"
exit 1
fi
echo "✓ Tunnel Token: $TUNNEL_TOKEN"
# 从 Token 中解析 Secret (base64 decode) Token 结构: {"a":"account_tag","t":"tunnel_id","s":"tunnel_secret"}
# 优先使用 base64 命令,如果不存在则尝试 openssl
if command -v base64 &> /dev/null; then
TOKEN_DECODED=$(echo "$TUNNEL_TOKEN" | base64 -d 2>/dev/null)
else
TOKEN_DECODED=$(echo "$TUNNEL_TOKEN" | openssl enc -d -base64 -A 2>/dev/null)
fi
# 提取 "s" (TunnelSecret)
TUNNEL_SECRET=$(echo "$TOKEN_DECODED" | get_json_value "s")
if [ -n "$TUNNEL_SECRET" ]; then
echo "✓ Tunnel Secret: $TUNNEL_SECRET"
fi
else
echo "未发现现有活跃隧道,正在创建新隧道..."
# 生成 32 字节随机 Secret 并 Base64 编码
TUNNEL_SECRET=$(openssl rand -base64 32)
CREATE_PAYLOAD="{\"name\":\"$TUNNEL_NAME\",\"config_src\":\"cloudflare\",\"tunnel_secret\":\"$TUNNEL_SECRET\"}"
CREATE_RESPONSE=$(wget "${WGET_ARGS[@]}" --method=POST --body-data="$CREATE_PAYLOAD" "${API_BASE}/accounts/${ACCOUNT_ID}/cfd_tunnel")
IS_SUCCESS=$(echo "$CREATE_RESPONSE" | get_json_value "success")
if [ "$IS_SUCCESS" != "true" ]; then
echo "✗ 创建 Tunnel 失败。"
echo "响应: $CREATE_RESPONSE"
exit 1
fi
TUNNEL_ID=$(echo "$CREATE_RESPONSE" | get_json_value "id")
TUNNEL_TOKEN=$(echo "$CREATE_RESPONSE" | get_json_value "token")
validate_uuid "$TUNNEL_ID"
echo "✓ 新隧道创建成功。"
echo "✓ Tunnel ID: $TUNNEL_ID"
echo "✓ Tunnel Token: $TUNNEL_TOKEN"
echo "✓ Tunnel Secret: $TUNNEL_SECRET"
fi
# ==========================================
# 步骤 3: 配置 Tunnel (Ingress Rules)
# ==========================================
echo ""
echo "[步骤 3] 配置 Tunnel ingress 规则..."
# 检查是否需要 HTTPS 配置
TLS_CONFIG=""
if [[ "$SERVICE_URL" == https* ]]; then
echo " 检测到 HTTPS 服务,添加 noTLSVerify 配置..."
TLS_CONFIG=',"originRequest":{"noTLSVerify":true}'
fi
# 手动构造复杂的 JSON 配置文件
# 注意:Shell 变量拼接 JSON 需要非常小心引号
CONFIG_PAYLOAD="{
\"config\": {
\"ingress\": [
{
\"service\": \"$SERVICE_URL\",
\"hostname\": \"$DOMAIN_NAME\"${TLS_CONFIG}
},
{
\"service\": \"http_status:404\"
}
],
\"warp-routing\": {
\"enabled\": false
}
}
}"
CONFIG_RESPONSE=$(wget "${WGET_ARGS[@]}" --method=PUT --body-data="$CONFIG_PAYLOAD" "${API_BASE}/accounts/${ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}/configurations")
CONFIG_SUCCESS=$(echo "$CONFIG_RESPONSE" | get_json_value "success")
if [ "$CONFIG_SUCCESS" != "true" ]; then
echo "✗ 配置 Tunnel 失败。"
echo "响应: $CONFIG_RESPONSE"
exit 1
fi
echo "✓ Tunnel 配置已更新: $SERVICE_URL -> $DOMAIN_NAME"
# ==========================================
# 步骤 4: 管理 DNS 记录
# ==========================================
echo ""
echo "[步骤 4] 管理 DNS 记录..."
# 查询现有 DNS 记录
DNS_LIST=$(wget "${WGET_ARGS[@]}" "${API_BASE}/zones/${ZONE_ID}/dns_records?type=CNAME&name=${DOMAIN_NAME}")
EXISTING_DNS_ID=$(echo "$DNS_LIST" | grep -o '"id":"[a-f0-9]\{32\}"' | head -n 1 | cut -d'"' -f4)
CNAME_TARGET="${TUNNEL_ID}.cfargotunnel.com"
DNS_PAYLOAD="{\"name\":\"${DOMAIN_NAME}\",\"type\":\"CNAME\",\"content\":\"${CNAME_TARGET}\",\"proxied\":true,\"settings\":{\"flatten_cname\":false}}"
if [ -n "$EXISTING_DNS_ID" ]; then
validate_hex32 "$EXISTING_DNS_ID"
echo "发现现有 DNS 记录 (ID: $EXISTING_DNS_ID),正在更新..."
DNS_RESPONSE=$(wget "${WGET_ARGS[@]}" --method=PATCH --body-data="$DNS_PAYLOAD" "${API_BASE}/zones/${ZONE_ID}/dns_records/${EXISTING_DNS_ID}")
else
echo "未找到现有 DNS 记录,正在创建..."
DNS_RESPONSE=$(wget "${WGET_ARGS[@]}" --method=POST --body-data="$DNS_PAYLOAD" "${API_BASE}/zones/${ZONE_ID}/dns_records")
fi
DNS_SUCCESS=$(echo "$DNS_RESPONSE" | get_json_value "success")
if [ "$DNS_SUCCESS" != "true" ]; then
echo "✗ 创建 DNS 记录失败: $DNS_RESPONSE"
exit 1
fi
echo "✓ DNS 记录创建成功"
# 完成
echo ""
echo "=========================================="
echo "✓ 所有操作完成!"
echo "=========================================="
echo "Zone ID: $ZONE_ID"
echo "Account ID: $ACCOUNT_ID"
echo "Tunnel ID: $TUNNEL_ID"
echo "Tunnel 名称: $TUNNEL_NAME"
echo "Tunnel 域名: ${DOMAIN_NAME}"
echo "Tunnel Target: ${TUNNEL_ID}.cfargotunnel.com"
echo "Service: $SERVICE_URL"
echo "=========================================="
echo "Tunnel Token: $TUNNEL_TOKEN"
echo "Tunnel Json: {\"AccountTag\":\"$ACCOUNT_ID\",\"TunnelSecret\":\"$TUNNEL_SECRET\",\"TunnelID\":\"$TUNNEL_ID\",\"Endpoint\":\"\"}"
echo "=========================================="