253 lines
6.7 KiB
Bash
253 lines
6.7 KiB
Bash
|
|
#!/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 与 Dockerfile(dashboard),需要时请显式指定,例如:
|
|||
|
|
./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 "完成!"
|