2026-06-19 23:06:43 +08:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
|
|
# 一键把源码同步到开发机测试目录:Memind / memind_adm / Plaza
|
|
|
|
|
|
# 适用:更新基于源码的开发机器环境。goose 不在本流程内。
|
|
|
|
|
|
|
|
|
|
|
|
HOST="${DEPLOY_HOST:-192.168.1.9}"
|
|
|
|
|
|
USER="${DEPLOY_USER:-john}"
|
|
|
|
|
|
TARGET="${DEPLOY_USER_HOST:-${USER}@${HOST}}"
|
|
|
|
|
|
|
|
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
|
|
|
EXCLUDE_FILE="${ROOT_DIR}/scripts/.rsync-exclude-test-host"
|
|
|
|
|
|
|
|
|
|
|
|
MEMIND_SRC="${MEMIND_SRC:-${ROOT_DIR}}"
|
|
|
|
|
|
ADM_SRC="${ADM_SRC:-${ROOT_DIR}/../memind_adm}"
|
2026-06-28 12:18:26 +08:00
|
|
|
|
PLAZA_SRC="${PLAZA_SRC:-${ROOT_DIR}/../memind_plaza}"
|
2026-06-19 23:06:43 +08:00
|
|
|
|
|
2026-06-28 12:18:26 +08:00
|
|
|
|
MEMIND_DST="${MEMIND_DST:-/Users/john/Project/Memind}"
|
|
|
|
|
|
ADM_DST="${ADM_DST:-/Users/john/Project/memind_adm}"
|
|
|
|
|
|
PLAZA_DST="${PLAZA_DST:-/Users/john/Project/memind_plaza}"
|
2026-06-19 23:06:43 +08:00
|
|
|
|
|
|
|
|
|
|
RSYNC_OPTS=(
|
|
|
|
|
|
-az
|
|
|
|
|
|
--delete
|
|
|
|
|
|
--exclude-from="${EXCLUDE_FILE}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
DRY_RUN=0
|
|
|
|
|
|
CHECK_ONLY=0
|
|
|
|
|
|
ONLY_MEMIND=0
|
|
|
|
|
|
ONLY_ADM=0
|
|
|
|
|
|
ONLY_PLAZA=0
|
|
|
|
|
|
SKIP_MV=0
|
|
|
|
|
|
AUTO_YES=0
|
|
|
|
|
|
|
|
|
|
|
|
usage() {
|
|
|
|
|
|
cat <<USAGE
|
|
|
|
|
|
用法: ./scripts/deploy-to-test-host.sh [选项]
|
|
|
|
|
|
|
|
|
|
|
|
目标目录(支持环境变量覆盖)
|
|
|
|
|
|
DEPLOY_HOST, DEPLOY_USER, DEPLOY_USER_HOST
|
|
|
|
|
|
MEMIND_SRC, ADM_SRC, PLAZA_SRC
|
|
|
|
|
|
MEMIND_DST, ADM_DST, PLAZA_DST
|
|
|
|
|
|
MEMIND_STATUS_URL, ADMIN_STATUS_URL, PLAZA_URL
|
|
|
|
|
|
|
|
|
|
|
|
常用选项
|
|
|
|
|
|
--only-memind 仅同步 test-memind
|
|
|
|
|
|
--only-adm 仅同步 test-memindadm
|
|
|
|
|
|
--only-plaza 仅同步 test-memindplaza
|
|
|
|
|
|
--skip-delete 同步时不做 --delete(避免误删未排除文件)
|
|
|
|
|
|
--dry-run 预览变更,不实际写入
|
|
|
|
|
|
--check-only 只做连通性和目录检查,不做 rsync
|
|
|
|
|
|
--yes 非交互,自动确认
|
|
|
|
|
|
--help 显示帮助
|
|
|
|
|
|
|
|
|
|
|
|
示例
|
|
|
|
|
|
./scripts/deploy-to-test-host.sh --dry-run
|
|
|
|
|
|
./scripts/deploy-to-test-host.sh --only-memind --skip-delete
|
|
|
|
|
|
USAGE
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
while [[ "$#" -gt 0 ]]; do
|
|
|
|
|
|
case "$1" in
|
|
|
|
|
|
--only-memind) ONLY_MEMIND=1 ;;
|
|
|
|
|
|
--only-adm) ONLY_ADM=1 ;;
|
|
|
|
|
|
--only-plaza) ONLY_PLAZA=1 ;;
|
|
|
|
|
|
--skip-delete) SKIP_MV=1 ;;
|
|
|
|
|
|
--dry-run) DRY_RUN=1 ;;
|
|
|
|
|
|
--check-only) CHECK_ONLY=1 ;;
|
|
|
|
|
|
--yes) AUTO_YES=1 ;;
|
|
|
|
|
|
--help|-h)
|
|
|
|
|
|
usage
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
;;
|
|
|
|
|
|
*)
|
|
|
|
|
|
echo "未知参数: $1" >&2
|
|
|
|
|
|
usage
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
shift
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "${ONLY_MEMIND}" -eq 1 && ("${ONLY_ADM}" -eq 1 || "${ONLY_PLAZA}" -eq 1) ]] ||
|
|
|
|
|
|
[[ "${ONLY_ADM}" -eq 1 && "${ONLY_PLAZA}" -eq 1 ]]; then
|
|
|
|
|
|
echo "错误: --only-* 只能选一个" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "${SKIP_MV}" -eq 1 ]]; then
|
|
|
|
|
|
RSYNC_OPTS=(
|
|
|
|
|
|
-az
|
|
|
|
|
|
--exclude-from="${EXCLUDE_FILE}"
|
|
|
|
|
|
)
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ ! -f "${EXCLUDE_FILE}" ]]; then
|
|
|
|
|
|
echo "错误: 未找到排除规则文件 ${EXCLUDE_FILE}" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=12)
|
|
|
|
|
|
|
|
|
|
|
|
log() { printf '%s\n' "$*"; }
|
|
|
|
|
|
err() { printf '%s\n' "$*" >&2; }
|
|
|
|
|
|
fail() { err "✗ $*"; exit 1; }
|
|
|
|
|
|
ok() { printf '%s\n' "✓ $*"; }
|
|
|
|
|
|
|
|
|
|
|
|
ssh_to_host() {
|
|
|
|
|
|
ssh "${SSH_OPTS[@]}" "${TARGET}" "$*"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
preflight_local() {
|
|
|
|
|
|
local src=$1
|
|
|
|
|
|
[[ -d "$src" ]] || fail "本地源目录不存在: $src"
|
|
|
|
|
|
[[ -f "$src/.gitignore" ]] || true
|
|
|
|
|
|
ok "本地源目录存在: $src"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
preflight_remote() {
|
|
|
|
|
|
local dst=$1
|
|
|
|
|
|
local label=$2
|
|
|
|
|
|
ssh_to_host "mkdir -p '${dst}'" >/dev/null
|
|
|
|
|
|
if ! ssh_to_host "test -w '${dst}'" >/dev/null 2>&1; then
|
|
|
|
|
|
fail "远端目标不可写: ${label} -> ${TARGET}:${dst}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
ok "远端目录可写: ${label} -> ${dst}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ensure_host() {
|
|
|
|
|
|
log "检查 SSH: ${TARGET}"
|
|
|
|
|
|
ssh_to_host "echo connected" >/dev/null || fail "SSH 连接失败:${TARGET}"
|
|
|
|
|
|
ok "SSH 连接成功: ${TARGET}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
need_confirm() {
|
|
|
|
|
|
local target=$1
|
|
|
|
|
|
[[ "${AUTO_YES}" -eq 1 ]] && return 0
|
|
|
|
|
|
read -r -p "确认同步 ${target}?[y/N] " input
|
|
|
|
|
|
[[ "$input" == "y" || "$input" == "Y" ]]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
do_sync() {
|
|
|
|
|
|
local label=$1
|
|
|
|
|
|
local src=$2
|
|
|
|
|
|
local dst=$3
|
|
|
|
|
|
local flags=("${RSYNC_OPTS[@]}")
|
|
|
|
|
|
|
|
|
|
|
|
preflight_local "$src"
|
|
|
|
|
|
preflight_remote "$dst" "$label"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
|
|
|
|
|
flags+=(--dry-run --itemize-changes)
|
|
|
|
|
|
log "[预览] ${label}: ${src} -> ${TARGET}:${dst}"
|
|
|
|
|
|
rsync "${flags[@]}" "${src}/" "${TARGET}:${dst}/" | head -80
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if ! need_confirm "$label"; then
|
|
|
|
|
|
fail "用户取消:${label}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
log "同步中 ${label}: ${src} -> ${TARGET}:${dst}"
|
|
|
|
|
|
rsync "${flags[@]}" "${src}/" "${TARGET}:${dst}/"
|
|
|
|
|
|
ok "同步完成 ${label}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
health_check() {
|
|
|
|
|
|
local label=$1
|
|
|
|
|
|
local url=$2
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -z "${url}" ]]; then
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if ssh_to_host "command -v curl >/dev/null && curl -fsS '${url}' >/dev/null" >/dev/null 2>&1; then
|
|
|
|
|
|
ok "${label} 健康检查通过:${url}"
|
|
|
|
|
|
else
|
|
|
|
|
|
log "⚠ ${label} 健康检查失败:${url}(仅提示)"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export MEMIND_STATUS_URL="${MEMIND_STATUS_URL:-http://127.0.0.1:18081/api/status}"
|
|
|
|
|
|
export ADMIN_STATUS_URL="${ADMIN_STATUS_URL:-http://127.0.0.1:18082/healthz}"
|
|
|
|
|
|
export PLAZA_URL="${PLAZA_URL:-http://127.0.0.1:13001/plaza}"
|
|
|
|
|
|
|
|
|
|
|
|
ensure_host
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "${CHECK_ONLY}" -eq 1 ]]; then
|
|
|
|
|
|
preflight_remote "${MEMIND_DST}" "Memind"
|
|
|
|
|
|
preflight_remote "${ADM_DST}" "memind_adm"
|
|
|
|
|
|
preflight_remote "${PLAZA_DST}" "Plaza"
|
|
|
|
|
|
ok "检查通过:远端目录可用"
|
|
|
|
|
|
health_check "Memind" "${MEMIND_STATUS_URL}"
|
|
|
|
|
|
health_check "memind_adm" "${ADMIN_STATUS_URL}"
|
|
|
|
|
|
health_check "Plaza" "${PLAZA_URL}"
|
|
|
|
|
|
exit 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
TARGETS=()
|
|
|
|
|
|
if [[ "${ONLY_MEMIND}" -eq 1 ]]; then
|
|
|
|
|
|
TARGETS+=("memind")
|
|
|
|
|
|
elif [[ "${ONLY_ADM}" -eq 1 ]]; then
|
|
|
|
|
|
TARGETS+=("adm")
|
|
|
|
|
|
elif [[ "${ONLY_PLAZA}" -eq 1 ]]; then
|
|
|
|
|
|
TARGETS+=("plaza")
|
|
|
|
|
|
else
|
|
|
|
|
|
TARGETS+=("memind" "adm" "plaza")
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
for t in "${TARGETS[@]}"; do
|
|
|
|
|
|
case "$t" in
|
|
|
|
|
|
memind)
|
|
|
|
|
|
do_sync "Memind" "${MEMIND_SRC}" "${MEMIND_DST}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
adm)
|
|
|
|
|
|
do_sync "memind_adm" "${ADM_SRC}" "${ADM_DST}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
plaza)
|
|
|
|
|
|
do_sync "Plaza" "${PLAZA_SRC}" "${PLAZA_DST}"
|
|
|
|
|
|
;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
|
|
log "同步完成。建议检查:"
|
|
|
|
|
|
log " Memind: ${MEMIND_STATUS_URL}"
|
|
|
|
|
|
log " memind_adm: ${ADMIN_STATUS_URL}"
|
|
|
|
|
|
log " Plaza: ${PLAZA_URL}"
|
|
|
|
|
|
|
|
|
|
|
|
echo "\n可在远端执行 restart(不包含 goose)"
|
|
|
|
|
|
echo " ssh ${TARGET}"
|
|
|
|
|
|
echo " cd ${MEMIND_DST} && pnpm install && pnpm dev:server 或 pm2 reload memind"
|
|
|
|
|
|
echo " cd ${ADM_DST} && pnpm install && pnpm start:??"
|
|
|
|
|
|
echo " cd ${PLAZA_DST} && pnpm install && pnpm dev:plaza"
|
|
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
|
echo "如果需要,我也可以把 restart 命令固定成你们当前启动脚架构(例如 launchctl/systemd/pm2/nodemon)。"
|