Files
2026-08-13 14:23:01 +08:00

253 lines
6.7 KiB
Bash
Executable File
Raw Permalink 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
# ============================================================
# 统一部署脚本:将本地产物同步到正式服务器(rsync over ssh
#
# 覆盖两类目标(统一 ssh 别名 luxsin-api2,按顶层目录自动路由远程路径):
# dashboard/ -> /data/project/dashboard/ frontend / backend / compose
# www/ -> /data/project/dashboard/www/ (默认仅 .output/nginx.conf / docker-compose.yml 需显式指定)
#
# 用法:./deploy/upload.sh [选项] [目标|路径...]
# ============================================================
set -euo pipefail
# ===== 连接(统一使用 ssh 别名) =====
SERVER="luxsin-api2"
REMOTE_ROOT="/data/project/dashboard"
# www 静态产物位于 ${REMOTE_ROOT}/www
WWW_REMOTE_ROOT="${REMOTE_ROOT}/www"
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
FRONTEND_PATHS=(
./dashboard/frontend/dist/
)
BACKEND_PATHS=(
./dashboard/backend/dist/
./dashboard/backend/package.json
./dashboard/backend/pnpm-lock.yaml
./dashboard/backend/pnpm-workspace.yaml
)
COMPOSE_PATHS=(
./dashboard/docker-compose.yml
)
WWW_PATHS=(
./www/.output/
)
DEFAULT_PATHS=(
"${FRONTEND_PATHS[@]}"
"${BACKEND_PATHS[@]}"
"${COMPOSE_PATHS[@]}"
)
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
show_help() {
cat <<EOF
用法: $(basename "$0") [选项] [目标|路径...]
将本地代码同步到远程服务器(rsync)。
远程(统一 ssh 别名 ${SERVER},按顶层目录自动路由):
dashboard/ -> ${SERVER}:${REMOTE_ROOT}/
www/ -> ${SERVER}:${WWW_REMOTE_ROOT}/
选项:
-n 虚拟执行,不实际传输或删除远程文件
-h, --help 显示此帮助信息
目标:
frontend, f 同步 dashboard 前端 dist/(默认跳过 uploads/
backend, b 同步 dashboard 后端 dist/、package.json、pnpm-lock.yaml
compose, c 同步 dashboard docker-compose.yml
www, w 同步 www 构建产物 .output/nginx.conf / docker-compose.yml 需显式指定路径)
all, a 同步 frontend + backend + compose(不含 www
(路径) 同步指定文件或目录,可传多个
默认不同步 nginx.conf 与 Dockerfiledashboard),需要时请显式指定,例如:
./dashboard/frontend/nginx.conf
./dashboard/backend/Dockerfile
www 同步前需先本地构建: cd www && pnpm generate
示例:
$(basename "$0") 同步 dashboard 默认内容
$(basename "$0") frontend 仅同步 dashboard 前端
$(basename "$0") www 仅同步 www .output/
$(basename "$0") www ./www/nginx.conf ./www/docker-compose.yml 同步 www 配置
$(basename "$0") backend www 同步后端与 www
$(basename "$0") all www 同步全部(dashboard + www
$(basename "$0") -n www 虚拟执行,预览 www 同步
EOF
}
resolve_preset_paths() {
local preset="$1"
case "$preset" in
frontend|f) printf '%s\n' "${FRONTEND_PATHS[@]}" ;;
backend|b) printf '%s\n' "${BACKEND_PATHS[@]}" ;;
compose|c) printf '%s\n' "${COMPOSE_PATHS[@]}" ;;
www|w) printf '%s\n' "${WWW_PATHS[@]}" ;;
all|a) printf '%s\n' "${DEFAULT_PATHS[@]}" ;;
*)
echo "$preset"
;;
esac
}
# www 静态产物同步前校验本地已构建(nuxt generate
ensure_www_built() {
local idx="${PROJECT_ROOT}/www/.output/public/index.html"
if [[ ! -f "$idx" ]]; then
echo "❌ 本地缺少 ${idx},请先执行:"
echo " cd ${PROJECT_ROOT}/www && pnpm generate"
exit 1
fi
}
rsync_item() {
local item="$1"
local rel local_full
if [[ "$item" == /* ]]; then
if [[ "$item" == "$PROJECT_ROOT"/* ]]; then
rel="${item#"$PROJECT_ROOT"/}"
local_full="$item"
else
warn "路径不在项目内,跳过: $item"
return 1
fi
else
rel="${item#./}"
rel="${rel%/}"
local_full="${PROJECT_ROOT}/${rel}"
fi
# 依据顶层目录映射远程路径(dashboard/ 内容在远程根,www/ 内容在远程 www 子目录)
local sub
if [[ "$rel" == dashboard/* ]]; then
sub="${rel#dashboard/}"
elif [[ "$rel" == www/* ]]; then
sub="$rel"
else
warn "路径不在 dashboard/ 或 www/ 下,跳过: $item"
return 1
fi
local remote_path="${REMOTE_ROOT}/${sub}"
if [[ ! -e "$local_full" ]]; then
warn "路径不存在: ${local_full}"
return 1
fi
local rsync_opts=(-avz --progress)
if [[ "$DRY_RUN" = true ]]; then
rsync_opts+=(-n)
fi
# dashboard 前端 dist 同步默认跳过 uploads/(服务器挂载的上传目录,避免 --delete 误删)
if [[ "$sub" == "frontend/dist" ]]; then
rsync_opts+=(--exclude '/uploads/')
fi
if [[ -d "$local_full" ]]; then
if [[ "$DRY_RUN" = true ]]; then
info "预览目录: ${rel}/ -> ${SERVER}:${remote_path}/"
else
info "同步目录: ${rel}/ -> ${SERVER}:${remote_path}/"
fi
rsync "${rsync_opts[@]}" --delete \
-e ssh \
"${local_full}/" "${SERVER}:${remote_path}/"
else
if [[ "$DRY_RUN" = true ]]; then
info "预览文件: ${rel} -> ${SERVER}:${remote_path}"
else
info "同步文件: ${rel} -> ${SERVER}:${remote_path}"
ssh "$SERVER" "mkdir -p $(dirname "$remote_path")"
fi
rsync "${rsync_opts[@]}" \
-e ssh \
"$local_full" "${SERVER}:${remote_path}"
fi
}
sync_paths() {
local paths=("$@")
local item
cd "$PROJECT_ROOT"
for item in "${paths[@]}"; do
rsync_item "$item"
done
}
DRY_RUN=false
ARGS=()
for arg in "$@"; do
case "$arg" in
-n)
DRY_RUN=true
;;
-h|--help)
show_help
exit 0
;;
*)
ARGS+=("$arg")
;;
esac
done
PATHS=()
if [[ ${#ARGS[@]} -eq 0 ]]; then
PATHS=("${DEFAULT_PATHS[@]}")
else
for arg in "${ARGS[@]}"; do
while IFS= read -r path; do
PATHS+=("$path")
done < <(resolve_preset_paths "$arg")
done
fi
# 若包含 www .output 产物,先校验本地已构建
for p in "${PATHS[@]}"; do
rel="${p#./}"
if [[ "$rel" == www/.output || "$rel" == www/.output/* ]]; then
ensure_www_built
break
fi
done
if [[ "$DRY_RUN" = true ]]; then
info "虚拟同步(dashboard -> ${SERVER}:${REMOTE_ROOT}/ www -> ${SERVER}:${WWW_REMOTE_ROOT}/"
else
info "同步(dashboard -> ${SERVER}:${REMOTE_ROOT}/ www -> ${SERVER}:${WWW_REMOTE_ROOT}/"
fi
sync_paths "${PATHS[@]}"
# www 部署后续提示
for p in "${PATHS[@]}"; do
rel="${p#./}"
if [[ "$rel" == www/* ]]; then
echo ""
ok "www 产物已上传,下一步(在服务器上执行):"
echo " cd ${WWW_REMOTE_ROOT} && docker compose up -d"
break
fi
done
echo ""
ok "完成!"