🔄 卡若AI 同步 2026-02-22 13:57 | 更新:金仓、水溪整理归档、卡木、运营中枢工作台 | 排除 >20MB: 8 个

This commit is contained in:
2026-02-22 13:57:31 +08:00
parent 1e99b78a3e
commit 31794af87f
11 changed files with 713 additions and 74 deletions

View File

@@ -140,11 +140,12 @@ sshpass -p 'zhiqun1984' ssh -p 22022 -o StrictHostKeyChecking=no ckb@43.139.27.9
SSH 风控时,在 **kr宝塔 宝塔面板 → 终端** 上传脚本后执行。详见 `references/宝塔Node项目管理_SKILL.md`
**kr宝塔 中文路径 + MODULE_NOT_FOUND 全量修复**(符号链接、修正启动命令、批量重启
**kr宝塔 中文目录改英文迁移**(删映射、重命名为英文、更新 site.db/Nginx、只用宝塔 Nginx、启动 Node
```bash
./scripts/.venv_tx/bin/python scripts/腾讯云_TAT_kr宝塔_中文路径与MODULE修复.py
# 宝塔终端执行(推荐):上传 scripts/kr宝塔_中文目录改英文_宝塔终端执行.sh 后 bash 执行
# 或 TAT./scripts/.venv_tx/bin/python scripts/腾讯云_TAT_kr宝塔_中文目录改英文迁移.py
```
脚本会:① 创建 ext→扩展、client→客户、self→自营 符号链接;② 修正 site.db 中 `node /path` 错误启动命令为 `cd /path && npm run start`;③ pnpm install④ 批量重启全部 Node 项目
迁移后路径:`/www/wwwroot/self/``/www/wwwroot/client/``/www/wwwroot/ext/`(不再使用 自营/客户/扩展)
### 4a. www.lytiao.com Docker 化(存客宝 · 可多服务器复用)
@@ -382,7 +383,8 @@ ss -tlnp | grep :端口号
| 脚本 | 功能 | 位置 |
|------|------|------|
| `腾讯云_TAT_kr宝塔_中文路径与MODULE修复.py` | kr宝塔 符号链接+修正启动命令+批量重启TAT | `./scripts/.venv_tx` |
| `腾讯云_TAT_kr宝塔_中文目录改英文迁移.py` | kr宝塔 中文目录改英文、删映射、更新 site.db/NginxTAT | `./scripts/.venv_tx` |
| `kr宝塔_中文目录改英文_宝塔终端执行.sh` | 同上,宝塔终端手动执行 | `./scripts/` |
| `腾讯云_TAT_word_ai_hair_is_phone_诊断修复.py` | word/ai_hair/is_phone 日志诊断、MODULE_NOT_FOUND 修复、重启(宝塔 API | `./scripts/` |
| `kr宝塔_node项目批量修复.py` | 批量启动 kr宝塔 Node 项目(服务器内执行,宝塔 API | `./scripts/` |
| `kr宝塔_宝塔API_修复502.py` | 修复 502重启 Nginx + soul 相关 Node | `./scripts/` |

View File

@@ -4,6 +4,20 @@
---
## 〇、中文目录改英文迁移(推荐)
**彻底消除中文路径**:删除映射、重命名目录、更新 site.db 与 Nginx、只用宝塔 Nginx、启动 Node。
```bash
# 方式 1宝塔终端推荐
# 打开 https://43.139.27.93:9988 → 终端 → 上传 scripts/kr宝塔_中文目录改英文_宝塔终端执行.sh → 执行 bash kr宝塔_中文目录改英文_宝塔终端执行.sh
# 方式 2TAT
./scripts/.venv_tx/bin/python scripts/腾讯云_TAT_kr宝塔_中文目录改英文迁移.py
```
迁移后路径:`/www/wwwroot/self/``/www/wwwroot/client/``/www/wwwroot/ext/`;子目录 `wanzhi``tools`
---
## 一、批量启动(先执行)
**宝塔面板 → 终端** 执行 `references/宝塔面板终端_Node批量启动指南.md` 中的脚本,先尝试批量启动所有未运行项目。
@@ -26,16 +40,16 @@ Error: Cannot find module '/www/wwwroot/自营/wzdj'
| 项目 | 根目录 | 建议启动命令 | 说明 |
|------|--------|--------------|------|
| **玩值大屏** | /www/wwwroot/自营/玩值/玩值大屏 | `cd /www/wwwroot/自营/玩值/玩值大屏 && node server.js``npm run start` | 先确认目录内有 server.js / package.json 的 scripts.start |
| **wzdj** | /www/wwwroot/自营/wzdj | `cd /www/wwwroot/自营/wzdj && node server.js``npm run start` | 同上 |
| **tongzhi** | /www/wwwroot/自营/玩值/tongzhi | `cd /www/wwwroot/自营/玩值/tongzhi && node server.js``npm run start` | 同上 |
| **is_phone** | /www/wwwroot/自营/kr/kr-phone | `cd /www/wwwroot/自营/kr/kr-phone && node server.js``npm run start` | 同上 |
| **ai_hair** | /www/wwwroot/客户/ai_hair | 同上 | 同上 |
| **word** | /www/wwwroot/自营/word 或 扩展/word | `cd 项目根目录 && npm run start` | Next.js按实际路径 |
| **AITOUFA** | /www/wwwroot/扩展/小工具/AITOUFA | `cd /www/wwwroot/扩展/小工具/AITOUFA && npm run start` | 参考 Skill §4.6 |
| **zhiji** | /www/wwwroot/... | 同上 | 按实际结构 |
| **ymao** | /www/wwwroot/扩展/ymao | 同上 | 同上 |
| **zhaoping** | /www/wwwroot/客户/zhaoping | 同上 | 同上 |
| **玩值大屏** | /www/wwwroot/self/wanzhi/玩值大屏 | `cd /www/wwwroot/self/wanzhi/玩值大屏 && (pnpm start || npm run start)` | 迁移后使用英文路径 |
| **wzdj** | /www/wwwroot/self/wzdj | 同上 | 同上 |
| **tongzhi** | /www/wwwroot/self/wanzhi/tongzhi | 同上 | 同上 |
| **is_phone** | /www/wwwroot/self/kr/kr-phone | 同上 | 同上 |
| **ai_hair** | /www/wwwroot/client/ai_hair | 同上 | 同上 |
| **word** | /www/wwwroot/self/word | 同上 | 同上 |
| **AITOUFA** | /www/wwwroot/ext/tools/AITOUFA | 同上 | 同上 |
| **zhiji** | /www/wwwroot/self/zhiji | 同上 | 同上 |
| **ymao** | /www/wwwroot/ext/ymao | 同上 | 同上 |
| **zhaoping** | /www/wwwroot/client/zhaoping | 同上 | 同上 |
---
@@ -44,6 +58,7 @@ Error: Cannot find module '/www/wwwroot/自营/wzdj'
1. **Next.js**`cd 项目根目录 && npm run start``pnpm start`;若用 standalone`node .next/standalone/server.js`
2. **Express / 普通 Node**`cd 项目根目录 && node server.js``node index.js`
3. **禁止**`node /www/wwwroot/xxx/项目名`(目录不能当入口)
4. **路径规范**:使用英文路径 `/www/wwwroot/self/``/www/wwwroot/client/``/www/wwwroot/ext/`,不再使用 自营/客户/扩展。
---
@@ -62,7 +77,7 @@ Error: Cannot find module '/www/wwwroot/自营/wzdj'
在宝塔终端执行,确认各项目有入口文件:
```bash
for d in /www/wwwroot/自营/玩值/玩值大屏 /www/wwwroot/自营/wzdj /www/wwwroot/自营/玩值/tongzhi /www/wwwroot/自营/kr/kr-phone; do
for d in /www/wwwroot/self/wanzhi/玩值大屏 /www/wwwroot/self/wzdj /www/wwwroot/self/wanzhi/tongzhi /www/wwwroot/self/kr/kr-phone; do
echo "=== $d ==="
ls -la "$d/" 2>/dev/null | grep -E "server\.js|index\.js|package\.json|\.next" || echo " (无常见入口)"
done

View File

@@ -179,12 +179,12 @@ python3 "01_卡资/金仓_存储备份/服务器管理/scripts/kr宝塔
### 4.6 MODULE_NOT_FOUND如 AITOUFA
**现象**`Error: Cannot find module '/www/wwwroot/扩展/小工具/AITOUFA'`Node 把项目根目录当入口执行。
**现象**`Error: Cannot find module '/www/wwwroot/ext/tools/AITOUFA'`(迁移前为 扩展/小工具)Node 把项目根目录当入口执行。
**原因**:启动命令配置错误,例如写成 `node /项目根目录` 而非 `node server.js``npm start`
**处理**:宝塔 **Node 项目 → 编辑该项目**,将启动命令改为:
- Next.js`cd /www/wwwroot/扩展/小工具/AITOUFA && npm run start``pnpm start`
- Next.js`cd /www/wwwroot/ext/tools/AITOUFA && npm run start``pnpm start`
- 或正确的入口:`node server.js` / `node index.js`(在项目根目录执行)
### 4.7 宝塔与 PM2 冲突

View File

@@ -0,0 +1,171 @@
#!/bin/bash
# kr宝塔 中文目录改英文迁移 - 在宝塔面板「终端」中执行
# 1. 删映射 2. 重命名目录 3. 更新 site.db 4. 更新 Nginx 5. 只用宝塔 Nginx 6. 启动 Node
echo "=== kr宝塔 中文目录改英文迁移 ==="
# 1. 停止 Node面板先到 Node 项目 手动停止,或下方 API
echo ""
echo "【1】停止 Node"
python3 -c '
import hashlib,json,time,urllib.request,urllib.parse,ssl
ssl._create_default_https_context=ssl._create_unverified_context
K="qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
t=int(time.time())
tk=hashlib.md5((str(t)+hashlib.md5(K.encode()).hexdigest()).encode()).hexdigest()
def post(p,d=None):
r=urllib.request.Request("https://127.0.0.1:9988"+p,data=urllib.parse.urlencode({"request_time":t,"request_token":tk,**({}if d is None else d)}).encode())
return json.loads(urllib.request.urlopen(r,timeout=15).read().decode())
for it in post("/project/nodejs/get_project_list").get("data")or post("/project/nodejs/get_project_list").get("list")or[]:
n=it.get("name")
if n:
try: post("/project/nodejs/stop_project",{"project_name":n});print(" stop:",n)
except: pass
time.sleep(0.3)
'
sleep 3
# 2. 删除符号链接
echo ""
echo "【2】删除符号链接"
cd /www/wwwroot
for x in ext client self archive test; do [ -L "$x" ] && rm -f "$x" && echo " rm $x"; done
# 3. 重命名
echo ""
echo "【3】重命名目录"
cd /www/wwwroot
([ -d "扩展" ] && [ ! -e "ext" ] && mv 扩展 ext && echo " 扩展->ext") || true
([ -d "客户" ] && [ ! -e "client" ] && mv 客户 client && echo " 客户->client") || true
([ -d "自营" ] && [ ! -e "self" ] && mv 自营 self && echo " 自营->self") || true
([ -d "self/玩值" ] && [ ! -e "self/wanzhi" ] && mv self/玩值 self/wanzhi && echo " 玩值->wanzhi") || true
([ -d "ext/小工具" ] && [ ! -e "ext/tools" ] && mv ext/小工具 ext/tools && echo " 小工具->tools") || true
([ -d "归档" ] && [ ! -e "archive" ] && mv 归档 archive && echo " 归档->archive") || true
([ -d "测试" ] && [ ! -e "test" ] && mv 测试 test && echo " 测试->test") || true
# 4. 更新 site.db
echo ""
echo "【4】更新 site.db"
python3 << 'PYDB'
import json,os,sqlite3
R=[("/www/wwwroot/自营/玩值/","/www/wwwroot/self/wanzhi/"),("/www/wwwroot/自营/","/www/wwwroot/self/"),("/www/wwwroot/扩展/小工具/","/www/wwwroot/ext/tools/"),("/www/wwwroot/扩展/","/www/wwwroot/ext/"),("/www/wwwroot/客户/","/www/wwwroot/client/"),("/www/wwwroot/归档/","/www/wwwroot/archive/"),("/www/wwwroot/测试/","/www/wwwroot/test/")]
def rp(s):
if not s: return s
for a,b in R: s=s.replace(a,b)
return s
def ro(o):
if isinstance(o,dict): return {k:ro(v) for k,v in o.items()}
if isinstance(o,list): return [ro(x) for x in o]
if isinstance(o,str) and "/www/wwwroot/" in o: return rp(o)
return o
db="/www/server/panel/data/db/site.db"
if os.path.isfile(db):
c=sqlite3.connect(db)
cur=c.cursor()
cur.execute("SELECT id,path,project_config FROM sites")
n=0
for row in cur.fetchall():
sid,path,cfg=row[0],row[1]or"",row[2]or"{}"
np=rp(path)
try: nc=json.dumps(ro(json.loads(cfg)),ensure_ascii=False)
except: nc=rp(cfg)
if np!=path or nc!=cfg: cur.execute("UPDATE sites SET path=?,project_config=? WHERE id=?",(np,nc,sid)); n+=1
c.commit();c.close()
print(" 更新%d条"%n)
PYDB
# 5. 更新 Nginx
echo ""
echo "【5】更新 Nginx 配置"
for d in /www/server/panel/vhost/nginx /www/server/nginx/conf/vhost; do
[ -d "$d" ] || continue
for f in "$d"/*.conf; do
[ -f "$f" ] || continue
if grep -qE '自营|扩展|客户|玩值|小工具|归档|测试' "$f" 2>/dev/null; then
sed -i 's|/www/wwwroot/自营/玩值/|/www/wwwroot/self/wanzhi/|g; s|/www/wwwroot/自营/|/www/wwwroot/self/|g; s|/www/wwwroot/扩展/小工具/|/www/wwwroot/ext/tools/|g; s|/www/wwwroot/扩展/|/www/wwwroot/ext/|g; s|/www/wwwroot/客户/|/www/wwwroot/client/|g; s|/www/wwwroot/归档/|/www/wwwroot/archive/|g; s|/www/wwwroot/测试/|/www/wwwroot/test/|g' "$f"
echo " 更新: $f"
fi
done
done
# 6. 只用宝塔 Nginx
echo ""
echo "【6】Nginx 只用宝塔版"
killall nginx 2>/dev/null || true
sleep 2
/www/server/nginx/sbin/nginx -c /www/server/nginx/conf/nginx.conf 2>/dev/null
sleep 1
nginx -t 2>/dev/null && nginx -s reload 2>/dev/null
echo " 宝塔 Nginx 已启动"
# 7. 更新 Node 启动命令
echo ""
echo "【7】更新 Node 启动命令"
python3 << 'PYNC'
import json,sqlite3
P={"玩值大屏":"/www/wwwroot/self/wanzhi/玩值大屏","tongzhi":"/www/wwwroot/self/wanzhi/tongzhi","is_phone":"/www/wwwroot/self/kr/kr-phone","ai_hair":"/www/wwwroot/client/ai_hair","AITOUFA":"/www/wwwroot/ext/tools/AITOUFA","wzdj":"/www/wwwroot/self/wzdj","zhiji":"/www/wwwroot/self/zhiji","ymao":"/www/wwwroot/ext/ymao","zhaoping":"/www/wwwroot/client/zhaoping","神射手":"/www/wwwroot/self/kr/kr-use","word":"/www/wwwroot/self/word"}
db="/www/server/panel/data/db/site.db"
if __import__("os").path.isfile(db):
c=sqlite3.connect(db)
cur=c.cursor()
cur.execute("SELECT id,name,project_config FROM sites WHERE project_type='Node'")
for row in cur.fetchall():
sid,name,cfg=row[0],row[1],row[2]or"{}"
path=P.get(name)
if path:
try: j=json.loads(cfg)
except: j={}
cmd="cd %s && (pnpm start 2>/dev/null || npm run start)"%path
j["project_script"]=j["run_cmd"]=cmd
cur.execute("UPDATE sites SET project_config=? WHERE id=?",(json.dumps(j,ensure_ascii=False),sid))
print(" ",name)
c.commit();c.close()
PYNC
# 8. 启动 Node
echo ""
echo "【8】启动 Node 项目"
python3 -c '
import hashlib,json,time,urllib.request,urllib.parse,ssl,subprocess,re,os
ssl._create_default_https_context=ssl._create_unverified_context
K="qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
def sign():
t=int(time.time())
return {"request_time":t,"request_token":hashlib.md5((str(t)+hashlib.md5(K.encode()).hexdigest()).encode()).hexdigest()}
def post(p,d=None):
pl=sign()
if d: pl.update(d)
r=urllib.request.Request("https://127.0.0.1:9988"+p,data=urllib.parse.urlencode(pl).encode())
return json.loads(urllib.request.urlopen(r,timeout=30).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=[]
if cfg.get("port"): p.append(int(cfg["port"]))
p.extend(int(m) for m in re.findall(r"-p\s*(\d+)",str(cfg.get("project_script",""))))
return p
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 2>/dev/null"%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})
time.sleep(0.5)
r=post("/project/nodejs/start_project",{"project_name":n})
print(" %s: %s"%(n,"OK" if r.get("status") or "成功" in str(r.get("msg","")) else "FAIL"))
except: print(" %s: ERR"%n)
time.sleep(1)
time.sleep(5)
r2=post("/project/nodejs/get_project_list")
items2=r2.get("data")or r2.get("list")or[]
print(" 运行 %d/%d"%(sum(1 for x in items2 if x.get("run")),len(items2)))
'
echo ""
echo "=== 完成 ==="

View File

@@ -0,0 +1,363 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
腾讯云 TATkr宝塔 中文目录改英文迁移
1. 停止全部 Node 项目
2. 删除 /www/wwwroot 下所有符号链接ext->扩展、client->客户、self->自营 等)
3. 重命名中文目录为英文扩展→ext、客户→client、自营→self、玩值→wanzhi、小工具→tools
4. 更新 site.db 中所有 path、project_config 路径
5. 更新 Nginx vhost 配置中所有中文路径
6. 强制只用宝塔 Nginxkillall nginx 后启动宝塔版)
7. 批量启动全部 Node 项目
"""
import base64
import json
import os
import re
import sys
import time
KR_INSTANCE_ID = "ins-aw0tnqjo"
REGION = "ap-guangzhou"
# 中文路径 → 英文路径(用于 site.db、nginx 批量替换)
PATH_REPLACES = [
("/www/wwwroot/自营/玩值/", "/www/wwwroot/self/wanzhi/"),
("/www/wwwroot/自营/", "/www/wwwroot/self/"),
("/www/wwwroot/扩展/小工具/", "/www/wwwroot/ext/tools/"),
("/www/wwwroot/扩展/", "/www/wwwroot/ext/"),
("/www/wwwroot/客户/", "/www/wwwroot/client/"),
("/www/wwwroot/归档/", "/www/wwwroot/archive/"),
("/www/wwwroot/测试/", "/www/wwwroot/test/"),
]
# 替换顺序:先替换更长的路径,避免 /自营/ 误替换 /自营/玩值/ 的前缀
SHELL_SCRIPT = r'''#!/bin/bash
echo "=== kr宝塔 中文目录改英文迁移 ==="
# 0. 宝塔面板
echo ""
echo "【0】宝塔面板"
if ! ss -tlnp 2>/dev/null | grep -q ':9988 '; then
/etc/init.d/bt start 2>/dev/null || /www/server/panel/bt start 2>/dev/null || true
sleep 5
fi
# 1. 停止全部 Node 项目
echo ""
echo "【1】停止 Node 项目"
python3 - << 'PY1'
import hashlib, json, time, urllib.request, urllib.parse, ssl
ssl._create_default_https_context = ssl._create_unverified_context
PANEL, K = "https://127.0.0.1:9988", "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
def sign():
t = int(time.time())
return {"request_time": t, "request_token": __import__("hashlib").md5((str(t) + __import__("hashlib").md5(K.encode()).hexdigest()).encode()).hexdigest()}
def post(p, d=None):
pl = sign()
if d: pl.update(d)
r = urllib.request.Request(PANEL + p, data=urllib.parse.urlencode(pl).encode())
with urllib.request.urlopen(r, timeout=30) as resp:
return json.loads(resp.read().decode())
items = post("/project/nodejs/get_project_list").get("data") or post("/project/nodejs/get_project_list").get("list") or []
for it in items:
name = it.get("name")
if name:
try: post("/project/nodejs/stop_project", {"project_name": name}); print(" 停:", name)
except: pass
time.sleep(0.3)
PY1
sleep 3
# 2. 删除符号链接
echo ""
echo "【2】删除符号链接"
cd /www/wwwroot
for x in ext client self archive test; do
if [ -L "$x" ]; then
rm -f "$x" && echo " 删除链接: $x"
fi
done
# 3. 重命名中文目录为英文(按依赖顺序)
echo ""
echo "【3】重命名目录"
cd /www/wwwroot
([ -d "扩展" ] && [ ! -e "ext" ] && mv "扩展" "ext" && echo " 扩展 -> ext") || true
([ -d "客户" ] && [ ! -e "client" ] && mv "客户" "client" && echo " 客户 -> client") || true
([ -d "自营" ] && [ ! -e "self" ] && mv "自营" "self" && echo " 自营 -> self") || true
([ -d "self/玩值" ] && [ ! -e "self/wanzhi" ] && mv "self/玩值" "self/wanzhi" && echo " 玩值 -> wanzhi") || true
([ -d "ext/小工具" ] && [ ! -e "ext/tools" ] && mv "ext/小工具" "ext/tools" && echo " 小工具 -> tools") || true
([ -d "归档" ] && [ ! -e "archive" ] && mv "归档" "archive" && echo " 归档 -> archive") || true
([ -d "测试" ] && [ ! -e "test" ] && mv "测试" "test" && echo " 测试 -> test") || true
# 4. 更新 site.db
echo ""
echo "【4】更新 site.db"
python3 - << 'PY2'
import json, os, re, sqlite3
REPLACES = [
("/www/wwwroot/自营/玩值/", "/www/wwwroot/self/wanzhi/"),
("/www/wwwroot/自营/", "/www/wwwroot/self/"),
("/www/wwwroot/扩展/小工具/", "/www/wwwroot/ext/tools/"),
("/www/wwwroot/扩展/", "/www/wwwroot/ext/"),
("/www/wwwroot/客户/", "/www/wwwroot/client/"),
("/www/wwwroot/归档/", "/www/wwwroot/archive/"),
("/www/wwwroot/测试/", "/www/wwwroot/test/"),
]
def replace_path(s):
if not s or not isinstance(s, str): return s
for a, b in REPLACES:
s = s.replace(a, b)
return s
def replace_in_obj(obj):
if isinstance(obj, dict):
return {k: replace_in_obj(v) for k, v in obj.items()}
if isinstance(obj, list):
return [replace_in_obj(x) for x in obj]
if isinstance(obj, str) and "/www/wwwroot/" in obj:
return replace_path(obj)
return obj
db = "/www/server/panel/data/db/site.db"
if os.path.isfile(db):
conn = sqlite3.connect(db)
c = conn.cursor()
# 获取需要 path 或 project_config 的列
c.execute("PRAGMA table_info(sites)")
cols = [r[1] for r in c.fetchall()]
path_cols = [x for x in cols if "path" in x.lower() or "config" in x.lower()]
c.execute("SELECT id, path, project_config FROM sites")
n = 0
for row in c.fetchall():
sid, path, cfg = row[0], row[1] or "", row[2] or "{}"
new_path = replace_path(path)
try:
cfg_obj = json.loads(cfg) if cfg else {}
new_cfg = replace_in_obj(cfg_obj)
new_cfg_str = json.dumps(new_cfg, ensure_ascii=False)
except:
new_cfg_str = replace_path(cfg)
if new_path != path or new_cfg_str != cfg:
c.execute("UPDATE sites SET path=?, project_config=? WHERE id=?", (new_path, new_cfg_str, sid))
n += 1
conn.commit()
conn.close()
print(" 更新 %d 条 sites 记录" % n)
else:
print(" site.db 不存在")
PY2
# 5. 更新 Nginx 配置
echo ""
echo "【5】更新 Nginx 配置"
for f in /www/server/panel/vhost/nginx/*.conf /www/server/nginx/conf/vhost/*.conf 2>/dev/null; do
[ -f "$f" ] || continue
if grep -q "自营\|扩展\|客户\|玩值\|小工具\|归档\|测试" "$f" 2>/dev/null; then
sed -i 's|/www/wwwroot/自营/玩值/|/www/wwwroot/self/wanzhi/|g' "$f"
sed -i 's|/www/wwwroot/自营/|/www/wwwroot/self/|g' "$f"
sed -i 's|/www/wwwroot/扩展/小工具/|/www/wwwroot/ext/tools/|g' "$f"
sed -i 's|/www/wwwroot/扩展/|/www/wwwroot/ext/|g' "$f"
sed -i 's|/www/wwwroot/客户/|/www/wwwroot/client/|g' "$f"
sed -i 's|/www/wwwroot/归档/|/www/wwwroot/archive/|g' "$f"
sed -i 's|/www/wwwroot/测试/|/www/wwwroot/test/|g' "$f"
echo " 已更新: $f"
fi
done
# 6. 强制只用宝塔 Nginx
echo ""
echo "【6】Nginx 只用宝塔版"
killall nginx 2>/dev/null || true
sleep 2
/www/server/nginx/sbin/nginx -c /www/server/nginx/conf/nginx.conf 2>/dev/null || true
sleep 1
nginx -t 2>/dev/null && nginx -s reload 2>/dev/null
echo " 宝塔 Nginx 已启动并重载"
# 7. 更新 Node 项目 project_config 中的启动命令路径
echo ""
echo "【7】更新 Node 启动命令路径"
python3 - << 'PY3'
import hashlib, json, os, sqlite3, time, urllib.request, urllib.parse, ssl
ssl._create_default_https_context = ssl._create_unverified_context
REPLACES = [
("/www/wwwroot/自营/玩值/", "/www/wwwroot/self/wanzhi/"),
("/www/wwwroot/自营/", "/www/wwwroot/self/"),
("/www/wwwroot/扩展/小工具/", "/www/wwwroot/ext/tools/"),
("/www/wwwroot/扩展/", "/www/wwwroot/ext/"),
("/www/wwwroot/客户/", "/www/wwwroot/client/"),
]
def replace_path(s):
for a, b in REPLACES: s = s.replace(a, b)
return s
PROJECT_CMD = {
"玩值大屏": "/www/wwwroot/self/wanzhi/玩值大屏",
"tongzhi": "/www/wwwroot/self/wanzhi/tongzhi",
"is_phone": "/www/wwwroot/self/kr/kr-phone",
"ai_hair": "/www/wwwroot/client/ai_hair",
"AITOUFA": "/www/wwwroot/ext/tools/AITOUFA",
"wzdj": "/www/wwwroot/self/wzdj",
"zhiji": "/www/wwwroot/self/zhiji",
"ymao": "/www/wwwroot/ext/ymao",
"zhaoping": "/www/wwwroot/client/zhaoping",
"神射手": "/www/wwwroot/self/kr/kr-use",
"word": "/www/wwwroot/self/word",
}
db = "/www/server/panel/data/db/site.db"
if os.path.isfile(db):
conn = sqlite3.connect(db)
c = conn.cursor()
c.execute("SELECT id, name, project_config FROM sites WHERE project_type='Node'")
for row in c.fetchall():
sid, name, cfg_str = row[0], row[1], row[2] or "{}"
path = PROJECT_CMD.get(name)
if not path: continue
try: cfg = json.loads(cfg_str)
except: cfg = {}
cmd = "cd %s && (pnpm start 2>/dev/null || npm run start)" % path
cfg["project_script"] = cmd
cfg["run_cmd"] = cmd
c.execute("UPDATE sites SET project_config=? WHERE id=?", (json.dumps(cfg, ensure_ascii=False), sid))
print(" %s" % name)
conn.commit()
conn.close()
PY3
# 8. 批量启动 Node 项目
echo ""
echo "【8】批量启动 Node 项目"
python3 - << 'PY4'
import hashlib, json, os, re, subprocess, time, urllib.request, urllib.parse, ssl
ssl._create_default_https_context = ssl._create_unverified_context
PANEL, K = "https://127.0.0.1:9988", "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
def sign():
t = int(time.time())
s = str(t) + hashlib.md5(K.encode()).hexdigest()
return {"request_time": t, "request_token": hashlib.md5(s.encode()).hexdigest()}
def post(p, d=None):
pl = sign()
if d: pl.update(d)
r = urllib.request.Request(PANEL + p, data=urllib.parse.urlencode(pl).encode())
with urllib.request.urlopen(r, timeout=30) as resp:
return json.loads(resp.read().decode())
def pids(port):
try:
o = subprocess.check_output("ss -tlnp 2>/dev/null | grep ':%s ' || true" % port, shell=True, universal_newlines=True)
return {int(x) for x in re.findall(r"pid=(\d+)", o)}
except: return set()
def ports(it):
cfg = it.get("project_config") or {}
if isinstance(cfg, str):
try: cfg = json.loads(cfg)
except: cfg = {}
ps = []
if cfg.get("port"): ps.append(int(cfg["port"]))
for m in re.findall(r"-p\s*(\d+)", str(cfg.get("project_script",""))): ps.append(int(m))
return ps
r0 = post("/project/nodejs/get_project_list")
items = r0.get("data") or r0.get("list") or []
for it in items:
name = it.get("name")
if not name: continue
try:
for port in ports(it):
for pid in pids(port):
subprocess.call("kill -9 %s 2>/dev/null" % pid, shell=True)
pf = "/www/server/nodejs/vhost/pids/%s.pid" % name
if os.path.exists(pf):
try: open(pf,"w").write("0")
except: pass
post("/project/nodejs/stop_project", {"project_name": name})
time.sleep(0.5)
r = post("/project/nodejs/start_project", {"project_name": name})
ok = r.get("status") is True or "成功" in str(r.get("msg",""))
print(" %s: %s" % (name, "OK" if ok else "FAIL"))
except Exception as e:
print(" %s: ERR" % name)
time.sleep(1)
time.sleep(5)
r1 = post("/project/nodejs/get_project_list")
items2 = r1.get("data") or r1.get("list") or []
run_c = sum(1 for x in items2 if x.get("run"))
print(" 运行 %d / %d" % (run_c, len(items2)))
PY4
echo ""
echo "=== 迁移完成 ==="
'''
def _read_creds():
d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
if os.path.isfile(os.path.join(d, "运营中枢", "工作台", "00_账号与API索引.md")):
with open(os.path.join(d, "运营中枢", "工作台", "00_账号与API索引.md")) as f:
t = f.read()
sid = skey = None
in_t = False
for line in t.splitlines():
if "### 腾讯云" in line: in_t = True; continue
if in_t and line.strip().startswith("###"): break
if not in_t: continue
m = re.search(r"SecretId[^|]*\|\s*`([^`]+)`", line, re.I)
if m and "AKID" in m.group(1): sid = m.group(1).strip()
m = re.search(r"SecretKey\s*\|\s*`([^`]+)`", line, 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 = _read_creds()
if not sid or not skey:
print("❌ 未配置腾讯云凭证"); return 1
try:
from tencentcloud.common import credential
from tencentcloud.tat.v20201028 import tat_client, models
except ImportError:
print("pip install tencentcloud-sdk-python-tat"); return 1
cred = credential.Credential(sid, skey)
client = tat_client.TatClient(cred, REGION)
req = models.RunCommandRequest()
req.Content = base64.b64encode(SHELL_SCRIPT.encode("utf-8")).decode()
req.InstanceIds = [KR_INSTANCE_ID]
req.CommandType = "SHELL"
req.Timeout = 600
req.CommandName = "kr宝塔_中文目录改英文迁移"
resp = client.RunCommand(req)
print("✅ TAT 已下发 InvocationId:", resp.InvocationId)
print(" 步骤: 停 Node → 删映射 → 重命名目录 → 更新 site.db → 更新 Nginx → 只启用宝塔 Nginx → 启动 Node")
print(" 等待 180s...")
time.sleep(180)
try:
req2 = models.DescribeInvocationTasksRequest()
f = models.Filter()
f.Name, f.Values = "invocation-id", [resp.InvocationId]
req2.Filters = [f]
r2 = client.DescribeInvocationTasks(req2)
for t in (r2.InvocationTaskSet or []):
print(" 状态:", getattr(t, "TaskStatus", ""))
tr = getattr(t, "TaskResult", None)
if tr:
j = json.loads(tr) if isinstance(tr, str) else {}
out = j.get("Output", "")
if out:
try: out = base64.b64decode(out).decode("utf-8", errors="replace")
except: pass
print(" 输出:\n", (out or "")[:6000])
except Exception as e:
print(" 查询:", e)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -21,9 +21,9 @@ TARGET_NAMES = ["word", "ai_hair", "is_phone"]
# 已知项目路径API 无 path 时的后备)
PATH_MAP = {
"word": "/www/wwwroot/自营/word",
"ai_hair": "/www/wwwroot/客户/ai_hair",
"is_phone": "/www/wwwroot/自营/kr/kr-phone",
"word": "/www/wwwroot/self/word",
"ai_hair": "/www/wwwroot/client/ai_hair",
"is_phone": "/www/wwwroot/self/kr/kr-phone",
}
SHELL_SCRIPT = r'''#!/bin/bash
@@ -33,7 +33,7 @@ python3 - << 'PYEOF'
import hashlib, json, os, subprocess, time, urllib.request, urllib.parse, ssl
ssl._create_default_https_context = ssl._create_unverified_context
PANEL, K = "https://127.0.0.1:9988", "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
PATH_MAP = {"word": "/www/wwwroot/自营/word", "ai_hair": "/www/wwwroot/客户/ai_hair", "is_phone": "/www/wwwroot/自营/kr/kr-phone"}
PATH_MAP = {"word": "/www/wwwroot/self/word", "ai_hair": "/www/wwwroot/client/ai_hair", "is_phone": "/www/wwwroot/self/kr/kr-phone"}
def sign():
t = int(time.time())
s = str(t) + hashlib.md5(K.encode()).hexdigest()