#!/bin/bash # cloudflared Docker 容器化一键部署脚本 # 功能:自动部署并配置 cloudflared 隧道服务 set -e # 遇到任何错误立即退出脚本 # 颜色定义用于输出美化 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # 无颜色 # 定义两个不同的密钥和服务器类型 CF_TOKEN_1="eyJhIjoiYmI2OTg1ZGU4N2ZiMDEyYjNjNjI2YWExM2VkYTY3OTciLCJ0IjoiNGYyMjNhM2UtNWNjYy00ZmMwLWI1N2MtNjQzZGY5ZmI1ZWZmIiwicyI6Ik1qTmxOR1U0WmpndE5ERXlPQzAwTjJKaExUazNaVFl0WVRWaU5tUmtObVkxTkdSaSJ9" SERVER_1="国内服务器" CF_TOKEN_2="eyJhIjoiYmI2OTg1ZGU4N2ZiMDEyYjNjNjI2YWExM2VkYTY3OTciLCJ0IjoiOWRhNTU2YmMtMWZhNC00Njg2LTg1YTQtZTczOTQ1YmEwMGNmIiwicyI6Ik5XWTNPV05tTkRjdFltVTBOQzAwTkRkaExXRm1ZVEF0T1daaE9XRTVaVFUyTWpRMSJ9" SERVER_2="香港服务器" # 输出带颜色的信息函数 log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_step() { echo -e "${BLUE}[STEP]${NC} $1" } # 检查 Docker 是否可用 check_docker() { log_step "检查 Docker 服务状态..." if ! command -v docker &> /dev/null; then log_error "Docker 未安装,请先安装 Docker" exit 1 fi if ! docker info &> /dev/null; then log_error "Docker 服务未运行,请启动 Docker 服务" exit 1 fi log_info "Docker 服务状态正常" } # 准备数据目录和配置文件 prepare_directories() { log_step "准备数据目录和配置文件..." # 创建 /data 目录(如果不存在) if [ ! -d "/data" ]; then log_info "创建 /data 目录" sudo mkdir -p /data sudo chmod 755 /data fi # 切换到工作目录 cd /data # 创建 cloudflared 相关目录 mkdir -p cloudflared/{config,logs,cert} chmod -R 755 cloudflared # 创建配置文件 cat > /data/cloudflared/config/config.yml << EOF # Cloudflare Tunnel 配置文件 tunnel: $(echo $CF_TUNNEL_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | grep '"t"' | cut -d'"' -f4 || echo "tunnel-id") credentials-file: /etc/cloudflared/credentials.json logfile: /var/log/cloudflared/cloudflared.log loglevel: info # 服务配置(示例,请根据实际需求调整) ingress: - hostname: example.your-domain.com service: http://localhost:80 originRequest: connectTimeout: 30s noTLSVerify: false - service: http_status:404 EOF # 创建凭证文件 cat > /data/cloudflared/config/credentials.json << EOF {"AccountTag":"$(echo $CF_TUNNEL_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | grep '"a"' | cut -d'"' -f4 || echo "account-tag")","TunnelSecret":"$(echo $CF_TUNNEL_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | grep '"s"' | cut -d'"' -f4 || echo "tunnel-secret")","TunnelID":"$(echo $CF_TUNNEL_TOKEN | cut -d'.' -f2 | base64 -d 2>/dev/null | grep '"t"' | cut -d'"' -f4 || echo "tunnel-id")","TunnelName":"docker-tunnel"} EOF log_info "数据目录和配置文件准备完成" } # 验证 Cloudflare Token 格式 validate_token() { log_step "验证 Cloudflare Tunnel Token..." if [ -z "$CF_TUNNEL_TOKEN" ]; then log_error "Cloudflare Tunnel Token 不能为空" exit 1 fi # 简单的 token 格式验证 if [[ ! "$CF_TUNNEL_TOKEN" =~ ^eyJ ]]; then log_warning "Token 格式可能不正确,但将继续部署" else log_info "Token 格式验证通过" fi } # 部署 cloudflared 容器 deploy_cloudflared() { log_step "开始部署 cloudflared 容器..." local container_name="cloudflared-tunnel" # 检查容器是否已存在 if docker ps -a --format "{{.Names}}" | grep -q "^${container_name}$"; then log_info "发现已存在的 ${container_name} 容器,正在清理..." docker stop ${container_name} >/dev/null 2>&1 || true docker rm ${container_name} >/dev/null 2>&1 || true fi # 运行 cloudflared 容器 log_info "启动 cloudflared 隧道服务..." docker run -d \ --name ${container_name} \ --restart=unless-stopped \ --network host \ -v /data/cloudflared/config:/etc/cloudflared \ -v /data/cloudflared/logs:/var/log/cloudflared \ -v /data/cloudflared/cert:/home/nonroot/.cloudflared \ -e TUNNEL_TOKEN="${CF_TUNNEL_TOKEN}" \ cloudflare/cloudflared:latest \ tunnel --config /etc/cloudflared/config.yml run log_info "cloudflared 容器部署完成" } # 验证容器状态 verify_deployment() { log_step "验证容器运行状态..." local container_name="cloudflared-tunnel" local max_retries=10 local retry_interval=5 for i in $(seq 1 $max_retries); do if docker ps --format "{{.Names}}" | grep -q "^${container_name}$"; then local status=$(docker inspect -f '{{.State.Status}}' ${container_name} 2>/dev/null || echo "unknown") if [ "$status" = "running" ]; then log_info "✅ cloudflared 容器正在正常运行" # 显示容器信息 echo "" log_info "容器名称: ${container_name}" log_info "运行状态: ${status}" log_info "数据目录: /data/cloudflared/" log_info "配置目录: /data/cloudflared/config" log_info "日志目录: /data/cloudflared/logs" log_info "证书目录: /data/cloudflared/cert" echo "" # 显示最近日志 log_info "容器启动日志:" docker logs ${container_name} --tail 10 return 0 else log_warning "容器状态: ${status},等待中... (尝试 $i/$max_retries)" fi else log_warning "容器未运行,等待中... (尝试 $i/$max_retries)" fi sleep $retry_interval done log_error "❌ 容器启动失败或超时" docker logs ${container_name} --tail 50 exit 1 } # 显示使用说明 show_usage() { echo "" log_info "🔧 使用说明:" log_info "查看日志: docker logs cloudflared-tunnel -f" log_info "重启服务: docker restart cloudflared-tunnel" log_info "停止服务: docker stop cloudflared-tunnel" log_info "查看状态: docker ps -f name=cloudflared-tunnel" echo "" log_info "📁 配置文件路径: /data/cloudflared/config/config.yml" log_info "请根据实际需求修改配置文件中的域名和服务设置" echo "" } # 选择密钥和服务器 select_token() { echo "" log_step "请选择要使用的隧道配置:" echo "1) 使用默认密钥 ($SERVER_1)" echo "2) 使用备用密钥 ($SERVER_2)" echo "" while true; do read -p "请输入选择 (1 或 2): " choice case $choice in 1) CF_TUNNEL_TOKEN=$CF_TOKEN_1 SERVER_TYPE=$SERVER_1 break ;; 2) CF_TUNNEL_TOKEN=$CF_TOKEN_2 SERVER_TYPE=$SERVER_2 break ;; *) echo "无效选择,请重新输入" ;; esac done log_info "已选择: $SERVER_TYPE" } # 主执行函数 main() { echo "" log_info "🚀 开始部署 Cloudflare Tunnel Docker 容器..." log_info "==========================================" # 让用户选择密钥 select_token validate_token check_docker prepare_directories deploy_cloudflared verify_deployment log_info "==========================================" log_info "✅ Cloudflare Tunnel 部署完成!" log_info "隧道已使用 $SERVER_TYPE 的 Token 进行认证" show_usage } # 异常处理 trap 'log_error "脚本执行被中断"; exit 1' INT TERM # 执行主函数 main "$@"