#!/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" < Label com.cloudflare.cloudflared ProgramArguments $CLOUDFLARED_BIN tunnel --url $LOCAL_ADDR RunAtLoad StandardOutPath $LOG_PATH StandardErrorPath $LOG_PATH 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" < $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