fix: podman can't return even succeed
This commit is contained in:
@@ -16,3 +16,4 @@ scripts/
|
|||||||
README.md
|
README.md
|
||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
.podman-compose.generated.yml
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -45,3 +45,4 @@ data/
|
|||||||
|
|
||||||
mariadb-data/
|
mariadb-data/
|
||||||
mariadb_data/
|
mariadb_data/
|
||||||
|
.podman-compose.generated.yml
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -85,11 +85,20 @@ Error: "lab-backend" is not a valid container, cannot be used as a dependency ..
|
|||||||
./scripts/podman-deploy.sh diagnose # 部署失败时输出诊断信息(镜像/容器/日志)
|
./scripts/podman-deploy.sh diagnose # 部署失败时输出诊断信息(镜像/容器/日志)
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本做了三件关键的事,规避 podman-compose 的兼容性问题:
|
脚本做了四件关键的事,规避 podman-compose 的兼容性问题:
|
||||||
|
|
||||||
1. `podman build` 显式构建 `lab-backend:latest` / `lab-frontend:latest`,避免短名拉取;
|
1. `podman build` 显式构建 `lab-backend:latest` / `lab-frontend:latest`,避免短名拉取;
|
||||||
2. `up -d --no-deps` 按「数据库 → 后端 → 前端」逐个启动并等待就绪,绕过 compose 依赖查找;
|
2. 启动前自动生成 `.podman-compose.generated.yml`(从 `docker-compose.yml` 中剔除 `depends_on`),
|
||||||
3. `down` 直接按容器名清理,避免对不存在容器的报错噪音。
|
避免旧版 podman-compose 在 `up` 时等待依赖健康检查而卡住;
|
||||||
|
3. 按「数据库 → 后端 → 前端」逐个启动并等待就绪(MariaDB 用 `healthcheck.sh`,后端/前端用 HTTP 探测);
|
||||||
|
4. `down` 直接按容器名清理,避免对不存在容器的报错噪音。
|
||||||
|
|
||||||
|
**防卡死**:所有外部命令都带超时,任何一步超时会打印原因并退出,不会无限等待。
|
||||||
|
可用环境变量调节:`CMD_TIMEOUT`(单条命令,默认 60s)、`BUILD_TIMEOUT`(构建,默认 1800s)、
|
||||||
|
`READY_TRIES`(就绪轮询次数,默认 60 次 × 2s);用 `DEBUG=1 ./scripts/podman-deploy.sh up`
|
||||||
|
可以看到每一步实际执行的命令,定位卡在哪一步。
|
||||||
|
|
||||||
|
镜像已构建过时可跳过构建:`./scripts/podman-deploy.sh up --no-build`。
|
||||||
|
|
||||||
若已有历史失败状态,先清理再重来:
|
若已有历史失败状态,先清理再重来:
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,20 @@
|
|||||||
# 使用 Podman 部署实验室预约平台
|
# 使用 Podman 部署实验室预约平台
|
||||||
#
|
#
|
||||||
# 用法:
|
# 用法:
|
||||||
# ./scripts/podman-deploy.sh up # 构建镜像并按 数据库->后端->前端 顺序启动(默认)
|
# ./scripts/podman-deploy.sh up [--no-build] # 构建镜像并按 数据库->后端->前端 顺序启动
|
||||||
# ./scripts/podman-deploy.sh build # 只构建镜像
|
# ./scripts/podman-deploy.sh build # 只构建镜像
|
||||||
# ./scripts/podman-deploy.sh down # 停止并删除容器(保留数据库数据卷)
|
# ./scripts/podman-deploy.sh down # 停止并删除容器(保留数据库数据目录)
|
||||||
# ./scripts/podman-deploy.sh down-v # 停止并删除数据目录(重建演示数据)
|
# ./scripts/podman-deploy.sh down-v # 停止并删除容器与数据目录(重建演示数据)
|
||||||
# ./scripts/podman-deploy.sh status # 查看容器状态
|
# ./scripts/podman-deploy.sh status # 查看容器状态
|
||||||
# ./scripts/podman-deploy.sh logs # 跟随查看日志
|
# ./scripts/podman-deploy.sh logs # 跟随查看后端日志
|
||||||
# ./scripts/podman-deploy.sh diagnose # 输出镜像/容器/日志等诊断信息
|
# ./scripts/podman-deploy.sh diagnose # 输出镜像/容器/日志等诊断信息
|
||||||
#
|
#
|
||||||
|
# 可选环境变量:
|
||||||
|
# CMD_TIMEOUT 单条 podman/compose 命令超时秒数(默认 60,防止卡死)
|
||||||
|
# BUILD_TIMEOUT 镜像构建超时秒数(默认 1800)
|
||||||
|
# READY_TRIES 等待就绪的轮询次数,每轮 2 秒(默认 60)
|
||||||
|
# DEBUG=1 输出每一步执行的命令(set -x)
|
||||||
|
#
|
||||||
# 背景:Podman 的 compose 实现(podman-compose)与 Docker Compose 在
|
# 背景:Podman 的 compose 实现(podman-compose)与 Docker Compose 在
|
||||||
# 「镜像名解析、depends_on 依赖查找」上存在差异,直接 up 常见报错:
|
# 「镜像名解析、depends_on 依赖查找」上存在差异,直接 up 常见报错:
|
||||||
# Error: short-name "lab-reservation_backend" did not resolve to an alias ...
|
# Error: short-name "lab-reservation_backend" did not resolve to an alias ...
|
||||||
@@ -18,38 +24,103 @@
|
|||||||
# 本脚本的处理方式:
|
# 本脚本的处理方式:
|
||||||
# 1) 先用 podman build 显式构建 lab-backend / lab-frontend 镜像,避免短名拉取;
|
# 1) 先用 podman build 显式构建 lab-backend / lab-frontend 镜像,避免短名拉取;
|
||||||
# 2) 再用 `up -d --no-deps` 逐个启动服务并等待就绪,绕过 compose 的依赖查找;
|
# 2) 再用 `up -d --no-deps` 逐个启动服务并等待就绪,绕过 compose 的依赖查找;
|
||||||
# 3) down 时直接按容器名清理,避免 podman-compose 对不存在容器的报错噪音。
|
# 3) down 时直接按容器名清理,避免 podman-compose 对不存在容器的报错噪音;
|
||||||
|
# 4) 启动前生成去除 depends_on 的临时 compose 文件,避免旧版 podman-compose
|
||||||
|
# 在 `up` 时等待依赖健康检查而卡死;
|
||||||
|
# 5) 所有外部命令都带超时,任何一步卡住都会明确报出并退出,不会无限等待。
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "${DEBUG:-0}" = "1" ]; then set -x; fi
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
cd "${PROJECT_DIR}"
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
# ---------- 可调参数 ----------
|
||||||
|
CMD_TIMEOUT="${CMD_TIMEOUT:-60}"
|
||||||
|
BUILD_TIMEOUT="${BUILD_TIMEOUT:-1800}"
|
||||||
|
READY_TRIES="${READY_TRIES:-60}"
|
||||||
|
|
||||||
# ---------- 服务/容器名 ----------
|
# ---------- 服务/容器名 ----------
|
||||||
MARIADB_CONTAINER="lab-mariadb"
|
MARIADB_CONTAINER="lab-mariadb"
|
||||||
BACKEND_CONTAINER="lab-backend"
|
BACKEND_CONTAINER="lab-backend"
|
||||||
FRONTEND_CONTAINER="lab-frontend"
|
FRONTEND_CONTAINER="lab-frontend"
|
||||||
NETWORK_NAME="lab-reservation_lab-net"
|
NETWORK_NAME="lab-reservation_lab-net"
|
||||||
|
|
||||||
|
# ---------- 日志与错误追踪 ----------
|
||||||
|
log() {
|
||||||
|
printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 出错时给出失败信息,便于定位卡住/失败的位置
|
||||||
|
on_error() {
|
||||||
|
local exit_code=$?
|
||||||
|
log "执行失败(退出码 ${exit_code}),出错位置约在脚本第 ${1:-?} 行"
|
||||||
|
log "可执行 '$0 diagnose' 查看容器与日志,或 DEBUG=1 $0 up 观察每一步命令"
|
||||||
|
exit "${exit_code}"
|
||||||
|
}
|
||||||
|
trap 'on_error ${LINENO}' ERR
|
||||||
|
|
||||||
|
# ---------- 带超时执行命令 ----------
|
||||||
|
# 说明:macOS 默认没有 timeout 命令,这里提供纯 bash 兜底实现。
|
||||||
|
# 返回 124 表示超时。
|
||||||
|
run_with_timeout() {
|
||||||
|
local seconds="$1"
|
||||||
|
shift
|
||||||
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
|
timeout "${seconds}" "$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
if command -v gtimeout >/dev/null 2>&1; then
|
||||||
|
gtimeout "${seconds}" "$@"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
"$@" &
|
||||||
|
local pid=$!
|
||||||
|
local waited=0
|
||||||
|
while kill -0 "${pid}" 2>/dev/null; do
|
||||||
|
if [ "${waited}" -ge "${seconds}" ]; then
|
||||||
|
kill -TERM "${pid}" 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
kill -KILL "${pid}" 2>/dev/null || true
|
||||||
|
wait "${pid}" 2>/dev/null || true
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
wait "${pid}"
|
||||||
|
}
|
||||||
|
|
||||||
# ---------- 检查 podman ----------
|
# ---------- 检查 podman ----------
|
||||||
if ! command -v podman >/dev/null 2>&1; then
|
if ! command -v podman >/dev/null 2>&1; then
|
||||||
echo "错误:未找到 podman,请先安装:https://podman.io/docs/installation" >&2
|
log "错误:未找到 podman,请先安装:https://podman.io/docs/installation"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- 选择可用的 compose 命令 ----------
|
# ---------- 选择可用的 compose 命令 ----------
|
||||||
if podman compose version >/dev/null 2>&1; then
|
COMPOSE=()
|
||||||
|
COMPOSE_FILE_ARGS=()
|
||||||
|
compose_available() {
|
||||||
|
if run_with_timeout 30 podman compose version >/dev/null 2>&1; then
|
||||||
COMPOSE=(podman compose)
|
COMPOSE=(podman compose)
|
||||||
elif command -v podman-compose >/dev/null 2>&1; then
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v podman-compose >/dev/null 2>&1; then
|
||||||
COMPOSE=(podman-compose)
|
COMPOSE=(podman-compose)
|
||||||
else
|
return 0
|
||||||
echo "错误:未找到 podman compose / podman-compose,请安装 podman-compose:" >&2
|
fi
|
||||||
echo " pipx install podman-compose 或 pip3 install --user podman-compose" >&2
|
return 1
|
||||||
|
}
|
||||||
|
if ! compose_available; then
|
||||||
|
log "错误:未找到可用的 podman compose / podman-compose,请安装 podman-compose:"
|
||||||
|
log " pipx install podman-compose 或 pip3 install --user podman-compose"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
log "使用 compose 命令:${COMPOSE[*]}"
|
||||||
|
|
||||||
# ---------- 读取 .env 端口配置(不 source,避免值中含空格被当作命令执行) ----------
|
# ---------- 读取 .env 配置(不 source,避免值中含空格被当作命令执行) ----------
|
||||||
read_env() {
|
read_env() {
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
grep -E "^$1=" .env 2>/dev/null | tail -n 1 | cut -d= -f2- | tr -d '"' || true
|
grep -E "^$1=" .env 2>/dev/null | tail -n 1 | cut -d= -f2- | tr -d '"' || true
|
||||||
@@ -65,93 +136,131 @@ DATA_DIR="${DATA_DIR%/}"
|
|||||||
|
|
||||||
# ---------- 通用检查函数(inspect 在 podman / docker 下语义一致) ----------
|
# ---------- 通用检查函数(inspect 在 podman / docker 下语义一致) ----------
|
||||||
image_exists() {
|
image_exists() {
|
||||||
podman image inspect "$1" >/dev/null 2>&1
|
run_with_timeout "${CMD_TIMEOUT}" podman image inspect "$1" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
container_exists() {
|
container_exists() {
|
||||||
podman inspect "$1" >/dev/null 2>&1
|
run_with_timeout "${CMD_TIMEOUT}" podman inspect "$1" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- 构建镜像 ----------
|
# ---------- 构建镜像 ----------
|
||||||
build_images() {
|
build_images() {
|
||||||
echo "==> 构建后端镜像 lab-backend:latest(首次构建需拉取 Maven 依赖,可能较慢)"
|
log "构建后端镜像 lab-backend:latest(首次构建需拉取 Maven 依赖,可能较慢)"
|
||||||
podman build -t lab-backend:latest .
|
if ! run_with_timeout "${BUILD_TIMEOUT}" podman build -t lab-backend:latest .; then
|
||||||
echo "==> 构建前端镜像 lab-frontend:latest"
|
log "错误:后端镜像构建失败或超时(BUILD_TIMEOUT=${BUILD_TIMEOUT}s)"
|
||||||
podman build -t lab-frontend:latest ./frontend
|
|
||||||
if ! image_exists lab-backend:latest || ! image_exists lab-frontend:latest; then
|
|
||||||
echo "错误:镜像构建失败,请查看上方构建日志" >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "==> 镜像构建完成"
|
log "构建前端镜像 lab-frontend:latest"
|
||||||
|
if ! run_with_timeout "${BUILD_TIMEOUT}" podman build -t lab-frontend:latest ./frontend; then
|
||||||
|
log "错误:前端镜像构建失败或超时(BUILD_TIMEOUT=${BUILD_TIMEOUT}s)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! image_exists lab-backend:latest || ! image_exists lab-frontend:latest; then
|
||||||
|
log "错误:镜像未生成,请查看上方构建日志"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "镜像构建完成"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- 启动单个服务(--no-deps 绕过 compose 依赖查找,失败则回退) ----------
|
# ---------- 生成 Podman 专用 compose 文件 ----------
|
||||||
|
# podman-compose 在 `up` 处理 depends_on 的 service_healthy 条件时可能长时间阻塞,
|
||||||
|
# 这里把 depends_on 段落剔除生成临时文件(服务启动顺序由脚本自己控制),
|
||||||
|
# 既保留 docker-compose.yml 作为唯一配置源,又规避 podman-compose 的兼容问题。
|
||||||
|
GENERATED_COMPOSE=".podman-compose.generated.yml"
|
||||||
|
prepare_compose_file() {
|
||||||
|
local base="docker-compose.yml"
|
||||||
|
if [ ! -f "${base}" ]; then
|
||||||
|
log "错误:未找到 ${base}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
awk '
|
||||||
|
/^ depends_on:[[:space:]]*$/ { skip=1; next }
|
||||||
|
skip && /^ / { next }
|
||||||
|
{ skip=0; print }
|
||||||
|
' "${base}" > "${GENERATED_COMPOSE}"
|
||||||
|
if ! grep -q "container_name: ${BACKEND_CONTAINER}" "${GENERATED_COMPOSE}"; then
|
||||||
|
log "错误:生成 ${GENERATED_COMPOSE} 失败,请检查 ${base} 格式"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "已生成 ${GENERATED_COMPOSE}(已移除 depends_on,规避 podman-compose 依赖等待)"
|
||||||
|
COMPOSE_FILE_ARGS=(-f "${GENERATED_COMPOSE}")
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- 启动单个服务 ----------
|
||||||
start_service() {
|
start_service() {
|
||||||
local service="$1"
|
local service="$1"
|
||||||
if ! "${COMPOSE[@]}" up -d --no-deps "${service}" 2>/dev/null; then
|
log "启动服务:${service}"
|
||||||
"${COMPOSE[@]}" up -d "${service}"
|
run_with_timeout "${CMD_TIMEOUT}" "${COMPOSE[@]}" "${COMPOSE_FILE_ARGS[@]}" up -d "${service}"
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- 等待 MySQL/MariaDB 就绪 ----------
|
# ---------- 等待 MySQL/MariaDB 就绪 ----------
|
||||||
wait_mariadb() {
|
wait_mariadb() {
|
||||||
local tries=60
|
local tries="${READY_TRIES}" waited=0
|
||||||
echo " 等待 MariaDB 就绪..."
|
log "等待 MariaDB 就绪(最多 $((tries * 2)) 秒)..."
|
||||||
for _ in $(seq 1 "${tries}"); do
|
while [ "${waited}" -lt "${tries}" ]; do
|
||||||
if podman exec "${MARIADB_CONTAINER}" healthcheck.sh --connect --innodb_initialized >/dev/null 2>&1; then
|
if run_with_timeout 15 podman exec "${MARIADB_CONTAINER}" \
|
||||||
echo " MariaDB 已就绪"
|
healthcheck.sh --connect --innodb_initialized >/dev/null 2>&1; then
|
||||||
|
log "MariaDB 已就绪"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
sleep 2
|
sleep 2
|
||||||
|
waited=$((waited + 1))
|
||||||
done
|
done
|
||||||
echo " 警告:MariaDB 未在预期时间内就绪" >&2
|
log "警告:MariaDB 未在预期时间内就绪,继续尝试启动后续服务"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- 等待 HTTP 服务响应 ----------
|
# ---------- 等待 HTTP 服务响应 ----------
|
||||||
wait_http() {
|
wait_http() {
|
||||||
local name="$1" url="$2" tries="${3:-60}" code
|
local name="$1" url="$2" tries="${3:-${READY_TRIES}}" waited=0 code
|
||||||
echo " 等待 ${name} 响应..."
|
log "等待 ${name} 响应(最多 $((tries * 2)) 秒):${url}"
|
||||||
for _ in $(seq 1 "${tries}"); do
|
while [ "${waited}" -lt "${tries}" ]; do
|
||||||
code="$(curl -s -o /dev/null -w '%{http_code}' "${url}" || true)"
|
code="$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 3 --max-time 8 "${url}" || true)"
|
||||||
if [ -n "${code}" ] && [ "${code}" != "000" ]; then
|
if [ -n "${code}" ] && [ "${code}" != "000" ]; then
|
||||||
echo " ${name} 已就绪:${url}(HTTP ${code})"
|
log "${name} 已就绪:${url}(HTTP ${code})"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
sleep 2
|
sleep 2
|
||||||
|
waited=$((waited + 1))
|
||||||
done
|
done
|
||||||
echo " 警告:${name} 未在预期时间内响应:${url}" >&2
|
log "警告:${name} 未在预期时间内响应:${url}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- 打印部署结果 ----------
|
# ---------- 打印部署结果 ----------
|
||||||
print_result() {
|
print_result() {
|
||||||
echo
|
log "部署完成:"
|
||||||
echo "部署完成:"
|
|
||||||
echo " 前端地址:http://localhost:${WEB_PORT}"
|
echo " 前端地址:http://localhost:${WEB_PORT}"
|
||||||
echo " 后端地址:http://localhost:${BACKEND_PORT}"
|
echo " 后端地址:http://localhost:${BACKEND_PORT}"
|
||||||
echo " 演示账号:admin/admin123、teacher1/123456、student1/123456"
|
echo " 演示账号:admin/admin123、teacher1/123456、student1/123456"
|
||||||
echo
|
echo
|
||||||
podman ps --filter "name=lab-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
show_status
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- 查看状态 ----------
|
||||||
|
show_status() {
|
||||||
|
run_with_timeout "${CMD_TIMEOUT}" podman ps -a --filter "name=lab-" \
|
||||||
|
--format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- 诊断信息 ----------
|
# ---------- 诊断信息 ----------
|
||||||
diagnose() {
|
diagnose() {
|
||||||
echo "== podman 版本 =="
|
log "== podman 版本 =="
|
||||||
podman version --format '{{.Client.Version}} (server {{.Server.Version}})' 2>/dev/null || podman --version
|
run_with_timeout "${CMD_TIMEOUT}" podman version 2>&1 | head -5 || podman --version
|
||||||
echo
|
echo
|
||||||
echo "== compose 命令 =="
|
log "== compose 命令 =="
|
||||||
echo "${COMPOSE[*]}"
|
echo "${COMPOSE[*]}"
|
||||||
echo
|
echo
|
||||||
echo "== 相关镜像 =="
|
log "== 相关镜像 =="
|
||||||
podman images --filter "reference=lab-*" --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Created}}"
|
run_with_timeout "${CMD_TIMEOUT}" podman images --format \
|
||||||
|
"table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Created}}" 2>/dev/null | grep -E "lab-|REPOSITORY" || true
|
||||||
echo
|
echo
|
||||||
echo "== 相关容器 =="
|
log "== 相关容器 =="
|
||||||
podman ps -a --filter "name=lab-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
show_status || true
|
||||||
echo
|
echo
|
||||||
for container in "${MARIADB_CONTAINER}" "${BACKEND_CONTAINER}" "${FRONTEND_CONTAINER}"; do
|
for container in "${MARIADB_CONTAINER}" "${BACKEND_CONTAINER}" "${FRONTEND_CONTAINER}"; do
|
||||||
if container_exists "${container}"; then
|
if container_exists "${container}"; then
|
||||||
echo "== ${container} 最近日志 =="
|
log "== ${container} 最近日志 =="
|
||||||
podman logs --tail 30 "${container}" 2>&1 || true
|
run_with_timeout "${CMD_TIMEOUT}" podman logs --tail 30 "${container}" 2>&1 || true
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -160,12 +269,17 @@ diagnose() {
|
|||||||
# ---------- 停止并清理 ----------
|
# ---------- 停止并清理 ----------
|
||||||
down() {
|
down() {
|
||||||
# 静默调用 compose down(不存在容器时的报错噪音直接忽略),再按容器名兜底清理
|
# 静默调用 compose down(不存在容器时的报错噪音直接忽略),再按容器名兜底清理
|
||||||
"${COMPOSE[@]}" down >/dev/null 2>&1 || true
|
local compose_args=()
|
||||||
|
if [ -f "${GENERATED_COMPOSE}" ]; then
|
||||||
|
compose_args=(-f "${GENERATED_COMPOSE}")
|
||||||
|
fi
|
||||||
|
run_with_timeout "${CMD_TIMEOUT}" "${COMPOSE[@]}" "${compose_args[@]}" down >/dev/null 2>&1 || true
|
||||||
for container in "${FRONTEND_CONTAINER}" "${BACKEND_CONTAINER}" "${MARIADB_CONTAINER}"; do
|
for container in "${FRONTEND_CONTAINER}" "${BACKEND_CONTAINER}" "${MARIADB_CONTAINER}"; do
|
||||||
podman rm -f "${container}" >/dev/null 2>&1 || true
|
run_with_timeout "${CMD_TIMEOUT}" podman rm -f "${container}" >/dev/null 2>&1 || true
|
||||||
done
|
done
|
||||||
podman network rm "${NETWORK_NAME}" >/dev/null 2>&1 || true
|
run_with_timeout "${CMD_TIMEOUT}" podman network rm "${NETWORK_NAME}" >/dev/null 2>&1 || true
|
||||||
echo "已停止并删除容器(数据库数据卷保留)"
|
rm -f "${GENERATED_COMPOSE}"
|
||||||
|
log "已停止并删除容器(数据库数据目录保留)"
|
||||||
}
|
}
|
||||||
|
|
||||||
down_volumes() {
|
down_volumes() {
|
||||||
@@ -173,10 +287,11 @@ down_volumes() {
|
|||||||
|
|
||||||
# 兼容历史配置:清理名为 *mariadb-data 的命名卷
|
# 兼容历史配置:清理名为 *mariadb-data 的命名卷
|
||||||
local volumes
|
local volumes
|
||||||
volumes="$(podman volume ls --format '{{.Name}}' 2>/dev/null | grep -E 'mariadb-data$' || true)"
|
volumes="$(run_with_timeout "${CMD_TIMEOUT}" podman volume ls --format '{{.Name}}' 2>/dev/null \
|
||||||
|
| grep -E 'mariadb-data$' || true)"
|
||||||
if [ -n "${volumes}" ]; then
|
if [ -n "${volumes}" ]; then
|
||||||
echo "${volumes}" | while read -r volume; do
|
echo "${volumes}" | while read -r volume; do
|
||||||
podman volume rm -f "${volume}" >/dev/null 2>&1 || true
|
run_with_timeout "${CMD_TIMEOUT}" podman volume rm -f "${volume}" >/dev/null 2>&1 || true
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -187,38 +302,60 @@ down_volumes() {
|
|||||||
"${PROJECT_DIR}"/*)
|
"${PROJECT_DIR}"/*)
|
||||||
if [ -d "${absolute_dir}" ]; then
|
if [ -d "${absolute_dir}" ]; then
|
||||||
rm -rf "${absolute_dir}"
|
rm -rf "${absolute_dir}"
|
||||||
echo "已删除数据库数据目录 ${absolute_dir},下次启动将重建演示数据"
|
log "已删除数据库数据目录 ${absolute_dir},下次启动将重建演示数据"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"")
|
"")
|
||||||
echo "提示:数据目录 ${DATA_DIR} 无法解析,请手动检查"
|
log "提示:数据目录 ${DATA_DIR} 无法解析,请手动检查"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "提示:数据目录 ${DATA_DIR} 不在项目目录内,出于安全考虑未自动删除,请手动处理"
|
log "提示:数据目录 ${DATA_DIR} 不在项目目录内,出于安全考虑未自动删除,请手动处理"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------- 启动流程 ----------
|
||||||
|
up() {
|
||||||
|
local skip_build="${1:-}"
|
||||||
|
if [ "${skip_build}" != "--no-build" ]; then
|
||||||
|
build_images
|
||||||
|
else
|
||||||
|
log "跳过镜像构建(--no-build)"
|
||||||
|
if ! image_exists lab-backend:latest || ! image_exists lab-frontend:latest; then
|
||||||
|
log "错误:本地缺少 lab-backend / lab-frontend 镜像,请先执行 '$0 build'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
prepare_compose_file
|
||||||
|
|
||||||
|
log "1/3 启动数据库"
|
||||||
|
start_service mariadb
|
||||||
|
wait_mariadb || true
|
||||||
|
|
||||||
|
log "2/3 启动后端"
|
||||||
|
start_service backend
|
||||||
|
if ! wait_http "后端" "http://localhost:${BACKEND_PORT}/api/labs"; then
|
||||||
|
log "后端未就绪,输出诊断信息:"
|
||||||
|
diagnose
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "3/3 启动前端"
|
||||||
|
start_service frontend
|
||||||
|
if ! wait_http "前端" "http://localhost:${WEB_PORT}/"; then
|
||||||
|
log "前端未就绪,输出诊断信息:"
|
||||||
|
diagnose
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_result
|
||||||
|
}
|
||||||
|
|
||||||
ACTION="${1:-up}"
|
ACTION="${1:-up}"
|
||||||
case "${ACTION}" in
|
case "${ACTION}" in
|
||||||
up)
|
up)
|
||||||
build_images
|
up "${2:-}"
|
||||||
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)
|
||||||
build_images
|
build_images
|
||||||
@@ -230,16 +367,17 @@ case "${ACTION}" in
|
|||||||
down_volumes
|
down_volumes
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
podman ps -a --filter "name=lab-" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
show_status
|
||||||
;;
|
;;
|
||||||
logs)
|
logs)
|
||||||
|
log "按 Ctrl+C 退出日志跟随"
|
||||||
podman logs -f --tail=100 "${BACKEND_CONTAINER}"
|
podman logs -f --tail=100 "${BACKEND_CONTAINER}"
|
||||||
;;
|
;;
|
||||||
diagnose)
|
diagnose)
|
||||||
diagnose
|
diagnose
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "用法:$0 [up|build|down|down-v|status|logs|diagnose]" >&2
|
echo "用法:$0 [up [--no-build]|build|down|down-v|status|logs|diagnose]" >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Reference in New Issue
Block a user