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

227 lines
6.8 KiB
Bash
Executable File

#!/bin/sh
# Post-receive hook: push 后自动部署容器
# 默认在服务器本机构建镜像;配置 .deploy.env 后也可从镜像仓库拉取 CI 构建好的镜像。
REPO_TOPLEVEL=""
LOG_FILE=""
CURRENT_BRANCH=""
DEPLOY_ENV=""
ENV_FILE=""
START_PORT=""
IMAGE_TAG=""
IMAGE_SOURCE=""
REMOTE_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)"
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
}
init_log_file() {
LOG_FILE="$REPO_TOPLEVEL/logs/post-receive.log"
mkdir -p "$(dirname "$LOG_FILE")"
}
write_metadata_header() {
{
echo "=== post-receive @ $(date +%Y-%m-%dT%H:%M:%S%z) ==="
echo "branch: $(git rev-parse --abbrev-ref HEAD)"
echo "commit: $(git rev-parse --short HEAD)"
echo "cwd: $(pwd)"
echo ""
} > "$LOG_FILE"
}
sync_worktree() {
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
}
load_deploy_config() {
if [ ! -f ".deploy.env" ]; then
echo "=== deploy config: .deploy.env not found, use defaults ===" >> "$LOG_FILE"
return 0
fi
echo "=== deploy config: loading .deploy.env ===" >> "$LOG_FILE"
set -a
# shellcheck disable=SC1091
. ./.deploy.env
set +a
}
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
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"
;;
*)
ENV_FILE=".env.local"
DEPLOY_ENV="$CURRENT_BRANCH"
START_PORT=9135
echo "=== env: UNKNOWN branch '$CURRENT_BRANCH', use defaults ===" >> "$LOG_FILE"
;;
esac
}
prepare_compose_env() {
SHORT_SHA="$(git rev-parse --short HEAD)"
IMAGE_SOURCE="${COZSWEET_DEPLOY_IMAGE_SOURCE:-build}"
REMOTE_IMAGE_TAG="${COZSWEET_REMOTE_IMAGE_TAG:-${DEPLOY_ENV}-${SHORT_SHA}}"
case "$IMAGE_SOURCE" in
build)
IMAGE_TAG="cozsweet-web:${DEPLOY_ENV}-${SHORT_SHA}"
;;
pull)
if [ -z "${COZSWEET_REGISTRY_IMAGE:-}" ]; then
echo "=== missing COZSWEET_REGISTRY_IMAGE for pull deploy ===" >> "$LOG_FILE"
exit 64
fi
IMAGE_TAG="${COZSWEET_REGISTRY_IMAGE}:${REMOTE_IMAGE_TAG}"
;;
*)
echo "=== unsupported COZSWEET_DEPLOY_IMAGE_SOURCE: $IMAGE_SOURCE ===" >> "$LOG_FILE"
exit 64
;;
esac
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 "=== image source: $IMAGE_SOURCE ==="
echo "=== env file: $COZSWEET_ENV_FILE ==="
echo "=== image: $COZSWEET_IMAGE ==="
echo "=== remote image tag: $REMOTE_IMAGE_TAG ==="
echo "=== container: $COZSWEET_CONTAINER_NAME ==="
echo "=== host port: $COZSWEET_HOST_PORT ==="
echo "=== compose project: $COMPOSE_PROJECT_NAME ==="
echo ""
} >> "$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 $COMPOSE_EXIT
}
pull_container_image() {
echo "=== docker pull START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
docker_compose pull web >> "$LOG_FILE" 2>&1
COMPOSE_EXIT=$?
echo "=== docker pull EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
return $COMPOSE_EXIT
}
launch_container() {
echo "=== docker compose up START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
if [ "$IMAGE_SOURCE" = "pull" ]; then
docker_compose up -d --remove-orphans --no-build web >> "$LOG_FILE" 2>&1
else
docker_compose up -d --remove-orphans web >> "$LOG_FILE" 2>&1
fi
COMPOSE_EXIT=$?
echo "=== docker compose up EXIT_CODE=$COMPOSE_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
return $COMPOSE_EXIT
}
log_abort() {
echo "=== ABORTED @ $(date +%Y-%m-%dT%H:%M:%S%z) ($1) ===" >> "$LOG_FILE"
}
main() {
resolve_repo_context
init_log_file
write_metadata_header
sync_worktree
load_deploy_config
copy_env_by_branch
prepare_compose_env
if [ "$IMAGE_SOURCE" = "pull" ]; then
if ! pull_container_image; then
log_abort "docker pull failed, old container kept running"
exit $COMPOSE_EXIT
fi
else
if ! run_container_build; then
log_abort "docker build failed, old container kept running"
exit $COMPOSE_EXIT
fi
fi
if ! launch_container; then
log_abort "docker compose up failed"
exit $COMPOSE_EXIT
fi
echo "=== DEPLOY DONE @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
}
main "$@"
exit 0