Files
cozsweet-frontend-nextjs/.githooks/post-receive
T

159 lines
6.6 KiB
Bash
Executable File
Raw 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.
#!/bin/sh
# Post-receive hook: push 后自动 build + start
#
# pnpm 环境变量
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
# git 调钩子时 cwd=GIT_DIR 并设了 GIT_DIR=.(相对)。此时:
# 1. `git rev-parse --show-toplevel` 返回 `/.../repo/.git`(不报错),
# 会让原版 `|| echo $PWD` 退路不触发、cd 误进 .git。
# 2. cd 到工作树后,GIT_DIR=. 不再指向 .git,下面 `git reset` 之类的
# 会报 `fatal: not a git repository: '.'`。
# 修正:拿绝对 git-dir,算出 worktree 根,并改用绝对 GIT_DIR。
GIT_DIR_ABS="$(git rev-parse --absolute-git-dir 2>/dev/null)"
if [ -n "$GIT_DIR_ABS" ] && [ -d "$GIT_DIR_ABS" ]; then
REPO_TOPLEVEL="$(cd "$GIT_DIR_ABS/.." && pwd)"
export GIT_DIR="$GIT_DIR_ABS"
else
REPO_TOPLEVEL="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")"
fi
unset GIT_WORK_TREE
cd "$REPO_TOPLEVEL" || { echo "FATAL: cannot cd to $REPO_TOPLEVEL" >&2; exit 1; }
echo "=== REPO_TOPLEVEL=$REPO_TOPLEVEL (GIT_DIR=$GIT_DIR) ===" >&2
# ============================================================
# 0. sync_worktree —— 将工作树同步到新 push 的 HEAD
# 背景:服务器 .git/config 设了 receive.denyCurrentBranch=updateInstead 时
# git 自动更新工作树但会跳过此钩子;改为 false 后
# git 不会更新工作树,手动 reset --hard 同步才能让下面
# 的 build 用上新代码。
# ============================================================
sync_worktree() {
echo "=== sync_worktree: git reset --hard HEAD @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
git reset --hard HEAD
}
# ============================================================
# 1. init_log_file —— 定义 LOG_FILE + 创建 logs/ 目录
# ============================================================
init_log_file() {
LOG_FILE="$REPO_TOPLEVEL/logs/post-receive.log"
mkdir -p "$(dirname "$LOG_FILE")"
}
# ============================================================
# 2. write_metadata_header —— 覆盖写入元信息(首次写 → 覆盖让日志开头是这个)
# ============================================================
write_metadata_header() {
{
echo "=== post-receive @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
echo "branch: $(git rev-parse --abbrev-ref HEAD)"
echo "commit: $(git rev-parse --short HEAD)"
echo "cwd: $(pwd)"
echo ""
} > "$LOG_FILE"
}
# ============================================================
# 3. copy_env_by_branch —— 根据分支选 env-example → .env
# main 分支 → .env.productionprod 部署)
# test 分支 → .env.localtest 不碰 .env.production,避免读到 prod 域名)
# dev 分支 → .env.local(未配远端用途,但保留)
# ============================================================
copy_env_by_branch() {
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
echo "=== branch: $CURRENT_BRANCH ===" >> "$LOG_FILE"
case "$CURRENT_BRANCH" in
main)
cp -f env-example/.env.production.example .env.production
echo "=== env: copied .env.production (main → prod) ===" >> "$LOG_FILE"
;;
test)
cp -f env-example/.env.local.example .env.local
echo "=== env: copied .env.local (test → test) ===" >> "$LOG_FILE"
;;
dev)
cp -f env-example/.env.development.example .env.local
echo "=== env: copied .env.local (dev → dev) ===" >> "$LOG_FILE"
;;
*)
echo "=== env: UNKNOWN branch '$CURRENT_BRANCH', skip env copy ===" >> "$LOG_FILE"
;;
esac
}
# ============================================================
# 4. run_build —— 跑 `pnpm run build`,输出丢,捕获退出码写日志
# 返回:build 退出码(main 用 if ! run_build 决定是否继续)
# ============================================================
run_build() {
echo "=== build START @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ===" >> "$LOG_FILE"
pnpm run build > /dev/null 2>&1
BUILD_EXIT=$?
echo "=== build EXIT_CODE=$BUILD_EXIT @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ===" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
return $BUILD_EXIT
}
# ============================================================
# 5. stop_existing_next —— 两阶段 kill + 等 1s 确保端口完全释放
# 1) 优雅关闭(SIGTERM)—— 旧进程自己 drain
# 2) 等 2s
# 3) 强杀(SIGKILL)—— 还在的话
# 4) 等 1s
# ============================================================
stop_existing_next() {
echo "=== stop existing next @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ===" >> "$LOG_FILE"
pkill -f "next start" 2>/dev/null
pkill -f "next-server" 2>/dev/null
sleep 2
pkill -9 -f "next start" 2>/dev/null
pkill -9 -f "next-server" 2>/dev/null
sleep 1
echo "=== stop DONE @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ===" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
}
# ============================================================
# 6. launch_next —— 后台启 `pnpm run start`,捕 PID
# ============================================================
launch_next() {
# next stdout/stderr 接到 logs/next-server.log(覆盖模式,每次 deploy 重新开始)
NEXT_LOG_FILE="$REPO_TOPLEVEL/logs/next-server.log"
echo "=== start LAUNCH @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ===" >> "$LOG_FILE"
nohup pnpm run start > "$NEXT_LOG_FILE" 2>&1 &
START_PID=$!
echo "=== start PID=$START_PID @ $(date -u +%Y-%m-%dT%H:%M:%SZ) ===" >> "$LOG_FILE"
}
# ============================================================
# 7. log_abort —— build 失败时写结束标记(不输出 START LAUNCH
# ============================================================
log_abort() {
echo "=== ABORTED @ $(date -u +%Y-%m-%dT%H:%M:%SZ) (build failed, start skipped) ===" >> "$LOG_FILE"
}
# ============================================================
# main —— 调度所有步骤
# ============================================================
main() {
init_log_file
sync_worktree # 先同步工作树到新 HEAD(与 git config denyCurrentBranch 配合)
write_metadata_header # 先覆盖写元信息(这样 env copy 日志能保留在后面)
copy_env_by_branch # 追加 env copy 日志
if ! run_build; then
log_abort
exit $BUILD_EXIT # build 失败 → 不启 start(避免用旧 .next 跑新代码)
fi
stop_existing_next # build 成功 → 先关旧进程
launch_next # 再启新
}
main "$@"
exit 0