Add TKMind platform extensions, H5/MindSpace stack, and deployment tooling.
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
*.log
|
||||
MindSpace
|
||||
.DS_Store
|
||||
@@ -0,0 +1,23 @@
|
||||
.git
|
||||
.target
|
||||
target/
|
||||
node_modules/
|
||||
.hermit/
|
||||
deploy/.cargo-linux-amd64/
|
||||
deploy/.target-linux-amd64/
|
||||
deploy/artifacts/
|
||||
ui/h5/MindSpace/
|
||||
ui/h5/users/
|
||||
ui/h5/node_modules/
|
||||
ui/h5/dist/
|
||||
ui/desktop/out/
|
||||
*.log
|
||||
.DS_Store
|
||||
.env.local
|
||||
ui/h5/.env
|
||||
.goosed.pid
|
||||
.h5.pid
|
||||
.h5-dev.pid
|
||||
.plaza.pid
|
||||
.cloudflared.pid
|
||||
build-goosed-no-local.exit
|
||||
@@ -0,0 +1,25 @@
|
||||
# 与 105(Alibaba Cloud Linux 3 / glibc 2.32)兼容的 goosed 构建镜像。
|
||||
# 勿用 debian bookworm:其 glibc/libstdc++ 过新,产物无法在 105 上运行。
|
||||
FROM almalinux:8
|
||||
|
||||
RUN dnf install -y epel-release && \
|
||||
/usr/bin/crb enable && \
|
||||
dnf install -y \
|
||||
gcc gcc-c++ make cmake \
|
||||
pkgconf-pkg-config \
|
||||
openssl-devel \
|
||||
dbus-devel \
|
||||
clang llvm \
|
||||
protobuf protobuf-compiler \
|
||||
git curl ca-certificates \
|
||||
&& dnf clean all
|
||||
|
||||
ARG RUST_VERSION=1.92.0
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
||||
sh -s -- -y --default-toolchain "${RUST_VERSION}" --profile minimal
|
||||
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
|
||||
RUN rustc --version
|
||||
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||
WORKDIR /build
|
||||
@@ -0,0 +1,14 @@
|
||||
# Cloudflare Tunnel 配置示例
|
||||
# 1. cloudflared tunnel login
|
||||
# 2. cloudflared tunnel create goose-local
|
||||
# 3. 复制本文件到 ~/.cloudflared/config.yml 并替换 <TUNNEL-ID>
|
||||
# 4. cloudflared tunnel route dns goose-local goo.tkmind.cn
|
||||
# 5. cloudflared tunnel run goose-local
|
||||
|
||||
tunnel: 17325277-5bf8-4a9e-a4be-2aee39e53e45
|
||||
credentials-file: /Users/john/.cloudflared/17325277-5bf8-4a9e-a4be-2aee39e53e45.json
|
||||
|
||||
ingress:
|
||||
- hostname: goo.tkmind.cn
|
||||
service: http://127.0.0.1:8080
|
||||
- service: http_status:404
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ENGINE="${1:-}"
|
||||
PROJECT_DIR="${2:-}"
|
||||
TASK="${3:-}"
|
||||
DRY_RUN="${4:-}"
|
||||
|
||||
AIDER_BIN="${AIDER_BIN:-${GOOSE_AIDER_BIN:-/root/aider/.venv/bin/aider}}"
|
||||
|
||||
if [[ -z "$ENGINE" || -z "$PROJECT_DIR" || -z "$TASK" ]]; then
|
||||
echo "Usage: coding_router.sh <aider|goose|auto> <project_dir> <task> [--dry-run]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$PROJECT_DIR" ]]; then
|
||||
echo "Project dir not found: $PROJECT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
choose_engine() {
|
||||
case "$ENGINE" in
|
||||
aider) echo "aider" ;;
|
||||
goose) echo "goose" ;;
|
||||
auto)
|
||||
if echo "$TASK" | grep -qE '修复|bug|Bug|BUG|重构|跨文件|实现|新增|改造|兼容|报错|测试失败|commit|提交|代码修改|开发|feature|refactor|fix'; then
|
||||
echo "aider"
|
||||
else
|
||||
echo "goose"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown engine: $ENGINE"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
SELECTED_ENGINE="$(choose_engine)"
|
||||
echo "Selected engine: $SELECTED_ENGINE"
|
||||
|
||||
if [[ "$DRY_RUN" == "--dry-run" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
echo "===== git status before ====="
|
||||
git status --short || true
|
||||
|
||||
case "$SELECTED_ENGINE" in
|
||||
aider)
|
||||
if [[ ! -x "$AIDER_BIN" ]]; then
|
||||
echo "Aider not executable: $AIDER_BIN"
|
||||
exit 1
|
||||
fi
|
||||
"$AIDER_BIN" --yes-always --message "$TASK"
|
||||
echo "===== git status after ====="
|
||||
git status --short || true
|
||||
;;
|
||||
goose)
|
||||
echo "Goose developer tools handle this task in-session: $TASK"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Executable
+178
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地编译 goosed(linux/amd64)并上传到 105,不在生产机 cargo build。
|
||||
#
|
||||
# Mac / 非 x86_64 Linux:Docker --platform linux/amd64
|
||||
# x86_64 Linux:本机 cargo,产物写入 deploy/.target-linux-amd64/
|
||||
#
|
||||
# 用法:
|
||||
# ./deploy/deploy-goosed-105.sh
|
||||
# ./deploy/deploy-goosed-105.sh --skip-build # 只上传已有二进制
|
||||
# ./deploy/deploy-goosed-105.sh --skip-source # 不上传 crates 源码
|
||||
# ./deploy/deploy-goosed-105.sh --no-restart
|
||||
# ./rsync_to_server.sh goosed
|
||||
# ./rsync_to_server.sh all
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DEPLOY_ENV="${ROOT}/deploy/h5-105.env"
|
||||
BUILDER_DOCKERFILE="${ROOT}/deploy/Dockerfile.goosed-build"
|
||||
BUILDER_IMAGE="${GOOSED_BUILDER_IMAGE:-goose-goosed-builder:1.92-al8}"
|
||||
CARGO_HOME="${ROOT}/deploy/.cargo-linux-amd64"
|
||||
CARGO_HOME_CONTAINER="/cache/cargo"
|
||||
TARGET_DIR="${ROOT}/deploy/.target-linux-amd64"
|
||||
LOCAL_BIN="${TARGET_DIR}/release/goosed"
|
||||
|
||||
H5_DEPLOY_HOST="${H5_DEPLOY_HOST:-root@120.26.184.105}"
|
||||
GOOSED_REMOTE_DIR="${GOOSED_REMOTE_DIR:-/root/tkmind_go}"
|
||||
GOOSED_REMOTE_BIN="${GOOSED_REMOTE_BIN:-${GOOSED_REMOTE_DIR}/target/release/goosed}"
|
||||
GOOSED_SYSTEMD_SERVICE="${GOOSED_SYSTEMD_SERVICE:-goosed-tkmind-go}"
|
||||
|
||||
SKIP_BUILD=0
|
||||
SKIP_SOURCE=0
|
||||
NO_RESTART=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--skip-source) SKIP_SOURCE=1 ;;
|
||||
--no-restart) NO_RESTART=1 ;;
|
||||
-h|--help)
|
||||
sed -n '2,13p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $arg(可用 --skip-build / --skip-source / --no-restart)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -f "${DEPLOY_ENV}" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${DEPLOY_ENV}"
|
||||
set +a
|
||||
fi
|
||||
|
||||
ssh_cmd() {
|
||||
ssh -o ConnectTimeout=15 -o BatchMode=yes "${H5_DEPLOY_HOST}" "$@"
|
||||
}
|
||||
|
||||
use_docker_build() {
|
||||
[[ "$(uname -s)" != "Linux" || "$(uname -m)" != "x86_64" ]]
|
||||
}
|
||||
|
||||
ensure_builder_image() {
|
||||
if docker image inspect "${BUILDER_IMAGE}" >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
echo "==> 构建 Docker 编译镜像 ${BUILDER_IMAGE}(AlmaLinux 8,兼容 105 glibc 2.32)..."
|
||||
docker build --platform linux/amd64 -f "${BUILDER_DOCKERFILE}" -t "${BUILDER_IMAGE}" "${ROOT}/deploy"
|
||||
}
|
||||
|
||||
build_with_docker() {
|
||||
ensure_builder_image
|
||||
mkdir -p "${CARGO_HOME}" "${TARGET_DIR}"
|
||||
echo "==> Docker 编译 goosed (linux/amd64) ..."
|
||||
docker run --rm \
|
||||
--platform linux/amd64 \
|
||||
-v "${ROOT}:/build:ro" \
|
||||
-v "${CARGO_HOME}:${CARGO_HOME_CONTAINER}" \
|
||||
-v "${TARGET_DIR}:/build/target" \
|
||||
-e CARGO_HOME="${CARGO_HOME_CONTAINER}" \
|
||||
-e CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \
|
||||
-w /build \
|
||||
"${BUILDER_IMAGE}" \
|
||||
cargo build --release -p goose-server --bin goosed
|
||||
}
|
||||
|
||||
build_native() {
|
||||
mkdir -p "${TARGET_DIR}"
|
||||
echo "==> 本机编译 goosed (linux/amd64) ..."
|
||||
(
|
||||
cd "${ROOT}"
|
||||
CARGO_TARGET_DIR="${TARGET_DIR}" \
|
||||
cargo build --release -p goose-server --bin goosed
|
||||
)
|
||||
}
|
||||
|
||||
build_goosed() {
|
||||
if use_docker_build; then
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "错误: 需要 Docker 才能在 $(uname -s)/$(uname -m) 上编译 linux/amd64 goosed" >&2
|
||||
exit 1
|
||||
fi
|
||||
build_with_docker
|
||||
else
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo "错误: 未找到 cargo" >&2
|
||||
exit 1
|
||||
fi
|
||||
build_native
|
||||
fi
|
||||
}
|
||||
|
||||
sync_source() {
|
||||
echo "==> rsync Rust 源码(不含 target/node_modules)..."
|
||||
rsync -az \
|
||||
--exclude target \
|
||||
--exclude node_modules \
|
||||
--exclude ui/h5/MindSpace \
|
||||
--exclude ui/h5/node_modules \
|
||||
--exclude ui/h5/dist \
|
||||
--exclude .git \
|
||||
--exclude deploy/.cargo-linux-amd64 \
|
||||
--exclude deploy/.target-linux-amd64 \
|
||||
"${ROOT}/crates" "${ROOT}/Cargo.toml" "${ROOT}/Cargo.lock" \
|
||||
"${H5_DEPLOY_HOST}:${GOOSED_REMOTE_DIR}/"
|
||||
}
|
||||
|
||||
upload_binary() {
|
||||
if [[ ! -x "${LOCAL_BIN}" ]]; then
|
||||
echo "错误: 未找到可执行文件 ${LOCAL_BIN},请先编译或去掉 --skip-build" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> 上传 goosed → ${H5_DEPLOY_HOST}:${GOOSED_REMOTE_BIN}"
|
||||
ssh_cmd "mkdir -p '$(dirname "${GOOSED_REMOTE_BIN}")'"
|
||||
rsync -az "${LOCAL_BIN}" "${H5_DEPLOY_HOST}:${GOOSED_REMOTE_BIN}.new"
|
||||
ssh_cmd "install -m 755 '${GOOSED_REMOTE_BIN}.new' '${GOOSED_REMOTE_BIN}' && rm -f '${GOOSED_REMOTE_BIN}.new'"
|
||||
}
|
||||
|
||||
echo "======================================"
|
||||
echo "goosed → 105 部署(本地编译上传)"
|
||||
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "本地二进制: ${LOCAL_BIN}"
|
||||
echo "远端: ${H5_DEPLOY_HOST}:${GOOSED_REMOTE_BIN}"
|
||||
echo "编译方式: $(use_docker_build && echo 'docker linux/amd64' || echo 'native cargo')"
|
||||
echo "======================================"
|
||||
|
||||
if [[ "${SKIP_BUILD}" -eq 0 ]]; then
|
||||
build_goosed
|
||||
else
|
||||
echo "==> 跳过编译 (--skip-build)"
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_SOURCE}" -eq 0 ]]; then
|
||||
sync_source
|
||||
else
|
||||
echo "==> 跳过源码同步 (--skip-source)"
|
||||
fi
|
||||
|
||||
upload_binary
|
||||
|
||||
if [[ "${NO_RESTART}" -eq 0 ]]; then
|
||||
echo "==> 重启 ${GOOSED_SYSTEMD_SERVICE} ..."
|
||||
ssh_cmd "systemctl restart '${GOOSED_SYSTEMD_SERVICE}'"
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 健康检查 ==="
|
||||
ssh_cmd "systemctl is-active '${GOOSED_SYSTEMD_SERVICE}' && echo '${GOOSED_SYSTEMD_SERVICE}: active' || echo '${GOOSED_SYSTEMD_SERVICE}: inactive'"
|
||||
ssh_cmd "curl -sk 'https://127.0.0.1:3000/status' && echo ' ← goosed'" || true
|
||||
ssh_cmd "'${GOOSED_REMOTE_BIN}' --version 2>/dev/null || file '${GOOSED_REMOTE_BIN}'" || true
|
||||
file "${LOCAL_BIN}" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "✅ goosed 已发布"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# 兼容入口,实际逻辑在根目录 rsync_to_server.sh
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
exec "${ROOT}/rsync_to_server.sh" h5 "$@"
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
# 生产发布脚本 — 仅当用户明确要求「发布生产」时由人工/Agent 执行
|
||||
# 本地 H5 测试通过后,一键构建并发布到本机 Cloudflare 隧道(goo.tkmind.cn)
|
||||
# 105 服务器生产环境请用: ./deploy/deploy-h5-105.sh → https://go.tkmind.cn
|
||||
#
|
||||
# 典型流程:
|
||||
# 1. cd ui/h5 && pnpm dev # 开发热更新 (5173)
|
||||
# 2. cd ui/h5 && pnpm run test:local # 本地验证生产构建 (8080)
|
||||
# 3. ./deploy/deploy-h5-prod.sh # 发布到本机隧道 goo.tkmind.cn
|
||||
#
|
||||
# 选项:
|
||||
# --skip-test 跳过 auth 单元测试
|
||||
# --skip-tunnel 不检查/启动 cloudflared
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
H5_DIR="${ROOT}/ui/h5"
|
||||
H5_PORT="${H5_PORT:-8080}"
|
||||
LOG_DIR="${ROOT}"
|
||||
PROD_URL="${H5_PROD_URL:-https://goo.tkmind.cn}"
|
||||
TUNNEL_NAME="${CLOUDFLARED_TUNNEL:-goose-local}"
|
||||
SKIP_TEST=0
|
||||
SKIP_TUNNEL=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--skip-test) SKIP_TEST=1 ;;
|
||||
--skip-tunnel) SKIP_TUNNEL=1 ;;
|
||||
-h|--help)
|
||||
sed -n '2,12p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $arg(可用 --skip-test / --skip-tunnel)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
load_env() {
|
||||
if [[ -f "${ROOT}/.env.local" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/.env.local"
|
||||
set +a
|
||||
fi
|
||||
if [[ -f "${H5_DIR}/.env" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "${H5_DIR}/.env"
|
||||
set +a
|
||||
fi
|
||||
}
|
||||
|
||||
kill_port() {
|
||||
local port="$1"
|
||||
if command -v lsof >/dev/null 2>&1; then
|
||||
local pids
|
||||
pids="$(lsof -ti TCP:"${port}" -s TCP:LISTEN 2>/dev/null || true)"
|
||||
if [[ -n "${pids}" ]]; then
|
||||
echo "==> 释放 :${port} (PID ${pids})"
|
||||
kill ${pids} 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -9 ${pids} 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
pkg_run() {
|
||||
cd "${H5_DIR}"
|
||||
if command -v pnpm >/dev/null 2>&1; then
|
||||
pnpm "$@"
|
||||
elif command -v npm >/dev/null 2>&1; then
|
||||
npm "$@"
|
||||
else
|
||||
echo "错误: 需要 pnpm 或 npm" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
load_env
|
||||
export TKMIND_API_TARGET="${TKMIND_API_TARGET:-${GOOSE_API_TARGET:-https://127.0.0.1:${GOOSE_PORT:-18006}}}"
|
||||
export TKMIND_SERVER__SECRET_KEY="${TKMIND_SERVER__SECRET_KEY:-${GOOSE_SERVER__SECRET_KEY:-local-dev-secret}}"
|
||||
export H5_PORT
|
||||
export VITE_TKMIND_WORKING_DIR="${VITE_TKMIND_WORKING_DIR:-${ROOT}}"
|
||||
|
||||
echo "==> H5 生产发布"
|
||||
echo " 工作目录: ${VITE_TKMIND_WORKING_DIR}"
|
||||
echo " API 代理: ${TKMIND_API_TARGET}"
|
||||
echo " 本地端口: ${H5_PORT}"
|
||||
echo " 公网地址: ${PROD_URL}"
|
||||
|
||||
if ! curl -sk "${TKMIND_API_TARGET}/status" >/dev/null 2>&1; then
|
||||
echo "⚠️ goosed 未运行,正在启动..."
|
||||
"${ROOT}/local_restart.sh"
|
||||
fi
|
||||
|
||||
cd "${H5_DIR}"
|
||||
if [[ ! -d node_modules ]]; then
|
||||
echo "==> 安装依赖..."
|
||||
pkg_run install
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_TEST}" -eq 0 ]]; then
|
||||
echo "==> 运行测试..."
|
||||
pkg_run test
|
||||
fi
|
||||
|
||||
echo "==> 构建生产包..."
|
||||
pkg_run run build
|
||||
|
||||
kill_port "${H5_PORT}"
|
||||
if [[ -f "${ROOT}/.h5.pid" ]]; then
|
||||
old_pid="$(<"${ROOT}/.h5.pid")"
|
||||
if kill -0 "${old_pid}" 2>/dev/null; then
|
||||
kill "${old_pid}" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "${ROOT}/.h5.pid"
|
||||
fi
|
||||
|
||||
echo "==> 重启 H5 服务 @ http://127.0.0.1:${H5_PORT}"
|
||||
nohup node server.mjs >"${LOG_DIR}/h5.log" 2>&1 &
|
||||
echo $! >"${ROOT}/.h5.pid"
|
||||
sleep 1
|
||||
|
||||
if [[ "${SKIP_TUNNEL}" -eq 0 ]]; then
|
||||
if pgrep -f "cloudflared tunnel run ${TUNNEL_NAME}" >/dev/null 2>&1; then
|
||||
echo "✅ cloudflared 已在运行"
|
||||
elif command -v cloudflared >/dev/null 2>&1 && [[ -f "${HOME}/.cloudflared/config.yml" ]]; then
|
||||
kill_port 20241
|
||||
echo "==> 启动 Cloudflare Tunnel (${PROD_URL})"
|
||||
nohup cloudflared tunnel run "${TUNNEL_NAME}" >"${LOG_DIR}/cloudflared.log" 2>&1 &
|
||||
echo $! >"${ROOT}/.cloudflared.pid"
|
||||
sleep 3
|
||||
else
|
||||
echo "⚠️ 未检测到 cloudflared,仅本地可用"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 健康检查 ==="
|
||||
curl -sk "${TKMIND_API_TARGET}/status" && echo " ← goosed"
|
||||
curl -s "http://127.0.0.1:${H5_PORT}/api/status" && echo " ← h5 本地"
|
||||
if [[ "${SKIP_TUNNEL}" -eq 0 ]] && command -v cloudflared >/dev/null 2>&1; then
|
||||
curl -s --max-time 15 -o /dev/null -w "${PROD_URL} → %{http_code}\n" "${PROD_URL}/" || true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ H5 已发布"
|
||||
echo " 本地: http://127.0.0.1:${H5_PORT}"
|
||||
echo " 公网: ${PROD_URL}"
|
||||
echo " 日志: ${LOG_DIR}/h5.log"
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env bash
|
||||
# 局域网 Mac 服务器部署:rsync 项目 → 192.168.71.100:/Users/john/Project/tkmind_go
|
||||
#
|
||||
# 前置:本机可免密 ssh 到目标机
|
||||
# ssh-copy-id john@192.168.71.100
|
||||
#
|
||||
# 用法:
|
||||
# ./deploy/deploy-lan-100.sh
|
||||
# ./deploy/deploy-lan-100.sh --skip-build
|
||||
# ./deploy/deploy-lan-100.sh --skip-goosed
|
||||
# ./deploy/deploy-lan-100.sh --skip-h5
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DEPLOY_ENV="${ROOT}/deploy/lan-100.env"
|
||||
EXCLUDE_FILE="${ROOT}/deploy/.rsync-exclude-project"
|
||||
|
||||
LAN_DEPLOY_HOST="${LAN_DEPLOY_HOST:-john@192.168.71.100}"
|
||||
LAN_REMOTE_DIR="${LAN_REMOTE_DIR:-/Users/john/Project/tkmind_go}"
|
||||
GOOSED_PORT="${GOOSED_PORT:-18006}"
|
||||
H5_PORT="${H5_PORT:-8080}"
|
||||
|
||||
SKIP_BUILD=0
|
||||
SKIP_GOOSED=0
|
||||
SKIP_H5=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--skip-build) SKIP_BUILD=1 ;;
|
||||
--skip-goosed) SKIP_GOOSED=1 ;;
|
||||
--skip-h5) SKIP_H5=1 ;;
|
||||
-h|--help)
|
||||
sed -n '2,12p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $arg" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -f "${DEPLOY_ENV}" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${DEPLOY_ENV}"
|
||||
set +a
|
||||
fi
|
||||
|
||||
ssh_cmd() {
|
||||
ssh -o ConnectTimeout=15 -o BatchMode=yes "${LAN_DEPLOY_HOST}" "$@"
|
||||
}
|
||||
|
||||
require_ssh() {
|
||||
if ! ssh_cmd 'echo ok' >/dev/null 2>&1; then
|
||||
echo "❌ 无法 SSH 到 ${LAN_DEPLOY_HOST}" >&2
|
||||
echo "请在本机终端执行一次(输入服务器密码):" >&2
|
||||
echo " ssh-copy-id ${LAN_DEPLOY_HOST}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "======================================"
|
||||
echo "TKMind → 局域网部署"
|
||||
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "本地: ${ROOT}"
|
||||
echo "远端: ${LAN_DEPLOY_HOST}:${LAN_REMOTE_DIR}"
|
||||
echo "======================================"
|
||||
|
||||
require_ssh
|
||||
|
||||
echo "==> 创建远端目录..."
|
||||
ssh_cmd "mkdir -p '${LAN_REMOTE_DIR}'"
|
||||
|
||||
echo "==> rsync 同步项目..."
|
||||
rsync -az --delete --exclude-from="${EXCLUDE_FILE}" \
|
||||
"${ROOT}/" \
|
||||
"${LAN_DEPLOY_HOST}:${LAN_REMOTE_DIR}/"
|
||||
|
||||
echo "==> 写入远端环境模板(不覆盖已有 .env)..."
|
||||
ssh_cmd "test -f '${LAN_REMOTE_DIR}/.env.local' || cat > '${LAN_REMOTE_DIR}/.env.local' <<'EOF'
|
||||
GOOSE_PORT=${GOOSED_PORT}
|
||||
GOOSE_HOST=127.0.0.1
|
||||
GOOSE_SERVER__SECRET_KEY=local-dev-secret
|
||||
GOOSE_CODING_ROUTER=${LAN_REMOTE_DIR}/deploy/coding_router.sh
|
||||
GOOSE_AIDER_BIN=/Users/john/PycharmProjects/aider/.venv/bin/aider
|
||||
EOF"
|
||||
ssh_cmd "test -f '${LAN_REMOTE_DIR}/ui/h5/.env' || cp '${LAN_REMOTE_DIR}/ui/h5/.env.example' '${LAN_REMOTE_DIR}/ui/h5/.env'"
|
||||
|
||||
if [[ "${SKIP_BUILD}" -eq 0 ]]; then
|
||||
if [[ "${SKIP_GOOSED}" -eq 0 ]]; then
|
||||
echo "==> 远端编译 goosed..."
|
||||
ssh_cmd "cd '${LAN_REMOTE_DIR}' && (command -v cargo >/dev/null 2>&1 || source bin/activate-hermit 2>/dev/null || true) && cargo build -p goose-server --bin goosed"
|
||||
fi
|
||||
if [[ "${SKIP_H5}" -eq 0 ]]; then
|
||||
echo "==> 远端构建 H5..."
|
||||
ssh_cmd "cd '${LAN_REMOTE_DIR}/ui/h5' && (command -v pnpm >/dev/null 2>&1 && pnpm install && pnpm run build || (npm install && npm run build))"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_GOOSED}" -eq 0 ]]; then
|
||||
echo "==> 重启远端 goosed..."
|
||||
ssh_cmd "cd '${LAN_REMOTE_DIR}' && ./local_restart.sh" || true
|
||||
fi
|
||||
|
||||
if [[ "${SKIP_H5}" -eq 0 ]]; then
|
||||
echo "==> 重启远端 H5..."
|
||||
ssh_cmd "cd '${LAN_REMOTE_DIR}/ui/h5' && (lsof -ti TCP:${H5_PORT} -s TCP:LISTEN | xargs kill 2>/dev/null || true); sleep 1; nohup node server.mjs >> '${LAN_REMOTE_DIR}/ui/h5/h5.log' 2>&1 & echo \$! > '${LAN_REMOTE_DIR}/ui/h5/.h5.pid'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 健康检查 ==="
|
||||
ssh_cmd "curl -sk 'https://127.0.0.1:${GOOSED_PORT}/status' && echo ' ← goosed'" || true
|
||||
ssh_cmd "curl -s 'http://127.0.0.1:${H5_PORT}/api/status' && echo ' ← h5'" || true
|
||||
|
||||
echo ""
|
||||
echo "✅ 已同步到 ${LAN_DEPLOY_HOST}:${LAN_REMOTE_DIR}"
|
||||
echo " H5: http://192.168.71.100:${H5_PORT}/"
|
||||
echo " goosed: https://127.0.0.1:${GOOSED_PORT}/ (仅服务器本机)"
|
||||
@@ -0,0 +1,66 @@
|
||||
upstream goose_h5 {
|
||||
server 127.0.0.1:8080;
|
||||
keepalive 16;
|
||||
}
|
||||
|
||||
upstream legacy_tkmind_h5 {
|
||||
server 127.0.0.1:18800;
|
||||
keepalive 16;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name go.tkmind.cn;
|
||||
|
||||
# 所有请求(含 /MindSpace/<username>/;旧 /temp/ 由 server.mjs 301 重定向)走 H5
|
||||
location / {
|
||||
proxy_pass http://goose_h5;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 86400;
|
||||
proxy_send_timeout 86400;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl ipv6only=on;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/go.tkmind.cn/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/go.tkmind.cn/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name goo.tkmind.cn;
|
||||
|
||||
location / {
|
||||
proxy_pass http://legacy_tkmind_h5;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 86400;
|
||||
proxy_send_timeout 86400;
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = go.tkmind.cn) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name go.tkmind.cn;
|
||||
return 404;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# 105 服务器 H5 部署配置(复制为 deploy/h5-105.env 后按需修改)
|
||||
H5_DEPLOY_HOST=root@120.26.184.105
|
||||
H5_REMOTE_DIR=/root/tkmind_go/ui/h5
|
||||
H5_PROD_URL=https://go.tkmind.cn
|
||||
# 若服务器尚未重命名 systemd unit,可设为实际服务名
|
||||
H5_SYSTEMD_SERVICE=goose-h5
|
||||
GOOSED_SYSTEMD_SERVICE=goosed-tkmind-go
|
||||
TKMIND_API_SYSTEMD_SERVICE=goosed-tkmind-go
|
||||
|
||||
# goosed 本地编译上传(Mac 用 Docker linux/amd64,不在 105 上 cargo build):
|
||||
# ./deploy/deploy-goosed-105.sh
|
||||
# ./rsync_to_server.sh goosed
|
||||
# ./rsync_to_server.sh all # H5 + goosed 一起发
|
||||
# GOOSED_REMOTE_DIR=/root/tkmind_go
|
||||
# GOOSED_REMOTE_BIN=/root/tkmind_go/target/release/goosed
|
||||
|
||||
# H5 MySQL 使用与 tkmind 相同的阿里云 RDS(参照 /root/tkmind/shared/config/local.json)
|
||||
# 一键建表 + 导数据 + 更新 .env:
|
||||
# ./deploy/sync-h5-db-105.sh
|
||||
# H5_RDS_DATABASE=goose
|
||||
|
||||
# 多用户系统(由 sync-h5-db-105.sh 写入服务器 ui/h5/.env)
|
||||
# H5_USERS_ROOT=/root/tkmind_go/ui/h5/users
|
||||
# H5_SIGNUP_BALANCE_CENTS=1000
|
||||
# H5_ADMIN_USERNAME=admin
|
||||
# H5_ADMIN_PASSWORD=your-admin-password
|
||||
|
||||
# 微信支付自助充值(配置后写入服务器 ui/h5/.env)
|
||||
# H5_WECHAT_PAY_ENABLED=1
|
||||
# H5_WECHAT_APP_ID=wx...
|
||||
# H5_WECHAT_MCH_ID=...
|
||||
# H5_WECHAT_API_V3_KEY=...
|
||||
# H5_WECHAT_SERIAL_NO=...
|
||||
# H5_WECHAT_PRIVATE_KEY_PATH=/root/tkmind_go/secrets/wechat_apiclient_key.pem
|
||||
# H5_WECHAT_PLATFORM_CERT_PATH=/root/tkmind_go/secrets/wechat_platform.pem
|
||||
# H5_WECHAT_NOTIFY_URL=https://go.tkmind.cn/webhooks/wechat-pay/notify
|
||||
# H5_PUBLIC_BASE_URL=https://go.tkmind.cn
|
||||
@@ -0,0 +1,12 @@
|
||||
# 局域网 192.168.71.100 部署配置(复制为 deploy/lan-100.env 后按需修改)
|
||||
LAN_DEPLOY_HOST=john@192.168.71.100
|
||||
LAN_REMOTE_DIR=/Users/john/Project/tkmind_go
|
||||
|
||||
# 远端 goosed / H5 端口(与本地 port.conf 保持一致即可)
|
||||
GOOSED_PORT=18006
|
||||
H5_PORT=8080
|
||||
|
||||
# 首次部署后可在远端自行维护 ui/h5/.env 与 .env.local,同步脚本不会覆盖已有 .env
|
||||
# LAN_SKIP_BUILD=1
|
||||
# LAN_SKIP_GOOSED=1
|
||||
# LAN_SKIP_H5=1
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
H5_DIR="${ROOT}/ui/h5"
|
||||
H5_PORT="${H5_PORT:-8080}"
|
||||
|
||||
if [[ -f "${ROOT}/.env.local" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "${ROOT}/.env.local"
|
||||
set +a
|
||||
fi
|
||||
|
||||
export GOOSE_API_TARGET="${GOOSE_API_TARGET:-https://127.0.0.1:${GOOSE_PORT:-18006}}"
|
||||
export GOOSE_SERVER__SECRET_KEY="${GOOSE_SERVER__SECRET_KEY:-local-dev-secret}"
|
||||
export H5_PORT
|
||||
export VITE_GOOSE_WORKING_DIR="${VITE_GOOSE_WORKING_DIR:-${ROOT}}"
|
||||
|
||||
echo "==> 检查 goosed..."
|
||||
if ! curl -sk "${GOOSE_API_TARGET}/status" >/dev/null 2>&1; then
|
||||
echo "goosed 未运行,正在启动..."
|
||||
"${ROOT}/local_restart.sh"
|
||||
fi
|
||||
|
||||
echo "==> 构建 H5..."
|
||||
cd "${H5_DIR}"
|
||||
if [[ ! -d node_modules ]]; then
|
||||
pnpm install
|
||||
fi
|
||||
pnpm run build
|
||||
|
||||
echo "==> 启动 H5 服务 @ http://127.0.0.1:${H5_PORT}"
|
||||
node server.mjs &
|
||||
H5_PID=$!
|
||||
echo "${H5_PID}" > "${ROOT}/.h5.pid"
|
||||
|
||||
if command -v cloudflared >/dev/null 2>&1 && [[ -f "${HOME}/.cloudflared/config.yml" ]]; then
|
||||
echo "==> 启动 Cloudflare Tunnel (goo.tkmind.cn)"
|
||||
cloudflared tunnel run goose-local &
|
||||
CF_PID=$!
|
||||
echo "${CF_PID}" > "${ROOT}/.cloudflared.pid"
|
||||
echo ""
|
||||
echo "✅ 已启动"
|
||||
echo " 本地: http://127.0.0.1:${H5_PORT}"
|
||||
echo " 公网: https://goo.tkmind.cn"
|
||||
else
|
||||
echo ""
|
||||
echo "✅ H5 已启动: http://127.0.0.1:${H5_PORT}"
|
||||
echo "ℹ️ 未检测到 cloudflared 配置,请参考 deploy/cloudflared/config.yml.example"
|
||||
fi
|
||||
Executable
+334
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env bash
|
||||
# 将本地 H5 用户数据同步到 105 RDS(数据库连接参照 tkmind shared/config/local.json)
|
||||
#
|
||||
# 用法:
|
||||
# ./deploy/sync-h5-db-105.sh # 完整:建表 + 导数据 + 更新 .env + 同步用户目录
|
||||
# ./deploy/sync-h5-db-105.sh --schema-only
|
||||
# ./deploy/sync-h5-db-105.sh --data-only
|
||||
# ./deploy/sync-h5-db-105.sh --no-restart
|
||||
# ./deploy/sync-h5-db-105.sh --baseline-only # 仅重置生产权限基线
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
H5_DIR="${ROOT}/ui/h5"
|
||||
DEPLOY_ENV="${ROOT}/deploy/h5-105.env"
|
||||
LOCAL_DB_ENV="${ROOT}/.env.local"
|
||||
|
||||
H5_DEPLOY_HOST="${H5_DEPLOY_HOST:-root@120.26.184.105}"
|
||||
H5_REMOTE_DIR="${H5_REMOTE_DIR:-/root/tkmind_go/ui/h5}"
|
||||
H5_SYSTEMD_SERVICE="${H5_SYSTEMD_SERVICE:-goose-h5}"
|
||||
TKMIND_SHARED_CONFIG="/root/tkmind/shared/config/local.json"
|
||||
H5_RDS_DATABASE="${H5_RDS_DATABASE:-goose}"
|
||||
LOCAL_MYSQL_DATABASE="${LOCAL_MYSQL_DATABASE:-goose}"
|
||||
|
||||
SCHEMA_ONLY=0
|
||||
DATA_ONLY=0
|
||||
BASELINE_ONLY=0
|
||||
NO_RESTART=0
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--schema-only) SCHEMA_ONLY=1 ;;
|
||||
--data-only) DATA_ONLY=1 ;;
|
||||
--baseline-only) BASELINE_ONLY=1 ;;
|
||||
--no-restart) NO_RESTART=1 ;;
|
||||
-h|--help)
|
||||
sed -n '2,10p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $arg" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -f "${DEPLOY_ENV}" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${DEPLOY_ENV}"
|
||||
set +a
|
||||
fi
|
||||
|
||||
if [[ -f "${LOCAL_DB_ENV}" ]]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${LOCAL_DB_ENV}"
|
||||
set +a
|
||||
fi
|
||||
|
||||
LOCAL_MYSQL_USER="${MYSQL_USER:-boot}"
|
||||
LOCAL_MYSQL_PASSWORD="${MYSQL_PASSWORD:-888888}"
|
||||
LOCAL_MYSQL_HOST="${MYSQL_HOST:-localhost}"
|
||||
LOCAL_MYSQL_PORT="${MYSQL_PORT:-3306}"
|
||||
|
||||
# 仅同步用户/计费数据;权限表不同步,导入后强制应用生产安全基线
|
||||
H5_DATA_TABLES=(
|
||||
h5_users
|
||||
h5_user_wallets
|
||||
h5_user_path_grants
|
||||
h5_user_sessions
|
||||
h5_session_billing_state
|
||||
h5_usage_records
|
||||
h5_billing_ledger
|
||||
)
|
||||
|
||||
ssh_cmd() {
|
||||
ssh -o ConnectTimeout=15 -o BatchMode=yes "${H5_DEPLOY_HOST}" "$@"
|
||||
}
|
||||
|
||||
b64() {
|
||||
printf '%s' "$1" | base64 | tr -d '\n'
|
||||
}
|
||||
|
||||
read_rds_config() {
|
||||
ssh_cmd "node -e \"
|
||||
const fs=require('fs');
|
||||
const cfg=JSON.parse(fs.readFileSync('${TKMIND_SHARED_CONFIG}','utf8'));
|
||||
const db=cfg.database||{};
|
||||
const out={
|
||||
host:db.host||'',
|
||||
port:db.port||3306,
|
||||
user:db.account||db.user||'boot',
|
||||
password:db.password||'',
|
||||
database:'${H5_RDS_DATABASE}'
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
\""
|
||||
}
|
||||
|
||||
transform_paths() {
|
||||
local file="$1"
|
||||
sed -i.bak \
|
||||
-e 's|/Users/john/PycharmProjects/goose/ui/h5/temp/|/root/tkmind_go/ui/h5/MindSpace/|g' \
|
||||
-e 's|/Users/john/PycharmProjects/goose/ui/h5/MindSpace/|/root/tkmind_go/ui/h5/MindSpace/|g' \
|
||||
-e 's|/Users/john/PycharmProjects/goose/ui/h5/users/|/root/tkmind_go/ui/h5/users/|g' \
|
||||
"${file}"
|
||||
rm -f "${file}.bak"
|
||||
}
|
||||
|
||||
dump_local_data() {
|
||||
local dump_file="$1"
|
||||
echo "==> 导出本地库 ${LOCAL_MYSQL_DATABASE} 的 H5 表..."
|
||||
MYSQL_PWD="${LOCAL_MYSQL_PASSWORD}" mysqldump \
|
||||
-h "${LOCAL_MYSQL_HOST}" -P "${LOCAL_MYSQL_PORT}" -u "${LOCAL_MYSQL_USER}" \
|
||||
--single-transaction --complete-insert --no-create-info --set-gtid-purged=OFF \
|
||||
"${LOCAL_MYSQL_DATABASE}" "${H5_DATA_TABLES[@]}" >"${dump_file}"
|
||||
transform_paths "${dump_file}"
|
||||
echo " 导出 $(wc -l <"${dump_file}" | tr -d ' ') 行 SQL"
|
||||
}
|
||||
|
||||
init_remote_schema() {
|
||||
local rds_b64="$1"
|
||||
echo "==> 在 RDS 初始化 H5 表结构..."
|
||||
ssh_cmd "cd '${H5_REMOTE_DIR}' && H5_RDS_B64='${rds_b64}' node --input-type=module -e \"
|
||||
import { createDbPool, initSchema } from './db.mjs';
|
||||
const cfg = JSON.parse(Buffer.from(process.env.H5_RDS_B64, 'base64').toString('utf8'));
|
||||
process.env.MYSQL_HOST = cfg.host;
|
||||
process.env.MYSQL_PORT = String(cfg.port);
|
||||
process.env.MYSQL_USER = cfg.user;
|
||||
process.env.MYSQL_PASSWORD = cfg.password;
|
||||
process.env.MYSQL_DATABASE = cfg.database;
|
||||
const pool = createDbPool();
|
||||
await initSchema(pool);
|
||||
await pool.end();
|
||||
console.log('schema ok');
|
||||
\""
|
||||
}
|
||||
|
||||
import_remote_data() {
|
||||
local rds_b64="$1"
|
||||
local remote_dump="/tmp/h5_data_sync.sql"
|
||||
echo "==> 导入数据到 RDS..."
|
||||
scp -q "${DUMP_FILE}" "${H5_DEPLOY_HOST}:${remote_dump}"
|
||||
local tables_json tables_b64
|
||||
tables_json="$(printf '%s\n' "${H5_DATA_TABLES[@]}" | node -e "const fs=require('fs'); process.stdout.write(JSON.stringify(fs.readFileSync(0,'utf8').trim().split('\\n')))")"
|
||||
tables_b64="$(b64 "${tables_json}")"
|
||||
ssh_cmd "cd '${H5_REMOTE_DIR}' && H5_RDS_B64='${rds_b64}' H5_DUMP_FILE='${remote_dump}' H5_TABLES_B64='${tables_b64}' node --input-type=module -e \"
|
||||
import fs from 'node:fs';
|
||||
import { createDbPool } from './db.mjs';
|
||||
const cfg = JSON.parse(Buffer.from(process.env.H5_RDS_B64, 'base64').toString('utf8'));
|
||||
const tables = JSON.parse(Buffer.from(process.env.H5_TABLES_B64, 'base64').toString('utf8'));
|
||||
process.env.MYSQL_HOST = cfg.host;
|
||||
process.env.MYSQL_PORT = String(cfg.port);
|
||||
process.env.MYSQL_USER = cfg.user;
|
||||
process.env.MYSQL_PASSWORD = cfg.password;
|
||||
process.env.MYSQL_DATABASE = cfg.database;
|
||||
const pool = createDbPool();
|
||||
const sql = fs.readFileSync(process.env.H5_DUMP_FILE, 'utf8');
|
||||
await pool.query('SET FOREIGN_KEY_CHECKS=0').catch(() => {});
|
||||
for (const table of tables) {
|
||||
await pool.query('TRUNCATE TABLE ??', [table]).catch(() => {});
|
||||
}
|
||||
const statements = sql
|
||||
.split(/;\\s*\\n/)
|
||||
.map(s => s.trim())
|
||||
.filter(s => s && !/^\\/\\*!|^SET @|^SET @@/i.test(s));
|
||||
for (const stmt of statements) {
|
||||
await pool.query(stmt);
|
||||
}
|
||||
await pool.query('SET FOREIGN_KEY_CHECKS=1').catch(() => {});
|
||||
await pool.end();
|
||||
fs.unlinkSync(process.env.H5_DUMP_FILE);
|
||||
console.log('import ok');
|
||||
\""
|
||||
}
|
||||
|
||||
update_remote_env() {
|
||||
local rds_b64="$1"
|
||||
echo "==> 更新 105 H5 .env(RDS 连接)..."
|
||||
local admin_user_b64 admin_pass_b64
|
||||
admin_user_b64="$(b64 "${H5_ADMIN_USERNAME:-admin}")"
|
||||
admin_pass_b64="$(b64 "${H5_ADMIN_PASSWORD:-}")"
|
||||
ssh_cmd "H5_RDS_B64='${rds_b64}' H5_ADMIN_USER_B64='${admin_user_b64}' H5_ADMIN_PASS_B64='${admin_pass_b64}' node -e \"
|
||||
const fs=require('fs');
|
||||
const cfg=JSON.parse(Buffer.from(process.env.H5_RDS_B64,'base64').toString('utf8'));
|
||||
const adminUser=Buffer.from(process.env.H5_ADMIN_USER_B64,'base64').toString('utf8');
|
||||
const adminPass=Buffer.from(process.env.H5_ADMIN_PASS_B64,'base64').toString('utf8');
|
||||
const envPath='${H5_REMOTE_DIR}/.env';
|
||||
const lines=fs.existsSync(envPath)?fs.readFileSync(envPath,'utf8').split('\\n'):[];
|
||||
const updates={
|
||||
MYSQL_HOST:cfg.host,
|
||||
MYSQL_PORT:String(cfg.port),
|
||||
MYSQL_USER:cfg.user,
|
||||
MYSQL_PASSWORD:cfg.password,
|
||||
MYSQL_DATABASE:cfg.database,
|
||||
DATABASE_URL:'mysql://'+encodeURIComponent(cfg.user)+':'+encodeURIComponent(cfg.password)+'@'+cfg.host+':'+cfg.port+'/'+cfg.database,
|
||||
H5_USERS_ROOT:'/root/tkmind_go/ui/h5/users',
|
||||
H5_SIGNUP_BALANCE_CENTS:'1000',
|
||||
H5_PUBLIC_BASE_URL:'https://go.tkmind.cn',
|
||||
};
|
||||
if(adminUser) updates.H5_ADMIN_USERNAME=adminUser;
|
||||
if(adminPass) updates.H5_ADMIN_PASSWORD=adminPass;
|
||||
const keys=new Set(Object.keys(updates));
|
||||
const kept=[];
|
||||
for(const line of lines){
|
||||
const m=line.match(/^([A-Z0-9_]+)=/);
|
||||
if(m && keys.has(m[1])) continue;
|
||||
kept.push(line);
|
||||
}
|
||||
while(kept.length && kept[kept.length-1]==='') kept.pop();
|
||||
for(const [k,v] of Object.entries(updates)) kept.push(k+'='+v);
|
||||
fs.writeFileSync(envPath, kept.join('\\n')+'\\n');
|
||||
console.log('env updated');
|
||||
\""
|
||||
}
|
||||
|
||||
migrate_remote_publish_dir() {
|
||||
echo "==> 迁移远端 temp → MindSpace(若存在)..."
|
||||
ssh_cmd "if [[ -d '${H5_REMOTE_DIR}/temp' && ! -d '${H5_REMOTE_DIR}/MindSpace' ]]; then mv '${H5_REMOTE_DIR}/temp' '${H5_REMOTE_DIR}/MindSpace'; fi"
|
||||
}
|
||||
|
||||
sync_user_dirs() {
|
||||
migrate_remote_publish_dir
|
||||
echo "==> 同步用户 workspace 目录..."
|
||||
for user_dir in "${H5_DIR}/MindSpace"/* "${H5_DIR}/users"/*; do
|
||||
[[ -d "${user_dir}" ]] || continue
|
||||
base="$(basename "${user_dir}")"
|
||||
if [[ "${user_dir}" == *"/MindSpace/"* ]]; then
|
||||
rsync -az "${user_dir}/" "${H5_DEPLOY_HOST}:${H5_REMOTE_DIR}/MindSpace/${base}/"
|
||||
else
|
||||
rsync -az "${user_dir}/" "${H5_DEPLOY_HOST}:${H5_REMOTE_DIR}/users/${base}/"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
apply_production_security_baseline() {
|
||||
local rds_b64="$1"
|
||||
echo "==> 应用生产安全基线(role=user 默认权限,清除用户级权限覆盖)..."
|
||||
ssh_cmd "cd '${H5_REMOTE_DIR}' && H5_RDS_B64='${rds_b64}' node --input-type=module -e \"
|
||||
import { createDbPool } from './db.mjs';
|
||||
import { applyProductionSecurityBaseline } from './security-baseline.mjs';
|
||||
const cfg = JSON.parse(Buffer.from(process.env.H5_RDS_B64, 'base64').toString('utf8'));
|
||||
process.env.MYSQL_HOST = cfg.host;
|
||||
process.env.MYSQL_PORT = String(cfg.port);
|
||||
process.env.MYSQL_USER = cfg.user;
|
||||
process.env.MYSQL_PASSWORD = cfg.password;
|
||||
process.env.MYSQL_DATABASE = cfg.database;
|
||||
const pool = createDbPool();
|
||||
await applyProductionSecurityBaseline(pool);
|
||||
await pool.end();
|
||||
console.log('security baseline ok');
|
||||
\""
|
||||
}
|
||||
|
||||
verify_remote() {
|
||||
local rds_b64="$1"
|
||||
echo "==> 验证 RDS 数据与 john 会话策略..."
|
||||
ssh_cmd "cd '${H5_REMOTE_DIR}' && H5_RDS_B64='${rds_b64}' node --input-type=module -e \"
|
||||
import { createDbPool } from './db.mjs';
|
||||
import { createUserAuth } from './user-auth.mjs';
|
||||
import fs from 'node:fs';
|
||||
const cfg = JSON.parse(Buffer.from(process.env.H5_RDS_B64, 'base64').toString('utf8'));
|
||||
process.env.MYSQL_HOST = cfg.host;
|
||||
process.env.MYSQL_PORT = String(cfg.port);
|
||||
process.env.MYSQL_USER = cfg.user;
|
||||
process.env.MYSQL_PASSWORD = cfg.password;
|
||||
process.env.MYSQL_DATABASE = cfg.database;
|
||||
for (const line of fs.readFileSync('.env','utf8').split('\\n')) {
|
||||
const t=line.trim(); if(!t||t.startsWith('#')) continue;
|
||||
const eq=t.indexOf('='); if(eq<0) continue;
|
||||
if(!process.env[t.slice(0,eq)]) process.env[t.slice(0,eq)]=t.slice(eq+1);
|
||||
}
|
||||
const pool = createDbPool();
|
||||
const [users] = await pool.query('SELECT username, role FROM h5_users ORDER BY username');
|
||||
const [usage] = await pool.query('SELECT COUNT(*) AS cnt FROM h5_usage_records');
|
||||
const [roleShell] = await pool.query(
|
||||
\\\"SELECT allowed FROM h5_capability_grants WHERE subject_type='role' AND subject_id='user' AND capability_key='shell'\\\"
|
||||
);
|
||||
console.log('users:', users.map(u => u.username + '(' + u.role + ')').join(', '));
|
||||
console.log('usage_records:', usage[0].cnt);
|
||||
console.log('role shell:', roleShell[0]?.allowed ?? 'default');
|
||||
const auth = createUserAuth(pool, { usersRoot: process.env.H5_USERS_ROOT, h5Root: process.cwd() });
|
||||
const [john] = await pool.query(\\\"SELECT id FROM h5_users WHERE username='john'\\\");
|
||||
if (john[0]) {
|
||||
const policy = await auth.getAgentSessionPolicy(john[0].id);
|
||||
const dev = policy.extensionOverrides?.find(e => e.name === 'developer');
|
||||
console.log('john developer tools:', dev?.available_tools?.join(',') ?? 'none');
|
||||
console.log('john goose_mode:', policy.gooseMode ?? 'null');
|
||||
}
|
||||
await pool.end();
|
||||
\""
|
||||
}
|
||||
|
||||
echo "======================================"
|
||||
echo "H5 数据库 → 105 RDS 同步"
|
||||
echo "参照: ${TKMIND_SHARED_CONFIG}"
|
||||
echo "目标库: ${H5_RDS_DATABASE}"
|
||||
echo "======================================"
|
||||
|
||||
RDS_JSON="$(read_rds_config)"
|
||||
RDS_B64="$(b64 "${RDS_JSON}")"
|
||||
echo "RDS: $(echo "${RDS_JSON}" | node -e "const c=JSON.parse(require('fs').readFileSync(0,'utf8')); console.log(c.host+'/'+c.database)")"
|
||||
|
||||
if [[ "${BASELINE_ONLY}" -eq 1 ]]; then
|
||||
apply_production_security_baseline "${RDS_B64}"
|
||||
else
|
||||
DUMP_FILE="$(mktemp /tmp/h5_data_XXXXXX.sql)"
|
||||
trap 'rm -f "${DUMP_FILE}"' EXIT
|
||||
|
||||
if [[ "${DATA_ONLY}" -eq 0 ]]; then
|
||||
init_remote_schema "${RDS_B64}"
|
||||
fi
|
||||
|
||||
if [[ "${SCHEMA_ONLY}" -eq 0 ]]; then
|
||||
dump_local_data "${DUMP_FILE}"
|
||||
import_remote_data "${RDS_B64}"
|
||||
sync_user_dirs
|
||||
apply_production_security_baseline "${RDS_B64}"
|
||||
fi
|
||||
fi
|
||||
|
||||
update_remote_env "${RDS_B64}"
|
||||
|
||||
if [[ "${NO_RESTART}" -eq 0 ]]; then
|
||||
echo "==> 重启 ${H5_SYSTEMD_SERVICE}..."
|
||||
ssh_cmd "systemctl restart '${H5_SYSTEMD_SERVICE}'"
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
verify_remote "${RDS_B64}"
|
||||
ssh_cmd "curl -s http://127.0.0.1:8080/api/status && echo ' ← h5'" || true
|
||||
|
||||
echo ""
|
||||
echo "✅ H5 已连接 RDS 并完成数据同步"
|
||||
Reference in New Issue
Block a user