Files
dingMCP/deploy/install.sh
T
gitea-admin 55e43e3451
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
fix: make release deployment safely repeatable
2026-08-18 17:28:03 +00:00

191 lines
8.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -Eeuo pipefail
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
domain="approval.justconn.top"
public_base_url="https://${domain}"
gateway_url="${public_base_url}/mcp"
public_download_base_url="${public_base_url}/downloads/dingmcp"
gitea_repo="gitea-admin/dingMCP"
release_dir="${project_root}/release"
web_base="/var/www/${domain}"
web_root="${web_base}/downloads/dingmcp"
web_releases="${web_base}/releases"
acme_root="${web_base}/acme"
nginx_site="/etc/nginx/conf.d/${domain}.conf"
certificate="/etc/letsencrypt/live/${domain}/fullchain.pem"
npm_cache="${project_root}/.npm-cache"
release_private_key="${project_root}/.secrets/release-signing-private.pem"
release_public_key="${project_root}/distribution/release-signing-public.pem"
for command_name in node npm curl systemctl sudo nginx certbot stat sha256sum tar find sort tail cut date git tea; do
command -v "${command_name}" >/dev/null 2>&1 || {
echo "缺少部署命令:${command_name}" >&2
exit 1
}
done
cd "${project_root}"
release_version="$(node -p "require('./package.json').version")"
release_tag="v${release_version}"
gitea_release_base_url="https://git.justconn.top/${gitea_repo}/releases/download/${release_tag}"
if [[ "$(git branch --show-current)" != "main" ]] || ! git diff --quiet || ! git diff --cached --quiet; then
echo "正式部署只能从无未提交改动的 main 分支执行。" >&2
exit 1
fi
release_commit="$(git rev-parse HEAD)"
remote_main="$(git ls-remote origin refs/heads/main | cut -f1)"
if [[ -z "${remote_main}" || "${remote_main}" != "${release_commit}" ]]; then
echo "当前 main 提交尚未同步到 Gitea origin/main,拒绝创建发布标签。" >&2
exit 1
fi
release_tag_commit="$(git ls-remote origin "refs/tags/${release_tag}" "refs/tags/${release_tag}^{}" | tail -n1 | cut -f1)"
release_created_at_commit="${release_tag_commit:-${release_commit}}"
if ! git cat-file -e "${release_created_at_commit}^{commit}" 2>/dev/null; then
echo "现有发布标签指向本地不可验证的提交,拒绝重新生成同版本发布物。" >&2
exit 1
fi
release_created_at="$(git show -s --format=%cI "${release_created_at_commit}")"
if [[ ! -f "${project_root}/.runtime/dingtalk-mcp.env" ]]; then
echo "缺少 .runtime/dingtalk-mcp.env;首次部署前需先配置网关企业凭证。" >&2
exit 1
fi
if [[ ! -f "${release_private_key}" || ! -f "${release_public_key}" ]]; then
echo "缺少 Ed25519 发布密钥;首次部署请先执行 npm run release:keygen 并安全备份私钥。" >&2
exit 1
fi
if [[ "$(stat -c '%a' "${release_private_key}")" != "600" ]]; then
echo "发布私钥权限必须为 600:${release_private_key}" >&2
exit 1
fi
echo "[1/8] 测试并构建"
npm --cache "${npm_cache}" test
npm run runtime:verify
echo "[2/8] 生成包含钉钉 OA 与薪福通的发行包"
npm --cache "${npm_cache}" run release:build -- \
--gateway-url "${gateway_url}" \
--download-base-url "${gitea_release_base_url}" \
--download-mirror-base-url "${public_download_base_url}" \
--created-at "${release_created_at}" \
--out release
npm run test-bundle:build -- \
--release-dir release \
--bundle release/dingmcp-test-bundle.tar.gz \
--bundle-url "${public_download_base_url}/dingmcp-test-bundle.tar.gz" \
--prompt release/agent-test-install.md
(cd "${release_dir}" && sha256sum -c SHA256SUMS)
npm run release:verify -- --release-dir release
echo "[3/8] 发布或复用 Gitea 国内首选下载源"
gitea_releases_json="$(mktemp)"
trap 'rm -f "${gitea_releases_json}"' EXIT
tea releases list --remote origin --limit 100 --output json >"${gitea_releases_json}"
if node scripts/release-tag-exists.mjs "${release_tag}" <"${gitea_releases_json}"; then
:
else
release_lookup_status=$?
if [[ "${release_lookup_status}" -ne 1 ]]; then
echo "无法可靠判断 Gitea Release 是否存在,拒绝继续部署。" >&2
exit "${release_lookup_status}"
fi
release_flags=()
[[ "${release_version}" == *-* ]] && release_flags+=(--prerelease)
tea releases create --remote origin --tag "${release_tag}" --target "${release_commit}" \
--title "${release_tag}" --note "Signed DingTalk approval client ${release_version}" "${release_flags[@]}"
fi
mapfile -t mirror_assets < <(find "${release_dir}" -mindepth 1 -maxdepth 1 -type f \
! -name 'agent-test-install.md' ! -name 'dingmcp-test-bundle.tar.gz' -print | sort)
missing_mirror_assets=()
mirror_check_file="$(mktemp)"
for asset in "${mirror_assets[@]}"; do
asset_name="$(basename "${asset}")"
if curl --fail --silent --location "${gitea_release_base_url}/${asset_name}" -o "${mirror_check_file}" 2>/dev/null; then
if [[ "$(sha256sum "${mirror_check_file}" | cut -d' ' -f1)" != "$(sha256sum "${asset}" | cut -d' ' -f1)" ]]; then
echo "Gitea 已存在同版本但内容不同的发布物:${asset_name};请提升版本号后重新发布。" >&2
exit 1
fi
else
missing_mirror_assets+=("${asset}")
fi
done
if ((${#missing_mirror_assets[@]} > 0)); then
tea releases assets create --remote origin "${release_tag}" "${missing_mirror_assets[@]}"
fi
for asset in "${mirror_assets[@]}"; do
asset_name="$(basename "${asset}")"
curl --fail --silent --show-error --location "${gitea_release_base_url}/${asset_name}" -o "${mirror_check_file}"
[[ "$(sha256sum "${mirror_check_file}" | cut -d' ' -f1)" == "$(sha256sum "${asset}" | cut -d' ' -f1)" ]] || {
echo "Gitea 首选发布物校验失败:${asset_name}" >&2
exit 1
}
done
rm -f "${mirror_check_file}" "${gitea_releases_json}"
trap - EXIT
echo "[4/8] 安装并重启用户级网关服务"
node dist/src/admin.js service start --project-root "${project_root}"
echo "[5/8] 原子发布安装文件"
release_digest="$(sha256sum "${release_dir}/manifest.json" | cut -c1-12)"
release_name="${release_version}-${release_digest}"
release_target="${web_releases}/${release_name}"
release_staging="${web_releases}/.staging-${release_name}-$$"
sudo install -d -m 0755 "${web_base}/downloads" "${web_releases}" "${acme_root}/.well-known/acme-challenge"
sudo rm -rf "${release_staging}"
sudo install -d -m 0755 "${release_staging}"
sudo cp -a "${release_dir}/." "${release_staging}/"
if ! sudo test -e "${release_target}"; then
sudo mv "${release_staging}" "${release_target}"
else
sudo rm -rf "${release_staging}"
fi
if sudo test -d "${web_root}" && ! sudo test -L "${web_root}"; then
sudo mv "${web_root}" "${web_releases}/legacy-$(date -u +%Y%m%dT%H%M%SZ)"
fi
sudo ln -sfn "${release_target}" "${web_root}.next"
sudo mv -Tf "${web_root}.next" "${web_root}"
mapfile -t obsolete_releases < <(find "${web_releases}" -mindepth 1 -maxdepth 1 -type d -name '[0-9]*' ! -name "${release_name}" -printf '%T@ %f\n' | sort -nr | tail -n +3 | cut -d' ' -f2-)
for obsolete in "${obsolete_releases[@]}"; do
[[ "${obsolete}" =~ ^[A-Za-z0-9._-]+$ ]] || continue
sudo rm -rf "${web_releases:?}/${obsolete}"
done
if ! sudo test -f "${certificate}"; then
echo "[6/8] 首次签发 HTTPS 证书"
sudo install -m 0644 "${project_root}/deploy/${domain}.bootstrap.nginx.conf" "${nginx_site}"
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certonly --webroot -w "${acme_root}" -d "${domain}"
else
echo "[6/8] 复用现有 HTTPS 证书"
fi
echo "[7/8] 安装正式 Nginx 配置"
sudo install -m 0644 "${project_root}/deploy/${domain}.nginx.conf" "${nginx_site}"
sudo nginx -t
sudo systemctl reload nginx
echo "[8/8] 验证公网服务与备用发布源"
curl --fail --silent --show-error --retry 10 --retry-delay 1 --retry-all-errors \
"${public_base_url}/healthz" >/dev/null
curl --fail --silent --show-error \
"${public_base_url}/.well-known/oauth-protected-resource/mcp" >/dev/null
curl --fail --silent --show-error "${public_download_base_url}/install.md" >/dev/null
curl --fail --silent --show-error "${public_download_base_url}/manifest.json" >/dev/null
curl --fail --silent --show-error "${public_download_base_url}/manifest.sig" >/dev/null
curl --fail --silent --show-error "${public_download_base_url}/dingtalk-approval-installer.mjs" >/dev/null
curl --fail --silent --show-error "${public_download_base_url}/release-signing-public.pem" >/dev/null
curl --fail --silent --show-error --location "${gitea_release_base_url}/manifest.json" >/dev/null
curl --fail --silent --show-error --location "${gitea_release_base_url}/manifest.sig" >/dev/null
echo "部署完成。"
echo "MCP:${gateway_url}"
echo "用户安装链接:${public_download_base_url}/install.md"
echo "国内静态发布源:${gitea_release_base_url}/install.md"
echo "黑盒测试提示词:${public_download_base_url}/agent-test-install.md"