fix: Dockerfile 构建上下文修复 + 部署脚本 SSH 别名

This commit is contained in:
aiyimickey 2026-07-01 18:55:09 +08:00
parent e63601b737
commit 2682232d85
2 changed files with 37 additions and 104 deletions

View File

@ -6,8 +6,8 @@ WORKDIR /app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci --production=false RUN npm ci --production=false
# 2. 复制共享库 # 2. 复制共享库(构建前由部署脚本创建 symlink: shared -> ../shared
COPY ../shared/ ./shared/ COPY shared/ ./shared/
# 3. 复制源码并构建 # 3. 复制源码并构建
COPY . . COPY . .

View File

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