258 lines
8.9 KiB
Bash
258 lines
8.9 KiB
Bash
#!/bin/bash
|
||
# https://github.com/sky22333/shell
|
||
# macOS 兼容版本 - 修复了 BSD grep 和 launchctl 兼容性问题
|
||
|
||
set -e
|
||
|
||
# === 颜色定义 ===
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[1;34m'
|
||
NC='\033[0m' # 清除颜色
|
||
|
||
# 检测操作系统与架构
|
||
OS_TYPE=$(uname -s)
|
||
ARCH=$(uname -m)
|
||
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
||
# macOS 配置
|
||
if [[ "$ARCH" == "arm64" ]]; then
|
||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/download/2025.11.1/cloudflared-darwin-arm64.tgz"
|
||
else
|
||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/download/2025.11.1/cloudflared-darwin-amd64.tgz"
|
||
fi
|
||
CLOUDFLARED_BIN="/usr/local/bin/cloudflared"
|
||
SERVICE_PATH="$HOME/Library/LaunchAgents/com.cloudflare.cloudflared.plist"
|
||
LOG_PATH="$HOME/Library/Logs/cloudflared.log"
|
||
USE_LAUNCHCTL=true
|
||
else
|
||
# Linux 配置
|
||
if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
|
||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/download/2025.11.1/cloudflared-linux-arm64"
|
||
else
|
||
CLOUDFLARED_URL="https://github.com/cloudflare/cloudflared/releases/download/2025.11.1/cloudflared-linux-amd64"
|
||
fi
|
||
CLOUDFLARED_BIN="/usr/local/bin/cloudflared"
|
||
SERVICE_PATH="/etc/systemd/system/cloudflared-tunnel.service"
|
||
LOG_PATH="/var/log/cloudflared.log"
|
||
USE_LAUNCHCTL=false
|
||
fi
|
||
|
||
# 检查 cloudflared 是否已存在
|
||
if [[ -f "$CLOUDFLARED_BIN" ]]; then
|
||
echo -e "${GREEN}已存在文件,跳过下载。${NC}"
|
||
else
|
||
echo -e "${BLUE}正在下载 cloudflared...${NC}"
|
||
if [[ "$OS_TYPE" == "Darwin" ]]; then
|
||
# macOS: 下载 tgz 包并解压出可执行文件
|
||
TMP_TGZ=$(mktemp /tmp/cloudflared.XXXXXX.tgz)
|
||
if ! curl -L "$CLOUDFLARED_URL" -o "$TMP_TGZ"; then
|
||
echo -e "${RED}下载失败,请检查网络连接或 URL。${NC}"
|
||
exit 1
|
||
fi
|
||
tar -xzf "$TMP_TGZ" -C /tmp
|
||
# 解压后通常得到 cloudflared 可执行文件
|
||
if [[ -f "/tmp/cloudflared" ]]; then
|
||
sudo mv /tmp/cloudflared "$CLOUDFLARED_BIN"
|
||
else
|
||
# 有些发布包会包含子目录
|
||
FOUND=$(find /tmp -maxdepth 2 -type f -name cloudflared 2>/dev/null | head -n1)
|
||
if [[ -n "$FOUND" ]]; then
|
||
sudo mv "$FOUND" "$CLOUDFLARED_BIN"
|
||
else
|
||
echo -e "${RED}未能在压缩包中找到 cloudflared 可执行文件${NC}"
|
||
exit 1
|
||
fi
|
||
fi
|
||
rm -f "$TMP_TGZ"
|
||
else
|
||
# Linux: 直接下载二进制
|
||
if ! curl -L "$CLOUDFLARED_URL" -o "$CLOUDFLARED_BIN"; then
|
||
echo -e "${RED}下载失败,请检查网络连接或 URL。${NC}"
|
||
exit 1
|
||
fi
|
||
fi
|
||
chmod +x "$CLOUDFLARED_BIN"
|
||
fi
|
||
|
||
# 检查服务是否存在(根据系统选择 launchctl 或 systemd)
|
||
SERVICE_EXISTS=false
|
||
if [[ "$USE_LAUNCHCTL" == true ]]; then
|
||
if launchctl list | grep -q 'com.cloudflare.cloudflared'; then
|
||
SERVICE_EXISTS=true
|
||
echo -e "${YELLOW}已检测到 cloudflared launchctl 服务${NC}"
|
||
read -p "是否要卸载旧服务?(y/n): " UNINSTALL
|
||
if [[ "$UNINSTALL" == "y" || "$UNINSTALL" == "Y" ]]; then
|
||
echo -e "${BLUE}正在卸载旧服务...${NC}"
|
||
launchctl unload "$SERVICE_PATH" 2>/dev/null || true
|
||
rm -f "$SERVICE_PATH"
|
||
rm -f "$LOG_PATH"
|
||
SERVICE_EXISTS=false
|
||
echo -e "${GREEN}服务卸载完成${NC}"
|
||
else
|
||
echo -e "${YELLOW}将保留旧服务配置,仅修改穿透地址${NC}"
|
||
fi
|
||
fi
|
||
else
|
||
if sudo systemctl list-units --full --all | grep -q 'cloudflared-tunnel.service'; then
|
||
SERVICE_EXISTS=true
|
||
echo -e "${YELLOW}已检测到 cloudflared-tunnel systemd 服务${NC}"
|
||
read -p "是否要卸载旧服务?(y/n): " UNINSTALL
|
||
if [[ "$UNINSTALL" == "y" || "$UNINSTALL" == "Y" ]]; then
|
||
echo -e "${BLUE}正在卸载旧服务...${NC}"
|
||
sudo systemctl stop cloudflared-tunnel || true
|
||
sudo systemctl disable cloudflared-tunnel || true
|
||
sudo rm -f "$SERVICE_PATH"
|
||
sudo rm -f "$LOG_PATH"
|
||
sudo systemctl daemon-reload
|
||
SERVICE_EXISTS=false
|
||
echo -e "${GREEN}服务卸载完成${NC}"
|
||
else
|
||
echo -e "${YELLOW}将保留旧服务配置,仅修改穿透地址${NC}"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 用户选择运行模式
|
||
echo ""
|
||
echo -e "${YELLOW}请选择运行模式:${NC}"
|
||
echo "1) 临时运行(前台运行并显示临时访问域名)"
|
||
echo "2) 后台运行(自动配置后台服务并显示访问域名)"
|
||
read -p "请输入 1 或 2: " MODE
|
||
|
||
# 输入内网地址
|
||
read -p "请输入要穿透的本地地址(例如 127.0.0.1:8080): " LOCAL_ADDR
|
||
|
||
if [[ "$MODE" == "1" ]]; then
|
||
echo -e "${BLUE}正在前台运行 cloudflared...${NC}"
|
||
|
||
LOGFILE=$(mktemp)
|
||
# macOS 无 stdbuf,用 script 捕获输出(同时兼容 Linux)
|
||
if command -v stdbuf >/dev/null 2>&1; then
|
||
stdbuf -oL "$CLOUDFLARED_BIN" tunnel --url "$LOCAL_ADDR" 2>&1 | tee "$LOGFILE" &
|
||
else
|
||
"$CLOUDFLARED_BIN" tunnel --url "$LOCAL_ADDR" 2>&1 | tee "$LOGFILE" &
|
||
fi
|
||
PID=$!
|
||
|
||
echo -e "${YELLOW}等待 cloudflared 输出访问域名...${NC}"
|
||
|
||
for i in {1..60}; do
|
||
# BSD grep 无 -P,用 -E 实现扩展正则
|
||
DOMAIN=$(grep -Eo 'https://[A-Za-z0-9-]+\.trycloudflare\.com' "$LOGFILE" | head -n1)
|
||
if [[ -n "$DOMAIN" ]]; then
|
||
echo ""
|
||
echo -e "${GREEN}成功获取公网临时访问域名:$DOMAIN${NC}"
|
||
echo ""
|
||
wait $PID
|
||
exit 0
|
||
fi
|
||
sleep 1
|
||
done
|
||
|
||
echo -e "${RED}超时未能获取临时域名,日志保存在:$LOGFILE${NC}"
|
||
kill $PID 2>/dev/null || true
|
||
exit 1
|
||
|
||
elif [[ "$MODE" == "2" ]]; then
|
||
if [[ "$USE_LAUNCHCTL" == true ]]; then
|
||
echo -e "${BLUE}正在配置 launchctl 服务...${NC}"
|
||
mkdir -p "$(dirname "$SERVICE_PATH")" "$(dirname "$LOG_PATH")"
|
||
cat > "$SERVICE_PATH" <<EOF
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0">
|
||
<dict>
|
||
<key>Label</key>
|
||
<string>com.cloudflare.cloudflared</string>
|
||
<key>ProgramArguments</key>
|
||
<array>
|
||
<string>$CLOUDFLARED_BIN</string>
|
||
<string>tunnel</string>
|
||
<string>--url</string>
|
||
<string>$LOCAL_ADDR</string>
|
||
</array>
|
||
<key>RunAtLoad</key>
|
||
<true/>
|
||
<key>StandardOutPath</key>
|
||
<string>$LOG_PATH</string>
|
||
<key>StandardErrorPath</key>
|
||
<string>$LOG_PATH</string>
|
||
</dict>
|
||
</plist>
|
||
EOF
|
||
launchctl unload "$SERVICE_PATH" 2>/dev/null || true
|
||
launchctl load "$SERVICE_PATH"
|
||
launchctl start com.cloudflare.cloudflared || true
|
||
else
|
||
echo -e "${BLUE}正在配置 systemd 服务...${NC}"
|
||
|
||
if [[ "$SERVICE_EXISTS" == false ]]; then
|
||
sudo bash -c "cat > $SERVICE_PATH" <<EOF
|
||
[Unit]
|
||
Description=Cloudflared Tunnel Service
|
||
After=network.target
|
||
|
||
[Service]
|
||
ExecStart=$CLOUDFLARED_BIN tunnel --url $LOCAL_ADDR
|
||
Restart=always
|
||
StandardOutput=append:$LOG_PATH
|
||
StandardError=append:$LOG_PATH
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
EOF
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now cloudflared-tunnel
|
||
else
|
||
echo -e "${YELLOW}更新 systemd 服务配置中的穿透地址...${NC}"
|
||
sudo truncate -s 0 "$LOG_PATH" || sudo bash -c "> $LOG_PATH"
|
||
sudo sed -i "s|ExecStart=.*|ExecStart=$CLOUDFLARED_BIN tunnel --url $LOCAL_ADDR|" "$SERVICE_PATH"
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl restart cloudflared-tunnel
|
||
fi
|
||
fi
|
||
|
||
echo -e "${GREEN}服务已启动,日志保存在 $LOG_PATH${NC}"
|
||
echo -e "${YELLOW}等待 cloudflared 输出访问域名...${NC}"
|
||
|
||
# 等待日志文件创建
|
||
for i in {1..10}; do
|
||
if [[ -f "$LOG_PATH" ]]; then
|
||
break
|
||
fi
|
||
sleep 1
|
||
done
|
||
|
||
# 如果日志文件仍不存在,尝试创建
|
||
if [[ ! -f "$LOG_PATH" ]]; then
|
||
mkdir -p "$(dirname "$LOG_PATH")"
|
||
touch "$LOG_PATH" 2>/dev/null || true
|
||
fi
|
||
|
||
for i in {1..60}; do
|
||
if [[ -f "$LOG_PATH" ]]; then
|
||
DOMAIN=$(grep -Eo 'https://[A-Za-z0-9-]+\.trycloudflare\.com' "$LOG_PATH" 2>/dev/null | head -n1)
|
||
if [[ -n "$DOMAIN" ]]; then
|
||
echo ""
|
||
echo -e "${GREEN}成功获取公网访问域名:$DOMAIN${NC}"
|
||
echo ""
|
||
exit 0
|
||
fi
|
||
fi
|
||
sleep 1
|
||
done
|
||
|
||
echo -e "${RED}超时未能获取公网访问域名,请稍后手动查看:$LOG_PATH${NC}"
|
||
if [[ "$USE_LAUNCHCTL" == true ]]; then
|
||
echo -e "${YELLOW}您也可以使用以下命令查看服务状态:${NC}"
|
||
echo "launchctl list | grep cloudflared"
|
||
echo "tail -f $LOG_PATH"
|
||
fi
|
||
exit 1
|
||
|
||
else
|
||
echo -e "${RED}无效输入,请输入 1 或 2${NC}"
|
||
exit 1
|
||
fi
|