# JNPF 整栈离线交付包 现场操作入口（本文件随包分发为 Makefile；流程详见 INSTALL.md）
# 现场机器不需要源码、不需要外网：镜像全部来自 images/ 目录的离线 tar。
.PHONY: help verify load preflight up down restart ps logs wait smoke

help: ## 显示本帮助
	@grep -E '^[a-z-]+:.*##' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf "  make %-10s %s\n", $$1, $$2}'

# up 强制前置 preflight：空卷/空库/缺 .env 键在 compose up 前拦下并给修复指引；
# 确要跳过（自担风险）：SKIP_PREFLIGHT=1 make up
preflight: ## 部署自举校验：docker/内存/.env 必填键/数据卷/四库 schema/种子数据/业务角色
	@if [ "$(SKIP_PREFLIGHT)" = "1" ]; then echo "跳过 preflight（SKIP_PREFLIGHT=1）"; \
	else ./docker/preflight.sh; fi

verify: ## 校验交付包完整性（sha256，镜像 tar 较大需数分钟）
	@command -v sha256sum >/dev/null && sha256sum -c SHA256SUMS || shasum -a 256 -c SHA256SUMS
	@echo "完整性校验通过 ✓"

load: ## docker load 全部离线镜像（运行镜像 + 迁移辅助镜像）
	docker load -i images/cx-images.tar.gz
	@test -f images/cx-migration-tools.tar.gz && docker load -i images/cx-migration-tools.tar.gz || true

up: preflight ## 用已 load 的本地镜像起全栈（绝不构建/拉取；冷启动 5-8 分钟后 make wait）
	docker compose up -d --no-build --pull never
	docker compose ps

wait: ## 等待全部带健康检查的容器 healthy（业务 start_period 180s，冷启动约 5-8 分钟）
	@for i in $$(seq 1 60); do \
	  total=$$(docker compose ps --format '{{.Name}} {{.Health}}' | awk 'NF==2' | wc -l | tr -d ' '); \
	  ok=$$(docker compose ps --format '{{.Name}} {{.Health}}' | awk '$$2=="healthy"' | wc -l | tr -d ' '); \
	  echo "healthy $$ok/$$total"; \
	  [ -n "$$total" ] && [ "$$total" != "0" ] && [ "$$ok" = "$$total" ] && { echo "全部 healthy ✓"; exit 0; }; \
	  sleep 10; \
	done; echo "超时：仍有容器未 healthy，docker compose ps 查看（L024：先确认内存 ≥12GB）"; exit 1

# --profile localdb 不能省：compose 的 down 只覆盖当前启用 profile 内的服务，漏带会把栈内 PG
# 原样留着跑。带上它，无论本环境用不用栈内 PG，停栈都是干净的（没起过则是空操作）。
down: ## 全部停止并移除容器（数据安全：pgdata 是 external 卷，永不删除）
	docker compose --profile localdb down

restart: ## 重启全栈容器
	docker compose restart

ps: ## 查看容器状态与健康
	docker compose ps

logs: ## 跟踪某服务日志：make logs s=cx-lims
	docker compose logs -f $(s)

smoke: ## 冒烟三段：gateway 登录 + 前端静态 200 + 经 80 反代登录（依赖 curl/openssl/md5sum 或 md5）
	@set -o pipefail; \
	MD5=$$(command -v md5 >/dev/null 2>&1 && echo md5 || echo md5sum); \
	hash5() { if [ "$$MD5" = md5 ]; then md5; else md5sum | cut -d' ' -f1; fi; }; \
	KEYHEX=$$(printf '%s' "$$(printf '%s' 'EY8WePvjM5GGwQzn' | hash5)" | xxd -p | tr -d '\n'); \
	ENC=$$(printf '%s' "$$(printf '%s' '123456' | hash5)" | openssl enc -aes-256-ecb -K "$$KEYHEX" -nosalt | xxd -p | tr -d '\n'); \
	RESP=$$(curl -fsS -X POST http://127.0.0.1:30000/api/oauth/Login -H 'Content-Type: application/x-www-form-urlencoded' \
	  --data-urlencode account=admin --data-urlencode "password=$$ENC" \
	  --data-urlencode grant_type=password --data-urlencode origin=password); \
	echo "$$RESP" | head -c 200; echo; \
	echo "$$RESP" | grep -q '"code":200' || { echo "冒烟失败：登录未返回 code 200（gateway 30000 直连）"; exit 1; }; \
	curl -fsS -o /dev/null http://127.0.0.1:80/ || { echo "冒烟失败：前端静态入口 http://127.0.0.1:80/ 不通"; exit 1; }; \
	RESP2=$$(curl -fsS -X POST http://127.0.0.1:80/api/oauth/Login -H 'Content-Type: application/x-www-form-urlencoded' \
	  --data-urlencode account=admin --data-urlencode "password=$$ENC" \
	  --data-urlencode grant_type=password --data-urlencode origin=password); \
	echo "$$RESP2" | grep -q '"code":200' || { echo "冒烟失败：经前端 80 反代登录未返回 code 200"; exit 1; }; \
	echo "冒烟通过 ✓（gateway 直连 + 前端静态 + 80 反代登录）"
