init: ops-assistant codebase
This commit is contained in:
17
runbooks/cf_dns_add.yaml
Normal file
17
runbooks/cf_dns_add.yaml
Normal 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
17
runbooks/cf_dns_del.yaml
Normal 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
17
runbooks/cf_dns_list.yaml
Normal 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"
|
||||
30
runbooks/cf_dns_proxy.yaml
Normal file
30
runbooks/cf_dns_proxy.yaml
Normal 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
30
runbooks/cf_dns_set.yaml
Normal 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"
|
||||
17
runbooks/cf_dns_update.yaml
Normal file
17
runbooks/cf_dns_update.yaml
Normal 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
10
runbooks/cf_status.yaml
Normal 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
|
||||
17
runbooks/cf_workers_list.yaml
Normal file
17
runbooks/cf_workers_list.yaml
Normal 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
17
runbooks/cf_zones.yaml
Normal 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
19
runbooks/cpa_status.yaml
Normal 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"
|
||||
38
runbooks/cpa_usage_backup.yaml
Normal file
38
runbooks/cpa_usage_backup.yaml
Normal 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}
|
||||
112
runbooks/cpa_usage_restore.yaml
Normal file
112
runbooks/cpa_usage_restore.yaml
Normal 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
10
runbooks/mail_status.yaml
Normal 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
|
||||
Reference in New Issue
Block a user