#!/bin/sh set -eu ding_source_bases='https://mirrors.aliyun.com/nodejs-release/v22.23.2/ https://npmmirror.com/mirrors/node/v22.23.2/ https://nodejs.org/dist/v22.23.2/' ding_installer_urls='https://git.justconn.top/gitea-admin/dingMCP/releases/download/v1.1.0-rc.16/dingtalk-approval-installer.mjs https://approval.justconn.top/downloads/dingmcp/dingtalk-approval-installer.mjs' ding_installer_sha256='d72e642f7296821ca8b25f0eda48c7104032f650dfe2f95582fb4a8991c10254' ding_public_key_urls='https://git.justconn.top/gitea-admin/dingMCP/releases/download/v1.1.0-rc.16/release-signing-public.pem https://approval.justconn.top/downloads/dingmcp/release-signing-public.pem' ding_public_key_sha256='1fb639eeb7d6ac9b112c7c6d3224ca7fb4f74db0c9cf84f28ad1d1cb89260adf' ding_manifest_url='https://git.justconn.top/gitea-admin/dingMCP/releases/download/v1.1.0-rc.16/manifest.json' ding_manifest_mirror_urls='https://approval.justconn.top/downloads/dingmcp/manifest.json' ding_temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/dingmcp-install.XXXXXX")" trap 'rm -rf "$ding_temp_dir"' EXIT HUP INT TERM ding_progress() { printf '[dingtalk-approval] %s\n' "$1" >&2 } ding_hash_file() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}' else echo '缺少 sha256sum 或 shasum,无法安全安装' >&2 return 1 fi } ding_fetch() { if command -v curl >/dev/null 2>&1; then if ding_fetch_http="$(curl --fail --location --silent --show-error --connect-timeout 10 --max-time 90 \ --speed-time 20 --speed-limit 1024 \ --write-out '%{http_code}' --output "$2" "$1")"; then return 0 else ding_fetch_status=$? fi if { [ "$ding_fetch_status" -eq 22 ] && [ "$ding_fetch_http" = '404' ]; } \ || [ "$ding_fetch_status" -eq 5 ] || [ "$ding_fetch_status" -eq 6 ] \ || [ "$ding_fetch_status" -eq 7 ] || [ "$ding_fetch_status" -eq 18 ] \ || [ "$ding_fetch_status" -eq 28 ] || [ "$ding_fetch_status" -eq 35 ] \ || [ "$ding_fetch_status" -eq 52 ] || [ "$ding_fetch_status" -eq 55 ] \ || [ "$ding_fetch_status" -eq 56 ]; then return 75 fi echo "下载失败且不允许换源(HTTP ${ding_fetch_http:-unknown} / curl $ding_fetch_status):$1" >&2 return 1 elif command -v wget >/dev/null 2>&1; then ding_wget_log="${2}.wget.log" if wget --https-only --timeout=90 --server-response --output-document="$2" "$1" 2>"$ding_wget_log"; then rm -f "$ding_wget_log" return 0 else ding_fetch_status=$? fi ding_fetch_http="$(awk '/^ HTTP\/[0-9.]+ [0-9][0-9][0-9]/{code=$2} END{print code}' "$ding_wget_log")" cat "$ding_wget_log" >&2 rm -f "$ding_wget_log" if [ "$ding_fetch_http" = '404' ] || [ "$ding_fetch_status" -eq 4 ] || [ "$ding_fetch_status" -eq 5 ]; then return 75 fi echo "下载失败且不允许换源(HTTP ${ding_fetch_http:-unknown} / wget $ding_fetch_status):$1" >&2 return 1 else echo '缺少 curl 或 wget,无法下载安装文件' >&2 return 1 fi } ding_download_verified() { ding_download_target="$1" ding_download_expected="$2" shift 2 ding_download_succeeded=0 for ding_download_url in "$@"; do ding_progress "正在下载并校验:$ding_download_url" rm -f "$ding_download_target" if ding_fetch "$ding_download_url" "$ding_download_target"; then ding_download_succeeded=1 ding_download_actual="$(ding_hash_file "$ding_download_target")" if [ "$ding_download_actual" != "$ding_download_expected" ]; then echo "下载文件 SHA-256 不匹配,停止安装:$ding_download_url" >&2 return 1 fi return 0 else ding_download_status=$? if [ "$ding_download_status" -ne 75 ]; then return 1; fi fi done if [ "$ding_download_succeeded" -eq 0 ]; then echo '所有下载源均不可用' >&2 fi return 1 } ding_test_node() { [ -x "$1" ] || return 1 "$1" -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>22||(a===22&&b>=13)?0:1)' >/dev/null 2>&1 } case "$(uname -s)" in Linux) ding_os='linux' ;; Darwin) ding_os='darwin' ;; *) echo "不支持的操作系统:$(uname -s)" >&2; exit 1 ;; esac case "$(uname -m)" in x86_64|amd64) ding_arch='x64' ;; arm64|aarch64) ding_arch='arm64' ;; *) echo "不支持的 CPU 架构:$(uname -m)" >&2; exit 1 ;; esac case "$ding_os-$ding_arch" in linux-x64) ding_node_file='node-v22.23.2-linux-x64.tar.gz'; ding_node_sha256='b294a556e639d64338823920e5866c21c02741742d2e1529ee1a225c1ec9252a' ;; linux-arm64) ding_node_file='node-v22.23.2-linux-arm64.tar.gz'; ding_node_sha256='013b59cfd2819703a6f4a14ab891fc46fc2a4e3f5bcd92de3fb4929b43e35b30' ;; darwin-x64) ding_node_file='node-v22.23.2-darwin-x64.tar.gz'; ding_node_sha256='58e99022c2ff89395576cc7fd4d98cea24bb68081475d5f88b801ee8729fb026' ;; darwin-arm64) ding_node_file='node-v22.23.2-darwin-arm64.tar.gz'; ding_node_sha256='61130f394c1630d211dd50aecc4353d379480f36d3ac913cd85dbba1aed585c6' ;; esac ding_progress "1/4 检查 $ding_os-$ding_arch 的 Node/npm 运行环境" ding_node_path='' if command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 && ding_test_node "$(command -v node)"; then ding_node_path="$(command -v node)" ding_progress "2/4 复用兼容的系统 Node" else if [ -n "${DINGTALK_APPROVAL_CLIENT_HOME:-}" ]; then ding_client_root="$DINGTALK_APPROVAL_CLIENT_HOME" elif [ "$ding_os" = 'darwin' ]; then ding_client_root="$HOME/Library/Application Support/dingtalk-approval" else ding_client_root="${XDG_DATA_HOME:-$HOME/.local/share}/dingtalk-approval" fi ding_runtime_name="${ding_node_file%.tar.gz}" ding_runtime_parent="$ding_client_root/node" ding_runtime_dir="$ding_runtime_parent/$ding_runtime_name" ding_node_path="$ding_runtime_dir/bin/node" ding_npm_cli="$ding_runtime_dir/lib/node_modules/npm/bin/npm-cli.js" if ! ding_test_node "$ding_node_path" || [ ! -f "$ding_npm_cli" ]; then ding_progress "2/4 系统 Node 不可用,准备插件私有 Node;下载可能需要一些时间" mkdir -p "$ding_runtime_parent" chmod 700 "$ding_client_root" "$ding_runtime_parent" 2>/dev/null || true ding_runtime_archive="$ding_temp_dir/$ding_node_file" ding_runtime_urls='' for ding_source_base in $ding_source_bases; do ding_runtime_urls="$ding_runtime_urls $ding_source_base$ding_node_file" done # The URLs are fixed by the signed release process and contain no whitespace. # shellcheck disable=SC2086 ding_download_verified "$ding_runtime_archive" "$ding_node_sha256" $ding_runtime_urls ding_extract_root="$ding_temp_dir/node-extract" mkdir -p "$ding_extract_root" tar -xzf "$ding_runtime_archive" -C "$ding_extract_root" ding_extracted_dir="$ding_extract_root/$ding_runtime_name" if ! ding_test_node "$ding_extracted_dir/bin/node" || [ ! -f "$ding_extracted_dir/lib/node_modules/npm/bin/npm-cli.js" ]; then echo '便携 Node 归档缺少可用的 node 或 npm-cli.js' >&2 exit 1 fi rm -rf "$ding_runtime_dir" mv "$ding_extracted_dir" "$ding_runtime_dir" else ding_progress "2/4 复用已安装的插件私有 Node" fi fi ding_progress '3/4 下载并校验签名安装器与发布公钥' ding_installer="$ding_temp_dir/installer.mjs" ding_public_key="$ding_temp_dir/release-signing-public.pem" ding_download_verified "$ding_installer" "$ding_installer_sha256" $ding_installer_urls ding_download_verified "$ding_public_key" "$ding_public_key_sha256" $ding_public_key_urls ding_progress '4/4 执行安装器;最终 JSON 输出到 stdout,阶段进度输出到 stderr' set -- "$ding_installer" install --manifest-url "$ding_manifest_url" for ding_manifest_mirror_url in $ding_manifest_mirror_urls; do set -- "$@" --manifest-mirror-url "$ding_manifest_mirror_url" done set -- "$@" --public-key "$ding_public_key" --json ding_node_proxy_arg='' if [ -n "${HTTPS_PROXY:-}${https_proxy:-}${HTTP_PROXY:-}${http_proxy:-}${ALL_PROXY:-}${all_proxy:-}" ] \ && ! printf '%s' "${NODE_OPTIONS:-}" | grep -Eq '(^|[[:space:]])--use-env-proxy($|[[:space:]])' \ && ! printf '%s' "${NODE_USE_ENV_PROXY:-}" | grep -Eiq '^(1|true)$' \ && "$ding_node_path" -e 'process.exit(process.allowedNodeEnvironmentFlags.has("--use-env-proxy") ? 0 : 1)'; then ding_node_proxy_arg='--use-env-proxy' fi if [ -n "$ding_node_proxy_arg" ]; then "$ding_node_path" "$ding_node_proxy_arg" "$@" else "$ding_node_path" "$@" fi