[全量] 初始化项目代码、配置、文档及Agent协同harness

This commit is contained in:
2026-04-02 11:36:05 +08:00
parent 0553309cdf
commit 87e571d9ec
1133 changed files with 221948 additions and 0 deletions

135
scripts/backend-pack.sh Executable file
View File

@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# 须用 bash 执行
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 在 chat_web_backend 执行 mvn package将产物复制为 backend/chat_web_yj.jar与 backend-restart.sh 一致)
# 日志gangyan/logs/backend-pack.log
set -euo pipefail
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/backend-pack.log"
if ! ( umask 022; : >>"$LOG_FILE" ) 2>/dev/null; then
mkdir -p "${HOME}/.gangyan/logs"
LOG_FILE="${HOME}/.gangyan/logs/backend-pack.log"
fi
export JAVA_HOME="${JAVA_HOME:-/usr/lib/jvm/java-11-openjdk-amd64}"
export PATH="$JAVA_HOME/bin:$PATH"
BACKEND_SRC="$GANGYAN_ROOT/chat_web_backend"
JAR_BUILD="$BACKEND_SRC/target/chat_web_backend.jar"
JAR_RUN="$GANGYAN_ROOT/backend/chat_web_yj.jar"
log_tee() {
log_line "$@" | tee -a "$LOG_FILE"
}
log_tee "======== 编译打包 Java 后端 ========"
log_tee "JAVA_HOME=$JAVA_HOME"
if ! command -v mvn >/dev/null 2>&1; then
log_tee "错误: 未找到 mvn请先安装 Maven 并加入 PATH"
exit 1
fi
if [ ! -f "$BACKEND_SRC/pom.xml" ]; then
log_tee "错误: 未找到 $BACKEND_SRC/pom.xml"
exit 1
fi
mkdir -p "$GANGYAN_ROOT/backend"
cd "$BACKEND_SRC"
# 目标目录若被 root 生成会导致普通用户无法写入常见表现Error while storing the mojo status
# 这里提前检测并给出可执行修复命令。
if [ -d "$BACKEND_SRC/target" ] && [ ! -w "$BACKEND_SRC/target" ]; then
log_tee "错误: $BACKEND_SRC/target 不可写(可能之前用 root 打过包)。"
log_tee "请执行其一后重试:"
log_tee " 1) sudo chown -R $(id -un):$(id -gn) \"$BACKEND_SRC/target\""
log_tee " 2) sudo rm -rf \"$BACKEND_SRC/target\""
exit 1
fi
# Maven 自身 JVM内存过小会频繁 GC可用 export MAVEN_OPTS='...' 覆盖
export MAVEN_OPTS="${MAVEN_OPTS:--Xmx1536m -XX:+TieredCompilation -XX:TieredStopAtLevel=1}"
# 依赖已缓存在 ~/.m2 时: export MAVEN_OFFLINE=1不访问远程仓库失败则去掉再跑
MVN_OFFLINE=()
if [ "${MAVEN_OFFLINE:-}" = "1" ]; then
MVN_OFFLINE=(-o)
log_tee "MAVEN_OFFLINE=1使用 mvn -o缓存不全会失败去掉环境变量再跑一次"
fi
# -------- 远程仓库预检:中央仓库/镜像太慢或不可达则直接退出,避免 mvn 长时间卡在 Downloading --------
# 跳过预检: export SKIP_MAVEN_NETCHECK=1
# 测速地址(默认与 pom 首选源一致:华为云): export MAVEN_NETCHECK_URL='https://mirrors.tencent.com/nexus/...'
# 总耗时超过即视为过慢(秒): export MAVEN_NETCHECK_MAX_SEC=15
if [ "${MAVEN_OFFLINE:-}" != "1" ] && [ "${SKIP_MAVEN_NETCHECK:-}" != "1" ]; then
if command -v curl >/dev/null 2>&1; then
NET_URL="${MAVEN_NETCHECK_URL:-https://repo.huaweicloud.com/repository/maven/org/apache/maven/apache-maven/maven-metadata.xml}"
# 判定「过慢」的耗时上限curl 单次请求上限单独设大些,便于测出真实耗时
MAX_SEC="${MAVEN_NETCHECK_MAX_SEC:-15}"
CURL_MAX="${MAVEN_NETCHECK_CURL_MAX_SEC:-60}"
log_tee "Maven 仓库预检(样本下载总耗时 > ${MAX_SEC}s 则中止curl 上限 ${CURL_MAX}s: $NET_URL"
# shellcheck disable=SC2086
OUT=$(curl -sS -o /dev/null -w '%{http_code} %{time_total} %{speed_download} %{size_download}' \
--connect-timeout 10 --max-time "$CURL_MAX" "$NET_URL" 2>/dev/null) || OUT="000 999 0 0"
read -r CODE T_SEC SPD SZ <<< "$OUT"
CODE=${CODE:-000}
T_SEC=${T_SEC:-999}
SPD=${SPD:-0}
SZ=${SZ:-0}
if [ "$CODE" != "200" ]; then
log_tee "预检失败HTTP $CODE,无法稳定访问该仓库。"
log_tee "可选:① 配置 ~/.m2/settings.xml 镜像后设 MAVEN_NETCHECK_URL 指向镜像测速;② 依赖已在 ~/.m2 时 export MAVEN_OFFLINE=1 再打包;③ 仍要尝试 export SKIP_MAVEN_NETCHECK=1"
exit 1
fi
# 小文件平均速度易受握手影响,以「总耗时」为主;大文件再参考速度
if awk -v t="$T_SEC" -v m="$MAX_SEC" 'BEGIN { exit !(t + 0 > m + 0) }'; then
log_tee "预检失败:下载耗时 ${T_SEC}s超过 ${MAX_SEC}s当前渠道过慢"
log_tee "可选:① 换网络或配置国内镜像;② export MAVEN_OFFLINE=1仅用本地缓存③ export SKIP_MAVEN_NETCHECK=1 强制继续"
exit 1
fi
if awk -v sz="$SZ" -v spd="$SPD" 'BEGIN { exit !((sz + 0 > 8000) && (spd + 0 < 15000)) }'; then
log_tee "预检失败:约 ${SZ}B 的样本下载平均仅 ${SPD} B/s持续过慢。"
log_tee "可选配置镜像、MAVEN_OFFLINE=1 或 SKIP_MAVEN_NETCHECK=1同上"
exit 1
fi
log_tee "预检通过HTTP $CODE,耗时 ${T_SEC}s样本 ${SZ}B。"
else
log_tee "未找到 curl跳过仓库预检建议安装 curl或设置 SKIP_MAVEN_NETCHECK=1"
fi
elif [ "${MAVEN_OFFLINE:-}" = "1" ]; then
log_tee "已 MAVEN_OFFLINE=1跳过仓库网速预检。"
elif [ "${SKIP_MAVEN_NETCHECK:-}" = "1" ]; then
log_tee "已 SKIP_MAVEN_NETCHECK=1跳过仓库网速预检。"
fi
# 默认跳过测试源码编译与执行(比仅 -DskipTests 更快)。要编译测试: export MAVEN_SKIP_TEST_COMPILE=0
if [ "${MAVEN_SKIP_TEST_COMPILE:-1}" = "1" ]; then
TEST_SKIP=(-Dmaven.test.skip=true)
log_tee "MAVEN_SKIP_TEST_COMPILE=1使用 -Dmaven.test.skip=true不编译 test 源码)"
else
TEST_SKIP=(-DskipTests)
log_tee "MAVEN_SKIP_TEST_COMPILE=0仅 -DskipTests仍编译 test 源码)"
fi
# -T 1C多核并行构建单模块也有少量收益
# 无 javadoc/source 插件时跳过属性无害
# 依赖解析缓存“找不到”时可强制刷新export MAVEN_FORCE_UPDATE=1等价 mvn -U
MVN_UPDATE=()
if [ "${MAVEN_FORCE_UPDATE:-0}" = "1" ]; then
MVN_UPDATE=(-U)
log_tee "MAVEN_FORCE_UPDATE=1使用 mvn -U强制刷新依赖解析缓存"
fi
log_tee "执行: mvn ${MVN_OFFLINE[*]} ${MVN_UPDATE[*]} -T 1C ${TEST_SKIP[*]} -Dmaven.javadoc.skip=true -Dmaven.source.skip=true package"
set -o pipefail
mvn "${MVN_OFFLINE[@]}" "${MVN_UPDATE[@]}" -T 1C "${TEST_SKIP[@]}" -Dmaven.javadoc.skip=true -Dmaven.source.skip=true package 2>&1 | tee -a "$LOG_FILE"
if [ ! -f "$JAR_BUILD" ]; then
log_tee "错误: 未生成 $JAR_BUILD"
exit 1
fi
cp -f "$JAR_BUILD" "$JAR_RUN"
log_tee "已复制: $JAR_BUILD -> $JAR_RUN"
ls -lh "$JAR_RUN" | tee -a "$LOG_FILE"
log_tee "完成。需要重启进程请执行: ./backend-restart.sh"

58
scripts/backend-restart.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 重启 Java 后端 chat_web_yj.jar与 start_all.sh 一致)
# 日志gangyan/logs/backend.log
# 勿在此加 -Dspring.datasource*:与 application-local.yml 打架易连错库。
# 若 jar 未重打、内嵌 yml 仍是远程库,必须依赖 backend/application-local.yml含完整 spring 数据源)。
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
# 默认 backend.log若曾被 root 创建导致当前用户不可写,则改用同目录 backend-<用户>.log仍在 gangyan/logs 下)
LOG_FILE="$LOG_DIR/backend.log"
if ! ( umask 022; : >>"$LOG_FILE" ) 2>/dev/null; then
ALT="$LOG_DIR/backend-$(id -un).log"
if ( umask 022; : >>"$ALT" ) 2>/dev/null; then
LOG_FILE="$ALT"
log_tee "注意: $LOG_DIR/backend.log 不可写(多为 root 遗留),已改用 $LOG_FILE"
log_tee "修复后可统一回 backend.log: sudo chown $(id -un):$(id -gn) \"$LOG_DIR/backend.log\" 或 sudo rm -f \"$LOG_DIR/backend.log\""
else
mkdir -p "${HOME}/.gangyan/logs"
LOG_FILE="${HOME}/.gangyan/logs/backend-restart.log"
log_tee "注意: gangyan/logs 不可写Java 日志暂存: $LOG_FILE"
fi
fi
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH"
log_tee "======== 停止 chat_web_yj.jar ========"
pkill -f "chat_web_yj.jar" 2>/dev/null && log_tee "已发送停止信号" || log_tee "未找到运行中的进程"
sleep 2
log_tee "======== 启动 chat_web_yj.jar ========"
cd "$GANGYAN_ROOT/backend"
LOCAL_CFG="$GANGYAN_ROOT/backend/application-local.yml"
EXTRA_JAVA_ARGS=()
if [ -f "$LOCAL_CFG" ]; then
log_tee "加载本地配置: $LOCAL_CFG"
EXTRA_JAVA_ARGS=(-Dspring.config.additional-location="file:${LOCAL_CFG}")
fi
nohup java -jar \
-Xms512m -Xmx2048m \
"${EXTRA_JAVA_ARGS[@]}" \
-Dspring.profiles.active=yj \
chat_web_yj.jar >> "$LOG_FILE" 2>&1 &
STARTED_PID=$!
log_tee "已后台启动PID=$STARTED_PID"
log_tee "Java 标准输出/错误写入: $LOG_FILE"
sleep 3
if kill -0 "$STARTED_PID" 2>/dev/null; then
if command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q ':8099'; then
log_tee "自检: 进程存活且 8099 已监听(后端已就绪或即将就绪)"
else
log_tee "自检: 进程存活,但 8099 尚未监听(可能仍在启动;若 10 秒后仍无,请 tail 上面日志路径)"
fi
else
log_tee "自检失败: 进程已退出。请查看日志末尾:"
tail -n 40 "$LOG_FILE" 2>/dev/null | while IFS= read -r line || [ -n "$line" ]; do log_line "$line"; done
fi

13
scripts/common-restart.sh Executable file
View File

@@ -0,0 +1,13 @@
# 被各 *-restart.sh source勿单独执行
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GANGYAN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
LOG_DIR="$GANGYAN_ROOT/logs"
mkdir -p "$LOG_DIR"
log_line() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
log_tee() {
log_line "$@" | tee -a "$LOG_FILE"
}

29
scripts/frontend-restart.sh Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 重启前端 vitenpm run dev按 chat_web_project 使用的端口释放(默认读 .env 中 VITE_GLOB_FRONT_PORT
# 日志gangyan/logs/frontend.log
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/frontend.log"
FRONT_ENV="$GANGYAN_ROOT/chat_web_front/.env"
FRONT_PORT=3000
if [ -f "$FRONT_ENV" ]; then
_p=$(grep -E '^[[:space:]]*VITE_GLOB_FRONT_PORT=' "$FRONT_ENV" | head -1 | cut -d= -f2 | tr -d '\r"[:space:]')
[ -n "${_p:-}" ] && FRONT_PORT="$_p"
fi
log_tee "======== 停止前端(端口 $FRONT_PORT========"
for pid in $(lsof -ti :"$FRONT_PORT" -sTCP:LISTEN 2>/dev/null || true); do
kill -TERM "$pid" 2>/dev/null && log_tee "TERM pid=$pid" || true
done
sleep 2
for pid in $(lsof -ti :"$FRONT_PORT" -sTCP:LISTEN 2>/dev/null || true); do
kill -KILL "$pid" 2>/dev/null && log_tee "KILL pid=$pid" || true
done
log_tee "======== 启动 npm run dev ========"
cd "$GANGYAN_ROOT/chat_web_front"
nohup npm run dev >> "$LOG_FILE" 2>&1 &
log_tee "已后台启动PID=$! ,端口约 $FRONT_PORT ,日志: $LOG_FILE"

157
scripts/langchain-restart.sh Executable file
View File

@@ -0,0 +1,157 @@
#!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 重启 langchain-chatstartup.py --all-api与 start_all.sh 一致)
# 日志gangyan/logs/langchain-chat.log
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/langchain-chat.log"
TRIM_LOG="$LOG_DIR/log-trim.log"
CONDA_SH="/opt/software/miniconda3/etc/profile.d/conda.sh"
LC_ROOT="$GANGYAN_ROOT/langchain-chat"
# 与 configs/server_config.py 一致,用于旧进程未退出时按端口释放
LANGCHAIN_LISTEN_PORTS=(7861 20000 20101 20002)
_kill_by_listen_ports() {
local port pid
for port in "${LANGCHAIN_LISTEN_PORTS[@]}"; do
for pid in $(lsof -ti :"$port" -sTCP:LISTEN 2>/dev/null || true); do
kill -TERM "$pid" 2>/dev/null && log_tee "按端口 ${port} TERM PID=${pid}"
done
done
sleep 2
for port in "${LANGCHAIN_LISTEN_PORTS[@]}"; do
for pid in $(lsof -ti :"$port" -sTCP:LISTEN 2>/dev/null || true); do
kill -KILL "$pid" 2>/dev/null && log_tee "按端口 ${port} KILL PID=${pid}"
done
done
}
# 用 ss 判断端口是否在监听。仅用 lsof 会漏掉 root 监听的端口(非 root 看不到 PID
# 会误判为已释放并再次启动,导致 [Errno 98] address already in use。
_langchain_ports_still_listen() {
local port
for port in "${LANGCHAIN_LISTEN_PORTS[@]}"; do
if ss -tln 2>/dev/null | grep -qE ":${port}[[:space:]]"; then
return 0
fi
done
return 1
}
_sudo_fuser_kill_ports() {
command -v sudo >/dev/null 2>&1 || return 1
if ! sudo -n true 2>/dev/null; then
log_tee "提示: 相关端口仍被占用(可能是 root 启动的进程)。当前用户无法用 lsof 看到其 PID。"
log_tee "请在本机执行其一: sudo bash $0 或 sudo fuser -k 7861/tcp 20000/tcp 20101/tcp 20002/tcp"
return 1
fi
local port
for port in "${LANGCHAIN_LISTEN_PORTS[@]}"; do
if ss -tln 2>/dev/null | grep -qE ":${port}[[:space:]]"; then
sudo -n fuser -k "${port}/tcp" 2>/dev/null && log_tee "sudo fuser -k ${port}/tcp"
fi
done
sleep 2
return 0
}
_stop_langchain() {
log_tee "======== 停止 langchain-chat ========"
# 多种命令行形式(-a / --all-api、conda 路径、直接 python
for pat in \
"/opt/software/miniconda3/envs/langchain-chat/bin/python"'.''*'"startup.py" \
"conda run -n langchain-chat"'.''*'"startup.py" \
"python"'.''*'"startup.py --all-api" \
"python"'.''*'"startup.py -a"
do
if pkill -f "$pat" 2>/dev/null; then
log_tee "pkill 已匹配并发送信号: $pat"
fi
done
# cwd 在 langchain-chat 且命令行含 startup.py兜底
while read -r pid; do
[ -z "${pid:-}" ] && continue
[ ! -d "/proc/$pid" ] && continue
local cwd cmdl
cwd=$(readlink -f "/proc/$pid/cwd" 2>/dev/null || true)
cmdl=$(tr '\0' ' ' <"/proc/$pid/cmdline" 2>/dev/null || true)
if [[ "$cmdl" == *startup.py* ]] && [[ "$cwd" == "$LC_ROOT" ]]; then
kill -TERM "$pid" 2>/dev/null && log_tee "按 cwd+argv TERM PID=$pid"
fi
done < <(pgrep -f 'startup\.py' 2>/dev/null || true)
sleep 2
# pkill 未匹配到旧进程时,端口仍可能被旧实例占用(含 root 启动、非 root 无法用 lsof 杀)
if _langchain_ports_still_listen; then
log_tee "langchain 相关端口仍监听 (7861/20000/20101/20002),按 lsof 可见 PID 清理…"
_kill_by_listen_ports
else
log_tee "相关端口已释放,跳过端口强杀"
fi
if _langchain_ports_still_listen; then
log_tee "端口仍被占用,尝试免密 sudo fuser 清理…"
_sudo_fuser_kill_ports || true
fi
local _w=0
while _langchain_ports_still_listen && [ "$_w" -lt 30 ]; do
sleep 1
_w=$((_w + 1))
done
if _langchain_ports_still_listen; then
log_tee "错误: 7861/20000/20101/20002 仍有端口在监听,无法安全启动第二个实例。"
log_tee "请先释放端口后再执行本脚本(见上方 sudo 提示)。"
exit 1
fi
sleep 1
}
_stop_langchain
# 停止旧的日志裁剪守护(避免重复多开)
pkill -f "log-trim-daemon\.sh --file ${LOG_FILE}" 2>/dev/null || true
log_tee "======== 启动 langchain-chat (--all-api) ========"
if [ ! -f "$CONDA_SH" ]; then
log_tee "错误: 未找到 $CONDA_SH"
exit 1
fi
# shellcheck source=/dev/null
source "$CONDA_SH"
conda activate langchain-chat
cd "$LC_ROOT"
export PYTHONPATH="$LC_ROOT"
# PDF 预览与 kb_config.PDF_CONVERT_KB_ROOT 一致(地址归一化在 Python 内完成)
export PDF_CONVERT_KB_ROOT="${PDF_CONVERT_KB_ROOT:-$LC_ROOT/knowledge_base}"
# 明确使用可请求的地址(:- 无法覆盖已设置的 0.0.0.0,此处强制纠正)
if [[ "${PDF_CONVERT_API_URL:-}" == *0.0.0.0* ]] || [[ -z "${PDF_CONVERT_API_URL:-}" ]]; then
export PDF_CONVERT_API_URL="http://127.0.0.1:6006/convert/"
fi
# 避免旧 .pyc 仍含硬编码的 0.0.0.0:6006
rm -f "$LC_ROOT/configs/__pycache__/kb_config."*.pyc 2>/dev/null || true
rm -f "$LC_ROOT/server/knowledge_base/__pycache__/file_converter."*.pyc 2>/dev/null || true
rm -f "$LC_ROOT/server/knowledge_base/__pycache__/cleanpdf."*.pyc 2>/dev/null || true
rm -f "$LC_ROOT/server/knowledge_base/__pycache__/pdf_convert_url."*.pyc 2>/dev/null || true
# 控制日志文件总大小(保留最后 5MB避免 langchain-chat.log 无限增长)
nohup bash "$GANGYAN_ROOT/scripts/log-trim-daemon.sh" --file "$LOG_FILE" --max-mb 5 --interval-sec 3 >> "$TRIM_LOG" 2>&1 &
log_tee "已启动日志裁剪守护,日志: $TRIM_LOG"
nohup python startup.py --all-api >> "$LOG_FILE" 2>&1 &
log_tee "已后台启动PID=$! API 约 7861 ,日志: $LOG_FILE"
# 本地 PDF 预览依赖的转换微服务(:6006未监听时再启动
if ! ss -tln 2>/dev/null | grep -qE ':6006[[:space:]]'; then
bash "$GANGYAN_ROOT/scripts/pdf-convert-service.sh" >> "$LOG_DIR/pdf-convert-service.log" 2>&1 || log_tee "提示: pdf-convert-service 启动失败,见 $LOG_DIR/pdf-convert-service.log"
else
log_tee "pdf-convert-service 端口 6006 已在监听,跳过"
fi

59
scripts/log-trim-daemon.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Keep a log file capped by trimming from the front.
# Usage:
# ./log-trim-daemon.sh --file /path/to/app.log --max-mb 5 --interval-sec 3
set -euo pipefail
FILE=""
MAX_MB=5
INTERVAL_SEC=3
while [ $# -gt 0 ]; do
case "$1" in
--file) FILE="${2:-}"; shift 2 ;;
--max-mb) MAX_MB="${2:-}"; shift 2 ;;
--interval-sec) INTERVAL_SEC="${2:-}"; shift 2 ;;
-h|--help)
echo "Usage: $0 --file PATH [--max-mb 5] [--interval-sec 3]"
exit 0
;;
*)
echo "Unknown arg: $1" >&2
exit 2
;;
esac
done
if [ -z "${FILE}" ]; then
echo "Missing --file" >&2
exit 2
fi
MAX_BYTES=$((MAX_MB * 1024 * 1024))
TMP="${FILE}.trimtmp.$$"
LOCK="${FILE}.trimlock"
mkdir -p "$(dirname "$FILE")"
touch "$FILE" 2>/dev/null || true
while true; do
if [ -f "$FILE" ]; then
size=$(stat -c%s "$FILE" 2>/dev/null || echo 0)
if [ "${size:-0}" -gt "$MAX_BYTES" ]; then
# Keep only the last MAX_BYTES bytes.
# IMPORTANT: do NOT replace the file inode (mv),否则 tail -f/编辑器可能还在看旧 inode
# 会出现“日志不更新/看不到最新内容”的错觉。这里用 copy+truncate 保持 inode 不变。
(
flock -w 2 9 || exit 0
tail -c "$MAX_BYTES" "$FILE" > "$TMP" 2>/dev/null || true
# 覆盖写回同一文件inode 不变)
if [ -s "$TMP" ]; then
cat "$TMP" > "$FILE" 2>/dev/null || true
fi
rm -f "$TMP" 2>/dev/null || true
) 9>"$LOCK" || true
fi
fi
sleep "$INTERVAL_SEC"
done

14
scripts/milvus-restart.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 重启 Milvusdocker compose 目录gangyan/milvus
# 日志gangyan/logs/milvus.log
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/milvus.log"
log_tee "======== 重启 Milvus ========"
cd "$GANGYAN_ROOT/milvus"
docker compose restart 2>&1 | tee -a "$LOG_FILE" || docker compose up -d 2>&1 | tee -a "$LOG_FILE"
log_tee "完成。docker compose ps ->"
docker compose ps 2>&1 | tee -a "$LOG_FILE"

14
scripts/mysql-restart.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 重启 MySQL 8.4docker compose 目录gangyan/mysql/mysql-8.4.4
# 日志gangyan/logs/mysql.log
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/mysql.log"
log_tee "======== 重启 MySQL ========"
cd "$GANGYAN_ROOT/mysql/mysql-8.4.4"
docker compose restart 2>&1 | tee -a "$LOG_FILE" || docker compose up -d 2>&1 | tee -a "$LOG_FILE"
log_tee "完成。docker compose ps ->"
docker compose ps 2>&1 | tee -a "$LOG_FILE"

49
scripts/pdf-convert-service.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# 本地 PDF→Markdown 微服务(与 langchain file_converter.pdf_to_html 约定一致,默认 :6006
# 依赖 conda 环境 langchain-chat含 fastapi、uvicorn、PyMuPDF
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/pdf-convert-service.log"
LC_ROOT="$GANGYAN_ROOT/langchain-chat"
CONDA_SH="/opt/software/miniconda3/etc/profile.d/conda.sh"
log_line() {
local s="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$s" >> "$LOG_FILE"
echo "$s"
}
log_line "======== 停止 pdf-convert-service (6006) ========"
pkill -f "uvicorn.*pdf_convert_service\.app:app" 2>/dev/null || true
sleep 1
if command -v lsof >/dev/null 2>&1; then
for pid in $(lsof -ti :6006 -sTCP:LISTEN 2>/dev/null || true); do
kill -TERM "$pid" 2>/dev/null && log_line "TERM pid=$pid :6006"
done
sleep 1
for pid in $(lsof -ti :6006 -sTCP:LISTEN 2>/dev/null || true); do
kill -KILL "$pid" 2>/dev/null && log_line "KILL pid=$pid :6006"
done
fi
log_line "======== 启动 pdf-convert-service ========"
if [ ! -f "$CONDA_SH" ]; then
log_line "错误: 未找到 $CONDA_SH"
exit 1
fi
# shellcheck source=/dev/null
source "$CONDA_SH"
conda activate langchain-chat
cd "$LC_ROOT"
export PYTHONPATH="$LC_ROOT"
export PDF_CONVERT_KB_ROOT="${PDF_CONVERT_KB_ROOT:-$LC_ROOT/knowledge_base}"
export PDF_CONVERT_HOST="${PDF_CONVERT_HOST:-127.0.0.1}"
export PDF_CONVERT_PORT="${PDF_CONVERT_PORT:-6006}"
nohup python -m uvicorn pdf_convert_service.app:app \
--host "$PDF_CONVERT_HOST" --port "$PDF_CONVERT_PORT" \
>> "$LOG_FILE" 2>&1 &
log_line "已后台启动 PID=$! 监听 ${PDF_CONVERT_HOST}:${PDF_CONVERT_PORT}"
log_line "日志: $LOG_FILE KB_ROOT=$PDF_CONVERT_KB_ROOT"
log_line "健康检查: curl -sS http://${PDF_CONVERT_HOST}:${PDF_CONVERT_PORT}/health"

18
scripts/redis-restart.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"}
# 重启 Redis 容器 redis-server若不存在则创建与 start_all.sh 一致)
# 日志gangyan/logs/redis.log
set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh"
LOG_FILE="$LOG_DIR/redis.log"
log_tee "======== 重启 Redis ========"
if docker ps -a --format '{{.Names}}' | grep -qx 'redis-server'; then
docker restart redis-server 2>&1 | tee -a "$LOG_FILE"
else
log_tee "容器不存在,尝试创建..."
docker run -d --name redis-server -p 6379:6379 redis:7-alpine 2>&1 | tee -a "$LOG_FILE"
fi
log_tee "完成。docker ps redis-server ->"
docker ps --filter name=redis-server 2>&1 | tee -a "$LOG_FILE"