160 lines
5.6 KiB
Bash
Executable File
160 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
HOST="${STUDIO_HOST:-10.10.0.2}"
|
|
GRAY_WEB_PORT="${GRAY_WEB_PORT:-15174}"
|
|
GRAY_API_PORT="${GRAY_API_PORT:-18085}"
|
|
REQUIRE_IMAGE_INTEGRATION=0
|
|
CANDIDATE_DIR=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
bash scripts/check-gray-ready.sh --candidate-dir DIR [--require-image-integration]
|
|
|
|
Read-only checks only. This script never uploads, restarts, or changes 103.
|
|
|
|
Checks:
|
|
- candidate checksum and traceability manifest
|
|
- current memind_adm production health
|
|
- reserved gray ports are free on 103
|
|
- expected image_make release is healthy on 103
|
|
- optional end-to-end Portal/image_make runtime wiring readiness
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--candidate-dir)
|
|
shift
|
|
[[ $# -gt 0 ]] || { echo "--candidate-dir requires a directory" >&2; exit 2; }
|
|
CANDIDATE_DIR="$1"
|
|
;;
|
|
--require-image-integration) REQUIRE_IMAGE_INTEGRATION=1 ;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1" >&2
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[[ -n "${CANDIDATE_DIR}" ]] || { usage >&2; exit 2; }
|
|
if [[ "${CANDIDATE_DIR}" != /* ]]; then
|
|
CANDIDATE_DIR="${ROOT}/${CANDIDATE_DIR}"
|
|
fi
|
|
[[ -d "${CANDIDATE_DIR}" ]] || { echo "Candidate directory not found: ${CANDIDATE_DIR}" >&2; exit 1; }
|
|
|
|
manifest="$(find "${CANDIDATE_DIR}" -maxdepth 1 -type f -name '*.manifest.txt' -print -quit)"
|
|
sha_file="$(find "${CANDIDATE_DIR}" -maxdepth 1 -type f -name '*.sha256' -print -quit)"
|
|
bundle="$(find "${CANDIDATE_DIR}" -maxdepth 1 -type f -name '*.tar.gz' -print -quit)"
|
|
[[ -n "${manifest}" && -n "${sha_file}" && -n "${bundle}" ]] || {
|
|
echo "Candidate bundle, manifest, or checksum file missing in ${CANDIDATE_DIR}" >&2
|
|
exit 1
|
|
}
|
|
|
|
(
|
|
cd "${CANDIDATE_DIR}"
|
|
shasum -a 256 -c "$(basename "${sha_file}")"
|
|
)
|
|
|
|
if tar -tzf "${bundle}" | grep -Eq '(^|/)(\.env($|\.)|[^/]*\.pid$|src/|release-candidates/)'; then
|
|
echo "Candidate contains forbidden runtime or development assets" >&2
|
|
tar -tzf "${bundle}" | grep -E '(^|/)(\.env($|\.)|[^/]*\.pid$|src/|release-candidates/)' >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
manifest_value() {
|
|
local key="$1"
|
|
sed -n "s/^${key}=//p" "${manifest}" | head -n 1
|
|
}
|
|
|
|
release_id="$(manifest_value release_id)"
|
|
git_head="$(manifest_value git_head)"
|
|
memind_head="$(manifest_value memind_git_head)"
|
|
memind_status="$(manifest_value memind_git_status)"
|
|
image_make_release_id="$(manifest_value image_make_release_id)"
|
|
|
|
[[ -n "${release_id}" && -n "${git_head}" && -n "${memind_head}" ]] || {
|
|
echo "Candidate manifest is missing release traceability fields" >&2
|
|
exit 1
|
|
}
|
|
[[ "${memind_status}" == "clean" ]] || {
|
|
echo "Candidate manifest does not prove a clean Memind shared source" >&2
|
|
exit 1
|
|
}
|
|
[[ "${image_make_release_id}" != "not-declared" && -n "${image_make_release_id}" ]] || {
|
|
echo "Candidate manifest must declare IMAGE_MAKE_RELEASE_ID" >&2
|
|
exit 1
|
|
}
|
|
|
|
echo "candidate_release=${release_id}"
|
|
echo "candidate_git_head=${git_head}"
|
|
echo "candidate_memind_head=${memind_head}"
|
|
echo "candidate_image_make_release=${image_make_release_id}"
|
|
|
|
remote_result="$(ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" \
|
|
"GRAY_WEB_PORT='${GRAY_WEB_PORT}' GRAY_API_PORT='${GRAY_API_PORT}' IMAGE_MAKE_RELEASE_ID='${image_make_release_id}' REQUIRE_IMAGE_INTEGRATION='${REQUIRE_IMAGE_INTEGRATION}' /bin/bash" <<'REMOTE'
|
|
set -euo pipefail
|
|
|
|
api_code="$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:8085/health || true)"
|
|
web_code="$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:5174/ops/ || true)"
|
|
image_code="$(curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:18083/health || true)"
|
|
[[ "${api_code}" == "200" && "${web_code}" == "200" && "${image_code}" == "200" ]] || {
|
|
echo "health_failed admin_api=${api_code} admin_web=${web_code} image_make=${image_code}" >&2
|
|
exit 1
|
|
}
|
|
|
|
for port in "${GRAY_WEB_PORT}" "${GRAY_API_PORT}"; do
|
|
if lsof -nP -iTCP:"${port}" -sTCP:LISTEN -t | grep -q .; then
|
|
echo "gray_port_in_use=${port}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
export DOCKER_HOST=unix:///Users/john/.colima/default/docker.sock
|
|
docker_bin=/usr/local/bin/docker
|
|
container="$(${docker_bin} ps --format '{{.Names}} {{.Ports}}' | awk '/18083/ {print $1; exit}')"
|
|
[[ -n "${container}" ]] || { echo "image_make_container_missing" >&2; exit 1; }
|
|
image="$(${docker_bin} inspect --format '{{.Config.Image}}' "${container}")"
|
|
health="$(${docker_bin} inspect --format '{{.State.Health.Status}}' "${container}")"
|
|
[[ "${image}" == *"${IMAGE_MAKE_RELEASE_ID}"* && "${health}" == "healthy" ]] || {
|
|
echo "image_make_release_mismatch image=${image} health=${health}" >&2
|
|
exit 1
|
|
}
|
|
|
|
worker_enabled="$(${docker_bin} inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "${container}" | sed -n 's/^WORKER_ENABLED=//p' | head -n 1)"
|
|
config_url_present=0
|
|
if ${docker_bin} inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "${container}" | grep -q '^IMAGE_MAKE_MEMIND_CONFIG_URL=.'; then
|
|
config_url_present=1
|
|
fi
|
|
portal_route_present=0
|
|
if grep -q '/internal/image-make/runtime-config' /Users/john/Project/Memind/server.mjs 2>/dev/null; then
|
|
portal_route_present=1
|
|
fi
|
|
|
|
echo "admin_api=200 admin_web=200 image_make=200"
|
|
echo "gray_ports_free=${GRAY_WEB_PORT},${GRAY_API_PORT}"
|
|
echo "image_make_image=${image}"
|
|
echo "image_make_worker_enabled=${worker_enabled:-unset}"
|
|
echo "image_make_config_url_present=${config_url_present}"
|
|
echo "portal_runtime_route_present=${portal_route_present}"
|
|
|
|
if [[ "${REQUIRE_IMAGE_INTEGRATION}" == "1" ]]; then
|
|
[[ "${worker_enabled}" == "true" && "${config_url_present}" == "1" && "${portal_route_present}" == "1" ]] || {
|
|
echo "image_make_integration_not_ready" >&2
|
|
exit 1
|
|
}
|
|
fi
|
|
REMOTE
|
|
)"
|
|
|
|
printf '%s\n' "${remote_result}"
|
|
echo "gray_preflight=passed"
|