init: ops-assistant codebase

This commit is contained in:
OpenClaw Agent
2026-03-19 21:23:28 +08:00
commit 81deba4766
94 changed files with 10767 additions and 0 deletions

17
runbooks/cf_dns_add.yaml Normal file
View File

@@ -0,0 +1,17 @@
version: 1
name: cf_dns_add
description: 新增 DNS 记录(按 name/content
steps:
- id: add_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_ACCOUNT_ID=${env_cf_account_id} CF_API_TOKEN=${env_cf_api_token} INPUT_NAME=${env.INPUT_NAME} INPUT_CONTENT=${env.INPUT_CONTENT} INPUT_TYPE=${env.INPUT_TYPE} INPUT_PROXIED=${env.INPUT_PROXIED} python3 -c 'import base64; exec(base64.b64decode(\"aW1wb3J0IG9zLHJlcXVlc3RzLGpzb24KbmFtZT1vcy5nZXRlbnYoJ0lOUFVUX05BTUUnLCcnKQpjb250ZW50PW9zLmdldGVudignSU5QVVRfQ09OVEVOVCcsJycpCnJlY190eXBlPW9zLmdldGVudignSU5QVVRfVFlQRScsJ0EnKQpwcm94aWVkPW9zLmdldGVudignSU5QVVRfUFJPWElFRCcsJ2ZhbHNlJykubG93ZXIoKT09J3RydWUnCmFjY291bnQ9b3MuZ2V0ZW52KCdDRl9BQ0NPVU5UX0lEJywnJykKdG9rZW49b3MuZ2V0ZW52KCdDRl9BUElfVE9LRU4nLCcnKQpoZWFkZXJzPXsnQXV0aG9yaXphdGlvbic6J0JlYXJlciAnK3Rva2VuLCdDb250ZW50LVR5cGUnOidhcHBsaWNhdGlvbi9qc29uJ30KcmVzcD1yZXF1ZXN0cy5nZXQoJ2h0dHBzOi8vYXBpLmNsb3VkZmxhcmUuY29tL2NsaWVudC92NC96b25lcycsIGhlYWRlcnM9aGVhZGVycywgcGFyYW1zPXsnYWNjb3VudC5pZCc6YWNjb3VudCwncGVyX3BhZ2UnOjIwMH0sIHRpbWVvdXQ9MTUpCmRhdGE9cmVzcC5qc29uKCkKem9uZT1Ob25lCmZvciB6IGluIGRhdGEuZ2V0KCdyZXN1bHQnLFtdKToKICAgIHpuPXouZ2V0KCduYW1lJywnJykKICAgIGlmIG5hbWU9PXpuIG9yIG5hbWUuZW5kc3dpdGgoJy4nK3puKToKICAgICAgICB6b25lPXo7IGJyZWFrCmlmIG5vdCB6b25lOgogICAgcHJpbnQoanNvbi5kdW1wcyh7J3N1Y2Nlc3MnOkZhbHNlLCdzdGFnZSc6J21hdGNoX3pvbmUnLCdlcnJvcnMnOlsnem9uZV9ub3RfZm91bmQnXSwnbmFtZSc6bmFtZX0sIGVuc3VyZV9hc2NpaT1GYWxzZSkpOyByYWlzZSBTeXN0ZW1FeGl0KDIpCnpvbmVfaWQ9em9uZS5nZXQoJ2lkJykKcGF5bG9hZD17J3R5cGUnOnJlY190eXBlLCduYW1lJzpuYW1lLCdjb250ZW50Jzpjb250ZW50LCdwcm94aWVkJzpwcm94aWVkfQpyZXNwMj1yZXF1ZXN0cy5wb3N0KCdodHRwczovL2FwaS5jbG91ZGZsYXJlLmNvbS9jbGllbnQvdjQvem9uZXMvJyt6b25lX2lkKycvZG5zX3JlY29yZHMnLCBoZWFkZXJzPWhlYWRlcnMsIGpzb249cGF5bG9hZCwgdGltZW91dD0xNSkKcHJpbnQocmVzcDIudGV4dCkK\"), {})'"
- id: assert
action: assert.json
on_fail: stop
with:
source_step: add_dns
required_paths:
- "success"

17
runbooks/cf_dns_del.yaml Normal file
View File

@@ -0,0 +1,17 @@
version: 1
name: cf_dns_del
description: 删除 DNS 记录(按 record_id
steps:
- id: del_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_API_TOKEN=${env_cf_api_token} INPUT_RECORD_ID=${env.INPUT_RECORD_ID} python3 - <<'PY'\nimport os,requests,json\nrec=os.getenv('INPUT_RECORD_ID','')\ntoken=os.getenv('CF_API_TOKEN','')\nemail=os.getenv('CF_API_EMAIL','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\n# find record across zones\nzones=requests.get('https://api.cloudflare.com/client/v4/zones?per_page=200', headers=headers, timeout=15).json().get('result',[])\nzone_id=None\nfor z in zones:\n zid=z.get('id')\n r=requests.get(f'https://api.cloudflare.com/client/v4/zones/{zid}/dns_records/{rec}', headers=headers, timeout=15)\n if r.status_code==200 and r.json().get('success'):\n zone_id=zid\n break\nif not zone_id:\n print(json.dumps({'success':False,'errors':['record_not_found']}))\n raise SystemExit(1)\nresp=requests.delete(f'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec}', headers=headers, timeout=15)\nprint(resp.text)\nPY"
- id: assert
action: assert.json
on_fail: stop
with:
source_step: del_dns
required_paths:
- "success"

17
runbooks/cf_dns_list.yaml Normal file
View File

@@ -0,0 +1,17 @@
version: 1
name: cf_dns_list
description: 列出某个 Zone 的 DNS 记录
steps:
- id: list_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_ACCOUNT_ID=${env_cf_account_id} CF_API_TOKEN=${env_cf_api_token} INPUT_ZONE_ID=${env.INPUT_ZONE_ID} python3 -c \"import os,requests; zone=os.getenv('INPUT_ZONE_ID',''); token=os.getenv('CF_API_TOKEN',''); email=os.getenv('CF_API_EMAIL',''); headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'}; url='https://api.cloudflare.com/client/v4/zones/%s/dns_records'%zone; resp=requests.get(url, headers=headers, timeout=15); print(resp.text)\""
- id: assert
action: assert.json
on_fail: stop
with:
source_step: list_dns
required_paths:
- "success"

View File

@@ -0,0 +1,30 @@
version: 1
name: cf_dns_proxy
description: 修改 DNS 代理开关(按 record_id 或 name
steps:
- id: resolve_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_API_TOKEN=${env_cf_api_token} INPUT_RECORD_ID=${env.INPUT_RECORD_ID} INPUT_NAME=${env.INPUT_NAME} python3 - <<'PY'\nimport os,requests,json\nrec=os.getenv('INPUT_RECORD_ID','').strip()\nname=os.getenv('INPUT_NAME','').strip()\nif rec=='__empty__':\n rec=''\nif name=='__empty__':\n name=''\ntoken=os.getenv('CF_API_TOKEN','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\nresp=requests.get('https://api.cloudflare.com/client/v4/zones?per_page=200', headers=headers, timeout=15)\nresp.raise_for_status()\nfor z in resp.json().get('result',[]):\n zid=z.get('id')\n if rec:\n r=requests.get(f'https://api.cloudflare.com/client/v4/zones/{zid}/dns_records/{rec}', headers=headers, timeout=15)\n if r.status_code==200:\n data=r.json()\n if data.get('success') and data.get('result'):\n out=data.get('result')\n out['_zone_id']=zid\n print(json.dumps({'success':True,'result':out}))\n raise SystemExit(0)\n continue\n if name:\n r=requests.get(f'https://api.cloudflare.com/client/v4/zones/{zid}/dns_records', headers=headers, params={'name': name, 'per_page': 100}, timeout=15)\n if r.status_code==200:\n data=r.json()\n if data.get('success') and data.get('result'):\n rec0=data['result'][0]\n rec0['_zone_id']=zid\n print(json.dumps({'success':True,'result':rec0}))\n raise SystemExit(0)\nprint(json.dumps({'success':False,'errors':['record_not_found']}))\nPY"
- id: assert_resolve
action: assert.json
on_fail: stop
with:
source_step: resolve_dns
required_paths:
- "success"
- id: update_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_API_TOKEN=${env_cf_api_token} INPUT_PROXIED=${env.INPUT_PROXIED} INPUT_JSON='${steps.resolve_dns.output}' python3 - <<'PY'\nimport os,requests,json\nproxied=os.getenv('INPUT_PROXIED','false').lower()=='true'\ntoken=os.getenv('CF_API_TOKEN','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\nraw=os.getenv('INPUT_JSON','')\ntry:\n data=json.loads(raw)\nexcept Exception:\n data={}\nres=data.get('result') or {}\nzone_id=res.get('_zone_id')\nrec_id=res.get('id')\nif not zone_id or not rec_id:\n print(json.dumps({'success':False,'errors':['record_not_found']}))\n raise SystemExit(1)\npayload={\n 'type': res.get('type'),\n 'name': res.get('name'),\n 'content': res.get('content'),\n 'proxied': proxied\n}\nresp=requests.put(f'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec_id}', headers=headers, json=payload, timeout=15)\nprint(resp.text)\nPY"
- id: assert_update
action: assert.json
on_fail: stop
with:
source_step: update_dns
required_paths:
- "success"

30
runbooks/cf_dns_set.yaml Normal file
View File

@@ -0,0 +1,30 @@
version: 1
name: cf_dns_set
description: 修改 DNS 记录内容(按 record_id
steps:
- id: get_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_API_TOKEN=${env_cf_api_token} INPUT_RECORD_ID=${env.INPUT_RECORD_ID} python3 - <<'PY'\nimport os,requests\nrec=os.getenv('INPUT_RECORD_ID','')\ntoken=os.getenv('CF_API_TOKEN','')\nemail=os.getenv('CF_API_EMAIL','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\nurl=f'https://api.cloudflare.com/client/v4/zones?per_page=200'\nresp=requests.get(url, headers=headers, timeout=15)\nresp.raise_for_status()\n# find record across zones\nfor z in resp.json().get('result',[]):\n zone_id=z.get('id')\n rurl=f'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec}'\n r=requests.get(rurl, headers=headers, timeout=15)\n if r.status_code==200:\n print(r.text)\n raise SystemExit(0)\nprint('{"success":false,"errors":["record_not_found"]}')\nPY"
- id: assert_get
action: assert.json
on_fail: stop
with:
source_step: get_dns
required_paths:
- "success"
- id: update_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_API_TOKEN=${env_cf_api_token} INPUT_RECORD_ID=${env.INPUT_RECORD_ID} INPUT_CONTENT=${env.INPUT_CONTENT} INPUT_PROXIED=${env.INPUT_PROXIED} python3 - <<'PY'\nimport os,requests,json\nrec=os.getenv('INPUT_RECORD_ID','')\ncontent=os.getenv('INPUT_CONTENT','')\nproxied=os.getenv('INPUT_PROXIED','false').lower()=='true'\ntoken=os.getenv('CF_API_TOKEN','')\nemail=os.getenv('CF_API_EMAIL','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\n# find record and zone\nzones=requests.get('https://api.cloudflare.com/client/v4/zones?per_page=200', headers=headers, timeout=15).json().get('result',[])\nzone_id=None\nrecord=None\nfor z in zones:\n zid=z.get('id')\n r=requests.get(f'https://api.cloudflare.com/client/v4/zones/{zid}/dns_records/{rec}', headers=headers, timeout=15)\n if r.status_code==200:\n data=r.json()\n if data.get('success'):\n zone_id=zid\n record=data.get('result')\n break\nif not zone_id or not record:\n print(json.dumps({'success':False,'errors':['record_not_found']}))\n raise SystemExit(1)\npayload={\n 'type': record.get('type'),\n 'name': record.get('name'),\n 'content': content,\n 'proxied': proxied\n}\nresp=requests.put(f'https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec}', headers=headers, json=payload, timeout=15)\nprint(resp.text)\nPY"
- id: assert_update
action: assert.json
on_fail: stop
with:
source_step: update_dns
required_paths:
- "success"

View File

@@ -0,0 +1,17 @@
version: 1
name: cf_dns_update
description: 更新 DNS 记录(按 record_id
steps:
- id: update_dns
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_ACCOUNT_ID=${env_cf_account_id} CF_API_TOKEN=${env_cf_api_token} INPUT_ZONE_ID=${env.INPUT_ZONE_ID} INPUT_RECORD_ID=${env.INPUT_RECORD_ID} INPUT_TYPE=${env.INPUT_TYPE} INPUT_NAME=${env.INPUT_NAME} INPUT_CONTENT=${env.INPUT_CONTENT} INPUT_TTL=${env.INPUT_TTL} INPUT_PROXIED=${env.INPUT_PROXIED} python3 - <<'PY'\nimport os, requests\nzone=os.getenv('INPUT_ZONE_ID','')\nrec=os.getenv('INPUT_RECORD_ID','')\ntoken=os.getenv('CF_API_TOKEN','')\nemail=os.getenv('CF_API_EMAIL','')\nheaders={'Authorization':'Bearer '+token,'Content-Type':'application/json'}\nurl=f'https://api.cloudflare.com/client/v4/zones/{zone}/dns_records/{rec}'\npayload={\n 'type': os.getenv('INPUT_TYPE',''),\n 'name': os.getenv('INPUT_NAME',''),\n 'content': os.getenv('INPUT_CONTENT',''),\n}\nif os.getenv('INPUT_TTL',''):\n payload['ttl']=int(os.getenv('INPUT_TTL'))\nif os.getenv('INPUT_PROXIED','')!='':\n payload['proxied']=os.getenv('INPUT_PROXIED').lower()=='true'\nresp=requests.put(url, headers=headers, json=payload, timeout=15)\nprint(resp.text)\nPY"
- id: assert
action: assert.json
on_fail: stop
with:
source_step: update_dns
required_paths:
- "success"

10
runbooks/cf_status.yaml Normal file
View File

@@ -0,0 +1,10 @@
version: 1
name: cf_status
description: CF 模块状态检查占位 runbook不执行外部操作
inputs: []
steps:
- id: cf_noop
action: sleep
on_fail: stop
with:
ms: 10

View File

@@ -0,0 +1,17 @@
version: 1
name: cf_workers_list
description: 列出账户下 Workers 脚本
steps:
- id: list_workers
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_ACCOUNT_ID=${env_cf_account_id} CF_API_TOKEN=${env_cf_api_token} python3 -c \"import os,requests,json; acct=os.getenv('CF_ACCOUNT_ID',''); token=os.getenv('CF_API_TOKEN',''); headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'}; url='https://api.cloudflare.com/client/v4/accounts/%s/workers/scripts'%acct; resp=requests.get(url, headers=headers, timeout=15); data=resp.json(); workers=[(w.get('id') or w.get('name')) for w in data.get('result',[])]; print(json.dumps({'workers':workers}, ensure_ascii=False))\""
- id: assert
action: assert.json
on_fail: stop
with:
source_step: list_workers
required_paths:
- "success"

17
runbooks/cf_zones.yaml Normal file
View File

@@ -0,0 +1,17 @@
version: 1
name: cf_zones
description: 列出 Cloudflare 账号下的 Zone
steps:
- id: list_zones
action: ssh.exec
on_fail: stop
with:
target: hwsg
command: "CF_ACCOUNT_ID=${env_cf_account_id} CF_API_TOKEN=${env_cf_api_token} python3 -c \"import os,requests,json; acct=os.getenv('CF_ACCOUNT_ID',''); token=os.getenv('CF_API_TOKEN',''); email=os.getenv('CF_API_EMAIL',''); headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'}; url='https://api.cloudflare.com/client/v4/zones'; params={'account.id':acct,'per_page':200}; resp=requests.get(url, headers=headers, params=params, timeout=15); data=resp.json(); zones=[{'name':z.get('name'), 'id':z.get('id')} for z in data.get('result',[])]; print(json.dumps({'success':data.get('success',False),'zones':zones,'errors':data.get('errors',[])}, ensure_ascii=False))\""
- id: assert
action: assert.json
on_fail: stop
with:
source_step: list_zones
required_paths:
- "success"

19
runbooks/cpa_status.yaml Normal file
View File

@@ -0,0 +1,19 @@
version: 1
name: cpa_status
description: 获取 CPA 服务状态与 usage 快照
inputs: []
steps:
- id: usage_snapshot
action: shell.exec
on_fail: stop
with:
command: "CPA_TOKEN=${env.cpa_management_token} CPA_BASE=https://cpa.pao.xx.kg/v0/management python3 -c 'import base64,os; code=base64.b64decode(\"Y3VybCAtc1MgLUggIkF1dGhvcml6YXRpb246IEJlYXJlciAke0NQQV9UT0tFTn0iICR7Q1BBX0JBU0V9L3VzYWdlIHwgcHl0aG9uMyAtYyAiaW1wb3J0IGpzb24sc3lzOyBkYXRhPWpzb24ubG9hZChzeXMuc3RkaW4pOyBvdXQ9eyd1c2FnZSc6IHsndG90YWxfcmVxdWVzdHMnOiBkYXRhLmdldCgndXNhZ2UnLHt9KS5nZXQoJ3RvdGFsX3JlcXVlc3RzJyksICd0b3RhbF90b2tlbnMnOiBkYXRhLmdldCgndXNhZ2UnLHt9KS5nZXQoJ3RvdGFsX3Rva2VucycpfX07IHByaW50KGpzb24uZHVtcHMob3V0LCBlbnN1cmVfYXNjaWk9RmFsc2UpKSIK\"); os.system(code.decode())'"
- id: usage_assert
action: assert.json
on_fail: stop
with:
source_step: usage_snapshot
required_paths:
- "usage.total_requests"
- "usage.total_tokens"

View File

@@ -0,0 +1,38 @@
version: 1
name: cpa_usage_backup
description: 实时导出 usage 并打包备份(公网管理接口)
inputs: []
steps:
- id: export_and_package
action: shell.exec
on_fail: stop
with:
command: |
CPA_TOKEN=${env.cpa_management_token}
CPA_BASE=https://cpa.pao.xx.kg/v0/management
ts=$(date +%F_%H%M%S)
out=/root/cliproxyapi/usage_export_${ts}.json
curl -sS -H "Authorization: Bearer ${CPA_TOKEN}" ${CPA_BASE}/usage/export -o ${out}
echo ${out}
latest=$(ls -1t /root/cliproxyapi/usage_export_*.json | head -n 1)
ts=$(date +%Y-%m-%d_%H%M%S)
out=/root/backups/cpa-runtime-daily/hwsg_usage_realtime_${ts}.tar.gz
meta=/root/backups/cpa-runtime-daily/hwsg_usage_realtime_${ts}.meta.txt
mkdir -p /root/backups/cpa-runtime-daily
tar -czf ${out} ${latest}
sha=$(sha256sum ${out} | awk '{print $1}')
size=$(du -h ${out} | awk '{print $1}')
req=$(python3 -c "import json; data=json.load(open('${latest}','r',encoding='utf-8')); u=data.get('usage',{}); print(u.get('total_requests', data.get('total_requests','unknown')))" )
tok=$(python3 -c "import json; data=json.load(open('${latest}','r',encoding='utf-8')); u=data.get('usage',{}); print(u.get('total_tokens', data.get('total_tokens','unknown')))" )
{
echo "time=$(date '+%F %T %z')"
echo "source=${latest}"
echo "backup=${out}"
echo "sha256=${sha}"
echo "size=${size}"
echo "total_requests=${req}"
echo "total_tokens=${tok}"
} > ${meta}
cat ${meta}

View File

@@ -0,0 +1,112 @@
version: 1
name: cpa_usage_restore
description: 从备份包恢复 usage公网管理接口双重校验
inputs:
- backup_id
steps:
- id: pre_backup
action: shell.exec
on_fail: stop
with:
command: |
CPA_TOKEN=${env.cpa_management_token}
CPA_BASE=https://cpa.pao.xx.kg/v0/management
ts=$(date +%F_%H%M%S)
out=/root/cliproxyapi/usage_export_${ts}.json
curl -sS -H "Authorization: Bearer ${CPA_TOKEN}" ${CPA_BASE}/usage/export -o ${out}
echo ${out}
latest=$(ls -1t /root/cliproxyapi/usage_export_*.json | head -n 1)
ts=$(date +%Y-%m-%d_%H%M%S)
out=/root/backups/cpa-runtime-daily/hwsg_usage_realtime_${ts}.tar.gz
meta=/root/backups/cpa-runtime-daily/hwsg_usage_realtime_${ts}.meta.txt
mkdir -p /root/backups/cpa-runtime-daily
tar -czf ${out} ${latest}
sha=$(sha256sum ${out} | awk '{print $1}')
size=$(du -h ${out} | awk '{print $1}')
req=$(python3 -c "import json; data=json.load(open('${latest}','r',encoding='utf-8')); u=data.get('usage',{}); print(u.get('total_requests', data.get('total_requests','unknown')))" )
tok=$(python3 -c "import json; data=json.load(open('${latest}','r',encoding='utf-8')); u=data.get('usage',{}); print(u.get('total_tokens', data.get('total_tokens','unknown')))" )
{
echo "time=$(date '+%F %T %z')"
echo "source=${latest}"
echo "backup=${out}"
echo "sha256=${sha}"
echo "size=${size}"
echo "total_requests=${req}"
echo "total_tokens=${tok}"
} > ${meta}
cat ${meta}
- id: find_backup
action: shell.exec
on_fail: stop
with:
command: "ls -1 /root/backups/cpa-runtime-daily/${inputs.backup_id}.tar.gz"
- id: extract_backup
action: shell.exec
on_fail: stop
with:
command: "mkdir -p /tmp/cpa-restore && tar -xzf /root/backups/cpa-runtime-daily/${inputs.backup_id}.tar.gz -C /tmp/cpa-restore"
- id: import_usage
action: shell.exec
on_fail: stop
with:
command: |
CPA_TOKEN=${env.cpa_management_token}
CPA_BASE=https://cpa.pao.xx.kg/v0/management
latest=$(ls -1 /tmp/cpa-restore/root/cliproxyapi/usage_export_*.json 2>/dev/null | head -n 1)
if [ -z "$latest" ]; then
latest=$(ls -1 /tmp/cpa-restore/root/cliproxyapi/stats_persistence-*.json 2>/dev/null | head -n 1)
fi
if [ -z "$latest" ]; then
echo "no usage file found"; exit 1
fi
python3 -c "import json; json.load(open('${latest}','r',encoding='utf-8')); print('json_ok')"
resp=$(curl -sS -H "Authorization: Bearer ${CPA_TOKEN}" -H "Content-Type: application/json" --data @${latest} ${CPA_BASE}/usage/import)
echo "$resp"
python3 -c "import json,sys; r=json.loads(sys.argv[1]); import sys as _s; _s.exit(r.get('error')) if isinstance(r,dict) and r.get('error') else print('import_ok')" "$resp"
- id: verify_now
action: shell.exec
on_fail: stop
with:
command: |
CPA_TOKEN=${env.cpa_management_token}
CPA_BASE=https://cpa.pao.xx.kg/v0/management
curl -sS -H "Authorization: Bearer ${CPA_TOKEN}" ${CPA_BASE}/usage
- id: verify_now_assert
action: assert.json
on_fail: stop
with:
source_step: verify_now
required_paths:
- "usage.total_requests"
- "usage.total_tokens"
- id: wait_10s
action: sleep
on_fail: continue
with:
ms: 10000
- id: verify_later
action: shell.exec
on_fail: stop
with:
command: |
CPA_TOKEN=${env.cpa_management_token}
CPA_BASE=https://cpa.pao.xx.kg/v0/management
curl -sS -H "Authorization: Bearer ${CPA_TOKEN}" ${CPA_BASE}/usage
- id: verify_later_assert
action: assert.json
on_fail: stop
with:
source_step: verify_later
required_paths:
- "usage.total_requests"
- "usage.total_tokens"

10
runbooks/mail_status.yaml Normal file
View File

@@ -0,0 +1,10 @@
version: 1
name: mail_status
description: Mail 模块状态检查占位 runbook不执行外部操作
inputs: []
steps:
- id: mail_noop
action: sleep
on_fail: stop
with:
ms: 10