280 lines
7.7 KiB
Bash
280 lines
7.7 KiB
Bash
#!/usr/bin/env bash
|
||
#
|
||
# 构建离线 Docker 交付包:包含应用镜像、pgvector(PostgreSQL)、Redis 及统一的 docker-compose
|
||
# 使用方式:
|
||
# chmod +x build-docker-bundle.sh
|
||
# ./build-docker-bundle.sh # 使用默认版本号(时间戳)
|
||
# VERSION=v1.2.3 ./build-docker-bundle.sh # 指定版本号
|
||
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
||
APP_NAME="gdyd-zhpb-zgf"
|
||
VERSION="${VERSION:-${TIMESTAMP}}"
|
||
OUTPUT_DIR="${ROOT_DIR}/docker-bundle-${VERSION}"
|
||
IMAGES_DIR="${OUTPUT_DIR}/images"
|
||
CONFIG_DIR="${OUTPUT_DIR}/config"
|
||
INIT_DIR="${OUTPUT_DIR}/pgvector-init"
|
||
|
||
APP_IMAGE="${APP_NAME}-app:${VERSION}"
|
||
PGVECTOR_IMAGE="${PGVECTOR_IMAGE:-pgvector/pgvector:pg16-trixie}"
|
||
REDIS_IMAGE="${REDIS_IMAGE:-redis:7-alpine}"
|
||
|
||
# 默认账号密码,可在执行脚本前通过环境变量覆盖
|
||
POSTGRES_DB="${POSTGRES_DB:-vector_db}"
|
||
POSTGRES_USER="${POSTGRES_USER:-vectoruser}"
|
||
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-vectorpass}"
|
||
POSTGRES_PORT="${POSTGRES_PORT:-5433}"
|
||
|
||
REDIS_PASSWORD="${REDIS_PASSWORD:-TNh8jiy8WpEweGLZ}"
|
||
REDIS_PORT="${REDIS_PORT:-26739}"
|
||
|
||
APP_PORT="${APP_PORT:-8000}"
|
||
|
||
print_step() {
|
||
printf '\n\033[1;34m==> %s\033[0m\n' "$1"
|
||
}
|
||
|
||
print_info() {
|
||
printf ' %s\n' "$1"
|
||
}
|
||
|
||
abort() {
|
||
printf '\033[1;31m[错误]\033[0m %s\n' "$1" >&2
|
||
exit 1
|
||
}
|
||
|
||
check_command() {
|
||
command -v "$1" >/dev/null 2>&1 || abort "未找到命令:$1,请先安装。"
|
||
}
|
||
|
||
APT_UPDATED=0
|
||
SUDO_CMD=""
|
||
if command -v sudo >/dev/null 2>&1 && [[ ${EUID:-0} -ne 0 ]]; then
|
||
SUDO_CMD="sudo"
|
||
fi
|
||
|
||
update_apt_once() {
|
||
if [[ ${APT_UPDATED} -eq 0 ]]; then
|
||
print_step "更新 apt 软件源"
|
||
${SUDO_CMD} env DEBIAN_FRONTEND=noninteractive apt-get update -y
|
||
APT_UPDATED=1
|
||
fi
|
||
}
|
||
|
||
install_with_apt() {
|
||
local package="$1"
|
||
local friendly="$2"
|
||
if ! command -v apt-get >/dev/null 2>&1; then
|
||
abort "缺少 ${friendly},但当前环境不支持自动安装(未检测到 apt-get)。"
|
||
fi
|
||
update_apt_once
|
||
print_step "安装 ${friendly}"
|
||
${SUDO_CMD} env DEBIAN_FRONTEND=noninteractive apt-get install -y "${package}"
|
||
}
|
||
|
||
ensure_maven() {
|
||
if ! command -v mvn >/dev/null 2>&1; then
|
||
install_with_apt "maven" "Maven"
|
||
fi
|
||
}
|
||
|
||
ensure_java21() {
|
||
local major=""
|
||
if command -v java >/dev/null 2>&1; then
|
||
local version_line
|
||
version_line="$(java -version 2>&1 | head -n 1 | cut -d'"' -f2)"
|
||
major="${version_line%%.*}"
|
||
if [[ "${major}" == "1" ]]; then
|
||
major="$(echo "${version_line}" | cut -d'.' -f2)"
|
||
fi
|
||
if [[ -n "${major}" && "${major}" -ge 21 ]]; then
|
||
return
|
||
fi
|
||
print_info "检测到 Java 版本 ${major:-unknown},低于 21,准备自动安装 OpenJDK 21。"
|
||
else
|
||
print_info "未检测到 Java,准备自动安装 OpenJDK 21。"
|
||
fi
|
||
install_with_apt "openjdk-21-jdk" "OpenJDK 21"
|
||
}
|
||
|
||
ensure_clean_dir() {
|
||
if [[ -d "$1" ]]; then
|
||
rm -rf "$1"
|
||
fi
|
||
mkdir -p "$1"
|
||
}
|
||
|
||
ensure_java21
|
||
ensure_maven
|
||
check_command docker
|
||
|
||
print_step "编译 Spring Boot 应用"
|
||
mvn -q -U -DskipTests clean package
|
||
|
||
JAR_FILE="$(find "${ROOT_DIR}/target" -maxdepth 1 -type f -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" | head -n 1)"
|
||
[[ -n "${JAR_FILE}" ]] || abort "未找到可用的 JAR 文件,请检查 Maven 构建结果。"
|
||
print_info "找到 JAR:${JAR_FILE}"
|
||
|
||
print_step "构建应用镜像 ${APP_IMAGE}"
|
||
docker build \
|
||
--build-arg VERSION="${VERSION}" \
|
||
--build-arg BUILD_TIME="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
||
-t "${APP_IMAGE}" \
|
||
"${ROOT_DIR}"
|
||
|
||
print_step "拉取依赖镜像"
|
||
docker pull "${PGVECTOR_IMAGE}"
|
||
docker pull "${REDIS_IMAGE}"
|
||
|
||
print_step "生成离线交付包目录 ${OUTPUT_DIR}"
|
||
ensure_clean_dir "${OUTPUT_DIR}"
|
||
mkdir -p "${IMAGES_DIR}" "${CONFIG_DIR}" "${INIT_DIR}"
|
||
|
||
if [[ -f "${ROOT_DIR}/src/main/resources/application.yml" ]]; then
|
||
cp "${ROOT_DIR}/src/main/resources/application.yml" "${CONFIG_DIR}/application.yml"
|
||
print_info "已复制默认 application.yml 到 ${CONFIG_DIR}/application.yml"
|
||
else
|
||
print_info "未找到 src/main/resources/application.yml,可手动放置到 ${CONFIG_DIR}/application.yml"
|
||
fi
|
||
|
||
cat > "${OUTPUT_DIR}/.env" <<EOF
|
||
# PostgreSQL (pgvector)
|
||
POSTGRES_DB=${POSTGRES_DB}
|
||
POSTGRES_USER=${POSTGRES_USER}
|
||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
POSTGRES_PORT=${POSTGRES_PORT}
|
||
|
||
# Redis
|
||
REDIS_PASSWORD=${REDIS_PASSWORD}
|
||
REDIS_PORT=${REDIS_PORT}
|
||
|
||
# 应用
|
||
APP_PORT=${APP_PORT}
|
||
EOF
|
||
|
||
cat > "${INIT_DIR}/001-create-vector-extension.sql" <<'EOF'
|
||
CREATE EXTENSION IF NOT EXISTS vector;
|
||
EOF
|
||
|
||
cat > "${OUTPUT_DIR}/docker-compose.yml" <<EOF
|
||
version: "3.8"
|
||
|
||
services:
|
||
pgvector:
|
||
image: ${PGVECTOR_IMAGE}
|
||
container_name: ${APP_NAME}-pgvector
|
||
restart: unless-stopped
|
||
environment:
|
||
POSTGRES_DB: \${POSTGRES_DB}
|
||
POSTGRES_USER: \${POSTGRES_USER}
|
||
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD}
|
||
PGDATA: /var/lib/postgresql/data/pgdata
|
||
ports:
|
||
- "\${POSTGRES_PORT}:5432"
|
||
volumes:
|
||
- pgvector_data:/var/lib/postgresql/data
|
||
- ./pgvector-init:/docker-entrypoint-initdb.d
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER} -d \${POSTGRES_DB}"]
|
||
interval: 20s
|
||
timeout: 5s
|
||
retries: 5
|
||
|
||
redis:
|
||
image: ${REDIS_IMAGE}
|
||
container_name: ${APP_NAME}-redis
|
||
restart: unless-stopped
|
||
command: ["redis-server", "--appendonly", "yes", "--requirepass", "\${REDIS_PASSWORD}"]
|
||
ports:
|
||
- "\${REDIS_PORT}:6379"
|
||
volumes:
|
||
- redis_data:/data
|
||
healthcheck:
|
||
test: ["CMD", "redis-cli", "-a", "\${REDIS_PASSWORD}", "ping"]
|
||
interval: 20s
|
||
timeout: 5s
|
||
retries: 5
|
||
|
||
app:
|
||
image: ${APP_IMAGE}
|
||
container_name: ${APP_NAME}-app
|
||
restart: unless-stopped
|
||
depends_on:
|
||
pgvector:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_healthy
|
||
ports:
|
||
- "\${APP_PORT}:8000"
|
||
environment:
|
||
SPRING_DATASOURCE_URL: jdbc:postgresql://pgvector:5432/\${POSTGRES_DB}
|
||
SPRING_DATASOURCE_USERNAME: \${POSTGRES_USER}
|
||
SPRING_DATASOURCE_PASSWORD: \${POSTGRES_PASSWORD}
|
||
SPRING_DATA_REDIS_HOST: redis
|
||
SPRING_DATA_REDIS_PORT: 6379
|
||
SPRING_DATA_REDIS_PASSWORD: \${REDIS_PASSWORD}
|
||
SERVER_PORT: 8000
|
||
volumes:
|
||
- ./config/application.yml:/app/config/application.yml:ro
|
||
- app_uploads:/app/uploads
|
||
- app_extracts:/app/extracts
|
||
- app_logs:/app/logs
|
||
|
||
volumes:
|
||
pgvector_data:
|
||
redis_data:
|
||
app_uploads:
|
||
app_extracts:
|
||
app_logs:
|
||
EOF
|
||
|
||
cat > "${OUTPUT_DIR}/README.md" <<EOF
|
||
# Docker 离线部署包(${APP_NAME} ${VERSION})
|
||
|
||
## 包含内容
|
||
- 应用镜像:${APP_IMAGE}
|
||
- pgvector 数据库镜像:${PGVECTOR_IMAGE}
|
||
- Redis 缓存镜像:${REDIS_IMAGE}
|
||
- docker-compose.yml / .env / config / pgvector-init 目录
|
||
|
||
## 使用步骤
|
||
1. 将整个目录拷贝到目标服务器并进入该目录:
|
||
\`\`\`
|
||
cd $(basename "${OUTPUT_DIR}")
|
||
\`\`\`
|
||
2. 导入离线镜像:
|
||
\`\`\`
|
||
docker load -i images/app-${VERSION}.tar
|
||
docker load -i images/pgvector.tar
|
||
docker load -i images/redis.tar
|
||
\`\`\`
|
||
3. 根据需要修改 \`.env\`、\`config/application.yml\`。
|
||
4. 启动:
|
||
\`\`\`
|
||
docker compose up -d
|
||
\`\`\`
|
||
5. 查看状态:
|
||
\`\`\`
|
||
docker compose ps
|
||
docker compose logs -f app
|
||
\`\`\`
|
||
|
||
缺省账号密码可通过执行脚本前设置环境变量覆盖,如:
|
||
\`\`\`
|
||
POSTGRES_PASSWORD=StrongPass REDIS_PASSWORD=Secret ./build-docker-bundle.sh
|
||
\`\`\`
|
||
EOF
|
||
|
||
print_step "保存离线镜像到 ${IMAGES_DIR}"
|
||
docker save "${APP_IMAGE}" -o "${IMAGES_DIR}/app-${VERSION}.tar"
|
||
docker save "${PGVECTOR_IMAGE}" -o "${IMAGES_DIR}/pgvector.tar"
|
||
docker save "${REDIS_IMAGE}" -o "${IMAGES_DIR}/redis.tar"
|
||
|
||
print_step "打包完成"
|
||
print_info "输出目录:${OUTPUT_DIR}"
|
||
print_info "镜像文件:${IMAGES_DIR}"
|
||
print_info "可根据需要编辑 ${OUTPUT_DIR}/.env 与 ${CONFIG_DIR}/application.yml 后,执行 docker compose up -d 启动。"
|
||
|