Files
cc-web/start.sh
2026-06-24 10:36:03 +08:00

330 lines
7.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -Eeuo pipefail
APP_NAME="${CC_WEB_PM2_NAME:-ccweb}"
ENTRY_FILE="${CC_WEB_ENTRY:-server.js}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="${SCRIPT_DIR}"
ENTRY_PATH="${APP_DIR}/${ENTRY_FILE}"
ENV_FILE="${APP_DIR}/.env"
ENV_EXAMPLE_FILE="${APP_DIR}/.env.example"
log() {
printf '[cc-web pm2] %s\n' "$*"
}
fail() {
printf '[cc-web pm2] ERROR: %s\n' "$*" >&2
exit 1
}
warn() {
printf '[cc-web pm2] WARNING: %s\n' "$*" >&2
}
node_upgrade_hint() {
cat <<'EOF'
可选处理方式:
# 已安装 nvm 时,使用当前 LTS 版本
nvm install --lts
nvm use --lts
node -v
# Ubuntu / Debian / WSL可固定安装 Node.js 22root 用户可去掉 sudo
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v
# RHEL / Rocky / AlmaLinux 8/9可固定安装 Node.js 22root 用户可去掉 sudo
curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash -
sudo dnf install -y nodejs || sudo yum install -y nodejs
node -v
# CentOS 7 / glibc 2.17 不适合安装 NodeSource Node.js 22。
# 推荐在较新的构建机生成 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
EOF
}
ensure_command() {
local cmd="$1"
local hint="$2"
if ! command -v "${cmd}" >/dev/null 2>&1; then
fail "${cmd} 未安装。${hint}"
fi
}
get_env_value() {
local key="$1"
[[ -f "${ENV_FILE}" ]] || return 0
awk -v key="${key}" '
$0 ~ /^[[:space:]]*#/ { next }
{
line = $0
sub(/^[[:space:]]*/, "", line)
if (index(line, key "=") == 1) {
sub(/^[^=]*=/, "", line)
print line
exit
}
}
' "${ENV_FILE}"
}
set_env_value() {
local key="$1"
local value="$2"
local tmp_file
tmp_file="$(mktemp)"
if [[ -f "${ENV_FILE}" ]]; then
awk -v key="${key}" -v value="${value}" '
BEGIN { updated = 0 }
{
line = $0
sub(/^[[:space:]]*/, "", line)
if (index(line, key "=") == 1) {
print key "=" value
updated = 1
next
}
print
}
END {
if (!updated) print key "=" value
}
' "${ENV_FILE}" > "${tmp_file}"
else
printf '%s=%s\n' "${key}" "${value}" > "${tmp_file}"
fi
mv "${tmp_file}" "${ENV_FILE}"
chmod 600 "${ENV_FILE}"
}
validate_password() {
local password="$1"
local types=0
if [[ "${#password}" -lt 8 ]]; then
printf '密码长度至少 8 位\n'
return 1
fi
[[ "${password}" =~ [a-z] ]] && types=$((types + 1))
[[ "${password}" =~ [A-Z] ]] && types=$((types + 1))
[[ "${password}" =~ [0-9] ]] && types=$((types + 1))
[[ "${password}" =~ [^a-zA-Z0-9] ]] && types=$((types + 1))
if [[ "${types}" -lt 2 ]]; then
printf '密码需包含至少 2 种字符类型(大写/小写/数字/特殊字符)\n'
return 1
fi
return 0
}
resolve_config_dir() {
local configured
configured="${CC_WEB_CONFIG_DIR:-$(get_env_value CC_WEB_CONFIG_DIR)}"
if [[ -n "${configured}" ]]; then
if [[ "${configured}" = /* ]]; then
printf '%s\n' "${configured}"
else
printf '%s/%s\n' "${APP_DIR}" "${configured}"
fi
return
fi
printf '%s/config\n' "${APP_DIR}"
}
ensure_env_file() {
if [[ -f "${ENV_FILE}" ]]; then
log "检测到 .env 配置文件"
chmod 600 "${ENV_FILE}"
return
fi
if [[ -f "${ENV_EXAMPLE_FILE}" ]]; then
cp "${ENV_EXAMPLE_FILE}" "${ENV_FILE}"
chmod 600 "${ENV_FILE}"
log "已从 .env.example 创建 .env"
else
: > "${ENV_FILE}"
chmod 600 "${ENV_FILE}"
log "已创建空 .env"
fi
}
ensure_port_config() {
local port
port="$(get_env_value PORT)"
if [[ -z "${port}" ]]; then
set_env_value PORT "8002"
log "未配置 PORT已写入默认端口 8002"
return
fi
log "服务端口: ${port}"
}
ensure_initial_password() {
local config_dir auth_file env_password password confirm message
config_dir="$(resolve_config_dir)"
auth_file="${config_dir}/auth.json"
if [[ -f "${auth_file}" ]]; then
log "检测到已有登录配置: ${auth_file},跳过初始密码设置"
return
fi
env_password="$(get_env_value CC_WEB_PASSWORD)"
if [[ -n "${env_password}" && "${env_password}" != "changeme" ]]; then
log "检测到 .env 已配置 CC_WEB_PASSWORD首次启动时会迁移到 auth.json"
return
fi
if [[ ! -t 0 ]]; then
fail "首次启动缺少初始密码。请在 .env 中设置 CC_WEB_PASSWORD或在交互终端执行 ./start.sh。"
fi
log "首次启动需要设置 Web 登录初始密码"
while true; do
read -r -s -p "请输入初始密码: " password
printf '\n'
read -r -s -p "请再次输入初始密码: " confirm
printf '\n'
if [[ "${password}" != "${confirm}" ]]; then
log "两次输入不一致,请重新输入"
continue
fi
if ! message="$(validate_password "${password}")"; then
log "${message}"
continue
fi
set_env_value CC_WEB_PASSWORD "${password}"
log "初始密码已写入 .env服务首次启动后会迁移到 config/auth.json"
break
done
}
check_agent_cli() {
local key="$1"
local default_cmd="$2"
local label="$3"
local configured
configured="$(get_env_value "${key}")"
configured="${configured:-${default_cmd}}"
if command -v "${configured}" >/dev/null 2>&1; then
log "${label} CLI: $(command -v "${configured}")"
return
fi
log "未检测到 ${label} CLI (${configured});服务可启动,但对应 Agent 功能需要安装并登录后才能使用"
}
check_runtime_config() {
ensure_env_file
ensure_port_config
ensure_initial_password
check_agent_cli CLAUDE_PATH claude "Claude"
check_agent_cli CODEX_PATH codex "Codex"
}
install_pm2() {
if command -v pm2 >/dev/null 2>&1; then
log "PM2 已安装: $(command -v pm2)"
return
fi
log "未检测到 PM2开始安装..."
if npm install -g pm2; then
log "PM2 安装完成"
return
fi
if command -v sudo >/dev/null 2>&1; then
log "普通 npm 全局安装失败,尝试使用 sudo 安装 PM2..."
sudo npm install -g pm2
log "PM2 安装完成"
return
fi
fail "PM2 自动安装失败,且当前环境没有 sudo。请先手动安装: npm install -g pm2"
}
install_dependencies() {
cd "${APP_DIR}"
if [[ -f package-lock.json ]]; then
log "安装项目依赖: npm ci"
npm ci
else
log "安装项目依赖: npm install"
npm install
fi
}
start_or_restart_app() {
cd "${APP_DIR}"
if pm2 describe "${APP_NAME}" >/dev/null 2>&1; then
log "PM2 应用已存在,执行重启: ${APP_NAME}"
pm2 restart "${APP_NAME}" --update-env
else
log "启动 PM2 应用: ${APP_NAME}"
pm2 start "${ENTRY_PATH}" --name "${APP_NAME}" --cwd "${APP_DIR}"
fi
log "保存 PM2 进程快照"
pm2 save
log "当前 PM2 状态"
pm2 status "${APP_NAME}"
}
main() {
if [[ "$(id -u)" -eq 0 ]]; then
warn "当前正在使用 root 用户执行。该服务会启动 Claude/Codex 子进程root 运行风险较高,建议改用非 root 用户。"
fi
[[ -f "${ENTRY_PATH}" ]] || fail "找不到入口文件: ${ENTRY_PATH}"
ensure_command node "请先安装 Node.js 18 或更高版本。
$(node_upgrade_hint)"
ensure_command npm "请先安装 npm。"
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。
$(node_upgrade_hint)"
fi
check_runtime_config
install_pm2
install_dependencies
start_or_restart_app
log "完成。若需要开机自启,请执行 pm2 startup并按输出提示执行生成的命令。"
}
main "$@"