xykj-plus/claude-ecc/admin-xy/deploy.sh

229 lines
8.7 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# deploy.sh — admin-xy 生产发布脚本
#
# 用法:
# ./deploy.sh # 构建镜像并部署(首次或更新)
# ./deploy.sh --no-cache # 强制全量重新构建镜像
# ./deploy.sh --stop # 停止并移除 admin-xy 前后端容器(保留数据和配置)
# ./deploy.sh --restart # 重启 admin-xy 前后端容器(不重新构建)
#
# 前提:
# - Docker 及 Docker Compose 已安装
# - /opt/admin-xy/.env 已配置(首次运行时自动生成模板)
set -euo pipefail
# ── 常量 ────────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RELEASE_DIR="/opt/admin-xy"
ENV_FILE="${RELEASE_DIR}/.env"
COMPOSE_FILE="${RELEASE_DIR}/docker-compose.yml"
GIT_CONFIG_DIR="${RELEASE_DIR}/git"
ADMIN_SERVICES=(server web)
# ── 辅助函数 ─────────────────────────────────────────────────────────────────
log() { echo "[$(date '+%H:%M:%S')] $*"; }
err() { echo "[ERROR] $*" >&2; exit 1; }
warn() { echo "[WARN] $*" >&2; }
require_root() {
if [[ $EUID -ne 0 ]]; then
err "该脚本需要 root 权限(请使用 sudo ./deploy.sh"
fi
}
load_env_file() {
local line key value
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" == *"="* ]] || continue
key="${line%%=*}"
value="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [[ "$value" != \"*\" && "$value" != \'*\' ]]; then
value="${value%%[[:space:]]#*}"
fi
[[ "$value" =~ ^\"(.*)\"$ ]] && value="${BASH_REMATCH[1]}"
[[ "$value" =~ ^\'(.*)\'$ ]] && value="${BASH_REMATCH[1]}"
export "${key}=${value}"
done < "${ENV_FILE}"
}
write_git_config_files() {
local git_name git_email credentials_file
git_name="${GIT_AUTHOR_NAME:-dev-assistant}"
git_email="${GIT_AUTHOR_EMAIL:-dev-assistant@example.com}"
credentials_file="${GIT_CONFIG_DIR}/.git-credentials"
mkdir -p "${GIT_CONFIG_DIR}"
chmod 700 "${GIT_CONFIG_DIR}"
cat > "${GIT_CONFIG_DIR}/.gitconfig" <<EOF
[user]
name = ${git_name}
email = ${git_email}
[credential]
helper = store
[safe]
directory = /workspace
EOF
: > "${credentials_file}"
if [[ -n "${GITEE_USERNAME:-}" && -n "${GITEE_TOKEN:-}" ]]; then
printf 'https://%s:%s@gitee.com\n' "${GITEE_USERNAME}" "${GITEE_TOKEN}" > "${credentials_file}"
else
warn "未配置 GITEE_USERNAME/GITEE_TOKEN容器内 git push 可能需要重新认证"
fi
chmod 600 "${GIT_CONFIG_DIR}/.gitconfig" "${credentials_file}"
}
# ── 参数解析 ──────────────────────────────────────────────────────────────────
ACTION="deploy"
NO_CACHE=""
for arg in "$@"; do
case "$arg" in
--no-cache) NO_CACHE="--no-cache" ;;
--stop) ACTION="stop" ;;
--restart) ACTION="restart" ;;
--help|-h)
sed -n '2,13p' "$0" | sed 's/^# \?//'
exit 0
;;
*) err "未知参数:$arg(使用 --help 查看用法)" ;;
esac
done
# ── 发布目录初始化 ────────────────────────────────────────────────────────────
init_release_dir() {
log "初始化发布目录 ${RELEASE_DIR} ..."
mkdir -p "${RELEASE_DIR}"
# 写入 .env 模板(仅首次,避免覆盖已有配置)
if [[ ! -f "${ENV_FILE}" ]]; then
log "生成 .env 配置模板 → ${ENV_FILE}"
cat > "${ENV_FILE}" <<'EOF'
# ── 数据库连接(必填)────────────────────────────────────────────────────────
# JDBC URL 示例jdbc:mysql://127.0.0.1:3306/adminxy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia%2FShanghai
SPRING_DATASOURCE_ADMIN_URL=jdbc:mysql://127.0.0.1:3306/adminxy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia%2FShanghai
SPRING_DATASOURCE_ADMIN_USERNAME=adminxy
SPRING_DATASOURCE_ADMIN_PASSWORD=changeme
# ── JWT 密钥(必填,至少 32 个字符)─────────────────────────────────────────
JWT_SECRET=change-me-in-production-32bytes-min-secret-key-please
# ── 容器内 Git 身份与 Gitee HTTPS 凭据(可选,用于 Agent 提交/推送)────────
GIT_AUTHOR_NAME=dev-assistant
GIT_AUTHOR_EMAIL=dev-assistant@example.com
GITEE_USERNAME=
GITEE_TOKEN=
EOF
warn "已生成 .env 模板,请编辑后再次运行:${ENV_FILE}"
exit 0
fi
# 校验必填变量
local missing=()
load_env_file
[[ -z "${SPRING_DATASOURCE_ADMIN_URL:-}" ]] && missing+=("SPRING_DATASOURCE_ADMIN_URL")
[[ -z "${SPRING_DATASOURCE_ADMIN_USERNAME:-}" ]] && missing+=("SPRING_DATASOURCE_ADMIN_USERNAME")
[[ -z "${SPRING_DATASOURCE_ADMIN_PASSWORD:-}" ]] && missing+=("SPRING_DATASOURCE_ADMIN_PASSWORD")
[[ -z "${JWT_SECRET:-}" ]] && missing+=("JWT_SECRET")
if [[ ${#missing[@]} -gt 0 ]]; then
err "以下必填项未配置:${missing[*]}(请检查 ${ENV_FILE}"
fi
write_git_config_files
# 同步 docker-compose.yml
log "同步 docker-compose.yml → ${COMPOSE_FILE}"
cp "${SCRIPT_DIR}/docker-compose.yml" "${COMPOSE_FILE}"
}
# ── 构建镜像 ──────────────────────────────────────────────────────────────────
build_images() {
log "构建 Docker 镜像 ..."
"${SCRIPT_DIR}/build-images.sh" ${NO_CACHE}
}
# ── 部署 ─────────────────────────────────────────────────────────────────────
deploy() {
log "启动 admin-xy 前后端服务 ..."
docker compose \
--project-directory "${RELEASE_DIR}" \
--file "${COMPOSE_FILE}" \
--env-file "${ENV_FILE}" \
up -d "${ADMIN_SERVICES[@]}"
log "当前 admin-xy 前后端容器状态:"
docker compose \
--project-directory "${RELEASE_DIR}" \
--file "${COMPOSE_FILE}" \
ps "${ADMIN_SERVICES[@]}"
}
# ── 停止 ─────────────────────────────────────────────────────────────────────
stop_services() {
if [[ ! -f "${COMPOSE_FILE}" ]]; then
err "未找到 ${COMPOSE_FILE},无法停止(是否已部署?)"
fi
log "停止并移除 admin-xy 前后端容器 ..."
docker compose \
--project-directory "${RELEASE_DIR}" \
--file "${COMPOSE_FILE}" \
stop "${ADMIN_SERVICES[@]}"
docker compose \
--project-directory "${RELEASE_DIR}" \
--file "${COMPOSE_FILE}" \
rm -f "${ADMIN_SERVICES[@]}"
log "admin-xy 前后端容器已停止并移除。"
}
# ── 重启 ─────────────────────────────────────────────────────────────────────
restart_services() {
if [[ ! -f "${COMPOSE_FILE}" ]]; then
err "未找到 ${COMPOSE_FILE},请先执行完整部署:./deploy.sh"
fi
log "重启 admin-xy 前后端服务 ..."
docker compose \
--project-directory "${RELEASE_DIR}" \
--file "${COMPOSE_FILE}" \
--env-file "${ENV_FILE}" \
restart "${ADMIN_SERVICES[@]}"
log "当前 admin-xy 前后端容器状态:"
docker compose \
--project-directory "${RELEASE_DIR}" \
--file "${COMPOSE_FILE}" \
ps "${ADMIN_SERVICES[@]}"
}
# ── 主流程 ────────────────────────────────────────────────────────────────────
require_root
case "${ACTION}" in
deploy)
log "=== admin-xy 生产部署开始 ==="
init_release_dir
build_images
deploy
log "=== 部署完成 ==="
log "前端http://<server-ip>:9001"
log "后端http://<server-ip>:9010"
;;
stop)
stop_services
;;
restart)
restart_services
;;
esac