Files
lab/scripts/podman-deploy.sh
2026-09-21 13:23:30 +08:00

246 lines
8.2 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.
#!/usr/bin/env bash
# =====================================================================
# 使用 Podman 部署实验室预约平台
#
# 用法:
# ./scripts/podman-deploy.sh up # 构建镜像并按 数据库->后端->前端 顺序启动(默认)
# ./scripts/podman-deploy.sh build # 只构建镜像
# ./scripts/podman-deploy.sh down # 停止并删除容器(保留数据库数据卷)
# ./scripts/podman-deploy.sh down-v # 停止并删除数据目录(重建演示数据)
# ./scripts/podman-deploy.sh status # 查看容器状态
# ./scripts/podman-deploy.sh logs # 跟随查看日志
# ./scripts/podman-deploy.sh diagnose # 输出镜像/容器/日志等诊断信息
#
# 背景:Podman 的 compose 实现(podman-compose)与 Docker Compose 在
# 「镜像名解析、depends_on 依赖查找」上存在差异,直接 up 常见报错:
# Error: short-name "lab-reservation_backend" did not resolve to an alias ...
# Error: "lab-backend" is not a valid container, cannot be used as a dependency
# 本脚本的处理方式:
# 1) 先用 podman build 显式构建 lab-backend / lab-frontend 镜像,避免短名拉取;
# 2) 再用 `up -d --no-deps` 逐个启动服务并等待就绪,绕过 compose 的依赖查找;
# 3) down 时直接按容器名清理,避免 podman-compose 对不存在容器的报错噪音。
# =====================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${PROJECT_DIR}"
# ---------- 服务/容器名 ----------
MARIADB_CONTAINER="lab-mariadb"
BACKEND_CONTAINER="lab-backend"
FRONTEND_CONTAINER="lab-frontend"
NETWORK_NAME="lab-reservation_lab-net"
# ---------- 检查 podman ----------
if ! command -v podman >/dev/null 2>&1; then
echo "错误:未找到 podman,请先安装:https://podman.io/docs/installation" >&2
exit 1
fi
# ---------- 选择可用的 compose 命令 ----------
if podman compose version >/dev/null 2>&1; then
COMPOSE=(podman compose)
elif command -v podman-compose >/dev/null 2>&1; then
COMPOSE=(podman-compose)
else
echo "错误:未找到 podman compose / podman-compose,请安装 podman-compose:" >&2
echo " pipx install podman-compose 或 pip3 install --user podman-compose" >&2
exit 1
fi
# ---------- 读取 .env 端口配置(不 source,避免值中含空格被当作命令执行) ----------
read_env() {
if [ -f .env ]; then
grep -E "^$1=" .env 2>/dev/null | tail -n 1 | cut -d= -f2- | tr -d '"' || true
fi
}
WEB_PORT="$(read_env WEB_PORT)"
BACKEND_PORT="$(read_env BACKEND_PORT)"
DATA_DIR_ENV="$(read_env DATA_DIR)"
WEB_PORT="${WEB_PORT:-8088}"
BACKEND_PORT="${BACKEND_PORT:-8080}"
DATA_DIR="${DATA_DIR:-${DATA_DIR_ENV:-./mariadb-data}}"
DATA_DIR="${DATA_DIR%/}"
# ---------- 通用检查函数(inspect 在 podman / docker 下语义一致) ----------
image_exists() {
podman image inspect "$1" >/dev/null 2>&1
}
container_exists() {
podman inspect "$1" >/dev/null 2>&1
}
# ---------- 构建镜像 ----------
build_images() {
echo "==> 构建后端镜像 lab-backend:latest(首次构建需拉取 Maven 依赖,可能较慢)"
podman build -t lab-backend:latest .
echo "==> 构建前端镜像 lab-frontend:latest"
podman build -t lab-frontend:latest ./frontend
if ! image_exists lab-backend:latest || ! image_exists lab-frontend:latest; then
echo "错误:镜像构建失败,请查看上方构建日志" >&2
exit 1
fi
echo "==> 镜像构建完成"
}
# ---------- 启动单个服务(--no-deps 绕过 compose 依赖查找,失败则回退) ----------
start_service() {
local service="$1"
if ! "${COMPOSE[@]}" up -d --no-deps "${service}" 2>/dev/null; then
"${COMPOSE[@]}" up -d "${service}"
fi
}
# ---------- 等待 MySQL/MariaDB 就绪 ----------
wait_mariadb() {
local tries=60
echo " 等待 MariaDB 就绪..."
for _ in $(seq 1 "${tries}"); do
if podman exec "${MARIADB_CONTAINER}" healthcheck.sh --connect --innodb_initialized >/dev/null 2>&1; then
echo " MariaDB 已就绪"
return 0
fi
sleep 2
done
echo " 警告:MariaDB 未在预期时间内就绪" >&2
return 1
}
# ---------- 等待 HTTP 服务响应 ----------
wait_http() {
local name="$1" url="$2" tries="${3:-60}" code
echo " 等待 ${name} 响应..."
for _ in $(seq 1 "${tries}"); do
code="$(curl -s -o /dev/null -w '%{http_code}' "${url}" || true)"
if [ -n "${code}" ] && [ "${code}" != "000" ]; then
echo " ${name} 已就绪:${url}(HTTP ${code})"
return 0
fi
sleep 2
done
echo " 警告:${name} 未在预期时间内响应:${url}" >&2
return 1
}
# ---------- 打印部署结果 ----------
print_result() {
echo
echo "部署完成:"
echo " 前端地址:http://localhost:${WEB_PORT}"
echo " 后端地址:http://localhost:${BACKEND_PORT}"
echo " 演示账号:admin/admin123、teacher1/123456、student1/123456"
echo
podman ps --filter "name=lab-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
}
# ---------- 诊断信息 ----------
diagnose() {
echo "== podman 版本 =="
podman version --format '{{.Client.Version}} (server {{.Server.Version}})' 2>/dev/null || podman --version
echo
echo "== compose 命令 =="
echo "${COMPOSE[*]}"
echo
echo "== 相关镜像 =="
podman images --filter "reference=lab-*" --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Created}}"
echo
echo "== 相关容器 =="
podman ps -a --filter "name=lab-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo
for container in "${MARIADB_CONTAINER}" "${BACKEND_CONTAINER}" "${FRONTEND_CONTAINER}"; do
if container_exists "${container}"; then
echo "== ${container} 最近日志 =="
podman logs --tail 30 "${container}" 2>&1 || true
echo
fi
done
}
# ---------- 停止并清理 ----------
down() {
# 静默调用 compose down(不存在容器时的报错噪音直接忽略),再按容器名兜底清理
"${COMPOSE[@]}" down >/dev/null 2>&1 || true
for container in "${FRONTEND_CONTAINER}" "${BACKEND_CONTAINER}" "${MARIADB_CONTAINER}"; do
podman rm -f "${container}" >/dev/null 2>&1 || true
done
podman network rm "${NETWORK_NAME}" >/dev/null 2>&1 || true
echo "已停止并删除容器(数据库数据卷保留)"
}
down_volumes() {
down
# 兼容历史配置:清理名为 *mariadb-data 的命名卷
local volumes
volumes="$(podman volume ls --format '{{.Name}}' 2>/dev/null | grep -E 'mariadb-data$' || true)"
if [ -n "${volumes}" ]; then
echo "${volumes}" | while read -r volume; do
podman volume rm -f "${volume}" >/dev/null 2>&1 || true
done
fi
# 当前配置:数据库数据持久化在宿主机目录(DATA_DIR,默认 ./mariadb-data)
local absolute_dir
absolute_dir="$(cd "$(dirname "${DATA_DIR}")" 2>/dev/null && pwd)/$(basename "${DATA_DIR}")" || absolute_dir=""
case "${absolute_dir}" in
"${PROJECT_DIR}"/*)
if [ -d "${absolute_dir}" ]; then
rm -rf "${absolute_dir}"
echo "已删除数据库数据目录 ${absolute_dir},下次启动将重建演示数据"
fi
;;
"")
echo "提示:数据目录 ${DATA_DIR} 无法解析,请手动检查"
;;
*)
echo "提示:数据目录 ${DATA_DIR} 不在项目目录内,出于安全考虑未自动删除,请手动处理"
;;
esac
}
ACTION="${1:-up}"
case "${ACTION}" in
up)
build_images
echo "==> 1/3 启动数据库"
start_service mariadb
wait_mariadb || true
echo "==> 2/3 启动后端"
start_service backend
if ! wait_http "后端" "http://localhost:${BACKEND_PORT}/api/labs" 60; then
diagnose
exit 1
fi
echo "==> 3/3 启动前端"
start_service frontend
if ! wait_http "前端" "http://localhost:${WEB_PORT}/" 60; then
diagnose
exit 1
fi
print_result
;;
build)
build_images
;;
down)
down
;;
down-v)
down_volumes
;;
status)
podman ps -a --filter "name=lab-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
;;
logs)
podman logs -f --tail=100 "${BACKEND_CONTAINER}"
;;
diagnose)
diagnose
;;
*)
echo "用法:$0 [up|build|down|down-v|status|logs|diagnose]" >&2
exit 1
;;
esac