chore(deploy): containerize server deployment
This commit is contained in:
+104
-121
@@ -1,19 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Post-receive hook: push 后自动 build + start
|
||||
# Post-receive hook: push 后自动构建 Docker 镜像并重启容器
|
||||
|
||||
# pnpm 环境变量
|
||||
# pnpm / node 环境仍保留,便于服务器缺少 Docker 时做人工排查。
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
|
||||
# 全局变量统一声明。具体值由各步骤函数写入。
|
||||
REPO_TOPLEVEL=""
|
||||
LOG_FILE=""
|
||||
CURRENT_BRANCH=""
|
||||
DEPLOY_ENV=""
|
||||
ENV_FILE=""
|
||||
START_PORT=""
|
||||
BUILD_EXIT=0
|
||||
STOP_WAIT_SECONDS=3
|
||||
STOP_VERIFY_RETRIES=3
|
||||
IMAGE_TAG=""
|
||||
CONTAINER_NAME=""
|
||||
COMPOSE_PROJECT_NAME=""
|
||||
COMPOSE_EXIT=0
|
||||
|
||||
resolve_repo_context() {
|
||||
GIT_DIR_ABS="$(git rev-parse --absolute-git-dir 2>/dev/null)"
|
||||
@@ -44,8 +46,8 @@ write_metadata_header() {
|
||||
}
|
||||
|
||||
sync_worktree() {
|
||||
echo "=== sync_worktree: git reset --hard HEAD @ $(date +%Y-%m-%dT%H:%M:%S%z) ==="
|
||||
git reset --hard HEAD
|
||||
echo "=== sync_worktree: git reset --hard HEAD @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
git reset --hard HEAD >> "$LOG_FILE" 2>&1
|
||||
}
|
||||
|
||||
copy_env_by_branch() {
|
||||
@@ -54,67 +56,86 @@ copy_env_by_branch() {
|
||||
case "$CURRENT_BRANCH" in
|
||||
main)
|
||||
cp -f env-example/.env.production.example .env.production
|
||||
ENV_FILE=".env.production"
|
||||
DEPLOY_ENV="prod"
|
||||
START_PORT=9185
|
||||
echo "=== env: copied .env.production (main → prod) ===" >> "$LOG_FILE"
|
||||
;;
|
||||
test)
|
||||
cp -f env-example/.env.local.example .env.local
|
||||
ENV_FILE=".env.local"
|
||||
DEPLOY_ENV="test"
|
||||
START_PORT=9135
|
||||
echo "=== env: copied .env.local (test → test) ===" >> "$LOG_FILE"
|
||||
;;
|
||||
dev)
|
||||
cp -f env-example/.env.development.example .env.local
|
||||
ENV_FILE=".env.local"
|
||||
DEPLOY_ENV="dev"
|
||||
START_PORT=9135
|
||||
echo "=== env: copied .env.local (dev → dev) ===" >> "$LOG_FILE"
|
||||
;;
|
||||
*)
|
||||
echo "=== env: UNKNOWN branch '$CURRENT_BRANCH', skip env copy ===" >> "$LOG_FILE"
|
||||
ENV_FILE=".env.local"
|
||||
DEPLOY_ENV="$CURRENT_BRANCH"
|
||||
START_PORT=9135
|
||||
echo "=== env: UNKNOWN branch '$CURRENT_BRANCH', use defaults ===" >> "$LOG_FILE"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 4. select_port_by_branch —— 根据分支选择 next start 端口
|
||||
# main 分支 → 9185
|
||||
# test 分支 → 9135
|
||||
# dev 分支 → 9135
|
||||
# ============================================================
|
||||
select_port_by_branch() {
|
||||
case "$CURRENT_BRANCH" in
|
||||
main)
|
||||
START_PORT=9185
|
||||
;;
|
||||
test|dev)
|
||||
START_PORT=9135
|
||||
;;
|
||||
*)
|
||||
START_PORT=9135
|
||||
;;
|
||||
esac
|
||||
export START_PORT
|
||||
echo "=== start port: $START_PORT ===" >> "$LOG_FILE"
|
||||
prepare_compose_env() {
|
||||
SHORT_SHA="$(git rev-parse --short HEAD)"
|
||||
IMAGE_TAG="cozsweet-web:${DEPLOY_ENV}-${SHORT_SHA}"
|
||||
CONTAINER_NAME="cozsweet-web-${DEPLOY_ENV}"
|
||||
COMPOSE_PROJECT_NAME="cozsweet-web-${DEPLOY_ENV}"
|
||||
|
||||
export COZSWEET_IMAGE="$IMAGE_TAG"
|
||||
export COZSWEET_CONTAINER_NAME="$CONTAINER_NAME"
|
||||
export COZSWEET_ENV_FILE="$ENV_FILE"
|
||||
export COZSWEET_HOST_PORT="$START_PORT"
|
||||
export COMPOSE_PROJECT_NAME
|
||||
export DOCKER_BUILDKIT=1
|
||||
export COMPOSE_DOCKER_CLI_BUILD=1
|
||||
|
||||
{
|
||||
echo "=== deploy env: $DEPLOY_ENV ==="
|
||||
echo "=== env file: $COZSWEET_ENV_FILE ==="
|
||||
echo "=== image: $COZSWEET_IMAGE ==="
|
||||
echo "=== container: $COZSWEET_CONTAINER_NAME ==="
|
||||
echo "=== host port: $COZSWEET_HOST_PORT ==="
|
||||
echo "=== compose project: $COMPOSE_PROJECT_NAME ==="
|
||||
echo ""
|
||||
} >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 5. run_build —— 跑 `pnpm install` + `pnpm run build`,输出丢,捕获退出码写日志
|
||||
# 返回:build 退出码(main 用 if ! run_build 决定是否继续)
|
||||
# ============================================================
|
||||
run_build() {
|
||||
echo "=== build START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
pnpm run build:deploy > /dev/null 2>&1
|
||||
BUILD_EXIT=$?
|
||||
echo "=== build EXIT_CODE=$BUILD_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
docker_compose() {
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
docker compose "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
docker-compose "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
echo "=== docker compose command not found ===" >> "$LOG_FILE"
|
||||
return 127
|
||||
}
|
||||
|
||||
run_container_build() {
|
||||
echo "=== docker build START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
docker_compose build web >> "$LOG_FILE" 2>&1
|
||||
COMPOSE_EXIT=$?
|
||||
echo "=== docker build EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
echo "" >> "$LOG_FILE"
|
||||
return $BUILD_EXIT
|
||||
return $COMPOSE_EXIT
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 6. stop_existing_next —— 强杀当前分支端口上的旧进程
|
||||
# 1) 使用 ss 查找占用 START_PORT 的进程
|
||||
# 2) 直接 SIGKILL,避免旧服务 drain 太久导致新服务无法绑定端口
|
||||
# 3) 等待 STOP_WAIT_SECONDS,让系统释放端口
|
||||
# 4) 循环复查端口占用并再次强杀残留
|
||||
# ============================================================
|
||||
find_port_pids() {
|
||||
if ! command -v ss >/dev/null 2>&1; then
|
||||
echo "=== ss command not found; cannot inspect port $START_PORT ===" >> "$LOG_FILE"
|
||||
echo "=== ss command not found; cannot inspect legacy port $START_PORT ===" >> "$LOG_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -124,99 +145,61 @@ find_port_pids() {
|
||||
| sort -u
|
||||
}
|
||||
|
||||
force_kill_pids() {
|
||||
PIDS_TO_KILL="$1"
|
||||
if [ -z "$PIDS_TO_KILL" ]; then
|
||||
container_exists() {
|
||||
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -Fx "$CONTAINER_NAME" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
stop_legacy_next_if_needed() {
|
||||
if container_exists; then
|
||||
echo "=== existing container found; skip legacy port cleanup ===" >> "$LOG_FILE"
|
||||
return 0
|
||||
fi
|
||||
echo "=== force kill PIDS: $PIDS_TO_KILL ===" >> "$LOG_FILE"
|
||||
kill -9 $PIDS_TO_KILL 2>/dev/null || true
|
||||
}
|
||||
|
||||
wait_for_port_release() {
|
||||
RETRY=0
|
||||
while [ "$RETRY" -lt "$STOP_VERIFY_RETRIES" ]; do
|
||||
REMAINING_PIDS="$(find_port_pids)"
|
||||
if [ -z "$REMAINING_PIDS" ]; then
|
||||
echo "=== port $START_PORT released ===" >> "$LOG_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "=== port $START_PORT still busy, retry=$RETRY, pids=$REMAINING_PIDS ===" >> "$LOG_FILE"
|
||||
force_kill_pids "$REMAINING_PIDS"
|
||||
sleep "$STOP_WAIT_SECONDS"
|
||||
RETRY=$((RETRY + 1))
|
||||
done
|
||||
|
||||
REMAINING_PIDS="$(find_port_pids)"
|
||||
if [ -n "$REMAINING_PIDS" ]; then
|
||||
echo "=== stop FAILED: port $START_PORT still occupied by PIDS: $REMAINING_PIDS ===" >> "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "=== port $START_PORT released ===" >> "$LOG_FILE"
|
||||
return 0
|
||||
}
|
||||
|
||||
stop_existing_next() {
|
||||
echo "=== stop existing next on port $START_PORT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
PIDS="$(find_port_pids)"
|
||||
|
||||
if [ -n "$PIDS" ]; then
|
||||
force_kill_pids "$PIDS"
|
||||
else
|
||||
echo "=== no process found on port $START_PORT ===" >> "$LOG_FILE"
|
||||
if [ -z "$PIDS" ]; then
|
||||
echo "=== no legacy process found on port $START_PORT ===" >> "$LOG_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep "$STOP_WAIT_SECONDS"
|
||||
echo "=== first container deploy: kill legacy PIDS on port $START_PORT: $PIDS ===" >> "$LOG_FILE"
|
||||
kill -9 $PIDS 2>/dev/null || true
|
||||
sleep 3
|
||||
}
|
||||
|
||||
if ! wait_for_port_release; then
|
||||
echo "=== stop ABORT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
echo "" >> "$LOG_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "=== stop DONE @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
launch_container() {
|
||||
echo "=== docker compose up START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
stop_legacy_next_if_needed
|
||||
docker_compose up -d --remove-orphans web >> "$LOG_FILE" 2>&1
|
||||
COMPOSE_EXIT=$?
|
||||
echo "=== docker compose up EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
echo "" >> "$LOG_FILE"
|
||||
return 0
|
||||
return $COMPOSE_EXIT
|
||||
}
|
||||
|
||||
launch_next() {
|
||||
echo "=== start LAUNCH port=$START_PORT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
PORT="$START_PORT" nohup pnpm run start >> "$LOG_FILE" 2>&1 &
|
||||
START_PID=$!
|
||||
echo "=== start PID=$START_PID @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 8. log_abort —— build 失败时写结束标记(不输出 START LAUNCH)
|
||||
# ============================================================
|
||||
log_abort() {
|
||||
echo "=== ABORTED @ $(date +%Y-%m-%dT%H:%M:%S%z) (build failed, start skipped) ===" >> "$LOG_FILE"
|
||||
echo "=== ABORTED @ $(date +%Y-%m-%dT%H:%M:%S%z) ($1) ===" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# main —— 调度所有步骤
|
||||
# ============================================================
|
||||
main() {
|
||||
resolve_repo_context
|
||||
init_log_file
|
||||
write_metadata_header # 先覆盖写元信息(这样 env copy 日志能保留在后面)
|
||||
write_metadata_header
|
||||
|
||||
sync_worktree # 先同步工作树到新 HEAD(与 git config denyCurrentBranch 配合)
|
||||
copy_env_by_branch # 追加 env copy 日志
|
||||
sync_worktree
|
||||
copy_env_by_branch
|
||||
prepare_compose_env
|
||||
|
||||
select_port_by_branch # 根据当前分支决定 next start 端口
|
||||
|
||||
if ! run_build; then
|
||||
log_abort
|
||||
exit $BUILD_EXIT # build 失败 → 不启 start(避免用旧 .next 跑新代码)
|
||||
if ! run_container_build; then
|
||||
log_abort "docker build failed, old container kept running"
|
||||
exit $COMPOSE_EXIT
|
||||
fi
|
||||
|
||||
if ! stop_existing_next; then
|
||||
exit 1 # 端口未释放 → 不启动新服务,避免 EADDRINUSE
|
||||
if ! launch_container; then
|
||||
log_abort "docker compose up failed"
|
||||
exit $COMPOSE_EXIT
|
||||
fi
|
||||
launch_next # 再启新
|
||||
|
||||
echo "=== DEPLOY DONE @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user