napcat / entrypoint.sh
flyminos's picture
Update entrypoint.sh
8bf1bbf verified
Raw
History Blame Contribute Delete
8.8 kB
save_to_hf
#!/bin/bash
trap "" SIGPIPE
NAP_CONFIG="/app/napcat/config"
NAP_PLUGINS="/app/napcat/plugins"
QQ_CONFIG1="/app/.config/QQ"
QQ_CONFIG2="/home/user/.config/QQ"
mkdir -p $NAP_CONFIG $QQ_CONFIG1 $QQ_CONFIG2 $NAP_PLUGINS
# --- 通用上传函数 ---
_upload_to_hf() {
local file=$1
local repo_path=$2
local label=$3
for i in 1 2 3; do
python3 -c "
import os
from huggingface_hub import HfApi
try:
HfApi().upload_file(
path_or_fileobj='$file',
path_in_repo='$repo_path',
repo_id=os.environ['DATASET_REPO'],
repo_type='dataset',
token=os.environ['HF_TOKEN']
)
print('[$label] 备份成功!')
except Exception as e:
print(f'[$label] 备份失败: {e}')
exit(1)
" && break
echo "[$label] 重试 $i/3..."
sleep 5
done
}
# --- 备份 NapCat 配置 ---
save_napcat_to_hf() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] --- 备份 NapCat 配置 ---"
rm -f /tmp/napcat.zip
zip -r /tmp/napcat.zip $NAP_CONFIG \
--exclude "*/logs/*" \
--exclude "*/webui/*"
_upload_to_hf /tmp/napcat.zip napcat.zip NapCat配置
}
# --- 备份 QQ 配置(不含聊天记录)---
save_qq_to_hf() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] --- 备份 QQ 配置 ---"
rm -f /tmp/qq.zip
zip -r /tmp/qq.zip $QQ_CONFIG1 $QQ_CONFIG2 \
--exclude "*/logs/*" \
--exclude "*/Cache/*" \
--exclude "*/GPUCache/*" \
--exclude "*/nt_db/*" \
--exclude "*/nt_data/*" \
--exclude "*/richMediaProxyFiles/*" \
--exclude "*/cacheFile/*" \
--exclude "*/crash/*" \
--exclude "*/__MACOSX/*"
_upload_to_hf /tmp/qq.zip qq.zip QQ配置
}
# --- 备份插件 ---
save_plugins_to_hf() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] --- 备份插件数据 ---"
rm -f /tmp/plugins.zip
zip -r /tmp/plugins.zip $NAP_PLUGINS \
--exclude "*/logs/*" \
--exclude "*/Cache/*"
_upload_to_hf /tmp/plugins.zip plugins.zip 插件
}
save_to_hf() {
save_napcat_to_hf
save_qq_to_hf
save_plugins_to_hf
}
trap 'echo "Received SIGTERM"; save_to_hf; exit 0' SIGTERM SIGINT
# --- 恢复单个zip ---
restore_zip() {
local zip_file="$1"
local label="$2"
python3 -c "
import zipfile, sys
zip_path = sys.argv[1]
label = sys.argv[2]
try:
skipped = 0
extracted = 0
with zipfile.ZipFile(zip_path, 'r') as z:
members = z.namelist()
print(f'[恢复/{label}] 共 {len(members)} 个文件')
for member in members:
try:
z.extract(member, '/')
extracted += 1
except Exception:
skipped += 1
print(f'[恢复/{label}] 成功 {extracted} 个,跳过 {skipped} 个')
except Exception as e:
print(f'[恢复/{label}] 失败: {e}')
" "$zip_file" "$label"
}
# --- 1. 从 HF 恢复数据 ---
if [ -n "${HF_TOKEN}" ] && [ -n "${DATASET_REPO}" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] --- 正在同步:从 HF Dataset 获取备份 ---"
python3 -c "
import os
from huggingface_hub import hf_hub_download
# napcat.zip 兼容旧格式(旧版同时含 NapCat + QQ 配置)
# qq.zip 新格式单独的 QQ 配置(若存在则覆盖旧版内容)
for filename, label in [('napcat.zip', 'NapCat/QQ配置'), ('qq.zip', 'QQ配置'), ('plugins.zip', '插件')]:
try:
path = hf_hub_download(
repo_id=os.environ['DATASET_REPO'],
filename=filename,
repo_type='dataset',
token=os.environ['HF_TOKEN']
)
print(f'[下载] {filename} -> {path}')
with open(f'/tmp/restore_{filename}.path', 'w') as f:
f.write(path)
except Exception as e:
print(f'[下载] {filename} 不存在或失败: {e}')
"
for zip_label in "napcat.zip:NapCat/QQ配置" "qq.zip:QQ配置" "plugins.zip:插件"; do
zip_file="${zip_label%%:*}"
label="${zip_label##*:}"
path_file="/tmp/restore_${zip_file}.path"
if [ -f "$path_file" ]; then
restore_zip "$(cat $path_file)" "$label"
fi
done
echo "[$(date '+%Y-%m-%d %H:%M:%S')] --- 恢复完成,目录状态 ---"
python3 -c "
import glob
for path in ['/app/.config/QQ', '/home/user/.config/QQ', '/app/napcat/plugins']:
files = glob.glob(path + '/**', recursive=True)
print(f' {path}: {len(files)} 个文件/目录')
"
fi
# --- 2. 检查 WebUI 配置 ---
CONFIG_PATH="$NAP_CONFIG/webui.json"
if [ ! -f "${CONFIG_PATH}" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 生成默认 WebUI 配置..."
cat > "${CONFIG_PATH}" << EOF
{
"host": "0.0.0.0",
"port": 6099,
"token": "${NAPCAT_WEBUI_SECRET_KEY:-admin}",
"loginRate": 3
}
EOF
fi
rm -rf /tmp/.X*
# --- 3. 定时备份(每30分钟)---
periodic_backup() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [备份] 将在 3 分钟后执行首次备份..."
sleep 180
save_to_hf
while true; do
sleep 1800
echo "[$(date '+%Y-%m-%d %H:%M:%S')] --- 定时备份(每30分钟)---"
save_to_hf
done
}
periodic_backup &
# --- 4. 调试WS保活 ---
debug_keepalive() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [保活] 等待 NapCat WebUI 启动..."
while ! curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:6099/api/auth/check | grep -q "200"; do
sleep 3
done
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [保活] WebUI 已就绪,启动保活进程..."
python3 -u << 'PYEOF'
import os, time, json, asyncio, hashlib
import urllib.request
BASE = "http://127.0.0.1:6099"
TOKEN = os.environ.get("NAPCAT_WEBUI_SECRET_KEY") or os.environ.get("WEBUI_TOKEN", "admin")
def make_hash(token):
return hashlib.sha256((token + ".napcat").encode()).hexdigest()
def login():
for i in range(5):
try:
data = json.dumps({"hash": make_hash(TOKEN)}).encode()
req = urllib.request.Request(
f"{BASE}/api/auth/login",
data=data,
headers={"Content-Type": "application/json"},
method="POST"
)
with urllib.request.urlopen(req, timeout=10) as r:
resp = json.loads(r.read())
credential = resp.get("data", {}).get("Credential")
if credential:
print(f"[保活] 登录成功", flush=True)
return credential
print(f"[保活] 登录响应: {resp}", flush=True)
except Exception as e:
print(f"[保活] 登录失败({i+1}/5): {e}", flush=True)
time.sleep(5)
return None
def create_debug_adapter(credential):
try:
req = urllib.request.Request(
f"{BASE}/api/Debug/create",
data=b"{}",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {credential}"
},
method="POST"
)
with urllib.request.urlopen(req, timeout=10) as r:
resp = json.loads(r.read())
ws_token = resp.get("data", {}).get("token")
if ws_token:
print(f"[保活] 调试适配器创建成功", flush=True)
return ws_token
print(f"[保活] 创建适配器响应: {resp}", flush=True)
except Exception as e:
print(f"[保活] 创建适配器失败: {e}", flush=True)
return None
async def keep_ws_alive(ws_token):
import websockets
url = f"ws://127.0.0.1:6099/api/Debug/ws?token={ws_token}"
try:
async with websockets.connect(
url,
ping_interval=30,
ping_timeout=10,
) as ws:
print(f"[保活] WS 连接已建立,保持中...", flush=True)
async for msg in ws:
pass
except Exception as e:
print(f"[保活] WS 连接断开: {e}", flush=True)
def run():
while True:
credential = login()
if not credential:
print("[保活] 无法登录,60秒后重试...", flush=True)
time.sleep(60)
continue
ws_token = create_debug_adapter(credential)
if not ws_token:
print("[保活] 无法获取 WS token,30秒后重试...", flush=True)
time.sleep(30)
continue
asyncio.run(keep_ws_alive(ws_token))
print("[保活] 连接断开,30秒后重新建立...", flush=True)
time.sleep(30)
run()
PYEOF
}
debug_keepalive &
export FFMPEG_PATH=/usr/bin/ffmpeg
export QT_X11_NO_MITSHM=1
nginx &
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Run NapCat QQ..."
if [ -n "${ACCOUNT}" ]; then
xvfb-run -a /opt/QQ/qq --no-sandbox -q "${ACCOUNT}" &
else
xvfb-run -a /opt/QQ/qq --no-sandbox &
fi
QQ_PID=$!
wait $QQ_PID
save_to_hf