🔄 卡若AI 同步 2026-03-03 22:01 | 更新:Cursor规则、金仓、水溪整理归档、卡木、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-03 22:01:48 +08:00
parent f3660382df
commit 86b133076c
29 changed files with 1420 additions and 53 deletions

View File

@@ -24,6 +24,8 @@ alwaysApply: true
**MAX Mode**卡若AI 每次调用均为 MAX Mode定义在**卡若AI 本体** `BOOTSTRAP.md` 第四节MAX Mode与第五节执行流程不在此重复本文件仅补充 Cursor 特有行为。
**多线程并行16**:当任务可拆为多个相对独立的子任务时,**优先并行处理**。由卡若AI 划定各子任务边界与归属域(五行/成员/技能),一次派发 **16 个**并行子任务(如 Cursor 内使用 mcp_task 等多 agent 能力);各子任务在各自边界内独立判断、全力处理,完成后汇总结果。详见 `BOOTSTRAP.md` 四.1 与 `运营中枢/参考资料/多线程并行处理规范.md`。
## 异常处理与红线(强制)
执行时遵守 `运营中枢/参考资料/卡若AI异常处理与红线.md`:未匹配→推荐 23 技能或学习扩展API 失败→搜索并循环直到成功;多技能→合并不让用户选;复盘遗漏→强制补发。**红线**不改变卡若AI 整体结构、不导致电脑无法启动、不删除重大文件。
@@ -32,6 +34,7 @@ alwaysApply: true
### 第一步~第四步(执行流程与 MAX Mode
- 执行流程、思考与拆解、验证与复盘**以 `BOOTSTRAP.md` 第四节MAX Mode与第五节执行流程为准**卡若AI 每次调用均为 MAX Mode此处不重复。
- **强制**:每次对话必须先**在对话中以详细文字展示**「思考结果 + 任务拆解 + 执行计划」,展示完毕后再执行;禁止只写提纲或省略,禁止不展示直接动手。
### 复盘格式Cursor 内强制)
- **卡若AI 内所有对话的 AI 回复一律采用「复盘形式」——此为强制行为,无例外。** 按 `运营中枢/参考资料/卡若复盘格式_固定规则.md`
@@ -59,9 +62,10 @@ alwaysApply: true
### 复盘所有对话强制卡若AI 统一回复形式)
- **卡若AI 内所有对话AI 的回复一律用复盘形式。** 复盘格式**永远只有一种**:完整复盘;**复盘块内不用表格**;每块用**小图标识别**(🎯📌💡📝▶);标题必须写**具体日期 + 具体时间**(真实 YYYY-MM-DD HH:mm**能加颜色则加**(日期时间、各块标题)。详见 `运营中枢/参考资料/卡若复盘格式_固定规则.md`。
### 终端命令与常规操作
- **终端命令**直接执行不询问50 字内说明后执行
- **常规操作**优先用命令行完成执行前先搜索本仓库SKILL、参考资料、脚本、已有文档及网上是否有**类似/现成的命令或流程**,有则**直接按现成方式帮用户完成**,不重复造轮子、不向用户提问「是否执行」「请确认」
### 终端命令与常规操作(强制:需终端的一律直接执行)
- **需在终端执行的,一律由 AI 直接执行,不把命令交给用户自己跑。** 包括但不限于:下载/同步脚本(如从 GitHub 拉最新)、部署脚本、构建、同步 Gitea、运行 SKILL 内脚本等。不输出「请你在终端执行」「请运行以下命令」让用户自己复制执行。
- **终端命令**直接执行不询问50 字内说明后执行。
- **常规操作**优先用命令行完成执行前先搜索本仓库SKILL、参考资料、脚本、已有文档及网上是否有**类似/现成的命令或流程**,有则**直接按现成方式帮用户完成**,不重复造轮子、不向用户提问「是否执行」「请确认」。
### 工作台路径
- `/Users/karuo/Documents/个人/卡若AI/`

View File

@@ -58,6 +58,8 @@ kr宝塔: qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT
5. **Skill 迭代**:每次有新的配置、教训、流程变更时,必须同步更新本 SKILL.md 或 references保证下次调用时信息一致。
6. **卡若AI 复盘**每次任务结束必须用卡若AI 复盘格式收尾(目标·结果·达成率、过程、反思、总结、下一步)。
7. **站点/Nginx/SSL 类操作 · 默认宝塔 API被拒则降级**凡涉及「添加站点、Nginx 反代、重载、申请 SSL、开启 HTTPS」等**默认一律先用宝塔 API**(本机执行对应脚本,需执行机公网 IP 已加入该面板「设置 → API 接口」白名单)。若调用被拒绝(如 **IP 校验失败**、连接超时等),则**降级一:用 SSH 直接操作**(在服务器上写 Nginx 配置、重载、或在服务器内用 127.0.0.1 调宝塔 API 免白名单)。若 **SSH 操作不了**(连接被关、风控、无密钥等),则**降级二:用腾讯云 API**TAT 下发命令、或 CVM/轻量 API 等)在服务器内完成同样操作。以后碰到此类问题,直接按「宝塔 API → SSH → 腾讯云 API」顺序执行不反复询问用户。
8. **执行形式强制顺序**:宝塔服务器上任何操作,**一律按「宝塔 API → SSH → TAT」顺序尝试**。能通过宝塔 API 完成的先用 APIAPI 不可用超时、IP 校验失败等)再用 SSH 在机内执行SSH 不可用再用腾讯云 TAT。不得跳过顺序。
9. **前置检查(强制)**:每次对服务器做**修改类操作**(改配置、改站点、改 Node 项目、重启服务等)之前,**必须先检查目标项目及周边项目/应用**(如通过宝塔 API `get_project_list` 看 Node 项目状态、或 SSH 看进程与端口),确认修改不会导致其他应用不可用后再执行;执行后再次确认目标与周边状态正常。
---

View File

@@ -3,6 +3,34 @@
> 当一种方式失败时,依次尝试其他方式。终极备选:**宝塔面板 → 终端**(无需 SSH
> **存客宝 SSH 修复**:在存客宝宝塔终端执行 `scripts/存客宝_SSH修复_宝塔终端执行.sh` 内容。
## 启动 SSH 并保证连接成功kr宝塔 43.139.27.93
按顺序执行即可:
**① 安全组放行 22、22022**在卡若AI 项目根目录执行):
```bash
cd "/Users/karuo/Documents/个人/卡若AI"
.venv_tencent/bin/python3 "01_卡资/金仓_存储备份/服务器管理/scripts/腾讯云_kr宝塔安全组放行SSH.py"
```
**② 在服务器上启动 sshd**(二选一):
- **能连上 SSH 时**`ssh -p 22022 root@43.139.27.93` 登录后执行
`systemctl enable sshd && systemctl start sshd && systemctl status sshd`
- **连不上时**:用 **宝塔面板终端**:打开 https://43.139.27.93:9988 → 登录 → 终端 → 执行上述三条命令。
**③ 测试连接**
```bash
ssh -p 22022 -o StrictHostKeyChecking=no root@43.139.27.93
# 密码Zhiqun1984首字母大写 Z
```
若仍失败见下方「Connection closed 原因与处理」和「终极备选:宝塔面板终端」。
---
## 零、SSH IP 被封禁防护2026-02-23 已配置)
**问题**sshpass/密钥连接被 `Connection closed by remote host`原因是外部暴力破解19,690 次错误尝试)占满 sshd 连接池。

View File

@@ -2,6 +2,8 @@
> 当 TAT 超时或 Node 批量启动失败时,在**宝塔面板终端**执行修复脚本,无时间限制。
**执行顺序(强制)**:宝塔 API 优先 → SSH → TAT。每次修改前**必须先检查目标项目及周边项目/应用**,确认不影响其他应用后再执行。详见 SKILL.md 强制规则 8、9。
---
## 一、执行步骤
@@ -52,3 +54,42 @@
- 本地:`01_卡资/金仓_存储备份/服务器管理/scripts/kr宝塔_运行堵塞与Node深度修复_宝塔终端执行.sh`
- 宝塔 API 密钥:`qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT`(脚本内已配置)
---
## 四、wzdj.quwanzhi.com 单独修复启动失败Cannot find module '/www/wwwroot/self/wzdj'
**原因**:宝塔 Node 项目把目录路径当模块执行(`node /www/wwwroot/self/wzdj`),应改为在项目目录下执行 `PORT=3055 pnpm start`Nginx 反代 3055。若出现 **EADDRINUSE: 3055**,先在服务器执行 `fuser -k 3055/tcp` 释放端口再启动。
**执行顺序(强制)**:宝塔 API → SSH → TAT。每次修改前先检查本项目及周边 Node 项目状态,防止影响其他应用。
**方式一(推荐)**:本机执行完整流程脚本(前置检查 + API 停/启 + SSH 修复,失败则 TAT
```bash
cd /Users/karuo/Documents/个人/卡若AI
python3 "01_卡资/金仓_存储备份/服务器管理/scripts/kr宝塔_wzdj修复_完整流程.py"
```
**方式二**:本机执行 TAT 脚本(需腾讯云 API 凭证,当 SSH 不可用时)
```bash
cd /Users/karuo/Documents/个人/卡若AI
python3 "01_卡资/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_kr宝塔_修复wzdj启动.py"
```
**方式二**SSH 或宝塔终端执行
1. SSH`sshpass -p 'Zhiqun1984' ssh -p 22022 -o PubkeyAuthentication=no root@43.139.27.93`
2. 上传或在服务器上创建脚本后执行:
`bash /root/kr宝塔_仅修复wzdj_宝塔终端执行.sh`
3. 或打开本地 `scripts/kr宝塔_仅修复wzdj_宝塔终端执行.sh`,全选复制,在宝塔终端粘贴执行。
脚本会:停止 wzdj → 修复 site.db 与 wzdj.sh 启动命令 → 再启动 wzdj。
**若仍启动失败(推荐·一步到位)**:在宝塔面板里**手动改启动命令**
1. 宝塔 → **网站****Node 项目** → 找到 **wzdj** → 点 **设置**(或「编辑」)。
2. 找到「**启动命令**」或「**运行命令**」输入框,**整段替换**为(复制下面一行):
```bash
cd /www/wwwroot/self/wzdj && (PORT=3055 pnpm start 2>/dev/null || PORT=3055 npm run start)
```
3. **保存** → 回到 Node 项目列表 → 对 wzdj 点 **启动**
这样不依赖脚本是否在机内执行成功,直接改面板配置即可让文字电竞站点跑起来。

View File

@@ -0,0 +1,204 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""wzdj.quwanzhi.com 修复完整流程:前置检查 → 宝塔 API → SSH → TAT直至网站可访问。"""
import hashlib
import json
import os
import re
import subprocess
import sys
import time
import urllib.request
import urllib.parse
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
KR_PANEL = "https://43.139.27.93:9988"
KR_API_KEY = "qcWubCdlfFjS2b2DMT1lzPFaDfmv1cBT"
KR_SSH = "root@43.139.27.93"
KR_SSH_PORT = "22022"
KR_SSH_PASS = "Zhiqun1984"
WZDJ_DOMAIN = "wzdj.quwanzhi.com"
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
FIX_SH = os.path.join(SCRIPT_DIR, "kr宝塔_仅修复wzdj_宝塔终端执行.sh")
def bt_sign():
t = int(time.time())
token = hashlib.md5((str(t) + hashlib.md5(KR_API_KEY.encode()).hexdigest()).encode()).hexdigest()
return {"request_time": t, "request_token": token}
def bt_post(path, data=None):
pl = bt_sign()
if data:
pl.update(data)
req = urllib.request.Request(KR_PANEL + path, data=urllib.parse.urlencode(pl).encode(), method="POST")
with urllib.request.urlopen(req, timeout=15) as r:
return json.loads(r.read().decode())
def bt_stop_wzdj():
"""宝塔 API 停止 wzdj"""
try:
bt_post("/project/nodejs/stop_project", {"project_name": "wzdj"})
print(" API 已停止 wzdj")
return True
except Exception as e:
print(" API 停止 wzdj 失败:", e)
return False
def pre_check():
"""前置检查:本项目及周边 Node 项目状态(优先宝塔 API"""
print("【前置检查】目标项目 wzdj 及周边 Node 项目状态")
try:
r = bt_post("/project/nodejs/get_project_list")
items = r.get("data") or r.get("list") or []
if not items:
print(" API 返回无项目列表")
return None
for it in items:
name = it.get("name") or it.get("project_name") or ""
run = it.get("run", False)
path = it.get("path") or it.get("project_path") or ""
print(" -", name, "运行:" if run else "未运行", path[:50] if path else "")
wzdj = next((x for x in items if (x.get("name") or x.get("project_name")) == "wzdj"), None)
return {"ok": True, "wzdj": wzdj, "all": items}
except Exception as e:
print(" 宝塔 API 不可用:", str(e)[:80])
return None
def fix_via_ssh():
"""通过 SSH 在服务器上执行修复脚本"""
if not os.path.isfile(FIX_SH):
print(" 修复脚本不存在:", FIX_SH)
return False
try:
with open(FIX_SH, "r", encoding="utf-8") as f:
script = f.read()
cmd = [
"sshpass", "-p", KR_SSH_PASS,
"ssh", "-p", KR_SSH_PORT,
"-o", "StrictHostKeyChecking=no", "-o", "PubkeyAuthentication=no",
KR_SSH, "bash -s"
]
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
out, _ = p.communicate(input=script, timeout=120)
print(out[:2000] if out else "(无输出)")
return p.returncode == 0
except FileNotFoundError:
print(" sshpass 未安装,跳过 SSH")
return False
except subprocess.TimeoutExpired:
print(" SSH 执行超时")
return False
except Exception as e:
print(" SSH 失败:", e)
return False
def fix_via_tat():
"""通过 TAT 在服务器上执行修复"""
tat_script = os.path.join(SCRIPT_DIR, "腾讯云_TAT_kr宝塔_修复wzdj启动.py")
if not os.path.isfile(tat_script):
print(" TAT 脚本不存在:", tat_script)
return False
try:
venv_py = os.path.join(SCRIPT_DIR, ".venv_tx", "bin", "python")
karuo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_DIR))))
py = venv_py if os.path.isfile(venv_py) else sys.executable
p = subprocess.run([py, tat_script], capture_output=True, text=True, timeout=90, cwd=karuo_root)
if p.stdout:
print(p.stdout[:1500])
if p.returncode != 0 and p.stderr:
print("stderr:", p.stderr[:500])
return p.returncode == 0
except Exception as e:
print(" TAT 失败:", e)
return False
def start_wzdj_api():
"""宝塔 API 启动 wzdj"""
try:
r = bt_post("/project/nodejs/start_project", {"project_name": "wzdj"})
ok = r.get("status") or ("成功" in str(r.get("msg", "")))
print(" API 启动 wzdj:", "成功" if ok else r.get("msg", r))
return ok
except Exception as e:
print(" API 启动失败:", e)
return False
def verify_site():
"""验证站点可访问"""
try:
req = urllib.request.Request("https://" + WZDJ_DOMAIN, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(req, timeout=10) as r:
code = r.getcode()
print(" %s HTTP %s" % (WZDJ_DOMAIN, code))
return 200 <= code < 400
except Exception as e:
print(" 访问 %s 失败: %s" % (WZDJ_DOMAIN, e))
return False
def main():
# 1) 前置检查
pre_check()
print()
# 2) 先尝试 API 停止 wzdj便于后续改配置
print("【停止 wzdj】优先宝塔 API")
bt_stop_wzdj()
print()
# 3) 修复:优先 SSH失败再用 TAT改 site.db / wzdj.sh 只能机内执行)
print("【修复】按顺序尝试SSH → TAT")
if fix_via_ssh():
print(" 已通过 SSH 完成修复")
else:
print(" 改用 TAT 执行修复")
fix_via_tat()
print()
time.sleep(3)
# 4) 启动 wzdj优先宝塔 API
print("【启动 wzdj】优先宝塔 API")
if not start_wzdj_api():
print(" API 启动失败,修复脚本内已含启动步骤,请稍后查看面板")
print()
time.sleep(5)
# 5) 再次检查项目与周边
print("【修复后检查】目标及周边项目")
pre_check()
print()
# 6) 验证站点
print("【验证站点】")
ok = verify_site()
if ok:
print(" 结果: wzdj.quwanzhi.com 可正常访问")
else:
print(" 若仍不可访问,请到宝塔面板查看 Node 项目 wzdj 状态与日志")
# 写入工作台结果文件
try:
karuo = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_DIR)))))
out_path = os.path.join(karuo, "运营中枢", "工作台", "wzdj_flow_result.txt")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
with open(out_path, "w", encoding="utf-8") as f:
f.write("wzdj 修复流程 %s\n" % time.strftime("%Y-%m-%d %H:%M:%S"))
f.write("站点可访问: %s\n" % ok)
except Exception:
pass
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,91 @@
#!/bin/bash
# 仅修复 wzdj.quwanzhi.com 启动失败Cannot find module '/www/wwwroot/self/wzdj'
# 在宝塔终端或 SSH 执行bash 本脚本 或 直接粘贴内容执行
set -e
echo "【1】停止 wzdj"
python3 -c "
import hashlib,json,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()
if d: pl.update(d)
r=urllib.request.Request(P+u,data=urllib.parse.urlencode(pl).encode())
return json.loads(urllib.request.urlopen(r,timeout=15).read().decode())
try:
post('/project/nodejs/stop_project',{'project_name':'wzdj'})
print('stop wzdj ok')
except Exception as e: print('stop',e)
" 2>/dev/null || true
echo "【2】修复 site.db 中 wzdj 的 project_script/run_cmd"
python3 -c "
import json,sqlite3
db='/www/server/panel/data/db/site.db'
path='/www/wwwroot/self/wzdj'
cmd='cd %s && (PORT=3055 pnpm start 2>/dev/null || PORT=3055 npm run start)' % path
conn=sqlite3.connect(db)
c=conn.cursor()
c.execute(\"SELECT id,name,path,project_config FROM sites WHERE name='wzdj' AND project_type='Node'\")
row=c.fetchone()
if row:
sid,nm,oldpath,cfg=row[0],row[1],row[2] or '',row[3] or '{}'
try: cfg=json.loads(cfg)
except: cfg={}
cfg['project_script']=cfg['run_cmd']=cmd
cfg['path']=path
c.execute('UPDATE sites SET path=?, project_config=? WHERE id=?',(path,json.dumps(cfg,ensure_ascii=False),sid))
conn.commit()
print('site.db wzdj fixed')
else:
print('wzdj not found in sites')
conn.close()
"
echo "【3】修复 wzdj.sh 启动脚本"
SH=/www/server/nodejs/vhost/scripts/wzdj.sh
if [ -f \"$SH\" ]; then
python3 -c "
p='/www/server/nodejs/vhost/scripts/wzdj.sh'
path='/www/wwwroot/self/wzdj'
new_cmd='cd %s && (PORT=3055 pnpm start 2>/dev/null || PORT=3055 npm run start)' % path
with open(p,'r') as f: lines=f.readlines()
out=[]
for line in lines:
# 仅替换“执行该路径”的行:含路径且含 node/exec/$(避免改 export 等)
if '/www/wwwroot/self/wzdj' in line and not line.strip().startswith('#') and ('node' in line.lower() or 'exec' in line or '\$' in line):
out.append(' ' + new_cmd + '\n')
else:
out.append(line)
with open(p,'w') as f: f.writelines(out)
print('wzdj.sh updated')
"
else
echo \"wzdj.sh not found, skip\"
fi
echo "【3.5】释放 3055 端口(避免 EADDRINUSE"
fuser -k 3055/tcp 2>/dev/null || true
sleep 1
echo "【4】启动 wzdj"
python3 -c "
import hashlib,json,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()
if d: pl.update(d)
r=urllib.request.Request(P+u,data=urllib.parse.urlencode(pl).encode())
return json.loads(urllib.request.urlopen(r,timeout=15).read().decode())
r=post('/project/nodejs/start_project',{'project_name':'wzdj'})
print('start wzdj:', r.get('msg') or r)
"
echo "【5】完成"

View File

@@ -0,0 +1,195 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""TAT修复 wzdj.quwanzhi.com 启动失败Cannot find module '/www/wwwroot/self/wzdj'
原因:宝塔用 node /path 当入口,应改为 cd /path && (pnpm start || npm run start)
"""
import base64, json, os, re, sys, time
KR_INSTANCE_ID, REGION = "ins-aw0tnqjo", "ap-guangzhou"
SHELL = r'''#!/bin/bash
set -e
echo "【1】确保宝塔 9988 监听"
if ! ss -tlnp 2>/dev/null | grep -q ':9988 '; then
/etc/init.d/bt start 2>/dev/null || true
sleep 8
fi
echo "【2】停止 wzdj"
python3 -c "
import hashlib,json,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()
if d: pl.update(d)
r=urllib.request.Request(P+u,data=urllib.parse.urlencode(pl).encode())
return json.loads(urllib.request.urlopen(r,timeout=15).read().decode())
try:
post('/project/nodejs/stop_project',{'project_name':'wzdj'})
print('stop wzdj ok')
except Exception as e: print('stop',e)
" 2>/dev/null || true
echo "【3】修复 site.db 中 wzdj 的 project_script/run_cmd"
python3 -c "
import json,sqlite3,os
db='/www/server/panel/data/db/site.db'
path='/www/wwwroot/self/wzdj'
cmd='cd %s && (PORT=3055 pnpm start 2>/dev/null || PORT=3055 npm run start)' % path
conn=sqlite3.connect(db)
c=conn.cursor()
c.execute('SELECT id,name,path,project_config FROM sites WHERE name=\"wzdj\" AND project_type=\"Node\"')
row=c.fetchone()
if row:
sid,nm,oldpath,cfg=row[0],row[1],row[2] or '',row[3] or '{}'
try: cfg=json.loads(cfg)
except: cfg={}
cfg['project_script']=cfg['run_cmd']=cmd
cfg['path']=path
c.execute('UPDATE sites SET path=?, project_config=? WHERE id=?',(path,json.dumps(cfg,ensure_ascii=False),sid))
conn.commit()
print('site.db wzdj fixed:',cmd[:60])
else:
print('wzdj not found in sites')
conn.close()
"
echo "【4】修复 wzdj.sh 启动脚本(避免 node /path"
SH=/www/server/nodejs/vhost/scripts/wzdj.sh
if [ -f \"$SH\" ]; then
python3 -c "
p='$SH'
path='/www/wwwroot/self/wzdj'
new_cmd='cd %s && (PORT=3055 pnpm start 2>/dev/null || PORT=3055 npm run start)' % path
with open(p,'r') as f: lines=f.readlines()
out=[]
for line in lines:
if '/www/wwwroot/self/wzdj' in line and not line.strip().startswith('#') and ('node' in line.lower() or 'exec' in line or '\$' in line):
out.append(' ' + new_cmd + '\n')
else:
out.append(line)
with open(p,'w') as f: f.writelines(out)
print('wzdj.sh updated')
"
else
echo \"wzdj.sh not found, skip\"
fi
echo "【5】启动 wzdj"
python3 -c "
import hashlib,json,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()
if d: pl.update(d)
r=urllib.request.Request(P+u,data=urllib.parse.urlencode(pl).encode())
return json.loads(urllib.request.urlopen(r,timeout=15).read().decode())
r=post('/project/nodejs/start_project',{'project_name':'wzdj'})
print('start wzdj:', r.get('msg') or r)
" 2>&1
echo "【6】完成"
'''
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():
base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
log_path = os.path.join(base, "运营中枢", "工作台", "wzdj_fix_result.txt")
log_lines = []
def log(s):
log_lines.append(s)
print(s)
try:
os.makedirs(os.path.dirname(log_path), exist_ok=True)
with open(log_path, "w", encoding="utf-8") as f:
f.write("start\n")
except Exception:
pass
sid, skey = _creds()
if not sid or not skey:
log("❌ 未配置凭证")
try:
with open(log_path, "w", encoding="utf-8") as f:
f.write("no creds\n")
except Exception:
pass
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 = 60
req.CommandName = "kr宝塔_修复wzdj启动"
r = cli.RunCommand(req)
log("✅ TAT inv: " + str(r.InvocationId))
time.sleep(25)
req2 = models.DescribeInvocationTasksRequest()
f = models.Filter()
f.Name, f.Values = "invocation-id", [r.InvocationId]
req2.Filters = [f]
r2 = cli.DescribeInvocationTasks(req2)
for t in (r2.InvocationTaskSet or []):
log("状态: " + str(getattr(t, "TaskStatus", "")))
tr = getattr(t, "TaskResult", None)
if tr:
d = tr.__dict__ if hasattr(tr, "__dict__") else {}
out = d.get("Output", getattr(tr, "Output", ""))
if out:
try:
decoded = base64.b64decode(out).decode("utf-8", errors="replace")
log(decoded)
except Exception:
log(str(out)[:2000])
try:
os.makedirs(os.path.dirname(log_path), exist_ok=True)
with open(log_path, "w", encoding="utf-8") as f:
f.write("\n".join(log_lines))
except Exception:
pass
return 0
if __name__ == "__main__":
log_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))), "运营中枢", "工作台", "wzdj_fix_result.txt")
code = 1
try:
code = main()
with open(log_path, "w", encoding="utf-8") as f:
f.write("OK exit=%s\n" % code)
except Exception as e:
try:
os.makedirs(os.path.dirname(log_path), exist_ok=True)
with open(log_path, "w", encoding="utf-8") as f:
f.write("ERR: %s\n" % e)
except Exception:
pass
raise
sys.exit(code)

View File

@@ -28,7 +28,7 @@ def ports(it):
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']}
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'],'wzdj':['/www/wwwroot/wzdj']}
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'\")

View File

@@ -78,6 +78,7 @@ PATH_FALLBACK = {
"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"],
"wzdj": ["/www/wwwroot/wzdj"],
}
# 【1】停止全部 Node

View File

@@ -0,0 +1,159 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
腾讯云 TAT仅修正 wzdj 在 site.db 中的路径与启动命令并重启
问题:宝塔里 wzdj 路径为 /www/wwwroot/self/wzdj 导致 MODULE_NOT_FOUNDNode 把路径当模块加载)
处理:将 path 与 project_script 改为 /www/wwwroot/wzdj再重启 wzdj
"""
import base64
import json
import os
import re
import sys
import time
KR_INSTANCE_ID = "ins-aw0tnqjo"
REGION = "ap-guangzhou"
WZDJ_CORRECT_PATH = "/www/wwwroot/wzdj"
DB_PATH = "/www/server/panel/data/db/site.db"
def _read_creds():
d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
root = d
p = os.path.join(root, "运营中枢", "工作台", "00_账号与API索引.md")
if os.path.isfile(p):
with open(p, "r", encoding="utf-8") as f:
text = f.read()
sid = skey = None
in_tx = False
for line in text.splitlines():
if "### 腾讯云" in line:
in_tx = True
continue
if in_tx and line.strip().startswith("###"):
break
if not in_tx:
continue
m = re.search(r"\|\s*[^|]*(?:SecretId|密钥)[^|]*\|\s*`([^`]+)`", line, re.I)
if m and m.group(1).strip().startswith("AKID"):
sid = m.group(1).strip()
m = re.search(r"\|\s*SecretKey\s*\|\s*`([^`]+)`", line, re.I)
if m:
skey = m.group(1).strip()
return sid or None, skey or None
d = os.path.dirname(d)
return None, None
SHELL = f'''#!/bin/bash
set -e
echo "=== 修正 wzdj 路径并重启 ==="
db="{DB_PATH}"
if [ ! -f "$db" ]; then
echo "ERROR: site.db not found"; exit 1
fi
# 1. 修正 site.db 中 wzdj 的 path 与 project_config
python3 -c "
import json, sqlite3, os
db = '{DB_PATH}'
path = '{WZDJ_CORRECT_PATH}'
conn = sqlite3.connect(db)
c = conn.cursor()
c.execute(\\\"SELECT id, name, path, project_config FROM sites WHERE project_type='Node' AND name='wzdj'\\\")
row = c.fetchone()
if not row:
print('wzdj 未在 site.db 中找到')
exit(1)
sid, name, old_path, cfg_str = row
cfg = json.loads(cfg_str or '{{}}')
cmd = f'cd {{path}} && (pnpm start 2>/dev/null || npm run start)'
cfg['project_script'] = cfg['run_cmd'] = cmd
cfg['path'] = path
c.execute('UPDATE sites SET path=?, project_config=? WHERE id=?', (path, json.dumps(cfg, ensure_ascii=False), sid))
conn.commit()
conn.close()
print('已更新 wzdj path ->', path)
"
# 2. 重启 wzdj宝塔 API
python3 -c "
import hashlib, json, urllib.request, urllib.parse, ssl, time
ssl._create_default_https_context = ssl._create_unverified_context
P, 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(path, d=None):
pl = sign()
if d: pl.update(d)
r = urllib.request.Request(P+path, data=urllib.parse.urlencode(pl).encode())
with urllib.request.urlopen(r, timeout=25) 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:
nm = (it.get('name') or '').lower()
if nm == 'wzdj':
post('/project/nodejs/restart_project', {{'project_name': it.get('name') or it.get('project_name')}})
print(' 已重启 wzdj')
break
else:
print(' 未找到 wzdj 项目')
"
echo "=== 完成 ==="
'''
def main():
sid = os.environ.get("TENCENTCLOUD_SECRET_ID")
skey = os.environ.get("TENCENTCLOUD_SECRET_KEY")
if not sid or not skey:
sid, skey = _read_creds()
if not sid or not skey:
print("❌ 未配置腾讯云 SecretId/SecretKey见 00_账号与API索引.md")
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.encode()).decode()
req.InstanceIds = [KR_INSTANCE_ID]
req.CommandType = "SHELL"
req.Timeout = 90
req.CommandName = "wzdj_fix_path_restart"
resp = client.RunCommand(req)
print("✅ TAT 已下发InvocationId:", resp.InvocationId)
print(" 修正 wzdj path 为", WZDJ_CORRECT_PATH, "并重启")
print(" 等待约 55s...")
time.sleep(55)
try:
req2 = models.DescribeInvocationTasksRequest()
f = models.Filter()
f.Name = "invocation-id"
f.Values = [resp.InvocationId]
req2.Filters = [f]
r2 = client.DescribeInvocationTasks(req2)
for t in (r2.InvocationTaskSet or []):
print(" 状态:", getattr(t, "TaskStatus", ""))
tr = getattr(t, "TaskResult", None)
out = None
if tr is not None:
out = getattr(tr, "Output", tr.__dict__.get("Output", ""))
if not out and hasattr(t, "Output"):
out = t.Output
if out:
try:
text = base64.b64decode(out).decode("utf-8", errors="replace")
print(" 输出:", text[:1500])
except Exception:
print(" 输出:", (out or "")[:1000])
except Exception as e:
print(" 查询:", e)
print(" 验证: https://wzdj.quwanzhi.com")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
腾讯云 API 为 kr宝塔 43.139.27.93 安全组放行 SSH 端口 22、22022便于远程连接。
凭证00_账号与API索引.md 或环境变量
"""
import os, re, sys
KR_IP = "43.139.27.93"
KR_INSTANCE_ID = "ins-aw0tnqjo"
REGIONS = ["ap-guangzhou", "ap-beijing", "ap-shanghai"]
def _read_creds():
d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
if os.path.isdir(os.path.join(d, "运营中枢")) and os.path.isdir(os.path.join(d, "01_卡资")):
p = os.path.join(d, "运营中枢", "工作台", "00_账号与API索引.md")
if os.path.isfile(p):
with open(p) 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.cvm.v20170312 import cvm_client, models as cvm_models
from tencentcloud.vpc.v20170312 import vpc_client, models as vpc_models
except ImportError:
print("pip install tencentcloud-sdk-python-cvm tencentcloud-sdk-python-vpc"); return 1
cred = credential.Credential(sid, skey)
sg_ids, region = [], None
for r in REGIONS:
try:
c = cvm_client.CvmClient(cred, r)
req = cvm_models.DescribeInstancesRequest()
req.InstanceIds = [KR_INSTANCE_ID]
resp = c.DescribeInstances(req)
for ins in (getattr(resp, "InstanceSet", None) or []):
sg_ids = list(getattr(ins, "SecurityGroupIds", None) or [])
region = r
break
except Exception:
try:
req = cvm_models.DescribeInstancesRequest()
req.Limit = 100
resp = c.DescribeInstances(req)
for ins in (getattr(resp, "InstanceSet", None) or []):
if KR_IP in list(getattr(ins, "PublicIpAddresses", None) or []):
sg_ids = list(getattr(ins, "SecurityGroupIds", None) or [])
region = r
break
except Exception:
continue
if sg_ids: break
if not sg_ids:
print("❌ kr宝塔 %s 未在 CVM 中找到" % KR_IP); return 1
print("kr宝塔 %s 安全组放行 SSH 22、22022" % KR_IP)
vc = vpc_client.VpcClient(cred, region)
for port, desc in [("22", "SSH"), ("22022", "SSH-宝塔")]:
for sg_id in sg_ids:
try:
req = vpc_models.CreateSecurityGroupPoliciesRequest()
req.SecurityGroupId = sg_id
ps = vpc_models.SecurityGroupPolicySet()
ing = vpc_models.SecurityGroupPolicy()
ing.Protocol, ing.Port, ing.CidrBlock = "TCP", port, "0.0.0.0/0"
ing.Action, ing.PolicyDescription = "ACCEPT", desc
ps.Ingress = [ing]
req.SecurityGroupPolicySet = ps
vc.CreateSecurityGroupPolicies(req)
print("%s 已添加 %s/TCP" % (sg_id, port))
except Exception as e:
if "RuleAlreadyExists" in str(e) or "已存在" in str(e) or "duplicate" in str(e).lower():
print("%s %s 规则已存在" % (sg_id, port))
else:
print("%s %s: %s" % (sg_id, port, e))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -688,6 +688,18 @@ ssh nas "netstat -tlnp | grep 27017"
| DSM | http://192.168.110.29:5000 |
| 账号 | admin密码见上小写 |
### 家里 NAS开辟约 1TB 备份盘 · Mac 当硬盘用 + 时间机器
**需求**:在群晖(家里 DiskStation上开辟约 1000GB 空间,在 Mac 上挂载成**像真实硬盘**,可用于时间机器 + 日常读写。
- **操作指南**`参考资料/群晖1TB备份盘_Mac挂载与时间机器.md`NAS 新建共享 → 开 Time Machine → Mac 挂载 → 时间机器选盘 → 可选开机自动挂载)
- **一键挂载**(内网优先,外网走 frp 4452
```bash
/Users/karuo/Documents/个人/卡若AI/01_卡资/金仓_存储备份/群晖NAS管理/scripts/mount_diskstation_1tb.sh
```
- 挂载点:`~/DiskStation-1TB`;新建共享名若为 `MacBackup`,可执行:`MACBACKUP_SHARE=MacBackup ./scripts/mount_diskstation_1tb.sh`
- **开机自动挂载**:复制 `scripts/com.karuo.mount_diskstation_1tb.plist` 到 `~/Library/LaunchAgents/` 后 `launchctl load`,详见操作指南。
---
## 运维规范
@@ -718,6 +730,7 @@ ssh nas "netstat -tlnp | grep 27017"
| 脚本 | 功能 | 位置 | 快速运行 |
|------|------|------|----------|
| `time_machine_diskstation_auto.sh` | Time Machine → 家里 DiskStation 检测/验证,输出材料路径供按参考资料处理 | `./scripts/` | `./scripts/time_machine_diskstation_auto.sh` |
| `mount_diskstation_1tb.sh` | 家里 NAS 约 1TB 备份盘挂载到 Mac内网优先当硬盘用 + 时间机器 | `./scripts/` | `./scripts/mount_diskstation_1tb.sh` |
| `export_macos_vm_to_downloads.sh` | CKB NAS 上 macOS VM 打包下载到本机「下载」文件夹,实时显示大小与用时,并生成使用说明 | `./scripts/` | 见下方「macOS VM 导出到本机」 |
| `optimize_macos_vm_compose.sh` | 本机→NASmacOS VM 流畅度优化 | `./scripts/` | 需本机与 NAS 同网 |
| `optimize_macos_vm_on_nas.sh` | **NAS 上直接执行**macOS VM 流畅度优化(外网推荐) | `./scripts/` | SSH 登录 NAS 后运行 |

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.karuo.mount_diskstation_1tb</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/karuo/Documents/个人/卡若AI/01_卡资/金仓_存储备份/群晖NAS管理/scripts/mount_diskstation_1tb.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/mount_diskstation_1tb.log</string>
<key>StandardErrorPath</key>
<string>/tmp/mount_diskstation_1tb.err</string>
</dict>
</plist>

View File

@@ -1,19 +1,19 @@
#!/bin/bash
# ============================================
# 家里 DiskStation 1TB 共享 - 外网挂载到 Finder 侧栏「位置」
# 挂载后可直接存文件Finder 拷贝时会显示速率
# 外网通过 frp 端口 4452 访问 SMB需先在 NAS 添加 frpc 配置)
# 家里 DiskStation 1TB 备份盘 - Mac 挂载成像真实硬盘
# 内网优先192.168.110.29),外网用 opennas2:4452
# 挂载后可用于时间机器、Finder 侧栏当硬盘用、拷贝看速率
# ============================================
NAS_HOST="opennas2.quwanzhi.com"
NAS_PORT="4452"
NAS_USER="admin"
# 密码:与 DSM 登录一致
NAS_PASS="zhiqun1984"
# 共享名DSM 中的共享文件夹名,常见为 共享、homes
SHARE="共享"
# 共享名DSM 里新建的备份盘可用 MacBackup已有共享可用 共享
SHARE_RAW="${MACBACKUP_SHARE:-共享}"
MOUNT_POINT="$HOME/DiskStation-1TB"
# 中文共享名需 URL 编码,否则 mount_smbfs 会报 URL parsing failed
SHARE=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$SHARE_RAW'))" 2>/dev/null || echo "$SHARE_RAW")
# 已挂载则先卸载
if mount | grep -q "DiskStation-1TB"; then
echo "正在卸载旧挂载..."
@@ -22,15 +22,27 @@ if mount | grep -q "DiskStation-1TB"; then
fi
mkdir -p "$MOUNT_POINT"
echo "正在挂载家里 DiskStation (${NAS_HOST}:${NAS_PORT})..."
mount_smbfs "//${NAS_USER}:${NAS_PASS}@${NAS_HOST}:${NAS_PORT}/${SHARE}" "$MOUNT_POINT" 2>&1
# 内网优先:能 ping 通 192.168.110.29 则用内网SMB 默认 445
if ping -c 1 -W 2 192.168.110.29 >/dev/null 2>&1; then
echo "使用内网 192.168.110.29 挂载..."
SMB_URL="//${NAS_USER}:${NAS_PASS}@192.168.110.29/${SHARE}"
else
echo "使用外网 opennas2.quwanzhi.com:4452 挂载..."
SMB_URL="//${NAS_USER}:${NAS_PASS}@opennas2.quwanzhi.com:4452/${SHARE}"
fi
mount_smbfs "$SMB_URL" "$MOUNT_POINT" 2>&1
if mount | grep -q "DiskStation-1TB"; then
echo "挂载成功: $MOUNT_POINT"
echo "添加到 Finder 侧栏:在 Finder 中把「DiskStation-1TB」拖到侧栏「位置」下即可"
echo "直接往里拷贝文件Finder 会显示传输速率"
echo "→ 时间机器:系统设置 → 时间机器 → 选择该磁盘"
echo "→ 侧栏固定:在 Finder 中把「DiskStation-1TB」拖到「位置」"
open "$MOUNT_POINT"
else
echo "挂载失败。请确认:1) 家里 NAS frpc 已添加 SMB 4452 端口 2) NAS_PASS 正确 3) 共享名为 ${SHARE}"
echo "挂载失败。请确认:"
echo " 1) NAS 上已建共享文件夹(如 ${SHARE_RAW}SMB 已开"
echo " 2) 外网时 frpc 已添加 SMB 4452"
echo " 3) 密码正确(可改本脚本 NAS_PASS"
exit 1
fi

View File

@@ -0,0 +1,138 @@
# 群晖 NAS 开辟 1TB 备份盘 · Mac 当硬盘用 + 时间机器
在群晖(家里 DiskStation上开辟约 **1000GB** 空间,在 Mac 上挂载成**像真实硬盘一样**使用,并可用于**时间机器**备份。
---
## 一、NAS 端:开辟 1TB 空间DSM 操作)
在浏览器打开 **http://192.168.110.29:5000** 登录 DSM按顺序做
### 1. 新建共享文件夹(若还没有专用备份盘)
1. **文件服务****共享文件夹****新增**
2. 名称:`MacBackup`(或 `备份盘`,英文更省事)
3. 位置:选 **volume1**
4. 容量:**不勾选「启用共享文件夹配额」** 即使用整个卷可用空间;若需限制为 1TB勾选并设 **1024 GB**
5. 权限:给 **admin** 读写
6. 高级:**关闭回收站**(时间机器兼容更好)
7. 完成
若已有「共享」且空间够 1TB可跳过新建直接用该共享下面以 **共享名 = MacBackup** 为例(你用「共享」则替换成 `共享`)。
### 2. 开启 Time Machine 支持(可选,用于时间机器)
1. **控制面板****文件服务****高级****Time Machine**
2. 勾选 **「启用 Time Machine 备份」**
3. 选择刚建的 **MacBackup**(或你用的共享名)
4. 保存
### 3. 确认 SMB 已启用
1. **控制面板****文件服务****SMB**
2. 勾选 **启用 SMB 服务**
3. 高级里建议:**最大 SMB 协议 = SMB3**,勾选 **SMB2 租约**、**持久句柄**
4. 保存
### 4. 外网访问可选frpc 添加 SMB 端口
不在家时也要挂载,需在 NAS 上给 frpc 加 SMB 穿透:
- SSH 登录:`ssh admin@192.168.110.29`(需内网或已配好 SSH
- 编辑:`/volume1/homes/admin/frpc/frpc.ini`,在末尾加:
```ini
# SMB外网 4452 → NAS 445
[home-nas-smb]
type = tcp
local_ip = 127.0.0.1
local_port = 445
remote_port = 4452
```
- 重启 frpc`/volume1/homes/admin/frpc/start_frpc.sh`
---
## 二、Mac 端:挂载成像真实硬盘
### 方式 A内网挂载推荐在家时
1. Finder → **前往****连接服务器**⌘K
2. 输入(把 `MacBackup` 换成你的共享名):
- **`smb://192.168.110.29/MacBackup`**
3. 连接,输入 DSM 账号密码,勾选「在钥匙串中记住」
4. 挂载后会在 Finder 侧栏「位置」出现,**像本地硬盘一样**拖拽、拷贝、时间机器选它即可
### 方式 B外网挂载不在家时
1. 确保 NAS 已按「一、4」添加 SMB 的 frpc
2. Finder → ⌘K输入
- **`smb://opennas2.quwanzhi.com:4452/MacBackup`**
3. 连接并记住密码,同样当硬盘用
### 方式 C脚本一键挂载内网优先
已为你准备好脚本,**内网自动用 192.168.110.29,外网用 opennas2:4452**
```bash
/Users/karuo/Documents/个人/卡若AI/01_卡资/金仓_存储备份/群晖NAS管理/scripts/mount_diskstation_1tb.sh
```
- 挂载点:**~/DiskStation-1TB**
- 在 Finder 里把 **DiskStation-1TB** 拖到侧栏「位置」,就像本机硬盘一样常驻
---
## 三、用作时间机器备份盘
1. **系统设置****通用****时间机器**
2.**「选择备份磁盘」** 或 **「+」**
3. 选择刚挂载的 **MacBackup****DiskStation-1TB**
4. 输入 DSM 账号密码(若提示)
5. 若提示「未识别备份磁盘」:先移除该目标,再重新添加一次同一共享即可
时间机器会像用外接硬盘一样使用这块约 1TB 空间。
---
## 四、开机自动挂载(像真实硬盘常驻)
让 Mac 每次开机自动挂载,无需每次手动 ⌘K
### 方法 1登录项 + 脚本(简单)
1. 打开 **系统设置****通用****登录项**
2.**「+」**,选 **「其他」**
3. 找到并选中脚本:
`卡若AI/01_卡资/金仓_存储备份/群晖NAS管理/scripts/mount_diskstation_1tb.sh`
4. 之后每次登录会自动执行脚本挂载(需家里网络或外网 frp 可用)
### 方法 2LaunchAgent后台静默
脚本同目录下已有 **`com.karuo.mount_diskstation_1tb.plist`**。安装步骤:
```bash
# 复制到用户级 LaunchAgents
cp "/Users/karuo/Documents/个人/卡若AI/01_卡资/金仓_存储备份/群晖NAS管理/scripts/com.karuo.mount_diskstation_1tb.plist" ~/Library/LaunchAgents/
# 加载,下次登录会自动挂载
launchctl load ~/Library/LaunchAgents/com.karuo.mount_diskstation_1tb.plist
```
卸载:`launchctl unload ~/Library/LaunchAgents/com.karuo.mount_diskstation_1tb.plist`
---
## 五、小结
| 步骤 | 位置 | 动作 |
|:-----|:-----|:-----|
| 1 | NAS DSM | 新建共享文件夹(如 MacBackup约 1TB关回收站开 SMB |
| 2 | NAS DSM | 启用 Time Machine 并选中该共享 |
| 3 | NAS可选 | frpc 添加 SMB 4452外网可挂载 |
| 4 | Mac | ⌘K 连 `smb://192.168.110.29/MacBackup` 或运行挂载脚本 |
| 5 | Mac | 时间机器里选该卷;侧栏固定后当真实硬盘用 |
| 6 | Mac可选 | 登录项加脚本,开机自动挂载 |
按上述做完后,你这台苹果电脑就可以把群晖上的约 1TB 当**一块真实硬盘**使用,并用于时间机器备份。

View File

@@ -0,0 +1,28 @@
# wzdj 启动失败 MODULE_NOT_FOUND 修复经验
> 来源卡若AI 服务器管理 | 2026-03-03
## 现象
- 宝塔 Node 项目 **wzdj** 启动失败,弹窗报错:`Error: Cannot find module '/www/wwwroot/self/wzdj'`MODULE_NOT_FOUND
- 站点 https://wzdj.quwanzhi.com 无法打开(超时或 502
## 根因
- 宝塔里 wzdj 的**项目路径**配置为 `/www/wwwroot/self/wzdj`,而实际项目部署在 **`/www/wwwroot/wzdj`**(无 self 子目录)。
- 启动命令错误时Node 会把「路径」当作模块入口加载,导致 MODULE_NOT_FOUND。
## 处理
1. **仅修正路径并重启**(推荐)
执行:`01_卡资/金仓_存储备份/服务器管理/scripts/腾讯云_TAT_wzdj_修正路径并重启.py`
- 在 kr宝塔 上把 site.db 中 wzdj 的 `path` 改为 `/www/wwwroot/wzdj``project_script` 改为 `cd /www/wwwroot/wzdj && (pnpm start 2>/dev/null || npm run start)`,并调用宝塔 API 重启 wzdj。
2. **若仍无法访问**:在 kr宝塔 上确认 `/www/wwwroot/wzdj` 下有 `package.json`、已执行过 `pnpm build`,再执行一次「拉取构建并重启」:
`scripts/腾讯云_TAT_wzdj_拉取构建并重启.py`
3. **批量修复时兜底**`腾讯云_TAT_kr宝塔_强制停启Node.py``腾讯云_TAT_kr宝塔_运行堵塞与Node深度修复.py` 的 PATH_FALLBACK 已加入 `"wzdj": ["/www/wwwroot/wzdj"]`,路径错误时会被自动纠正。
## 可复用
- 同类问题:宝塔 Node 项目报 MODULE_NOT_FOUND 且错误里是「路径」→ 先查 site.db 里该项目 path 是否指向真实项目目录,再改 path + project_script`cd 正确路径 && (pnpm start || npm run start)`)并重启。

View File

@@ -46,11 +46,14 @@
## 第五步:加封面 + 烧录字幕 → 成片
1. **封面**:前 3 秒使用高光时刻/提问文案半透明质感Soul 绿风格。
2. **字幕**:烧录到画面,封面结束后显示,居中去语助词。
3. **输出**:竖屏 498×1080可选、文件名为纯标题全部写入 **成片/**
成片**必做**四项且执行时有进度输出【成片进度】1/6 …、[1/5] 封面 [2/5] 字幕 [3/5] 加速 [4/5] 裁剪 [5/5] 完成):
**产出****成片/** 目录下的成片 mp4封面+字幕+去语助词)
1. **封面**前 3 秒使用高光时刻/提问文案半透明质感Soul 绿风格
2. **字幕**:烧录到画面、**随语音时间轴走动**(非单张图),封面结束后显示,居中去语助词。若转录稿异常(如整篇同一句),会自动跳过字幕并提示「请用 MLX Whisper 重新生成 transcript.srt」。
3. **加速 10%**:成片统一加速,节奏更紧凑。
4. **竖屏裁剪**:高光区域裁成 498×1080 直出。
**产出****成片/** 目录下的成片 mp4封面+字幕+加速+竖屏)。
---
@@ -68,6 +71,9 @@
**命名与标题统一**:成片文件名 = 封面显示标题 = `highlights.json``title`;对 title 做「去杠」(`:|、—、/` 等替换为空格),保证无序号、无多余符号,名字与标题一致。
**切片标题要求**:每条切片的 `title` 要写清楚、与时间段内容一致,且尽量用**热门观点**式表达(一句说清话题、有观点或悬念),便于封面与文件名统一且好懂。
**字幕烧录前提**:烧录依赖正确的 `transcript.srt`(须为该视频的 MLX Whisper 等转录结果);若转录稿错误或全篇重复句,会无有效字幕或烧录内容错误,需重新转录后再跑成片。
---
## 本地处理说明(与剪映逆向分析一致)

View File

@@ -278,6 +278,21 @@ def _filter_relevant_subtitles(subtitles):
return out
def _is_bad_transcript(subtitles, min_lines=15, max_repeat_ratio=0.85):
"""检测是否为异常转录(如整篇同一句话):若大量重复则视为无效,不烧录错误字幕"""
if not subtitles or len(subtitles) < min_lines:
return False
from collections import Counter
texts = [ (s.get("text") or "").strip() for s in subtitles ]
most_common = Counter(texts).most_common(1)
if not most_common:
return False
_, count = most_common[0]
if count >= len(texts) * max_repeat_ratio:
return True
return False
def _sec_to_srt_time(sec):
"""秒数转为 SRT 时间格式 HH:MM:SS,mmm"""
h = int(sec) // 3600
@@ -739,7 +754,7 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
force_burn_subs=False, skip_subs=False, vertical=False):
"""增强单个切片。vertical=True 时最后裁成竖屏 498x1080 直出成片。"""
print(f"\n处理: {os.path.basename(clip_path)}")
print(f" 输入: {os.path.basename(clip_path)}", flush=True)
video_info = get_video_info(clip_path)
width, height = video_info['width'], video_info['height']
@@ -761,9 +776,10 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
overlay_pos = f"{OVERLAY_X}:0" if vertical else "0:0"
# 1. 生成封面
print(f" [1/5] 封面生成中…", flush=True)
cover_img = os.path.join(temp_dir, 'cover.png')
create_cover_image(hook_text, out_w, out_h, cover_img, clip_path)
print(f" ✓ 封面生成")
print(f" ✓ 封面生成", flush=True)
# 2. 字幕逻辑:有字幕则烧录(图像 overlay每张图 -loop 1 才能按时间 enable 显示)
sub_images = []
@@ -789,13 +805,19 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
sub['text'] = _translate_to_chinese(sub['text']) or sub['text']
# 仅过滤整句为规则/模板的条目,保留所有对白(含重复句,保证字幕连续)
subtitles = _filter_relevant_subtitles(subtitles)
print(f" ✓ 字幕解析 ({len(subtitles)}条)")
# 异常转录检测:若整篇几乎同一句话,不烧录错误字幕,避免成片出现“像图片”的无效字
if _is_bad_transcript(subtitles):
print(f" ⚠ 转录稿异常(大量重复同一句),已跳过字幕烧录;请用 MLX Whisper 对该视频重新生成 transcript.srt 后再跑成片", flush=True)
sys.stdout.flush()
subtitles = []
else:
print(f" ✓ 字幕解析 ({len(subtitles)}条),将烧录为随语音走动的字幕", flush=True)
for i, sub in enumerate(subtitles):
img_path = os.path.join(temp_dir, f'sub_{i:04d}.png')
create_subtitle_image(sub['text'], out_w, out_h, img_path)
sub_images.append({'path': img_path, 'start': sub['start'], 'end': sub['end']})
if sub_images:
print(f" ✓ 字幕图片 ({len(sub_images)}张)")
print(f" ✓ 字幕图片 ({len(sub_images)}张)", flush=True)
# 4. 检测静音
silences = detect_silence(clip_path, SILENCE_THRESHOLD, SILENCE_MIN_DURATION)
@@ -804,13 +826,14 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
# 5. 构建FFmpeg命令
current_video = clip_path
# 5.1 添加封面(竖屏时叠在 x=543,与最终裁切区域对齐
# 5.1 添加封面(封面图 -loop 1 保证前 3 秒完整显示;竖屏时叠在 x=543
print(f" [2/5] 封面烧录中…", flush=True)
cover_output = os.path.join(temp_dir, 'with_cover.mp4')
cmd = [
'ffmpeg', '-y',
'-i', current_video, '-i', cover_img,
'ffmpeg', '-y', '-i', current_video,
'-loop', '1', '-i', cover_img,
'-filter_complex', f"[0:v][1:v]overlay={overlay_pos}:enable='lt(t,{cover_duration})'[v]",
'-map', '[v]', '-map', '0:a',
'-map', '[v]', '-map', '0:a', '-shortest',
'-c:v', 'libx264', '-preset', 'fast', '-crf', '22',
'-c:a', 'copy', cover_output
]
@@ -818,11 +841,13 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
if os.path.exists(cover_output):
current_video = cover_output
print(f" ✓ 封面烧录")
print(f" ✓ 封面烧录", flush=True)
# 5.2 烧录字幕(图像 overlay每张图 -loop 1 才能按 enable=between(t,a,b) 显示)
# 5.2 烧录字幕(图像 overlay每张图 -loop 1 才能按 enable=between(t,a,b) 显示,随语音走动
if sub_images:
print(f" [3/5] 字幕烧录中({len(sub_images)} 条,随语音时间轴显示)…", flush=True)
batch_size = 5
total_batches = (len(sub_images) + batch_size - 1) // batch_size
for batch_idx in range(0, len(sub_images), batch_size):
batch = sub_images[batch_idx:batch_idx + batch_size]
inputs = ['-i', current_video]
@@ -854,9 +879,18 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
print(f" ⚠ 字幕批次 {batch_idx} 报错: {(result.stderr or '')[-500:]}", file=sys.stderr)
if result.returncode == 0 and os.path.exists(batch_output):
current_video = batch_output
print(f" ✓ 字幕烧录")
cur_batch = batch_idx // batch_size + 1
if total_batches > 1 and cur_batch <= total_batches:
print(f" 字幕批次 {cur_batch}/{total_batches} 完成", flush=True)
if sub_images:
print(f" ✓ 字幕烧录完成 ({len(sub_images)} 条)", flush=True)
else:
if do_burn_subs and os.path.exists(transcript_path):
print(f" ⚠ 未烧录字幕:解析后无有效字幕(请用 MLX Whisper 重新生成 transcript.srt", flush=True)
print(f" [3/5] 字幕跳过", flush=True)
# 5.3 加速10% + 音频同步
# 5.3 加速10% + 音频同步(成片必做)
print(f" [4/5] 加速 10% + 去语助词(已在上步字幕解析中清理)…", flush=True)
speed_output = os.path.join(temp_dir, 'speed.mp4')
atempo = 1.0 / SPEED_FACTOR # 音频需要反向调整
@@ -869,22 +903,31 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
speed_output
]
result = subprocess.run(cmd, capture_output=True)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0 and os.path.exists(speed_output):
current_video = speed_output
print(f" ✓ 加速10%")
print(f" ✓ 加速 10% 完成", flush=True)
else:
print(f" ⚠ 加速步骤失败,沿用当前视频继续", file=sys.stderr)
if result.stderr:
print(f" {str(result.stderr)[:300]}", file=sys.stderr)
# 5.4 输出:竖屏则裁成 498x1080 直出,否则直接复制
# 5.4 输出:竖屏则裁成 498x1080 直出(高光区域裁剪,成片必做)
print(f" [5/5] 竖屏裁剪中498×1080", flush=True)
if vertical:
r = subprocess.run([
'ffmpeg', '-y', '-i', current_video,
'-vf', CROP_VF, '-c:a', 'copy', output_path
], capture_output=True, text=True)
if r.returncode != 0:
print(f" 竖屏裁剪失败: {r.stderr[:200]}", file=sys.stderr)
if r.returncode == 0 and os.path.exists(output_path):
print(f" 竖屏裁剪完成", flush=True)
else:
print(f" ❌ 竖屏裁剪失败: {(r.stderr or '')[:300]}", file=sys.stderr)
shutil.copy(current_video, output_path)
print(f" ⚠ 已回退为未裁剪版本,请检查 FFmpeg", flush=True)
else:
shutil.copy(current_video, output_path)
print(f" ✓ 横版输出", flush=True)
if os.path.exists(output_path):
size_mb = os.path.getsize(output_path) / (1024 * 1024)
@@ -941,12 +984,17 @@ def main():
highlights = highlights if isinstance(highlights, list) else []
clips = sorted(clips_dir.glob('*.mp4'))
print(f"\n找到 {len(clips)} 个切片")
total = len(clips)
print(f"\n找到 {total} 个切片,开始成片(封面+字幕+加速10%+竖屏裁剪)\n", flush=True)
success_count = 0
for i, clip_path in enumerate(clips):
clip_num = _parse_clip_index(clip_path.name) or (i + 1)
highlight_info = highlights[clip_num - 1] if 0 < clip_num <= len(highlights) else {}
title_display = (highlight_info.get('title') or clip_path.stem)[:36]
print("=" * 60, flush=True)
print(f"【成片进度】 {i+1}/{total} {title_display}", flush=True)
print("=" * 60, flush=True)
if getattr(args, 'title_only', False):
title = (highlight_info.get('title') or highlight_info.get('hook_3sec') or clip_path.stem)

View File

@@ -28,7 +28,7 @@
---
## 三、启动顺序Boot Sequence
## 三、启动顺序与运转流程Boot Sequence
每次新对话开始,按以下顺序加载上下文(**只读必要的,不要全读**
@@ -37,6 +37,9 @@
| 1 | **本文件** `BOOTSTRAP.md` | 知道自己是谁、团队怎么分、怎么工作 |
| 2 | **技能注册表** `SKILL_REGISTRY.md` | 查找用户需求对应的技能和路径 |
| 3 | **对应技能的 SKILL.md** | 拿到具体执行指令(只读匹配到的那个) |
| 4 | **(强制)在对话中展示思考与拆解(文字版)** | 接到用户任务并完成理解后,**必须先在本轮对话中以详细文字输出**:① 思考结果 ② 任务拆解 ③ 执行计划;**每次对话均须展示,不可跳过**;展示完毕后再进入执行。 |
**运转流程强制一环**:了解完用户需求后 → **深度思考与拆解****在对话里用详细文字展示**(思考结果 + 任务拆解 + 计划)→ 再继续往下执行。此步为强制,不可省略。
**启动瘦身(按需加载)**:步骤 2 不需要全表扫描。优先只加载 `SKILL_REGISTRY.md` 中 🔴 热技能≤8 个)的触发词+路径;未命中时再懒加载其余部分。详见 SKILL_REGISTRY 中「技能热度分级」。
@@ -60,30 +63,51 @@
---
## 四.1、并行处理(多线程 · 一次对话内 16 线程)
**当任务可拆为多个相对独立的子任务时**卡若AI 应启用**多线程/多子任务并行处理**,在一次对话内同时推进多条线,发挥全部能力。
- **数量**:可开 **16 个**并行线程(子任务)。按任务复杂度与独立性决定:单一线索用 1 个;多域、多技能、可独立推进的拆成 26 个同时处理。
- **边界与域**卡若AI 负责**规范各线程的边界与归属域**,避免重叠与冲突:
- 按**五行/成员**划分:金(存储/安全)、水(整理/规划/对接)、木(内容/逆向/模板)、火(全栈/修复/追问/知识)、土(商业/技能/流量/财务)。
- 按**技能域**划分:每个子任务对应明确 SKILL 或子技能,边界内全力处理。
- **执行要求**:各线程在各自边界内**独立判断、独立处理**;能理解、能判断、能处理的事情在该线程内**全发挥**,不等待主线程逐项派单。主控只负责拆解、划界、派发与汇总。
- **汇总**所有并行线程完成后由卡若AI 汇总结果、去重、合并结论,再进入验证与复盘。
- **平台差异**:支持并行派发的平台(如 Cursor 的 mcp_task 等)可一次派发 16 个子任务同时执行;不支持则用显式「子任务 1/2/…」顺序执行并标注可并行域,便于后续平台升级。
详见 `运营中枢/参考资料/多线程并行处理规范.md`
---
## 五、执行流程(强制 · 含 MAX Mode
### 第一步:先思考,并在对话中展示拆解与计划(强制 · MAX Mode
### 第一步:先思考,并在对话中以详细文字展示拆解与计划(强制 · MAX Mode
接到用户任务后,**必须先做深度思考与调研**,再动手执行。思考要结合团队所有成员能力(金/水/木/火/土、14 成员、53 技能),想清楚:目标是什么、该谁干、怎么干、可能卡在哪;**思考更深度**(多角度、边界与风险),可结合 SKILL_REGISTRY 热技能与相关子技能做扩展。
**必须在对话里先展示以下内容,再继续执行**
**每次对话必须在对话里先以详细文字展示以下内容,再继续执行(强制,不可跳过)**
1. **思考结果**:调研/分析后的结论(目标、谁干、怎么干、卡点),用简洁几句话输出
2. **任务拆解**:把任务拆成 1、2、3… 的**细粒度**步骤(子步骤、依赖与顺序写清)
3. **执行计划**:先写清计划,尽量带**精确路径、命令、预期**再动手
1. **思考结果**:调研/分析后的结论(目标、谁干、怎么干、卡点),**用完整、详细的文字在对话中写出**,不是提纲或省略。
2. **任务拆解**:把任务拆成 1、2、3… 的**细粒度**步骤(子步骤、依赖与顺序写清)**在对话中以文字版完整展示**。
3. **执行计划**:先写清计划,尽量带**精确路径、命令、预期****在对话中展示完毕后再动手**。
**执行前**:检查是否有**联动子技能**需一并考虑(如视频切片→切片动效包装、全栈开发→需求拆解/智能追问)
**展示要求**:思考的整个过程、深度思考与拆解的结果,必须以**详细文字对话**的形式在本轮回复中显示,让用户能看到完整思考过程;禁止只写标题或省略,禁止不展示直接动手
**执行前**:① 检查是否有**联动子技能**需一并考虑(如视频切片→切片动效包装、全栈开发→需求拆解/智能追问)。② **若任务可拆为多个相对独立的子任务**:按「四.1 并行处理」划定边界与域,启用 **16 个并行线程**同时处理,各线程在各自边界内全力处理,最后汇总。
**格式示例**
```
## 思考与拆解
[调研后的结论:目标、该谁干、怎么干、可能卡点]
[调研后的结论:目标、该谁干、怎么干、可能卡点——详细文字]
## 任务拆解
1. 第一步…
2. 第二步…
3. 第三步…
## 执行计划
[具体计划与路径/命令/预期]
## 执行
[然后按计划执行]
```
@@ -107,10 +131,10 @@
---
### 流程小结(默认 MAX Mode
### 流程小结(默认 MAX Mode · 可 16 线程并行
```
输入 → 先思考(深度+细拆解+精确计划+技能联动)→ 在对话中展示 → 执行 → 至少两轮验证
输入 → 先思考(深度+细拆解+精确计划+技能联动)→ 在对话中展示 → 可拆则 16 线程并行(划界+派发+汇总)→ 执行 → 至少两轮验证
↑ │
└── 不匹配:回溯 → 搜索(GitHub/Skill/网上) → 再思考 → 再展示 → 再执行 ──┘

View File

@@ -34,7 +34,15 @@
---
## 四、官网控制台使用方式
## 四、让前端跑通 API 的两种方式
- **方式一(推荐)**在卡若AI 官网 **控制台 → API 网关** 中,至少配置一个网关并填写 API Key、选择模型如 v0 的 Key 从《00_账号与API索引》v0.dev 一节复制 Secret模型选 v0-1.5-md并将该网关「设为主用」。首页对话、技能 AI 等均走该网关。
- **方式二(环境变量回退)**未配置网关或数据库不可用时官网会读取环境变量作为备用网关与卡若AI《00_账号与API索引》对齐
- **v0**`V0_API_KEY``V0_SECRET`(值为索引中的 Secret可选 `V0_BASE_URL`(默认 `https://api.v0.dev/v1`)。
- **OpenAI**`OPENAI_API_KEY``CHAT_API_KEY`,可选 `OPENAI_API_BASE``OPENAI_MODEL`(默认 gpt-4o-mini
- 在网站项目根目录配置 `.env.local` 后重启,前端即可直接使用对话等 API 而无需先打开控制台。
## 五、官网控制台使用方式
- 打开 **卡若AI 官网 → 控制台 → API 网关**可见与主仓库一致的平台列表本机网关、OpenAI、OpenRouter、通义、v0、智增增可新增、编辑、删除以及「设为主用」选择当前主用网关。
- **参与轮询 / 未参与轮询**:页面分两块——「参与轮询」(已填 API Key、「未参与轮询」未填 Key。每个网关填写 Base URL 与 API Key**明文显示**便于与《00_账号与API索引》对照填好保存后即参与轮询。
@@ -43,7 +51,7 @@
---
## 、相关文档
## 、相关文档
- 主仓库网关说明:`运营中枢/scripts/karuo_ai_gateway/README.md`
- 接口排队与故障切换规则:`运营中枢/参考资料/卡若AI_API接口排队与故障切换规则.md`

View File

@@ -78,9 +78,10 @@
### 3.0 对话流程强制规则(每次对话必守)
1. **第一步:先思考,并在对话中展示拆解与计划**
1. **第一步:先思考,并在对话中以详细文字展示拆解与计划(强制)**
- 接到用户任务后,**必须先做深度思考/调研**再动手。思考要结合团队所有成员能力5 负责人、14 成员、53 技能),想清楚:目标是什么、该谁干、怎么干、可能卡在哪。
- **必须在对话里先展示**:① **思考结果**(调研后的结论,目标/谁干/怎么干/卡点)② **任务拆解**1、2、3… 具体步骤)③ **执行计划****展示完再继续执行**。**禁止不展示拆解直接动手。**
- **每次对话必须在对话里先以详细文字展示**:① **思考结果**(调研后的结论,目标/谁干/怎么干/卡点)② **任务拆解**1、2、3… 具体步骤)③ **执行计划****以完整、详细的文字在对话中写出**,不是提纲或省略;**展示完毕后再继续执行**。**禁止不展示拆解直接动手。**
- **运转流程强制一环**:了解完用户需求 → 深度思考与拆解 → **在对话里用详细文字展示(思考结果 + 任务拆解 + 计划)** → 再往下执行。
2. **执行后反复验证结果**
- 执行完成后,**必须验证**:最终结果是否与用户一开始输入的命令/目标相匹配。

View File

@@ -0,0 +1,50 @@
# 卡若AI · 多线程并行处理规范
> 一次对话内可启用 16 个并行线程/子任务由卡若AI 划定边界与域,同时处理、汇总结果。
> 更新2026-03-03
---
## 一、何时启用多线程
- **任务可拆**:用户需求可分解为多个**相对独立**的子任务(不同域、不同技能、不同产出),且子任务间无强顺序依赖。
- **数量****16 个**并行线程。单一线索用 1多域/多技能/多目标可拆成 26 个同时推进。
- **目标**:在一次对话内**同时**处理多条线,发挥全部能力,缩短总耗时。
---
## 二、边界与域卡若AI 规范)
卡若AI 负责在拆解时**明确各线程的边界与归属**,避免重叠与冲突:
| 划分方式 | 说明 |
|:---|:---|
| **按五行/成员** | 金(存储/安全)、水(整理/规划/对接)、木(内容/逆向/模板)、火(全栈/修复/追问/知识)、土(商业/技能/流量/财务)。每个线程对应明确负责人或成员。 |
| **按技能域** | 每个子任务对应明确 SKILL 或子技能(查 SKILL_REGISTRY边界内该技能全权处理。 |
| **按产出/目标** | 例如线程1 负责 A 文档、线程2 负责 B 模块、线程3 负责 C 数据,互不写同一文件。 |
各线程在**各自边界内**独立判断、独立执行;能理解、能判断、能处理的事情在该线程内**全发挥**,不等待主控逐项派单。
---
## 三、执行与汇总
1. **拆解**:在「思考与拆解」阶段标明哪些子任务可**并行**,并给出每个子任务的边界与归属域。
2. **派发**:一次派发 16 个子任务(平台支持时用并行能力,如 Cursor 的 mcp_task 等;不支持则显式列出可并行子任务并顺序执行、标注可并行)。
3. **汇总**所有并行线程完成后由卡若AI **汇总结果、去重、合并结论**,再进入验证与复盘。
---
## 四、平台差异
| 平台 | 实现方式 |
|:---|:---|
| **Cursor** | 使用 mcp_task或等效多 agent一次派发多个子任务每个子任务带清晰 prompt 与边界说明。 |
| **其他** | 若无并行派发能力,则在对话中显式列出「子任务 1/2/…」及边界,顺序执行并注明「可并行域」,便于后续平台升级后改为真并行。 |
---
## 五、与 BOOTSTRAP 的对应
- **BOOTSTRAP 四.1**:并行处理(多线程)总则。
- **执行流程 第一步 执行前**:若任务可拆,按本规范划定边界与域,启用 16 个并行线程,各线程在各自边界内全力处理,最后汇总。

View File

@@ -217,3 +217,4 @@
| 2026-03-03 10:20:17 | 🔄 卡若AI 同步 2026-03-03 10:20 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-03-03 12:01:38 | 🔄 卡若AI 同步 2026-03-03 12:01 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-03-03 14:29:21 | 🔄 卡若AI 同步 2026-03-03 14:29 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 |
| 2026-03-03 17:28:23 | 🔄 卡若AI 同步 2026-03-03 17:28 | 更新Cursor规则、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 |

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""启动 kr宝塔 sshd 并放行安全组,结果写文件。"""
import os, re, sys, base64, time
_here = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(_here)) # 卡若AI
INDEX = os.path.join(ROOT, "运营中枢", "工作台", "00_账号与API索引.md")
OUT = os.path.join(ROOT, "运营中枢", "工作台", "kr_ssh_start_result.txt")
def log(msg):
with open(OUT, "a", encoding="utf-8") as f:
f.write(msg + "\n")
def main():
# 确保输出目录存在
os.makedirs(os.path.dirname(OUT), exist_ok=True)
if os.path.isfile(OUT):
os.remove(OUT)
log("=== kr宝塔 SSH 启动与连接检查 ===\n")
# 读凭证
with open(INDEX) 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 in_t:
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()
if not sid or not skey:
log("ERR: 未找到腾讯云凭证"); return 1
from tencentcloud.common import credential
from tencentcloud.cvm.v20170312 import cvm_client, models as cvm_models
from tencentcloud.vpc.v20170312 import vpc_client, models as vpc_models
from tencentcloud.tat.v20201028 import tat_client, models as tat_models
cred = credential.Credential(sid, skey)
KR_ID = "ins-aw0tnqjo"
REGION = "ap-guangzhou"
# 1. 安全组放行 22、22022
log("1. 安全组放行 22、22022")
try:
cvm = cvm_client.CvmClient(cred, REGION)
r = cvm.DescribeInstances(cvm_models.DescribeInstancesRequest(InstanceIds=[KR_ID]))
ins = (r.InstanceSet or [None])[0]
if not ins: log(" ERR: 未找到实例"); return 1
sg_ids = list(getattr(ins, "SecurityGroupIds", None) or [])
vpc = vpc_client.VpcClient(cred, REGION)
for port, desc in [("22", "SSH"), ("22022", "SSH-宝塔")]:
for sg_id in sg_ids:
try:
req = vpc_models.CreateSecurityGroupPoliciesRequest()
req.SecurityGroupId = sg_id
ps = vpc_models.SecurityGroupPolicySet()
ing = vpc_models.SecurityGroupPolicy()
ing.Protocol, ing.Port, ing.CidrBlock = "TCP", port, "0.0.0.0/0"
ing.Action, ing.PolicyDescription = "ACCEPT", desc
ps.Ingress = [ing]
req.SecurityGroupPolicySet = ps
vpc.CreateSecurityGroupPolicies(req)
log(" OK %s -> %s/TCP" % (sg_id, port))
except Exception as e:
if "RuleAlreadyExists" in str(e) or "已存在" in str(e): log(" 已存在 %s" % port)
else: log(" ERR %s: %s" % (port, e))
except Exception as e:
log(" 安全组 ERR: %s" % e)
# 2. TAT 启动 sshd
log("\n2. TAT 启动 sshd")
CMD = """systemctl enable sshd; systemctl start sshd; sleep 1; systemctl is-active sshd; ss -tlnp | grep sshd"""
try:
tat = tat_client.TatClient(cred, REGION)
req = tat_models.RunCommandRequest()
req.Content = base64.b64encode(CMD.encode()).decode()
req.InstanceIds = [KR_ID]
req.CommandType = "SHELL"
req.Timeout = 30
req.CommandName = "kr_sshd_start"
r = tat.RunCommand(req)
inv_id = r.InvocationId
log(" InvocationId: %s" % inv_id)
for _ in range(8):
time.sleep(4)
req2 = tat_models.DescribeInvocationTasksRequest()
req2.Filters = [{"Name": "invocation-id", "Values": [inv_id]}]
r2 = tat.DescribeInvocationTasks(req2)
tasks = r2.InvocationTaskSet or []
if tasks and tasks[0].TaskStatus in ("SUCCESS", "FAILED", "TIMEOUT"):
res = tasks[0].TaskResult
log(" Status: %s" % tasks[0].TaskStatus)
if res and res.Output:
log(" Output:\n" + base64.b64decode(res.Output).decode("utf-8", errors="replace"))
break
except Exception as e:
log(" TAT ERR: %s" % e)
log("\n3. 请在本机执行 SSH 测试:")
log(" ssh -p 22022 -o StrictHostKeyChecking=no root@43.139.27.93")
log(" 密码: Zhiqun1984 (首字母大写Z)")
log("\n完成。")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,23 @@
# kr宝塔 SSH 启动与连接结果
请在本机终端按下面顺序执行,执行后本文件可改为记录实际输出。
## ① 安全组放行 22、22022
cd "/Users/karuo/Documents/个人/卡若AI"
.venv_tencent/bin/python3 "01_卡资/金仓_存储备份/服务器管理/scripts/腾讯云_kr宝塔安全组放行SSH.py"
(看到「已添加 22/TCP」「已添加 22022/TCP」或「规则已存在」即成功。
## ② 在服务器上启动 sshd
- 若能连上 SSHssh -p 22022 root@43.139.27.93,登录后执行:
systemctl enable sshd && systemctl start sshd && systemctl status sshd
- 若连不上:打开 https://43.139.27.93:9988 → 登录(ckb/zhiqun1984) → 终端 → 执行上述命令。
## ③ 测试连接
ssh -p 22022 -o StrictHostKeyChecking=no root@43.139.27.93
密码Zhiqun1984首字母大写 Z
详细说明见01_卡资/金仓_存储备份/服务器管理/references/SSH登录方式与故障排查.md

View File

@@ -0,0 +1,14 @@
执行记录 wzdj.quwanzhi.com 修复
================================
已执行操作:
1. 本机已调用 TAT 脚本腾讯云_TAT_kr宝塔_修复wzdj启动.pyexit code 0。
2. 已尝试 SSH 在 KR 宝塔上执行 kr宝塔_仅修复wzdj_宝塔终端执行.shexit code 0。
因当前环境无法捕获远程输出,请你在本机确认:
- 宝塔面板 → 网站 → Node 项目 → wzdj 是否显示「运行中」。
- 若仍为「启动失败」,请在宝塔终端或 SSH 手动执行:
bash 01_卡资/金仓_存储备份/服务器管理/scripts/kr宝塔_仅修复wzdj_宝塔终端执行.sh
(或打开该脚本复制全部内容到宝塔终端粘贴执行)
脚本会:停 wzdj → 修复 site.db 与 wzdj.sh 启动命令 → 再启动 wzdj。

View File

@@ -0,0 +1 @@
placeholder

View File

@@ -220,3 +220,4 @@
| 2026-03-03 10:20:17 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 10:20 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-03 12:01:38 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 12:01 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-03 14:29:21 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 14:29 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-03 17:28:23 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-03 17:28 | 更新Cursor规则、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |

View File

@@ -0,0 +1,52 @@
# 复盘文字电竞wzdj.quwanzhi.com启动修复
**[卡若复盘]****2026-02-20 15:00**
---
## 🎯 目标·结果·达成率
目标让文字电竞网站wzdj.quwanzhi.com可正常运行。结果卡若 AI 完成方案比选并已执行 SSH + TAT 双通道修复,脚本与规范已就绪。达成率:执行 100%,站点是否已恢复需你在面板或浏览器确认;若未恢复,按下一步在面板手动改启动命令即可达成。
---
## 🔀 决策链(方案比选)
- **方案 A仅宝塔 API 启停)**API 无「修改项目启动命令」接口无法根治「node /path」错误 → 不采纳。
- **方案 BSSH 执行修复脚本)**:在机内改 site.db 与 wzdj.sh脚本内用 127.0.0.1 调宝塔 API 停/启,不依赖本机 API 白名单 → **采纳,优先执行**
- **方案 CTAT 下发同逻辑)**SSH 不可用时在机内执行相同修复逻辑 → **采纳,作为补强已执行**
- **方案 D面板手动改启动命令**:宝塔 → Node 项目 → wzdj → 设置 → 启动命令改为 `cd /www/wwwroot/self/wzdj && (pnpm start 2>/dev/null || npm run start)`**兜底方案,若 B/C 未生效则必选**
置信度:高。
---
## 📌 过程
1. 按 SKILL 强制顺序(宝塔 API → SSH → TAT与前置检查要求对 wzdj 及周边 Node 项目做评估,确定需改启动命令而非仅重启。
2. 卡若 AI 比选四类方案,选定优先 SSH 执行 `kr宝塔_仅修复wzdj_宝塔终端执行.sh`,失败或不可用时用 TAT 执行 `腾讯云_TAT_kr宝塔_修复wzdj启动.py`
3. 已在本机依次触发 SSH 与 TAT 执行(两者均 exit 0当前环境无法捕获远程输出故无法直接确认机内是否已改 site.db / wzdj.sh 并重启成功。
4. 脚本逻辑:停 wzdj → 修 site.db 的 project_script/run_cmd → 修 wzdj.sh 中「执行该路径」的行 → 再启动 wzdj若仍失败需在面板手动改启动命令见下一步
---
## 💡 反思
1. 方案比选与执行顺序API → SSH → TAT写进复盘与 SKILL后续同类问题可复用。
2. 本环境无法看到 SSH/TAT 的机内输出,最终是否成功需你在宝塔面板看 wzdj 状态或访问 https://wzdj.quwanzhi.com 验证。
3. 兜底「面板改启动命令」一步到位,适合在自动脚本未生效时使用。
---
## 📝 总结
文字电竞wzdj的根因是宝塔用 `node /www/wwwroot/self/wzdj` 把目录当模块执行;正确做法为在项目目录下执行 `cd /path && (pnpm start || npm run start)`。已通过决策链选定并执行 SSH 优先、TAT 补强的方案;若站点仍未运行,在面板 wzdj 设置中手动改启动命令并保存、重启即可达成目标。
---
## ▶ 下一步执行
1. **你本地确认**:打开宝塔面板 → 网站 → Node 项目,看 wzdj 是否已为「运行中」;浏览器访问 https://wzdj.quwanzhi.com 是否正常。
2. **若仍「启动失败」**:在宝塔 → Node 项目 → wzdj → **设置**,将「启动命令」改为:
`cd /www/wwwroot/self/wzdj && (pnpm start 2>/dev/null || npm run start)`
保存后点击「启动」。
3. 无其他待跟进文档;目标为「文字电竞网站可运行」,完成上述任一路径即视为达成。