├── .github └── workflows │ ├── 删除指定区域所有SAP应用.yml │ ├── 自动部署SAP.yml │ └── 自动保活SAP.yml ├── README.md └── LICENSE /.github/workflows/删除指定区域所有SAP应用.yml: -------------------------------------------------------------------------------- 1 | name: 删除指定区域所有SAP应用 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | region: 7 | description: '选择要删除应用的区域 (SG 或 US)' 8 | required: true 9 | default: 'SG' 10 | type: choice 11 | options: 12 | - SG 13 | - US 14 | confirmation: 15 | description: '此操作不可逆!请输入 "确认删除" 来执行。' 16 | required: true 17 | type: string 18 | 19 | jobs: 20 | delete-apps: 21 | runs-on: ubuntu-latest 22 | name: 从区域 ${{ github.event.inputs.region }} 删除所有应用 23 | 24 | steps: 25 | - name: 验证操作确认 26 | run: | 27 | if [ "${{ github.event.inputs.confirmation }}" != "确认删除" ]; then 28 | echo "错误:输入不匹配 '确认删除',操作已取消。" 29 | exit 1 30 | fi 31 | 32 | - name: 安装 CF CLI 33 | run: | 34 | wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - 35 | echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list 36 | sudo apt-get update 37 | sudo apt-get install -y cf8-cli 38 | 39 | - name: 设置区域相关变量 40 | run: | 41 | if [ "${{ github.event.inputs.region }}" = "SG" ]; then 42 | echo "CF_API=https://api.cf.ap21.hana.ondemand.com" >> $GITHUB_ENV 43 | echo "使用新加坡(SG)区域" 44 | else 45 | echo "CF_API=https://api.cf.us10-001.hana.ondemand.com" >> $GITHUB_ENV 46 | echo "使用美国(US)区域" 47 | fi 48 | 49 | - name: 登录并自动选择组织和空间 50 | run: | 51 | cf login -a ${{ env.CF_API }} -u "${{ secrets.EMAIL }}" -p "${{ secrets.PASSWORD }}" 52 | SELECTED_ORG=$(cf orgs | awk 'NR>3 {print $1; exit}') 53 | if [ -z "$SELECTED_ORG" ]; then echo "错误:未找到可用组织"; exit 1; fi 54 | cf target -o "$SELECTED_ORG" 55 | SELECTED_SPACE=$(cf spaces | awk 'NR>3 {print $1; exit}') 56 | if [ -z "$SELECTED_SPACE" ]; then echo "错误:未找到可用空间"; exit 1; fi 57 | cf target -s "$SELECTED_SPACE" 58 | echo "将从 组织: $SELECTED_ORG / 空间: $SELECTED_SPACE 中删除应用" 59 | 60 | - name: 获取并删除所有应用 61 | run: | 62 | # 这里是修正后的关键部分,确保 awk 语句有完整的单引号 63 | apps=$(cf apps | awk 'NR>3 {print $1}') 64 | 65 | if [ -z "$apps" ]; then 66 | echo "在区域 ${{ github.event.inputs.region }} 中未找到任何应用,无需删除。" 67 | exit 0 68 | fi 69 | 70 | echo "发现以下应用,将全部删除:" 71 | echo "$apps" 72 | 73 | # 循环删除每个应用 74 | for app in $apps; do 75 | echo "----------------------------------------" 76 | echo "正在删除应用: $app" 77 | # 使用 -f 强制删除,-r 同时删除关联的路由 78 | cf delete -f -r "$app" 79 | echo "应用 $app 删除成功。" 80 | done 81 | 82 | - name: 最终验证 83 | run: | 84 | echo "----------------------------------------" 85 | echo "删除操作完成。验证区域 ${{ github.event.inputs.region }} 中剩余的应用:" 86 | cf apps 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 部署失败不要慌早上8点在部署 2 | ### 前置要求 3 | * GitHub 账户:需要有一个 GitHub 账户来创建仓库和设置工作流 4 | * SAP Cloud Foundry 账户:需要有 SAP Cloud Foundry 的有效账户,点此注册:https://www.sap.com 5 | 6 | ## 部署步骤 7 | 8 | 1. Fork本仓库 9 | 10 | 2. 在Actions菜单允许 `I understand my workflows, go ahead and enable them` 按钮 11 | 12 | 3. 在 GitHub 仓库中设置以下 secrets(Settings → Secrets and variables → Actions → New repository secret): 13 | - `EMAIL`: Cloud Foundry账户邮箱 14 | - `PASSWORD`: Cloud Foundry账户密码 15 | 16 | **注意:新版本工作流已自动检测组织和空间,无需再设置以下secrets:** 17 | - ~~`SG_ORG`: 新加坡组织名称~~(已自动获取) 18 | - ~~`US_ORG`: 美国组织名称~~(已自动获取) 19 | - ~~`SPACE`: Cloud Foundry空间名称~~(已自动获取) 20 | - `部署的时候支持支持选择直连`: 打钩即可 21 | 22 | 4. **设置Docker容器环境变量(也是在secrets里设置)** 23 | 24 | 使用固定隧道token部署,请在cloudflare里设置端口为8001: 25 | 26 | **设置基础环境变量:** 27 | - `UUID`:节点uuid,如果开启了哪吒v1,部署完一个之后一定要修改UUID,否则agent会被覆盖 28 | - `ARGO_DOMAIN`:固定隧道域名,未设置将使用临时隧道 29 | - `ARGO_AUTH`:固定隧道json或token,未设置将使用临时隧道 30 | - `SUB_PATH`:订阅token,未设置默认是sub 31 | 32 | **可选环境变量:** 33 | - `NEZHA_SERVER`:v1形式: nezha.xxx.com:8008 v0形式:nezha.xxx.com 34 | - `NEZHA_PORT`:V1哪吒没有这个 35 | - `NEZHA_KEY`:v1的NZ_CLIENT_SECRET或v0的agent密钥 36 | - `CFIP`:优选域名或优选ip 37 | - `CFPORT`:优选域名或优选ip对应端口 38 | - `CHAT_ID`:Telegram聊天ID(可选) 39 | - `BOT_TOKEN`:Telegram机器人令牌(可选) 40 | 41 | 5. **开始部署** 42 | * 在GitHub仓库的Actions页面找到"自动部署SAP"工作流 43 | * 点击"Run workflow"按钮 44 | * 根据需要选择或填写以下参数: 45 | - environment: 选择部署环境(staging/production) 46 | - region: 选择部署区域(SG/US) 47 | - app_name: (可选)指定应用名称,留空则自动生成 48 | * 点击绿色的"Run workflow"按钮开始部署 49 | 50 | **工作流会自动:** 51 | - 检测并选择第一个可用的组织 52 | - 检测并选择第一个可用的空间 53 | - 显示部署目标信息 54 | - 完成应用部署和配置 55 | 56 | 6. **获取节点信息** 57 | * 点开运行的actions,查看Deploy application步骤的日志 58 | * 在日志末尾可以看到完整的部署信息,包括: 59 | - 应用名称 60 | - 访问域名 61 | - 部署组织和空间 62 | - 部署区域 63 | * 订阅地址:域名/$SUB_PATH(SUB_PATH变量没设置默认是sub,即订阅为:域名/sub) 64 | 65 | ## 应用管理 66 | 67 | ### 删除应用 68 | * 在Actions页面找到"删除指定区域所有SAP应用"工作流 69 | * 点击"Run workflow"按钮 70 | * 选择要删除应用的区域(SG或US) 71 | * 在确认框中输入"确认删除" 72 | * 点击"Run workflow"开始删除 73 | * 工作流会自动检测组织和空间,并删除该区域下的所有应用 74 | 75 | ## 保活机制 76 | 77 | ### GitHub Actions自动保活(推荐) 78 | * 本仓库包含"自动保活SAP"工作流,会自动重启所有区域的应用 79 | * 默认每天tuc 23:00执行(可在工作流文件中调整cron时间) 80 | * 工作流会自动检测所有区域的组织和空间 81 | * 并行处理SG和US区域的应用重启 82 | * actions保活可能存在时间误差,建议根据前两天的情况进行适当调整cron时间 83 | 84 | 85 | ## 新版本优势 86 | 87 | ### 简化配置 88 | - 无需手动设置组织和空间secrets 89 | - 自动检测并使用第一个可用的组织和空间 90 | - 减少了配置错误的可能性 91 | 92 | ### 智能部署 93 | - 自动生成应用名称(可自定义) 94 | - 显示详细的部署目标信息 95 | - 提供完整的部署结果反馈 96 | 97 | ### 统一管理 98 | - 所有工作流都使用相同的自动检测逻辑 99 | - 支持多区域并行操作 100 | - 提供详细的操作日志和统计信息 101 | 102 | ## 注意事项 103 | 104 | 1. **必需配置**:确保EMAIL和PASSWORD这两个GitHub Secrets已正确配置 105 | 2. **区域权限**:多区域部署需先开通权限,确保目标区域有足够的内存配额 106 | 3. **安全设置**:建议设置SUB_PATH订阅token,防止节点泄露 107 | 4. **首次使用**:第一次使用时工作流会自动检测可用资源,请查看运行日志确认检测结果 108 | 5. **应用命名**:如果不指定应用名称,系统会自动生成格式为"区域代码+随机字符串"的名称 109 | 6. **环境变量**:可以通过.env文件设置额外的环境变量,工作流会自动读取并应用 110 | -------------------------------------------------------------------------------- /.github/workflows/自动部署SAP.yml: -------------------------------------------------------------------------------- 1 | name: 自动部署 SAP 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | environment: 7 | description: '选择部署环境' 8 | required: true 9 | default: 'production' 10 | type: choice 11 | options: 12 | - staging 13 | - production 14 | region: 15 | description: '选择部署区域' 16 | required: true 17 | default: 'SG' 18 | type: choice 19 | options: 20 | - SG 21 | - US 22 | is_direct_connect: 23 | description: '启用直连?理论上速度更快' 24 | required: true 25 | type: boolean 26 | default: false 27 | 28 | env: 29 | MEMORY: 256M 30 | 31 | jobs: 32 | deploy: 33 | runs-on: ubuntu-latest 34 | environment: ${{ github.event.inputs.environment }} 35 | 36 | steps: 37 | - name: 检出代码 38 | uses: actions/checkout@v4 39 | 40 | - name: 安装 CF CLI 41 | run: | 42 | wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - 43 | echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list 44 | sudo apt-get update 45 | sudo apt-get install -y cf8-cli 46 | 47 | - name: 根据输入选择Docker镜像 48 | run: | 49 | if [ "${{ github.event.inputs.is_direct_connect }}" = "true" ]; then 50 | echo "已选择“直连镜像”,使用 ghcr.io/eooce/ws:latest" 51 | echo "DOCKER_IMAGE=ghcr.io/eooce/ws:latest" >> $GITHUB_ENV 52 | else 53 | echo "未选择“直连镜像”,使用默认的 ghcr.io/eooce/nodejs:main" 54 | echo "DOCKER_IMAGE=ghcr.io/eooce/nodejs:main" >> $GITHUB_ENV 55 | fi 56 | 57 | - name: 生成应用名称并设置API端点 58 | run: | 59 | if [ "${{ github.event.inputs.region }}" = "SG" ]; then 60 | echo "CF_API=https://api.cf.ap21.hana.ondemand.com" >> $GITHUB_ENV 61 | RANDOM_STR=$(head /dev/urandom | tr -dc 'a-z' | head -c 6) 62 | DEFAULT_APP_NAME="sg$RANDOM_STR" 63 | elif [ "${{ github.event.inputs.region }}" = "US" ]; then 64 | echo "CF_API=https://api.cf.us10-001.hana.ondemand.com" >> $GITHUB_ENV 65 | RANDOM_STR=$(head /dev/urandom | tr -dc 'a-z' | head -c 6) 66 | DEFAULT_APP_NAME="us$RANDOM_STR" 67 | fi 68 | 69 | echo "APP_NAME=$DEFAULT_APP_NAME" >> $GITHUB_ENV 70 | echo "将使用API端点: ${{ env.CF_API }}" 71 | echo "已自动生成应用名称: $DEFAULT_APP_NAME" 72 | 73 | - name: 登录并自动选择组织和空间 74 | run: | 75 | cf login -a ${{ env.CF_API }} -u "${{ secrets.EMAIL }}" -p "${{ secrets.PASSWORD }}" 76 | 77 | echo "登录成功,开始自动检测组织和空间..." 78 | 79 | SELECTED_ORG=$(cf orgs | awk 'NR>3 {print $1; exit}') 80 | 81 | if [ -z "$SELECTED_ORG" ]; then 82 | echo "错误:未能在您的账户下找到任何可用的组织。" 83 | echo "请检查您的SAP BTP账户是否已正确设置并启用Cloud Foundry运行时。" 84 | exit 1 85 | fi 86 | 87 | echo "已自动选择组织: $SELECTED_ORG" 88 | cf target -o "$SELECTED_ORG" 89 | 90 | SELECTED_SPACE=$(cf spaces | awk 'NR>3 {print $1; exit}') 91 | 92 | if [ -z "$SELECTED_SPACE" ]; then 93 | echo "错误:在组织 '$SELECTED_ORG' 中未找到任何可用的空间。" 94 | exit 1 95 | fi 96 | 97 | echo "已自动选择空间: $SELECTED_SPACE" 98 | cf target -s "$SELECTED_SPACE" 99 | 100 | echo "----------------------------------------" 101 | echo "当前部署目标确认:" 102 | cf target 103 | echo "----------------------------------------" 104 | 105 | echo "SELECTED_ORG=$SELECTED_ORG" >> $GITHUB_ENV 106 | echo "SELECTED_SPACE=$SELECTED_SPACE" >> $GITHUB_ENV 107 | 108 | - name: 部署应用 109 | run: | 110 | echo "开始部署应用: ${{ env.APP_NAME }}" 111 | echo "使用镜像: ${{ env.DOCKER_IMAGE }}" 112 | echo "目标区域: ${{ github.event.inputs.region }}" 113 | echo "目标组织: ${{ env.SELECTED_ORG }}" 114 | echo "目标空间: ${{ env.SELECTED_SPACE }}" 115 | 116 | cf push ${{ env.APP_NAME }} --docker-image ${{ env.DOCKER_IMAGE }} -m ${{ env.MEMORY }} --health-check-type port --no-start 117 | 118 | - name: 设置环境变量 119 | run: | 120 | echo "为应用 ${{ env.APP_NAME }} 设置环境变量..." 121 | 122 | cf set-env ${{ env.APP_NAME }} NAME "SAP" 123 | cf set-env ${{ env.APP_NAME }} UUID "${{ secrets.UUID }}" 124 | cf set-env ${{ env.APP_NAME }} NEZHA_SERVER "${{ secrets.NEZHA_SERVER }}" 125 | cf set-env ${{ env.APP_NAME }} NEZHA_KEY "${{ secrets.NEZHA_KEY }}" 126 | cf set-env ${{ env.APP_NAME }} SUB_PATH "${{ secrets.SUB_PATH }}" 127 | cf set-env ${{ env.APP_NAME }} CFPORT "${{ secrets.CFPORT }}" 128 | cf set-env ${{ env.APP_NAME }} CHAT_ID "${{ secrets.CHAT_ID }}" 129 | cf set-env ${{ env.APP_NAME }} BOT_TOKEN "${{ secrets.BOT_TOKEN }}" 130 | cf set-env ${{ env.APP_NAME }} ARGO_PORT "${{ secrets.ARGO_PORT }}" 131 | 132 | if echo "${{ secrets.ARGO_AUTH }}" | grep -q '\n'; then 133 | echo "检测到组合格式的 ARGO_AUTH,将自动解析域名和Token..." 134 | ARGO_DOMAIN_VALUE=$(echo "${{ secrets.ARGO_AUTH }}" | head -n 1) 135 | ARGO_AUTH_VALUE=$(echo "${{ secrets.ARGO_AUTH }}" | tail -n +2) 136 | cf set-env ${{ env.APP_NAME }} ARGO_DOMAIN "$ARGO_DOMAIN_VALUE" 137 | cf set-env ${{ env.APP_NAME }} ARGO_AUTH "$ARGO_AUTH_VALUE" 138 | else 139 | echo "使用独立的 ARGO_DOMAIN 和 ARGO_AUTH secrets..." 140 | cf set-env ${{ env.APP_NAME }} ARGO_DOMAIN "${{ secrets.ARGO_DOMAIN }}" 141 | cf set-env ${{ env.APP_NAME }} ARGO_AUTH "${{ secrets.ARGO_AUTH }}" 142 | fi 143 | 144 | CFIP_VALUE="${{ secrets.CFIP }}" 145 | if [ -z "$CFIP_VALUE" ]; then 146 | CFIP_VALUE="cf.090227.xyz" 147 | echo "未提供 CFIP,使用默认值: $CFIP_VALUE" 148 | fi 149 | cf set-env ${{ env.APP_NAME }} CFIP "$CFIP_VALUE" 150 | 151 | if [ "${{ github.event.inputs.is_direct_connect }}" = "true" ]; then 152 | echo "已选择“直连镜像”模式,开始自动配置 DOMAIN 环境变量..." 153 | ROUTE=$(cf app ${{ env.APP_NAME }} | grep "routes:" | awk '{print $2}') 154 | if [ -n "$ROUTE" ]; then 155 | echo "已设置 DOMAIN 环境变量为: $ROUTE" 156 | cf set-env ${{ env.APP_NAME }} DOMAIN "$ROUTE" 157 | else 158 | echo "警告:未找到应用路由,无法自动设置 DOMAIN, 请在部署成功后手动将节点配置里的address、host和sni改为应用域名" 159 | fi 160 | fi 161 | 162 | echo "环境变量设置完成。" 163 | 164 | - name: 启动应用以应用环境变量 165 | id: start_app 166 | run: | 167 | echo "正在启动应用并应用所有环境变量..." 168 | cf start ${{ env.APP_NAME }} 169 | 170 | - name: 启动失败时自动删除应用 171 | if: failure() && steps.start_app.outcome == 'failure' 172 | run: | 173 | echo "应用 ${{ env.APP_NAME }} 启动失败。" 174 | echo "妈的部署失败... 早上八点来部署肯定他妈的可以" 175 | cf delete ${{ env.APP_NAME }} -f -r 176 | echo "应用 ${{ env.APP_NAME }} 已成功删除。" 177 | 178 | - name: 详细部署信息 179 | run: | 180 | echo "----------------------------------------" 181 | echo "部署完成!正在验证应用状态..." 182 | cf app ${{ env.APP_NAME }} 183 | 184 | APP_URL=$(cf app ${{ env.APP_NAME }} | grep "routes:" | awk '{print $2}') 185 | 186 | echo "----------------------------------------" 187 | echo "部署成功!详细信息如下:" 188 | echo "区域 (Region): ${{ github.event.inputs.region }}" 189 | echo "组织 (ORG): ${{ env.SELECTED_ORG }}" 190 | echo "空间 (SPACE): ${{ env.SELECTED_SPACE }}" 191 | echo "应用名称 (App Name): ${{ env.APP_NAME }}" 192 | 193 | if [ -n "$APP_URL" ]; then 194 | echo "应用 URL (APP_URL): https://$APP_URL" 195 | else 196 | echo "看到这里就证明出问题了。" 197 | fi 198 | echo "----------------------------------------" 199 | -------------------------------------------------------------------------------- /.github/workflows/自动保活SAP.yml: -------------------------------------------------------------------------------- 1 | name: 自动保活 SAP 2 | 3 | on: 4 | schedule: 5 | - cron: '45 23 * * *' 6 | workflow_dispatch: # 允许手动触发 7 | 8 | jobs: 9 | restart-sg-apps: 10 | runs-on: ubuntu-latest 11 | name: 重启新加坡(SG)区域应用 12 | if: always() 13 | continue-on-error: true 14 | timeout-minutes: 120 15 | steps: 16 | - name: 检出代码 17 | uses: actions/checkout@v4 18 | 19 | - name: 安装 CF CLI 20 | run: | 21 | wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - 22 | echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list 23 | sudo apt-get update 24 | sudo apt-get install -y cf8-cli 25 | 26 | - name: 设置 SG 区域 API 端点 27 | run: | 28 | echo "CF_API=https://api.cf.ap21.hana.ondemand.com" >> $GITHUB_ENV 29 | echo "使用API端点: $CF_API (区域: SG)" 30 | 31 | - name: 登录并自动检测组织和空间 32 | run: | 33 | cf login -a ${{ env.CF_API }} -u "${{ secrets.EMAIL }}" -p "${{ secrets.PASSWORD }}" 34 | echo "自动检测SG区域可用的组织和空间..." 35 | SELECTED_ORG=$(cf orgs | grep -v "^name$" | grep -v "^Getting orgs" | grep -v "^$" | head -n 1 | awk '{print $1}') 36 | if [ -z "$SELECTED_ORG" ]; then echo "错误: 未找到可用的组织"; exit 1; fi 37 | cf target -o "$SELECTED_ORG" 38 | SELECTED_SPACE=$(cf spaces | grep -v "^name$" | grep -v "^Getting spaces" | grep -v "^$" | head -n 1 | awk '{print $1}') 39 | if [ -z "$SELECTED_SPACE" ]; then echo "错误: 未找到可用的空间"; exit 1; fi 40 | cf target -s "$SELECTED_SPACE" 41 | echo "当前SG区域目标:" 42 | cf target 43 | 44 | - name: '[仅定时任务] 等待 UTC 00:00 开始任务' 45 | if: github.event_name == 'schedule' 46 | run: | 47 | echo "等待 UTC 时间到达 00:00 以开始重启流程..." 48 | target_time="00:00" 49 | deadline_time="01:00" 50 | 51 | while true; do 52 | date_header=$(curl -sI https://www.ntp.org/ | grep -i '^date:') 53 | if [ -z "$date_header" ]; then 54 | echo "无法获取 ntp.org 时间,15秒后重试..." 55 | sleep 15 56 | continue 57 | fi 58 | 59 | current_time=$(echo "$date_header" | awk '{print $6}' | cut -c 1-5) 60 | current_hour=$(echo "$current_time" | cut -c 1-2) 61 | 62 | if [[ "$current_hour" == "23" ]]; then 63 | echo "等待午夜... 当前 UTC 时间: $current_time" 64 | sleep 60 65 | continue 66 | fi 67 | 68 | if [[ "$current_time" > "$deadline_time" ]]; then 69 | echo "错误:已超过安全时间窗口 ($deadline_time UTC)。" 70 | exit 1 71 | fi 72 | 73 | if [[ "$current_time" > "$target_time" || "$current_time" == "$target_time" ]]; then 74 | echo "时间到达或超过 00:00 UTC (北京时间 08:00),开始执行重启任务。" 75 | break 76 | fi 77 | 78 | echo "时间未到 (当前 $current_time),将在 1 分钟后再次检查..." 79 | sleep 60 80 | done 81 | 82 | - name: 等待应用自行停止后,再尝试启动(直至成功) 83 | run: | 84 | apps=$(cf apps | awk 'NR>3 {print $1}' | grep -v '^$') 85 | if [ -z "$apps" ]; then 86 | echo "在SG区域中未发现任何应用,无需操作。" 87 | exit 0 88 | fi 89 | 90 | echo "发现应用列表:" 91 | echo "$apps" 92 | 93 | for app in $apps; do 94 | echo "----------------------------------------" 95 | echo "开始处理应用: $app" 96 | 97 | # 步骤 1: 等待应用自行停止 98 | echo "等待应用 $app 自行停止..." 99 | wait_timeout=3600 # 1小时的等待超时 100 | elapsed=0 101 | is_stopped=false 102 | while [[ $elapsed -lt $wait_timeout ]]; do 103 | if [[ "$(cf app "$app" | grep 'requested state:' | awk '{print $3}')" != "started" ]]; then 104 | echo "应用 $app 已确认停止。" 105 | is_stopped=true 106 | break 107 | fi 108 | echo "应用 $app 仍在运行,15秒后再次检查..." 109 | sleep 15 110 | elapsed=$((elapsed + 15)) 111 | done 112 | 113 | if [[ "$is_stopped" == "false" ]]; then 114 | echo "错误: 等待应用 $app 自行停止超时 (1小时)。跳过此应用。" 115 | continue 116 | fi 117 | 118 | # 步骤 2: 无限次尝试启动应用,直到成功 119 | echo "开始启动应用: $app" 120 | attempt=1 121 | while true; do 122 | echo "正在进行第 $attempt 次启动尝试..." 123 | if cf start "$app"; then 124 | echo "应用 $app 成功启动。" 125 | break # 成功则跳出循环 126 | else 127 | echo "启动失败。将在 1 分钟后重试..." 128 | sleep 60 129 | fi 130 | ((attempt++)) 131 | done 132 | 133 | # 步骤 3: 输出应用URL 134 | app_url=$(cf app "$app" | grep "urls:" | awk '{print $2}') 135 | if [ -n "$app_url" ]; then 136 | echo "应用 $app 的访问地址是: $app_url" 137 | fi 138 | done 139 | 140 | echo "----------------------------------------" 141 | echo "成功: SG区域所有应用均已处理完毕。" 142 | 143 | - name: 验证最终应用状态 144 | run: | 145 | echo "验证SG区域最终应用状态..." 146 | cf apps 147 | 148 | restart-us-apps: 149 | runs-on: ubuntu-latest 150 | name: 重启美国(US)区域应用 151 | if: always() 152 | continue-on-error: true 153 | timeout-minutes: 120 # 154 | steps: 155 | - name: 检出代码 156 | uses: actions/checkout@v4 157 | 158 | - name: 安装 CF CLI 159 | run: | 160 | wget -q -O - https://packages.cloudfoundry.org/debian/cli.cloudfoundry.org.key | sudo apt-key add - 161 | echo "deb https://packages.cloudfoundry.org/debian stable main" | sudo tee /etc/apt/sources.list.d/cloudfoundry-cli.list 162 | sudo apt-get update 163 | sudo apt-get install -y cf8-cli 164 | 165 | - name: 设置 US 区域 API 端点 166 | run: | 167 | echo "CF_API=https://api.cf.us10-001.hana.ondemand.com" >> $GITHUB_ENV 168 | echo "使用API端点: $CF_API (区域: US)" 169 | 170 | - name: 登录并自动检测组织和空间 171 | run: | 172 | cf login -a ${{ env.CF_API }} -u "${{ secrets.EMAIL }}" -p "${{ secrets.PASSWORD }}" 173 | echo "自动检测US区域可用的组织和空间..." 174 | SELECTED_ORG=$(cf orgs | grep -v "^name$" | grep -v "^Getting orgs" | grep -v "^$" | head -n 1 | awk '{print $1}') 175 | if [ -z "$SELECTED_ORG" ]; then echo "错误: 未找到可用的组织"; exit 1; fi 176 | cf target -o "$SELECTED_ORG" 177 | SELECTED_SPACE=$(cf spaces | grep -v "^name$" | grep -v "^Getting spaces" | grep -v "^$" | head -n 1 | awk '{print $1}') 178 | if [ -z "$SELECTED_SPACE" ]; then echo "错误: 未找到可用的空间"; exit 1; fi 179 | cf target -s "$SELECTED_SPACE" 180 | echo "当前US区域目标:" 181 | cf target 182 | 183 | - name: '[仅定时任务] 等待 UTC 00:00 开始任务' 184 | if: github.event_name == 'schedule' 185 | run: | 186 | echo "等待 UTC 时间到达 00:00 以开始重启流程..." 187 | target_time="00:00" 188 | deadline_time="01:00" 189 | 190 | while true; do 191 | date_header=$(curl -sI https://www.ntp.org/ | grep -i '^date:') 192 | if [ -z "$date_header" ]; then 193 | echo "无法获取 ntp.org 时间,15秒后重试..." 194 | sleep 15 195 | continue 196 | fi 197 | 198 | current_time=$(echo "$date_header" | awk '{print $6}' | cut -c 1-5) 199 | current_hour=$(echo "$current_time" | cut -c 1-2) 200 | 201 | if [[ "$current_hour" == "23" ]]; then 202 | echo "等待午夜... 当前 UTC 时间: $current_time" 203 | sleep 60 204 | continue 205 | fi 206 | 207 | if [[ "$current_time" > "$deadline_time" ]]; then 208 | echo "错误:已超过安全时间窗口 ($deadline_time UTC)。" 209 | exit 1 210 | fi 211 | 212 | if [[ "$current_time" > "$target_time" || "$current_time" == "$target_time" ]]; then 213 | echo "时间到达或超过 00:00 UTC (北京时间 08:00),开始执行重启任务。" 214 | break 215 | fi 216 | 217 | echo "时间未到 (当前 $current_time),将在 1 分钟后再次检查..." 218 | sleep 60 219 | done 220 | 221 | - name: 等待应用自行停止后,再尝试启动(直至成功) 222 | run: | 223 | apps=$(cf apps | awk 'NR>3 {print $1}' | grep -v '^$') 224 | if [ -z "$apps" ]; then 225 | echo "在US区域中未发现任何应用,无需操作。" 226 | exit 0 227 | fi 228 | 229 | echo "发现应用列表:" 230 | echo "$apps" 231 | 232 | for app in $apps; do 233 | echo "----------------------------------------" 234 | echo "开始处理应用: $app" 235 | 236 | echo "等待应用 $app 自行停止..." 237 | wait_timeout=3600 # 1小时的等待超时 238 | elapsed=0 239 | is_stopped=false 240 | while [[ $elapsed -lt $wait_timeout ]]; do 241 | if [[ "$(cf app "$app" | grep 'requested state:' | awk '{print $3}')" != "started" ]]; then 242 | echo "应用 $app 已确认停止。" 243 | is_stopped=true 244 | break 245 | fi 246 | echo "应用 $app 仍在运行,15秒后再次检查..." 247 | sleep 15 248 | elapsed=$((elapsed + 15)) 249 | done 250 | 251 | if [[ "$is_stopped" == "false" ]]; then 252 | echo "错误: 等待应用 $app 自行停止超时 (1小时)。跳过此应用。" 253 | continue 254 | fi 255 | 256 | echo "开始启动应用: $app" 257 | attempt=1 258 | while true; do 259 | echo "正在进行第 $attempt 次启动尝试..." 260 | if cf start "$app"; then 261 | echo "应用 $app 成功启动。" 262 | break 263 | else 264 | echo "启动失败。将在 1 分钟后重试..." 265 | sleep 60 266 | fi 267 | ((attempt++)) 268 | done 269 | 270 | # 步骤 3: 输出应用URL 271 | app_url=$(cf app "$app" | grep "urls:" | awk '{print $2}') 272 | if [ -n "$app_url" ]; then 273 | echo "应用 $app 的访问地址是: $app_url" 274 | fi 275 | done 276 | 277 | echo "----------------------------------------" 278 | echo "成功: US区域所有应用均已处理完毕。" 279 | 280 | - name: 验证最终应用状态 281 | run: | 282 | echo "验证US区域最终应用状态..." 283 | cf apps 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------