diff --git a/Dockerfile b/Dockerfile index 21cd9c9..af6f291 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,8 +6,8 @@ WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --production=false -# 2. 复制共享库 -COPY ../shared/ ./shared/ +# 2. 复制共享库(构建前由部署脚本创建 symlink: shared -> ../shared) +COPY shared/ ./shared/ # 3. 复制源码并构建 COPY . . diff --git a/scripts/deploy-monitor.sh b/scripts/deploy-monitor.sh index a578c7e..56a41bf 100755 --- a/scripts/deploy-monitor.sh +++ b/scripts/deploy-monitor.sh @@ -2,9 +2,8 @@ # monitor-ai 部署脚本 # # 用法: -# bash deploy-monitor.sh # 本地模式(默认) +# bash deploy-monitor.sh # 部署到 txjp 服务器(默认) # bash deploy-monitor.sh --dev # 开发模式(本地构建) -# bash deploy-monitor.sh --remote --host # 远程部署到指定服务器 # # 首次部署自动: # 1. 生成 OIDC_CLIENT_SECRET (openssl rand -hex 32) @@ -42,20 +41,9 @@ fi # ============================================================ # 解析参数 # ============================================================ -MODE="local" -HOST="" +MODE="remote" while [[ $# -gt 0 ]]; do case "$1" in - --remote) - MODE="remote" - info "模式: 远程部署" - shift - ;; - --host) - HOST="$2" - info "目标主机: $HOST" - shift 2 - ;; --dev) MODE="dev" info "模式: 开发构建" @@ -69,7 +57,7 @@ while [[ $# -gt 0 ]]; do done # ============================================================ -# 路径配置 +# 路径配置(对齐 deploy-ai.sh,使用 SSH 别名 txjp) # ============================================================ LOCAL_DIR="/Users/niuniu/programs/docker" MONITOR_DIR="$LOCAL_DIR/monitor-ai" @@ -79,13 +67,6 @@ CONTAINER="monitor-ai" SSH_TARGET="txjp" SNAPSHOT_FILE="/tmp/.snapshot.monitor.md5" -# 远程模式需要 host 参数 -if [[ "$MODE" == "remote" && -z "$HOST" ]]; then - error "远程部署需要指定 --host " - echo " 用法: bash deploy-monitor.sh --remote --host 43.133.38.210" - exit 1 -fi - # ============================================================ # 打包源码 # ============================================================ @@ -116,28 +97,10 @@ package_source() { # 首次部署:生成密钥和配置 # ============================================================ first_deploy_setup() { - local target="$1" # SSH target or "local" - local ssh_cmd="" - if [[ "$target" != "local" ]]; then - ssh_cmd="ssh $target" - fi - - local run - if [[ -n "$ssh_cmd" ]]; then - run="$ssh_cmd" - else - run="" - fi - info "首次部署检查..." - # 检查 .env 是否存在 local env_exists - if [[ -n "$run" ]]; then - env_exists=$($run "test -f $REMOTE_DIR/.env && echo yes || echo no" 2>/dev/null) - else - env_exists=$(test -f "$MONITOR_DIR/.env" && echo yes || echo no) - fi + env_exists=$(ssh txjp "test -f $REMOTE_DIR/.env && echo yes || echo no" 2>/dev/null) if [[ "$env_exists" == "yes" ]]; then info "已存在 .env,跳过密钥生成" @@ -151,10 +114,8 @@ first_deploy_setup() { LOCALADMIN_PASS=$(openssl rand -hex 16) JWT_SECRET=$(openssl rand -hex 32) - # 生成 pbkdf2 密码哈希(通过 Authelia 容器或本地 openssl) - if [[ -n "$run" ]]; then - PBKDF2_HASH=$($run "docker exec authelia authelia crypto hash generate pbkdf2 --password '$LOCALADMIN_PASS' 2>/dev/null | grep 'Digest:' | awk '{print \$2}'" 2>/dev/null) - fi + # 生成 pbkdf2 密码哈希(通过 Authelia 容器) + PBKDF2_HASH=$(ssh txjp "docker exec authelia authelia crypto hash generate pbkdf2 --password '$LOCALADMIN_PASS' 2>/dev/null | grep 'Digest:' | awk '{print \$2}'" 2>/dev/null) # 如果 Authelia 不可用,使用 openssl 生成备用哈希 if [[ -z "$PBKDF2_HASH" ]]; then @@ -164,8 +125,8 @@ first_deploy_setup() { fi # 写入 .env 文件 - local env_content - env_content="DATABASE_PATH=/app/data/monitor.db + ssh txjp "cat > $REMOTE_DIR/.env << 'ENVEOF' +DATABASE_PATH=/app/data/monitor.db MONITOR_MODE=local AUTHELIA_URL=https://sso.tlyq.ai OIDC_CLIENT_ID=monitor-oidc @@ -176,15 +137,8 @@ COOKIE_DOMAIN=.tlyq.ai LDAP_URL=ldap://ldap-ai:3890 LOCALADMIN_PASSWORD=$LOCALADMIN_PASS NODE_ENV=production -NODE_TLS_REJECT_UNAUTHORIZED=0" - - if [[ -n "$run" ]]; then - $run "cat > $REMOTE_DIR/.env << 'ENVEOF' -$env_content +NODE_TLS_REJECT_UNAUTHORIZED=0 ENVEOF" - else - echo "$env_content" > "$MONITOR_DIR/.env" - fi log "密钥已生成" info " OIDC_CLIENT_SECRET: $OIDC_SECRET" @@ -196,7 +150,6 @@ ENVEOF" # 更新 nginx 配置 # ============================================================ update_nginx() { - local target="$1" local nginx_conf="$LOCAL_DIR/nginx-proxy-ai/conf.d/monitor-ai.conf" if [[ ! -f "$nginx_conf" ]]; then @@ -204,21 +157,15 @@ update_nginx() { return 0 fi - if [[ "$target" == "local" ]]; then - return 0 - fi - log "更新 nginx 配置..." - scp "$nginx_conf" "$target:$REMOTE_DIR/../nginx-proxy-ai/conf.d/monitor-ai.conf" 2>/dev/null || true - ssh "$target" "docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload" && log "nginx 已重载" || warn "nginx 重载失败" + scp "$nginx_conf" txjp:/root/docker/nginx-proxy-ai/conf.d/monitor-ai.conf 2>/dev/null || true + ssh txjp "docker exec nginx-ai nginx -t && docker exec nginx-ai nginx -s reload" && log "nginx 已重载" || warn "nginx 重载失败" } # ============================================================ # 服务器构建 # ============================================================ build_on_server() { - local target="$1" - # 1. 计算源码快照 log "检查源码是否有变化..." local local_md5 @@ -238,13 +185,13 @@ build_on_server() { echo " 源码快照: ${local_md5}" local prev_md5 - prev_md5=$(ssh "$target" "cat ${SNAPSHOT_FILE} 2>/dev/null" 2>/dev/null || echo "") + prev_md5=$(ssh txjp "cat ${SNAPSHOT_FILE} 2>/dev/null" 2>/dev/null || echo "") if [[ -z "$prev_md5" ]]; then info "首次部署,执行完整构建" elif [[ "$prev_md5" == "$local_md5" ]]; then info "源码无变化,跳过构建,仅重启容器" - ssh "$target" "cd $REMOTE_DIR && docker compose down && docker compose up -d" + ssh txjp "cd $REMOTE_DIR && docker compose down && docker compose up -d" log "容器已重建" return 0 else @@ -256,14 +203,14 @@ build_on_server() { # 3. 上传 log "上传源码包..." - scp /tmp/monitor-ai.tar.gz "$target:/tmp/monitor-ai.tar.gz" || { + scp /tmp/monitor-ai.tar.gz txjp:/tmp/monitor-ai.tar.gz || { error "上传失败" return 1 } # 4. 解压源码到 /tmp,然后 rsync 到目标目录 log "解压源码并准备依赖..." - ssh "$target" "\ + ssh txjp "\ rm -rf /tmp/deploy_monitor && mkdir -p /tmp/deploy_monitor && \ tar xzf /tmp/monitor-ai.tar.gz -C /tmp/deploy_monitor 2>/dev/null || true && \ rsync -a --delete \ @@ -274,13 +221,14 @@ build_on_server() { rsync -a --delete \ --exclude='node_modules' --exclude='.git' \ /tmp/deploy_monitor/shared/ /root/docker/shared/ && \ + ln -sf /root/docker/shared $REMOTE_DIR/shared && \ rm -rf /tmp/deploy_monitor" # 5. 首次部署:上传 docker-compose.yml 和 Dockerfile - ssh "$target" "cd $REMOTE_DIR && docker compose up -d" + ssh txjp "cd $REMOTE_DIR && docker compose up -d" # 如果 host 上没有 node_modules,从容器内复制一份 - ssh "$target" "if [ ! -d '${REMOTE_DIR}/node_modules' ]; then + ssh txjp "if [ ! -d '${REMOTE_DIR}/node_modules' ]; then echo ' 首次:复制容器内 node_modules 到主机...' docker cp ${CONTAINER}:/app/node_modules ${REMOTE_DIR}/node_modules echo ' 完成' @@ -288,16 +236,16 @@ build_on_server() { # 6. 安装可能新增的依赖 log "安装新增依赖..." - ssh "$target" "cd ${REMOTE_DIR} && npm install --prefer-offline 2>&1 | tail -5 || true" + ssh txjp "cd ${REMOTE_DIR} && npm install --prefer-offline 2>&1 | tail -5 || true" # 7. 清理可能被上传的本地环境变量文件 - ssh "$target" "rm -f $REMOTE_DIR/.env.local $REMOTE_DIR/.env.development $REMOTE_DIR/.env.production 2>/dev/null; echo '已清理本地环境文件'" + ssh txjp "rm -f $REMOTE_DIR/.env.local $REMOTE_DIR/.env.development $REMOTE_DIR/.env.production 2>/dev/null; echo '已清理本地环境文件'" # 8. 服务器上执行 npm run build log "服务器上执行 npm run build..." local build_start build_end build_dur build_start=$(date +%s) - ssh "$target" "cd ${REMOTE_DIR} && npm run build 2>&1" | \ + ssh txjp "cd ${REMOTE_DIR} && npm run build 2>&1" | \ grep -vE "^(info|warn|npm warn|audited|packages|funding|vulnerability|npm notice|New major)" | \ tail -15 local build_exit_code=${PIPESTATUS[0]} @@ -311,30 +259,30 @@ build_on_server() { # 9. 重建容器 log "重建容器..." - ssh "$target" "cd $REMOTE_DIR && docker compose up -d && docker compose restart" + ssh txjp "cd $REMOTE_DIR && docker compose up -d && docker compose restart" # 10. 清理构建缓存 log "清理构建缓存..." - ssh "$target" "docker image prune -f 2>/dev/null || true" + ssh txjp "docker image prune -f 2>/dev/null || true" local disk_usage - disk_usage=$(ssh "$target" "df / --output=pcent | tail -1 | tr -d ' %'" 2>/dev/null || echo "50") + disk_usage=$(ssh txjp "df / --output=pcent | tail -1 | tr -d ' %'" 2>/dev/null || echo "50") if [ "$disk_usage" -gt 95 ]; then warn "磁盘使用率 ${disk_usage}%,执行紧急清理" - ssh "$target" "docker builder prune -f 2>/dev/null || true" + ssh txjp "docker builder prune -f 2>/dev/null || true" elif [ "$disk_usage" -gt 85 ]; then info "磁盘使用率 ${disk_usage}%,执行激进清理(6小时)" - ssh "$target" "docker builder prune -f --filter 'until=6h' 2>/dev/null || true" + ssh txjp "docker builder prune -f --filter 'until=6h' 2>/dev/null || true" elif [ "$disk_usage" -gt 70 ]; then info "磁盘使用率 ${disk_usage}%,执行正常清理(24小时)" - ssh "$target" "docker builder prune -f --filter 'until=24h' 2>/dev/null || true" + ssh txjp "docker builder prune -f --filter 'until=24h' 2>/dev/null || true" else info "磁盘使用率 ${disk_usage}%,执行保守清理(48小时)" - ssh "$target" "docker builder prune -f --filter 'until=48h' 2>/dev/null || true" + ssh txjp "docker builder prune -f --filter 'until=48h' 2>/dev/null || true" fi # 11. 保存快照 - ssh "$target" "echo '$local_md5' > ${SNAPSHOT_FILE}" + ssh txjp "echo '$local_md5' > ${SNAPSHOT_FILE}" } # ============================================================ @@ -372,34 +320,19 @@ echo "" BUILD_START=$(date +%s) case "$MODE" in - local) - # 本地模式:部署到 txjp 服务器(默认行为) + remote) + # 远程模式:部署到 txjp 服务器 log "部署目标: $MONITOR_DIR → txjp:$REMOTE_DIR" echo "" # 首次部署检查 - first_deploy_setup "$SSH_TARGET" + first_deploy_setup # 构建 - build_on_server "$SSH_TARGET" + build_on_server # 更新 nginx - update_nginx "$SSH_TARGET" - ;; - - remote) - # 远程模式:部署到指定服务器 - log "部署目标: $MONITOR_DIR → $HOST:$REMOTE_DIR" - echo "" - - # 首次部署检查 - first_deploy_setup "$HOST" - - # 构建 - build_on_server "$HOST" - - # 更新 nginx - update_nginx "$HOST" + update_nginx ;; dev) @@ -421,7 +354,7 @@ if [[ "$MODE" == "dev" ]]; then exit 0 fi -STATUS=$(ssh "$SSH_TARGET" "curl -s -o /dev/null -w '%{http_code}' -k 'https://monitor.tlyq.ai/api/health' 2>/dev/null" 2>/dev/null || echo "???") +STATUS=$(ssh txjp "curl -s -o /dev/null -w '%{http_code}' -k 'https://monitor.tlyq.ai/api/health' 2>/dev/null" 2>/dev/null || echo "???") if [[ "$STATUS" == "200" ]]; then log "部署成功!总耗时: ${TOTAL_DUR}s | 访问 https://monitor.tlyq.ai" exit 0