199 lines
6.1 KiB
Bash
199 lines
6.1 KiB
Bash
#!/usr/bin/env bash
|
|
# Publishes only the independently versioned Deep Search runtime to 103.
|
|
# Portal and memindadm are not modified by this workflow.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
HOST="${STUDIO_HOST:-58.38.22.103}"
|
|
REMOTE_ROOT="${STUDIO_REMOTE_ROOT:-/Users/john/Project}"
|
|
RUNTIME_BASE="${DEEP_SEARCH_RUNTIME_BASE:-${REMOTE_ROOT}/deep-search-runtime}"
|
|
INCOMING="${REMOTE_ROOT}/incoming/deep-search-runtime"
|
|
RELEASE_ID="$(date +%Y%m%d-%H%M%S)-$(git -C "${ROOT}" rev-parse --short HEAD)"
|
|
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/deep-search-runtime-release.XXXXXX")"
|
|
DRY_RUN=0
|
|
AUTO_YES=0
|
|
|
|
cleanup() {
|
|
rm -rf "${TMP_DIR}"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dry-run) DRY_RUN=1 ;;
|
|
--yes) AUTO_YES=1 ;;
|
|
-h|--help)
|
|
cat <<'EOF'
|
|
Usage: bash scripts/release-deep-search-runtime-prod.sh [--dry-run] [--yes]
|
|
|
|
Required production gates:
|
|
- current branch is main
|
|
- HEAD exactly matches origin/main
|
|
- worktree is clean
|
|
- CONFIRMED_CI_SHA equals the full current HEAD
|
|
|
|
This installs only Deep Search on 103. It does not release Portal or memindadm.
|
|
EOF
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "unknown argument: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
git -C "${ROOT}" fetch origin main --quiet
|
|
branch="$(git -C "${ROOT}" branch --show-current)"
|
|
head_sha="$(git -C "${ROOT}" rev-parse HEAD)"
|
|
origin_sha="$(git -C "${ROOT}" rev-parse origin/main)"
|
|
[[ "${branch}" == "main" ]] || {
|
|
echo "Deep Search production release must run from main" >&2
|
|
exit 1
|
|
}
|
|
[[ "${head_sha}" == "${origin_sha}" ]] || {
|
|
echo "main must exactly match origin/main" >&2
|
|
exit 1
|
|
}
|
|
[[ -z "$(git -C "${ROOT}" status --porcelain=v1 --untracked-files=all)" ]] || {
|
|
echo "worktree must be clean" >&2
|
|
exit 1
|
|
}
|
|
[[ "${CONFIRMED_CI_SHA:-}" == "${head_sha}" ]] || {
|
|
echo "CONFIRMED_CI_SHA must equal the verified main HEAD: ${head_sha}" >&2
|
|
exit 1
|
|
}
|
|
ALLOW_MAIN_RELEASE=1 bash "${ROOT}/scripts/check-release-ready.sh"
|
|
|
|
(
|
|
cd "${ROOT}"
|
|
npm run test:deep-search
|
|
node --test capabilities.test.mjs scripts/deep-search-runtime-package.test.mjs
|
|
npm run build:deep-search-runtime
|
|
)
|
|
|
|
RUNTIME_DIR="${ROOT}/.runtime/deep-search"
|
|
for file in \
|
|
deep-search-server.mjs \
|
|
scripts/run-deep-search-prod.sh \
|
|
scripts/install-deep-search-runtime-prod.sh \
|
|
RUNBOOK.txt; do
|
|
[[ -f "${RUNTIME_DIR}/${file}" ]] || {
|
|
echo "missing runtime artifact: ${file}" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
|
|
MANIFEST="${RUNTIME_DIR}/release-manifest.txt"
|
|
{
|
|
echo "release_id=${RELEASE_ID}"
|
|
echo "git_head=${head_sha}"
|
|
echo "git_branch=${branch}"
|
|
echo "created_at=$(date '+%Y-%m-%d %H:%M:%S %z')"
|
|
echo "artifact=.runtime/deep-search"
|
|
echo "persistent_env=${RUNTIME_BASE}/shared/.env"
|
|
echo "persistent_db=${RUNTIME_BASE}/data/research.sqlite"
|
|
} > "${MANIFEST}"
|
|
|
|
ARCHIVE="${TMP_DIR}/deep-search-runtime-${RELEASE_ID}.tar.gz"
|
|
SHA_FILE="${ARCHIVE}.sha256"
|
|
tar -C "${RUNTIME_DIR}" -czf "${ARCHIVE}" .
|
|
(cd "${TMP_DIR}" && shasum -a 256 "$(basename "${ARCHIVE}")" > "$(basename "${SHA_FILE}")")
|
|
|
|
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
|
shasum -a 256 -c "${SHA_FILE}"
|
|
echo "Deep Search dry-run artifact verified: ${ARCHIVE}"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${AUTO_YES}" -ne 1 ]]; then
|
|
echo "Target: ${HOST}:${RUNTIME_BASE}"
|
|
echo "Release: ${RELEASE_ID}"
|
|
read -r -p "Install standalone Deep Search on 103? [y/N] " confirm </dev/tty
|
|
[[ "${confirm}" =~ ^[Yy]$ ]] || exit 0
|
|
fi
|
|
|
|
ssh -o BatchMode=yes -o ConnectTimeout=15 "${HOST}" "mkdir -p '${INCOMING}' '${RUNTIME_BASE}/releases' '${RUNTIME_BASE}/backups'"
|
|
scp -q "${ARCHIVE}" "${SHA_FILE}" "${HOST}:${INCOMING}/"
|
|
|
|
ssh -o BatchMode=yes "${HOST}" \
|
|
"RELEASE_ID='${RELEASE_ID}' RUNTIME_BASE='${RUNTIME_BASE}' INCOMING='${INCOMING}' /bin/bash" <<'REMOTE'
|
|
set -euo pipefail
|
|
|
|
archive="${INCOMING}/deep-search-runtime-${RELEASE_ID}.tar.gz"
|
|
sha_file="${archive}.sha256"
|
|
release_dir="${RUNTIME_BASE}/releases/${RELEASE_ID}"
|
|
backup_dir="${RUNTIME_BASE}/backups/${RELEASE_ID}"
|
|
current_link="${RUNTIME_BASE}/current"
|
|
plist="${HOME}/Library/LaunchAgents/cn.tkmind.memind-deep-search.plist"
|
|
label="cn.tkmind.memind-deep-search"
|
|
gui="gui/$(id -u)"
|
|
previous_target="$(readlink "${current_link}" 2>/dev/null || true)"
|
|
database_path="${RUNTIME_BASE}/data/research.sqlite"
|
|
|
|
mkdir -p "${release_dir}" "${backup_dir}"
|
|
cp "${plist}" "${backup_dir}/launchagent.plist" 2>/dev/null || true
|
|
if [[ -f "${database_path}" ]]; then
|
|
sqlite3 "${database_path}" ".timeout 10000" \
|
|
".backup '${backup_dir}/research.sqlite'" >/dev/null
|
|
fi
|
|
cd "${INCOMING}"
|
|
shasum -a 256 -c "$(basename "${sha_file}")"
|
|
tar -xzf "${archive}" -C "${release_dir}"
|
|
|
|
rollback() {
|
|
launchctl bootout "${gui}/${label}" >/dev/null 2>&1 || true
|
|
if [[ -n "${previous_target}" && -d "${previous_target}" ]]; then
|
|
ln -sfn "${previous_target}" "${current_link}"
|
|
else
|
|
rm -f "${current_link}"
|
|
fi
|
|
if [[ -f "${backup_dir}/launchagent.plist" ]]; then
|
|
cp "${backup_dir}/launchagent.plist" "${plist}"
|
|
launchctl bootstrap "${gui}" "${plist}" >/dev/null 2>&1 || true
|
|
launchctl kickstart -k "${gui}/${label}" >/dev/null 2>&1 || true
|
|
else
|
|
rm -f "${plist}"
|
|
fi
|
|
}
|
|
trap rollback ERR
|
|
|
|
DEEP_SEARCH_RUNTIME_BASE="${RUNTIME_BASE}" \
|
|
DEEP_SEARCH_RELEASE_DIR="${release_dir}" \
|
|
bash "${release_dir}/scripts/install-deep-search-runtime-prod.sh"
|
|
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "${RUNTIME_BASE}/shared/.env"
|
|
set +a
|
|
|
|
curl -fsS --max-time 5 "http://127.0.0.1:20100/health" >/dev/null
|
|
curl -fsS --max-time 20 \
|
|
-H "X-Secret-Key: ${TKMIND_DEEP_SEARCH_SECRET}" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"query":"OpenAI","limit":3}' \
|
|
"http://127.0.0.1:20100/v1/search" \
|
|
| grep -q '"results"'
|
|
|
|
docker_bin=""
|
|
for candidate in /usr/local/bin/docker /opt/homebrew/bin/docker; do
|
|
if [[ -x "${candidate}" ]]; then
|
|
docker_bin="${candidate}"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -n "${docker_bin}" ]]; then
|
|
goosed_container="$("${docker_bin}" ps --format '{{.Names}}' | grep -E '^goosed-prod-' | head -1)"
|
|
if [[ -n "${goosed_container}" ]]; then
|
|
"${docker_bin}" exec "${goosed_container}" sh -lc \
|
|
'curl -fsS --max-time 5 http://host.docker.internal:20100/health >/dev/null'
|
|
fi
|
|
fi
|
|
|
|
trap - ERR
|
|
printf '%s\n' "${RELEASE_ID}" > "${RUNTIME_BASE}/current-release.txt"
|
|
REMOTE
|
|
|
|
echo "Deep Search installed and verified on 103: ${RELEASE_ID}"
|