🔄 卡若AI 同步 2026-02-22 19:02 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个

This commit is contained in:
2026-02-22 19:02:31 +08:00
parent fd69240fb3
commit 35eb6d5db5
9 changed files with 266 additions and 9 deletions

View File

@@ -0,0 +1,54 @@
# kr 宝塔 · 宝塔终端执行指南
> 当 TAT 超时或 Node 批量启动失败时,在**宝塔面板终端**执行修复脚本,无时间限制。
---
## 一、执行步骤
### 1. 打开宝塔终端
1. 浏览器访问https://43.139.27.93:9988
2. 登录宝塔
3. 左侧菜单 → **终端**
4. 点击「**连接**」或创建新终端
### 2. 上传脚本(二选一)
**方式 A文件管理上传**
1. 宝塔 → **文件** → 进入 `/root`
2. 上传本地文件:`scripts/kr宝塔_运行堵塞与Node深度修复_宝塔终端执行.sh`
3. 终端执行:`bash /root/kr宝塔_运行堵塞与Node深度修复_宝塔终端执行.sh`
**方式 B直接粘贴执行**
1. 用本地编辑器打开 `scripts/kr宝塔_运行堵塞与Node深度修复_宝塔终端执行.sh`
2. 全选复制
3. 在宝塔终端中粘贴,回车执行
### 3. 脚本会完成
- 结束异常 Node 进程(含 PID 异常的项目)
- 停止全部 Node 项目
- 修复 site.db 启动命令为 `cd /path && (pnpm start || npm run start)`
- 查看 Node 日志
- 批量启动3 轮,每轮间隔 10s
- 输出最终运行状态
---
## 二、常见问题
| 问题 | 处理 |
|------|------|
| 部分项目仍 FAIL | 在宝塔「网站 → Node 项目」中,对该项目点「设置」,检查启动命令是否为 `cd /路径 && (pnpm start \|\| npm run start)`;查看日志排查依赖/端口 |
| 中文目录路径 | 如 `/www/wwwroot/self/wanzhi/玩值大屏` 可保留,脚本已支持;若需改英文需另做目录迁移 |
| 执行超时 | 宝塔终端无超时限制,可耐心等待(约 23 分钟) |
---
## 三、脚本路径
- 本地:`01_卡资/金仓_存储备份/服务器管理/scripts/kr宝塔_运行堵塞与Node深度修复_宝塔终端执行.sh`
- 宝塔 API 密钥:`qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT`(脚本内已配置)

View File

@@ -11,6 +11,8 @@ echo "--- 结束异常高 CPU node 进程 ---"
for pid in $(ps aux | awk '$3>80 && /node|npm|pnpm/ && !/grep/ {print $2}' 2>/dev/null); do
echo " kill $pid"; kill -9 $pid 2>/dev/null
done
echo "--- 清理所有 Node 相关孤儿进程(含异常 PID 状态)---"
pkill -9 -f "node.*www/wwwroot" 2>/dev/null || true
sleep 2
python3 << 'PY'
@@ -54,7 +56,7 @@ if os.path.isfile(db):
if not proj or not os.path.isdir(proj): print(" 跳过",name); continue
cmd="cd %s && (pnpm start 2>/dev/null || npm run start)"%proj
old=str(cfg.get("project_script")or cfg.get("run_cmd")or"").strip()
if "cd " not in old or proj not in old:
if True:
cfg["project_script"]=cfg["run_cmd"]=cmd; cfg["path"]=proj
cur.execute("UPDATE sites SET path=?,project_config=? WHERE id=?",(proj,json.dumps(cfg,ensure_ascii=False),sid)); fixed+=1
print(" 修复:",name,"->",proj)

View File

@@ -65,7 +65,7 @@ for it in to_start:
try:
r=post('/project/nodejs/start_project',{'project_name':n})
ok=r.get('status') or '成功' in str(r.get('msg',''))
print(n,':','OK' if ok else 'FAIL', r.get('msg','')[:80] if not ok else '')
print(n,':','OK' if ok else 'FAIL')
except Exception as e: print(n,': ERR',str(e)[:30])
time.sleep(1.5)
items2=post('/project/nodejs/get_project_list').get('data')or post('/project/nodejs/get_project_list').get('list')or[]

View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""TAT强制停止全部 Node + 修复 site.db + 批量启动(精简版,约 90s"""
import base64, json, os, re, sys, time
KR_INSTANCE_ID, REGION = "ins-aw0tnqjo", "ap-guangzhou"
SHELL = r'''#!/bin/bash
set -e
echo "【1】宝塔"
! ss -tlnp 2>/dev/null | grep -q ':9988 ' && /etc/init.d/bt start 2>/dev/null; sleep 5
echo "【2】强制结束 Node 进程"
pkill -9 -f "node.*www/wwwroot" 2>/dev/null || true
sleep 2
echo "【3】修复 site.db + 停+启"
PYTHONUNBUFFERED=1 python3 -u -c "
import hashlib,json,os,re,sqlite3,subprocess,time,urllib.request,urllib.parse,ssl
ssl._create_default_https_context=ssl._create_unverified_context
P,K='https://127.0.0.1:9988','qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT'
def sg(): t=int(time.time()); return {'request_time':t,'request_token':hashlib.md5((str(t)+hashlib.md5(K.encode()).hexdigest()).encode()).hexdigest()}
def post(u,d=None): pl=sg(); (pl.update(d) if d else None); r=urllib.request.Request(P+u,data=urllib.parse.urlencode(pl).encode()); return json.loads(urllib.request.urlopen(r,timeout=20).read().decode())
def pids(port):
try: return {int(x) for x in re.findall(r'pid=(\d+)',subprocess.check_output(\"ss -tlnp 2>/dev/null | grep ':%s ' || true\"%port,shell=True).decode())}
except: return set()
def ports(it):
cfg=it.get('project_config') or {}
if isinstance(cfg,str): cfg=json.loads(cfg) if cfg else {}
p=[int(cfg['port'])] if cfg.get('port') else []
p.extend(int(m) for m in re.findall(r'-p\s*(\d+)',str(cfg.get('project_script',''))))
return sorted(set(p))
FB={'玩值大屏':['/www/wwwroot/self/wanzhi/玩值大屏','/www/wwwroot/self/wanzhi/玩值'],'tongzhi':['/www/wwwroot/self/wanzhi/tongzhi','/www/wwwroot/self/wanzhi/tong'],'神射手':['/www/wwwroot/self/kr/kr-use','/www/wwwroot/self/kr/kr-users'],'AITOUFA':['/www/wwwroot/ext/tools/AITOUFA','/www/wwwroot/ext/tools/AITOL']}
db='/www/server/panel/data/db/site.db'
if os.path.isfile(db):
c=sqlite3.connect(db); cur=c.cursor(); cur.execute(\"SELECT id,name,path,project_config FROM sites WHERE project_type='Node'\")
for r in cur.fetchall():
sid,nm,path,cfg=r[0],r[1],r[2],r[3]or'{}'
proj=(json.loads(cfg) if cfg else {}).get('path') or path or ''
if not proj or not os.path.isdir(proj):
for p in FB.get(nm,[]):
if os.path.isdir(p): proj=p; break
if not proj or not os.path.isdir(proj): continue
cmd='cd %s && (pnpm start 2>/dev/null || npm run start)'%proj
cfg=json.loads(cfg) if isinstance(cfg,str) else (cfg or {})
cfg['project_script']=cfg['run_cmd']=cmd; cfg['path']=proj
cur.execute('UPDATE sites SET path=?, project_config=? WHERE id=?',(proj,json.dumps(cfg,ensure_ascii=False),sid))
c.commit(); c.close()
print('site.db 已修复')
items=post('/project/nodejs/get_project_list').get('data')or post('/project/nodejs/get_project_list').get('list')or[]
for it in items:
n=it.get('name')
if not n: continue
try:
for port in ports(it):
for pid in pids(port): subprocess.call('kill -9 %s'%pid,shell=True)
pf='/www/server/nodejs/vhost/pids/%s.pid'%n
if os.path.exists(pf): open(pf,'w').write('0')
post('/project/nodejs/stop_project',{'project_name':n})
except: pass
time.sleep(0.3)
time.sleep(3)
print('停止完成,开始启动')
for it in items:
n=it.get('name')
if not n: continue
try:
r=post('/project/nodejs/start_project',{'project_name':n})
ok=r.get('status') or '成功' in str(r.get('msg',''))
print(n,':','OK' if ok else 'FAIL')
except Exception as e: print(n,': ERR')
time.sleep(1.2)
r2=post('/project/nodejs/get_project_list').get('data')or post('/project/nodejs/get_project_list').get('list')or[]
print('运行',sum(1 for x in r2 if x.get('run')),'/',len(r2))
" 2>&1
echo "【4】完成"
uptime
'''
def _creds():
d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
p = os.path.join(d, "运营中枢", "工作台", "00_账号与API索引.md")
if os.path.isfile(p):
t = open(p).read()
sid = skey = None
for L in t.splitlines():
m = re.search(r"SecretId[^|]*\|\s*`([^`]+)`", L, re.I)
if m and "AKID" in m.group(1): sid = m.group(1).strip()
m = re.search(r"SecretKey\s*\|\s*`([^`]+)`", L, re.I)
if m: skey = m.group(1).strip()
return sid or os.environ.get("TENCENTCLOUD_SECRET_ID"), skey or os.environ.get("TENCENTCLOUD_SECRET_KEY")
d = os.path.dirname(d)
return None, None
def main():
sid, skey = _creds()
if not sid or not skey:
print("❌ 未配置凭证"); return 1
from tencentcloud.common import credential
from tencentcloud.tat.v20201028 import tat_client, models
cred = credential.Credential(sid, skey)
cli = tat_client.TatClient(cred, REGION)
req = models.RunCommandRequest()
req.Content = base64.b64encode(SHELL.encode("utf-8")).decode()
req.InstanceIds = [KR_INSTANCE_ID]
req.CommandType = "SHELL"
req.Timeout = 120
req.CommandName = "kr宝塔_强制停启Node"
r = cli.RunCommand(req)
print("✅ TAT inv:", r.InvocationId)
time.sleep(100)
req2 = models.DescribeInvocationTasksRequest()
f = models.Filter(); f.Name, f.Values = "invocation-id", [r.InvocationId]
req2.Filters = [f]
for t in (cli.DescribeInvocationTasks(req2).InvocationTaskSet or []):
print("状态:", getattr(t, "TaskStatus", ""))
tr = getattr(t, "TaskResult", None)
if tr:
out = getattr(tr, "Output", tr.__dict__.get("Output", ""))
if out:
print(base64.b64decode(out).decode("utf-8", errors="replace"))
return 0
if __name__ == "__main__": sys.exit(main())

View File

@@ -30,19 +30,15 @@ if ! ss -tlnp 2>/dev/null | grep -q ':9988 '; then
fi
ss -tlnp 2>/dev/null | grep 9988 || echo " 9988 未监听API 可能失败"
# 【0】运行堵塞诊断
# 【0】运行堵塞诊断 + 清理异常 Node 进程
echo ""
echo "【0】运行堵塞诊断"
echo "--- 负载 ---"
uptime
echo "--- 内存 ---"
free -m | head -2
echo "--- CPU TOP10 ---"
ps aux --sort=-%cpu 2>/dev/null | head -11
echo "--- 结束异常 node/npm/pnpm 进程(占用>80%%CPU) ---"
echo "--- 结束异常 node/npm/pnpm 进程 + 清理 www/wwwroot 下 Node ---"
for pid in $(ps aux | awk '$3>80 && /node|npm|pnpm/ && !/grep/ {print $2}' 2>/dev/null); do
echo " kill $pid"; kill -9 $pid 2>/dev/null
done
pkill -9 -f "node.*www/wwwroot" 2>/dev/null || true
sleep 2
python3 - << 'PYMAIN'

View File

@@ -0,0 +1,35 @@
#!/bin/bash
# ============================================
# 存客宝 CKB NAS 共享盘 - 外网挂载到 Finder 侧栏「位置」
# 挂载后可直接存文件Finder 拷贝时会显示速率
# 外网通过 frp 端口 4450 访问 SMB
# ============================================
NAS_HOST="open.quwanzhi.com"
NAS_PORT="4450"
NAS_USER="fnvtk"
# 密码:与 DSM 登录一致,若非 zhiqun1984 请修改
NAS_PASS="zhiqun1984"
SHARE="homes"
MOUNT_POINT="$HOME/CKB-NAS-1TB"
# 已挂载则先卸载
if mount | grep -q "CKB-NAS-1TB"; then
echo "正在卸载旧挂载..."
umount "$MOUNT_POINT" 2>/dev/null
sleep 1
fi
mkdir -p "$MOUNT_POINT"
echo "正在挂载存客宝 NAS (${NAS_HOST}:${NAS_PORT})..."
mount_smbfs "//${NAS_USER}:${NAS_PASS}@${NAS_HOST}:${NAS_PORT}/${SHARE}" "$MOUNT_POINT" 2>&1
if mount | grep -q "CKB-NAS-1TB"; then
echo "挂载成功: $MOUNT_POINT"
echo "添加到 Finder 侧栏:在 Finder 中把「CKB-NAS-1TB」拖到侧栏「位置」下即可"
echo "直接往里拷贝文件Finder 会显示传输速率"
open "$MOUNT_POINT"
else
echo "挂载失败。若提示 Authentication error请修改本脚本中的 NAS_PASS 为 DSM 登录密码"
exit 1
fi

View File

@@ -0,0 +1,45 @@
# 存客宝 CKB NAS 外网挂载到 Finder 侧栏「位置」
把 NAS 的 1TB 共享盘映射到 Finder 侧栏,外网可用,拷贝时显示速率。
---
## 一、已完成的配置
- **SMB 外网穿透**frp 已开放端口 **4450** → NAS 445
- **共享文件夹**`homes`(对应 /volume1/homes/fnvtk可用空间 21TB
---
## 二、挂载方式(任选其一)
### 方式 AFinder 连接服务器(推荐,可保存密码)
1. Finder → **前往****连接服务器**⌘K
2. 输入:`smb://open.quwanzhi.com:4450/homes`
3. 点**连接**,输入 DSM 用户名和密码,勾选「在钥匙串中记住此密码」
4. 连接成功后,该共享会出现在 Finder 侧栏「位置」下
5. 右键该共享 → **制作替身** 或 拖到侧栏,可固定显示
### 方式 B脚本挂载
```bash
/Users/karuo/Documents/个人/卡若AI/01_卡资/金仓_存储备份/群晖NAS管理/scripts/mount_ckb_nas_1tb.sh
```
- 若提示 **Authentication error**:编辑脚本,把 `NAS_PASS` 改为你的 DSM 登录密码
- 挂载后位置:`~/CKB-NAS-1TB`,在 Finder 中把它拖到侧栏「位置」即可固定
---
## 三、添加「位置」到侧栏
挂载成功后,在 Finder 左侧「位置」区域:
-`CKB-NAS-1TB``homes` 拖到侧栏,即可像 Synology Drive 一样固定显示
---
## 四、传输速率
在 Finder 中往该共享拷贝文件时,会显示进度和传输速率(与本地磁盘相同)。

View File

@@ -96,3 +96,4 @@
| 2026-02-22 14:44:11 | 🔄 卡若AI 同步 2026-02-22 14:43 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 |
| 2026-02-22 14:59:42 | 🔄 卡若AI 同步 2026-02-22 14:59 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 8 个 |
| 2026-02-22 15:08:03 | 🔄 卡若AI 同步 2026-02-22 15:07 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 |
| 2026-02-22 16:00:09 | 🔄 卡若AI 同步 2026-02-22 16:00 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 |

View File

@@ -99,3 +99,4 @@
| 2026-02-22 14:44:11 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 14:43 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-22 14:59:42 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 14:59 | 更新:金仓、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-22 15:08:03 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 15:07 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-22 16:00:09 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 16:00 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |