fix: fallback to CentOS 7 single executable on start failure

This commit is contained in:
shiyue
2026-08-23 12:59:32 +08:00
parent 17a924fd47
commit 265199a48e
4 changed files with 278 additions and 29 deletions

View File

@@ -173,6 +173,8 @@ cd cc-web
- 自动安装 PM2
- 使用 `npm ci``npm install` 安装依赖
-`ccweb` 为默认 PM2 应用名启动或重启服务
- 启动前自动释放配置端口(默认 `8002`
- 在 CentOS 7 上如果源码方式启动失败,自动回退到 `dist-exe/bun-linux-x64-baseline/cc-web`
临时前台运行:
@@ -211,9 +213,15 @@ dist-exe/bun-linux-x64-baseline/
```bash
cd /opt/cc-web
chmod +x cc-web
PORT=8002 CC_WEB_PASSWORD='请改成强密码' ./cc-web
PORT=8002 ./cc-web
```
如果项目根目录同时包含 `start.sh``dist-exe/`,直接执行 `./start.sh` 即可。
脚本默认先按原有 Node/PM2 方式启动;仅当源码方式启动失败且系统识别为 CentOS 7
时,才自动切换到 `dist-exe/bun-linux-x64-baseline/cc-web`。启动前会先释放监听端口,
默认端口为 `8002`。已有 `config/auth.json` 时会直接复用,不会强制要求重新设置
`CC_WEB_PASSWORD`
这个发布包只包含 cc-web 服务本体和前端资源,**不会把 Claude/Codex CLI 打进包里**。
运行时仍调用宿主机上的 CLI

View File

@@ -80,3 +80,11 @@
- 2026-08-23 重建成功:目录二进制 SHA-256 为 `28f2c2b99ef66cb523feef171c8c2438071e724629fed44f348734b982d4a4e7`tar.gz SHA-256 为 `d8a9de5813a6f4c8b27f05565926faa9267e9070df4241387568169d97ce5cbe`
- `tar -xOf` 提取归档内的 `bun-linux-x64-baseline/cc-web` 后,其 SHA-256 与目录二进制完全一致;包内 `package.json` 版本为 `1.2.11`
-`./dist-exe/bun-linux-x64-baseline/cc-web` 直接启动本机等价验证通过:端口 18082 可访问,启动日志包含 `CC-Web server listening`,运行进程 `/proc/<pid>/exe` 指向该发布目录且摘要一致。
## 2026-08-23 start.sh 启动回退修复
- `start.sh` 默认仍走原有 Node/PM2 源码启动;只在 `CC_WEB_START_MODE=auto`、源码启动失败且系统识别为 CentOS 7 时回退。
- 启动前会释放解析出的监听端口,默认端口为 8002源码路径和 single-exe 回退路径都会再次确认端口已释放。
- single-exe 通过 `CC_WEB_APP_DIR` 指向项目根目录,复用根目录 `config/auth.json`、sessions/logs 和 `.env`,不会强制设置 `CC_WEB_PASSWORD`
- 回退前会比较归档内二进制 SHA-256发现 `dist-exe/bun-linux-x64-baseline/cc-web` 过旧时自动从 `dist-exe/cc-web-bun-linux-x64-baseline.tar.gz` 刷新。
- 回归验证覆盖:源码/PM2 端口 18084 启动、single-exe 复用 auth.json、占用端口自动关闭后 single-exe 启动;均通过。

View File

@@ -29,3 +29,10 @@
- 已清理临时 `CentOS7重新打包 TO DO list.csv`;目标机部署仍需将新归档解压到目标目录并停止旧进程后启动新目录二进制。
- 发布技能规定的 `node --check` 六项检查、发布二进制 MCP `initialize` smoke 和 tar 清单检查均通过。
- 当前工作区仅发布归档、规划发现与进度日志有变更;未修改业务源码或构建脚本。
## 2026-08-23 start.sh 启动回退修复
- 保留原有 Node/PM2 为默认启动方式;新增启动前端口清理,默认处理 8002。
- 新增 CentOS 7 自动回退:源码启动失败时自动校验/刷新 `dist-exe` baseline 单文件并用 nohup 启动。
- 回退通过 `CC_WEB_APP_DIR` 复用现有 `config/auth.json`,未要求重新配置 `CC_WEB_PASSWORD`
- 已完成 shell 语法检查、源码 PM2 启动回归、single-exe auth 回归和占用端口清理回归。

282
start.sh
View File

@@ -9,6 +9,12 @@ APP_DIR="${SCRIPT_DIR}"
ENTRY_PATH="${APP_DIR}/${ENTRY_FILE}"
ENV_FILE="${APP_DIR}/.env"
ENV_EXAMPLE_FILE="${APP_DIR}/.env.example"
START_MODE="${CC_WEB_START_MODE:-auto}"
RELEASE_TARGET="bun-linux-x64-baseline"
RELEASE_DIR="${CC_WEB_RELEASE_DIR:-${APP_DIR}/dist-exe/${RELEASE_TARGET}}"
RELEASE_ENTRY_PATH="${RELEASE_DIR}/cc-web"
RELEASE_ARCHIVE="${CC_WEB_RELEASE_ARCHIVE:-${APP_DIR}/dist-exe/cc-web-${RELEASE_TARGET}.tar.gz}"
PORT_VALUE=""
log() {
printf '[cc-web pm2] %s\n' "$*"
@@ -23,6 +29,84 @@ warn() {
printf '[cc-web pm2] WARNING: %s\n' "$*" >&2
}
is_centos7() {
local release_file
for release_file in /etc/centos-release /etc/redhat-release; do
if [[ -r "${release_file}" ]] && grep -Eqi 'centos.*release[[:space:]]+7' "${release_file}"; then
return 0
fi
done
return 1
}
resolve_port() {
local configured="${PORT:-$(get_env_value PORT)}"
if [[ ! "${configured}" =~ ^[0-9]+$ ]] || ((configured < 1 || configured > 65535)); then
configured=8002
fi
printf '%s\n' "${configured}"
}
port_is_open() {
local port="$1"
( : >/dev/tcp/127.0.0.1/"${port}" ) 2>/dev/null
}
wait_for_port() {
local port="$1"
local attempts="${2:-20}"
local index
for ((index = 0; index < attempts; index += 1)); do
if port_is_open "${port}"; then
return 0
fi
sleep 0.5
done
return 1
}
stop_port_listener() {
local port="$1"
local pids=""
if ! port_is_open "${port}"; then
log "端口 ${port} 当前未被占用"
return 0
fi
if command -v fuser >/dev/null 2>&1; then
pids="$(fuser -n tcp "${port}" 2>/dev/null || true)"
log "关闭端口 ${port},占用进程: ${pids:-未知}"
fuser -k -TERM -n tcp "${port}" >/dev/null 2>&1 || true
elif command -v lsof >/dev/null 2>&1; then
pids="$(lsof -t -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null || true)"
log "关闭端口 ${port},占用进程: ${pids:-未知}"
for pid in ${pids}; do
kill -TERM "${pid}" 2>/dev/null || true
done
else
warn "端口 ${port} 被占用,但系统没有 fuser/lsof无法自动关闭"
return 1
fi
for _ in {1..10}; do
if ! port_is_open "${port}"; then
return 0
fi
sleep 0.3
done
warn "端口 ${port} 未能在宽限期内释放,尝试强制关闭"
if command -v fuser >/dev/null 2>&1; then
fuser -k -KILL -n tcp "${port}" >/dev/null 2>&1 || true
else
for pid in ${pids}; do
kill -KILL "${pid}" 2>/dev/null || true
done
fi
return 0
}
node_upgrade_hint() {
cat <<'EOF'
可选处理方式:
@@ -42,12 +126,11 @@ node_upgrade_hint() {
node -v
# CentOS 7 / glibc 2.17 不适合安装 NodeSource Node.js 22。
# 推荐在较新的构建机生成 Bun baseline 单文件发布包,再拷贝到 CentOS 7 直接运行
# 推荐在较新的构建机生成 Bun baseline 单文件发布包,再拷贝到 CentOS 7
npm install
npm run build:single-exe
# 拷贝 dist-exe/bun-linux-x64-baseline/ 到 CentOS 7 后
cd dist-exe/bun-linux-x64-baseline
PORT=8002 CC_WEB_PASSWORD='请改成强密码' ./cc-web
# 在项目根目录执行 ./start.sh 时,会自动优先尝试源码;源码失败后自动回退到
# dist-exe/bun-linux-x64-baseline/cc-web
EOF
}
@@ -260,12 +343,14 @@ install_pm2() {
if command -v sudo >/dev/null 2>&1; then
log "普通 npm 全局安装失败,尝试使用 sudo 安装 PM2..."
sudo npm install -g pm2
log "PM2 安装完成"
return
if sudo npm install -g pm2; then
log "PM2 安装完成"
return
fi
fi
fail "PM2 自动安装失败,且当前环境没有 sudo。请先手动安装: npm install -g pm2"
warn "PM2 自动安装失败"
return 1
}
install_dependencies() {
@@ -273,11 +358,18 @@ install_dependencies() {
if [[ -f package-lock.json ]]; then
log "安装项目依赖: npm ci"
npm ci
if npm ci; then
return 0
fi
else
log "安装项目依赖: npm install"
npm install
if npm install; then
return 0
fi
fi
warn "项目依赖安装失败"
return 1
}
start_or_restart_app() {
@@ -285,17 +377,140 @@ start_or_restart_app() {
if pm2 describe "${APP_NAME}" >/dev/null 2>&1; then
log "PM2 应用已存在,执行重启: ${APP_NAME}"
pm2 restart "${APP_NAME}" --update-env
if ! pm2 restart "${APP_NAME}" --update-env; then
warn "PM2 重启失败: ${APP_NAME}"
return 1
fi
else
log "启动 PM2 应用: ${APP_NAME}"
pm2 start "${ENTRY_PATH}" --name "${APP_NAME}" --cwd "${APP_DIR}"
if ! pm2 start "${ENTRY_PATH}" --name "${APP_NAME}" --cwd "${APP_DIR}"; then
warn "PM2 启动失败: ${APP_NAME}"
return 1
fi
fi
log "保存 PM2 进程快照"
pm2 save
if ! pm2 save; then
warn "PM2 进程快照保存失败"
return 1
fi
log "当前 PM2 状态"
pm2 status "${APP_NAME}"
if ! pm2 status "${APP_NAME}"; then
warn "PM2 状态检查失败: ${APP_NAME}"
return 1
fi
if ! wait_for_port "${PORT_VALUE}" 20; then
warn "源码服务未在端口 ${PORT_VALUE} 上监听"
return 1
fi
}
stop_existing_pm2_app() {
if command -v pm2 >/dev/null 2>&1 && pm2 describe "${APP_NAME}" >/dev/null 2>&1; then
log "停止失败的 PM2 应用: ${APP_NAME}"
pm2 delete "${APP_NAME}" >/dev/null 2>&1 || true
fi
}
refresh_release_from_archive() {
[[ -f "${RELEASE_ARCHIVE}" ]] || return 0
mkdir -p "${APP_DIR}/dist-exe"
if [[ -x "${RELEASE_ENTRY_PATH}" ]] && command -v sha256sum >/dev/null 2>&1; then
local current_hash archive_hash
current_hash="$(sha256sum "${RELEASE_ENTRY_PATH}" | awk '{print $1}')"
archive_hash="$(tar -xOf "${RELEASE_ARCHIVE}" "${RELEASE_TARGET}/cc-web" 2>/dev/null | sha256sum | awk '{print $1}')"
if [[ -n "${archive_hash}" && "${current_hash}" == "${archive_hash}" ]]; then
return 0
fi
log "检测到 single-exe 发布目录不是归档中的最新版本,自动刷新"
else
log "发布目录缺少 single-exe自动从归档解压"
fi
if ! tar -xzf "${RELEASE_ARCHIVE}" -C "${APP_DIR}/dist-exe"; then
warn "无法解压发布归档: ${RELEASE_ARCHIVE}"
return 1
fi
chmod +x "${RELEASE_ENTRY_PATH}" 2>/dev/null || true
}
ensure_release_binary() {
refresh_release_from_archive || return 1
if [[ ! -f "${RELEASE_ENTRY_PATH}" ]]; then
warn "找不到 CentOS 7 single-exe: ${RELEASE_ENTRY_PATH}"
return 1
fi
chmod +x "${RELEASE_ENTRY_PATH}" 2>/dev/null || true
[[ -x "${RELEASE_ENTRY_PATH}" ]]
}
start_single_exe() {
ensure_release_binary || return 1
stop_port_listener "${PORT_VALUE}" || true
local runtime_log_dir="${CC_WEB_LOGS_DIR:-${RELEASE_DIR}/logs}"
local output_log="${runtime_log_dir}/cc-web-single-exe.out"
local pid_file="${runtime_log_dir}/cc-web-single-exe.pid"
mkdir -p "${runtime_log_dir}"
log "启动 CentOS 7 single-exe: ${RELEASE_ENTRY_PATH}"
(
cd "${RELEASE_DIR}"
exec nohup env \
CC_WEB_APP_DIR="${APP_DIR}" \
CC_WEB_PUBLIC_DIR="${RELEASE_DIR}/public" \
PORT="${PORT_VALUE}" \
"${RELEASE_ENTRY_PATH}"
) >"${output_log}" 2>&1 &
local pid=$!
printf '%s\n' "${pid}" > "${pid_file}"
if ! wait_for_port "${PORT_VALUE}" 20; then
warn "single-exe 未能在端口 ${PORT_VALUE} 上监听,详见: ${output_log}"
kill "${pid}" 2>/dev/null || true
wait "${pid}" 2>/dev/null || true
return 1
fi
log "single-exe 已启动PID=${pid},日志: ${output_log}"
return 0
}
try_source_start() {
if [[ ! -f "${ENTRY_PATH}" ]]; then
warn "找不到源码入口: ${ENTRY_PATH}"
return 1
fi
if ! command -v node >/dev/null 2>&1; then
warn "未检测到 Node.js跳过源码启动"
return 1
fi
if ! command -v npm >/dev/null 2>&1; then
warn "未检测到 npm跳过源码启动"
return 1
fi
local node_major
if ! node_major="$(node -p "Number(process.versions.node.split('.')[0])")"; then
warn "Node.js 无法执行,跳过源码启动"
return 1
fi
if [[ "${node_major}" -lt 18 ]]; then
warn "Node.js 版本过低($(node -v)),跳过源码启动"
return 1
fi
check_runtime_config
install_pm2 || return 1
install_dependencies || return 1
start_or_restart_app || return 1
return 0
}
main() {
@@ -303,27 +518,38 @@ main() {
warn "当前正在使用 root 用户执行。该服务会启动 Claude/Codex 子进程root 运行风险较高,建议改用非 root 用户。"
fi
[[ -f "${ENTRY_PATH}" ]] || fail "找不到入口文件: ${ENTRY_PATH}"
PORT_VALUE="$(resolve_port)"
stop_port_listener "${PORT_VALUE}" || true
ensure_command node "请先安装 Node.js 18 或更高版本。
case "${START_MODE}" in
single|release|bun)
start_single_exe || fail "single-exe 启动失败,请检查 ${RELEASE_ENTRY_PATH}"
log "完成。当前使用 CentOS 7 single-exe。"
return
;;
auto|source|pm2)
;;
*)
fail "不支持的 CC_WEB_START_MODE: ${START_MODE}(可选 auto/source/single"
;;
esac
$(node_upgrade_hint)"
ensure_command npm "请先安装 npm。"
if try_source_start; then
log "完成。若需要开机自启,请执行 pm2 startup并按输出提示执行生成的命令。"
return
fi
local node_major
node_major="$(node -p "Number(process.versions.node.split('.')[0])")"
if [[ "${node_major}" -lt 18 ]]; then
fail "Node.js 版本过低,当前为 $(node -v),要求 >= 18。
if [[ "${START_MODE}" != "auto" ]] || ! is_centos7; then
fail "源码方式启动失败。Node.js 18+、npm、PM2、依赖和端口 ${PORT_VALUE} 均需可用。
$(node_upgrade_hint)"
fi
check_runtime_config
install_pm2
install_dependencies
start_or_restart_app
log "完成。若需要开机自启,请执行 pm2 startup并按输出提示执行生成的命令。"
warn "源码方式启动失败,检测到 CentOS 7回退到 dist-exe single-exe"
stop_existing_pm2_app
stop_port_listener "${PORT_VALUE}" || true
start_single_exe || fail "CentOS 7 single-exe 启动失败,请检查 ${RELEASE_ENTRY_PATH} 及日志"
log "完成。当前使用 CentOS 7 single-exe。"
}
main "$@"