fix: fallback to CentOS 7 single executable on start failure
This commit is contained in:
282
start.sh
282
start.sh
@@ -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 "$@"
|
||||
|
||||
Reference in New Issue
Block a user