personal-homepage/scripts/deploy-homepage.sh

314 lines
8.3 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
set -Eeuo pipefail
# 日志源目录:由日志生产服务写入
# 默认值:/home/basil/bot/data/git-summary
LOG_SOURCE_DIR="${LOG_SOURCE_DIR:-/home/basil/bot/data/git-summary}"
# 静态资源同步清单
# 默认值:$SITE_DIR/src/content/static-assets/index.json
STATIC_ASSET_MANIFEST="${STATIC_ASSET_MANIFEST:-src/content/static-assets/index.json}"
# 个人主页仓库目录
# 默认值:/home/basil/source/personal-homepage
SITE_DIR="${SITE_DIR:-/home/basil/source/personal-homepage}"
# 日志目标目录:复制到 homepage 仓库内
# 默认值:$SITE_DIR/src/content/logs
LOG_TARGET_DIR="${LOG_TARGET_DIR:-${SITE_DIR}/src/content/logs}"
# Docker Compose 配置与服务名
# 默认 compose 文件:$SITE_DIR/docker-compose.yml
# 默认服务名homepage
DOCKER_COMPOSE_FILE="${DOCKER_COMPOSE_FILE:-${SITE_DIR}/docker-compose.yml}"
DOCKER_SERVICE_NAME="${DOCKER_SERVICE_NAME:-homepage}"
# 是否在同步时删除目标目录中源目录已不存在的文件
# 默认值false
RSYNC_DELETE="${RSYNC_DELETE:-false}"
# 是否先在宿主机执行一次 npm run rebuild
# 默认值true
RUN_LOCAL_REBUILD="${RUN_LOCAL_REBUILD:-true}"
# 是否在 log 同步前先同步静态资源
# 默认值true
RUN_STATIC_ASSET_SYNC="${RUN_STATIC_ASSET_SYNC:-true}"
# 是否执行日志同步
# 默认值true
RUN_LOG_SYNC="${RUN_LOG_SYNC:-true}"
# 刷新 Docker 服务时是否强制重新构建镜像
# 默认值true
DOCKER_FORCE_BUILD="${DOCKER_FORCE_BUILD:-true}"
# 锁文件路径,避免定时任务重入
# 默认值:/tmp/personal-homepage-deploy.lock
LOCK_FILE="${LOCK_FILE:-/tmp/personal-homepage-deploy.lock}"
timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
log() {
printf '[%s] %s\n' "$(timestamp)" "$*"
}
fail() {
log "ERROR: $*"
exit 1
}
usage() {
cat <<'EOF'
用法:
deploy-homepage.sh [options]
选项:
--preset <dev|full|fast> 使用预设部署档位
--dev-deploy 等价于 --preset dev
--full-deploy 等价于 --preset full
--run-static-asset-sync <true|false> 是否同步静态资源
--run-log-sync <true|false> 是否同步日志目录
--run-local-rebuild <true|false> 是否在宿主机执行 npm run rebuild
--docker-force-build <true|false> docker compose up 时是否加 --build
--rsync-delete <true|false> rsync 时是否启用 --delete
--fast 快速模式(仅刷新容器,不做同步与宿主机 rebuild
-h, --help 显示帮助
说明:
- 预设档位:
dev = 仅代码/样式部署(不做静态资源同步与日志同步;仍会 docker --build
full = 全量部署(静态资源 + 日志 + 宿主机 rebuild + docker --build
fast = 仅快速刷新容器(不做同步、不本地 rebuild、且不 docker --build
- 命令行参数优先级高于环境变量。
- 布尔值支持: true/false/1/0/yes/no/on/off
EOF
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "缺少命令: $1"
}
is_true() {
case "${1,,}" in
1|true|yes|on) return 0 ;;
*) return 1 ;;
esac
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--preset)
[[ $# -ge 2 ]] || fail "参数缺少值: $1"
case "${2,,}" in
dev)
RUN_STATIC_ASSET_SYNC=false
RUN_LOG_SYNC=false
RUN_LOCAL_REBUILD=false
DOCKER_FORCE_BUILD=true
;;
full)
RUN_STATIC_ASSET_SYNC=true
RUN_LOG_SYNC=true
RUN_LOCAL_REBUILD=true
DOCKER_FORCE_BUILD=true
;;
fast)
RUN_STATIC_ASSET_SYNC=false
RUN_LOG_SYNC=false
RUN_LOCAL_REBUILD=false
DOCKER_FORCE_BUILD=false
;;
*)
fail "不支持的 preset: $2(可选: dev|full|fast"
;;
esac
shift 2
;;
--dev-deploy)
RUN_STATIC_ASSET_SYNC=false
RUN_LOG_SYNC=false
RUN_LOCAL_REBUILD=false
DOCKER_FORCE_BUILD=true
shift
;;
--full-deploy)
RUN_STATIC_ASSET_SYNC=true
RUN_LOG_SYNC=true
RUN_LOCAL_REBUILD=true
DOCKER_FORCE_BUILD=true
shift
;;
--run-static-asset-sync)
[[ $# -ge 2 ]] || fail "参数缺少值: $1"
RUN_STATIC_ASSET_SYNC="$2"
shift 2
;;
--run-log-sync)
[[ $# -ge 2 ]] || fail "参数缺少值: $1"
RUN_LOG_SYNC="$2"
shift 2
;;
--run-local-rebuild)
[[ $# -ge 2 ]] || fail "参数缺少值: $1"
RUN_LOCAL_REBUILD="$2"
shift 2
;;
--docker-force-build)
[[ $# -ge 2 ]] || fail "参数缺少值: $1"
DOCKER_FORCE_BUILD="$2"
shift 2
;;
--rsync-delete)
[[ $# -ge 2 ]] || fail "参数缺少值: $1"
RSYNC_DELETE="$2"
shift 2
;;
--fast)
RUN_STATIC_ASSET_SYNC=false
RUN_LOG_SYNC=false
RUN_LOCAL_REBUILD=false
DOCKER_FORCE_BUILD=false
shift
;;
-h|--help)
usage
exit 0
;;
*)
fail "未知参数: $1(使用 --help 查看可用参数)"
;;
esac
done
}
sync_logs() {
mkdir -p "$LOG_TARGET_DIR"
if command -v rsync >/dev/null 2>&1; then
local -a rsync_args=(-av)
if is_true "$RSYNC_DELETE"; then
rsync_args+=(--delete)
fi
log "同步日志: $LOG_SOURCE_DIR -> $LOG_TARGET_DIR"
rsync "${rsync_args[@]}" "${LOG_SOURCE_DIR}/" "${LOG_TARGET_DIR}/"
return 0
fi
log "未找到 rsync改用 cp -a 复制日志"
if is_true "$RSYNC_DELETE"; then
find "$LOG_TARGET_DIR" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
fi
cp -a "${LOG_SOURCE_DIR}/." "$LOG_TARGET_DIR/"
}
sync_static_assets() {
log "同步静态资源清单: $STATIC_ASSET_MANIFEST"
(
cd "$SITE_DIR"
STATIC_ASSET_MANIFEST="$STATIC_ASSET_MANIFEST" \
node --env-file-if-exists=.env --experimental-strip-types ./scripts/sync-static-assets.ts
)
}
run_local_rebuild() {
log "开始宿主机构建校验: npm run rebuild"
(
cd "$SITE_DIR"
npm run rebuild
)
}
refresh_docker_service() {
log "刷新 Docker 服务: $DOCKER_SERVICE_NAME"
(
cd "$SITE_DIR"
if is_true "$DOCKER_FORCE_BUILD"; then
docker compose -f "$DOCKER_COMPOSE_FILE" up -d --build "$DOCKER_SERVICE_NAME"
else
docker compose -f "$DOCKER_COMPOSE_FILE" up -d "$DOCKER_SERVICE_NAME"
fi
)
}
main_impl() {
require_command docker
if is_true "$RUN_STATIC_ASSET_SYNC"; then
require_command node
fi
if is_true "$RUN_LOCAL_REBUILD"; then
require_command npm
fi
docker compose version >/dev/null 2>&1 || fail "当前环境不可用 docker compose"
[[ -d "$LOG_SOURCE_DIR" ]] || fail "日志源目录不存在: $LOG_SOURCE_DIR"
[[ -d "$SITE_DIR" ]] || fail "站点目录不存在: $SITE_DIR"
[[ -f "$DOCKER_COMPOSE_FILE" ]] || fail "docker compose 文件不存在: $DOCKER_COMPOSE_FILE"
log "部署开始"
log "LOG_SOURCE_DIR=$LOG_SOURCE_DIR"
log "LOG_TARGET_DIR=$LOG_TARGET_DIR"
log "SITE_DIR=$SITE_DIR"
log "STATIC_ASSET_MANIFEST=$STATIC_ASSET_MANIFEST"
log "DOCKER_COMPOSE_FILE=$DOCKER_COMPOSE_FILE"
log "DOCKER_SERVICE_NAME=$DOCKER_SERVICE_NAME"
log "RSYNC_DELETE=$RSYNC_DELETE"
log "RUN_LOG_SYNC=$RUN_LOG_SYNC"
log "RUN_STATIC_ASSET_SYNC=$RUN_STATIC_ASSET_SYNC"
log "RUN_LOCAL_REBUILD=$RUN_LOCAL_REBUILD"
log "DOCKER_FORCE_BUILD=$DOCKER_FORCE_BUILD"
log "LOCK_FILE=$LOCK_FILE"
if is_true "$RUN_STATIC_ASSET_SYNC"; then
sync_static_assets
else
log "跳过静态资源同步RUN_STATIC_ASSET_SYNC=false"
fi
if is_true "$RUN_LOG_SYNC"; then
sync_logs
else
log "跳过日志同步RUN_LOG_SYNC=false"
fi
if is_true "$RUN_LOCAL_REBUILD"; then
run_local_rebuild
else
log "跳过宿主机 npm run rebuildRUN_LOCAL_REBUILD=false"
fi
refresh_docker_service
log "部署完成"
}
main() {
parse_args "$@"
if ! command -v flock >/dev/null 2>&1; then
log "未找到 flock跳过锁保护"
main_impl
return 0
fi
mkdir -p "$(dirname "$LOCK_FILE")"
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
fail "已有部署任务在执行中,锁文件: $LOCK_FILE"
fi
log "已获取部署锁: $LOCK_FILE"
main_impl
}
main "$@"