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

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 "$@"