feat: initial public release
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
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
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name approval.justconn.top;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/approval.justconn.top/acme;
|
||||
default_type text/plain;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 403;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name approval.justconn.top;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name approval.justconn.top;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/approval.justconn.top/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/approval.justconn.top/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
client_max_body_size 1m;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
|
||||
location = /downloads/dingmcp/install.md {
|
||||
alias /var/www/approval.justconn.top/downloads/dingmcp/install.md;
|
||||
default_type text/markdown;
|
||||
charset utf-8;
|
||||
charset_types text/markdown;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Content-Security-Policy "default-src 'none'" always;
|
||||
}
|
||||
|
||||
location = /downloads/dingmcp/manifest.json {
|
||||
alias /var/www/approval.justconn.top/downloads/dingmcp/manifest.json;
|
||||
default_type application/json;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Content-Security-Policy "default-src 'none'" always;
|
||||
}
|
||||
|
||||
location = /downloads/dingmcp/manifest.sig {
|
||||
root /var/www/approval.justconn.top;
|
||||
default_type application/json;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
location = /downloads/dingmcp/agent-test-install.md {
|
||||
root /var/www/approval.justconn.top;
|
||||
default_type text/markdown;
|
||||
charset utf-8;
|
||||
charset_types text/markdown;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
location ~ ^/downloads/dingmcp/(?:dingtalk-approval-installer\.mjs|dingtalk-approval-SKILL\.md|release-signing-public\.pem|SHA256SUMS|dingmcp-test-bundle\.tar\.gz)$ {
|
||||
root /var/www/approval.justconn.top;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
location /downloads/dingmcp/ {
|
||||
alias /var/www/approval.justconn.top/downloads/dingmcp/;
|
||||
autoindex off;
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3010;
|
||||
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 https;
|
||||
proxy_set_header Authorization $http_authorization;
|
||||
proxy_set_header Mcp-Session-Id $http_mcp_session_id;
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
# MCP sessions have a hard 24-hour lifetime in the gateway. Keep the
|
||||
# reverse proxy from expiring a healthy, otherwise idle stream first.
|
||||
proxy_read_timeout 25h;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header Cache-Control "no-store" always;
|
||||
add_header Pragma "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
}
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/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"
|
||||
download_base_url="${public_base_url}/downloads/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; do
|
||||
command -v "${command_name}" >/dev/null 2>&1 || {
|
||||
echo "缺少部署命令:${command_name}" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
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/7] 测试并构建"
|
||||
cd "${project_root}"
|
||||
npm --cache "${npm_cache}" test
|
||||
|
||||
echo "[2/7] 生成包含钉钉 OA 与薪福通的发行包"
|
||||
npm --cache "${npm_cache}" run release:build -- \
|
||||
--gateway-url "${gateway_url}" \
|
||||
--download-base-url "${download_base_url}" \
|
||||
--out release
|
||||
npm run test-bundle:build -- \
|
||||
--release-dir release \
|
||||
--bundle release/dingmcp-test-bundle.tar.gz \
|
||||
--bundle-url "${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/7] 安装并重启用户级网关服务"
|
||||
node dist/src/admin.js service start --project-root "${project_root}"
|
||||
|
||||
echo "[4/7] 原子发布安装文件"
|
||||
release_version="$(node -p "require('./package.json').version")"
|
||||
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 "[5/7] 首次签发 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 "[5/7] 复用现有 HTTPS 证书"
|
||||
fi
|
||||
|
||||
echo "[6/7] 安装正式 Nginx 配置"
|
||||
sudo install -m 0644 "${project_root}/deploy/${domain}.nginx.conf" "${nginx_site}"
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "[7/7] 验证公网服务"
|
||||
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 "${download_base_url}/install.md" >/dev/null
|
||||
curl --fail --silent --show-error "${download_base_url}/manifest.json" >/dev/null
|
||||
curl --fail --silent --show-error "${download_base_url}/manifest.sig" >/dev/null
|
||||
curl --fail --silent --show-error "${download_base_url}/dingtalk-approval-installer.mjs" >/dev/null
|
||||
curl --fail --silent --show-error "${download_base_url}/release-signing-public.pem" >/dev/null
|
||||
|
||||
echo "部署完成。"
|
||||
echo "MCP:${gateway_url}"
|
||||
echo "用户安装链接:${download_base_url}/install.md"
|
||||
echo "黑盒测试提示词:${download_base_url}/agent-test-install.md"
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
domain="approval.justconn.top"
|
||||
service_name="dingtalk-approval-mcp.service"
|
||||
unit_file="${XDG_CONFIG_HOME:-${HOME}/.config}/systemd/user/${service_name}"
|
||||
nginx_site="/etc/nginx/conf.d/${domain}.conf"
|
||||
web_root="/var/www/${domain}"
|
||||
|
||||
if [[ "${nginx_site}" != "/etc/nginx/conf.d/approval.justconn.top.conf" || "${web_root}" != "/var/www/approval.justconn.top" ]]; then
|
||||
echo "拒绝卸载:部署目标校验失败。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${1:-}" != "--yes" ]]; then
|
||||
read -r -p "卸载 ${domain} 网关和公开文件?凭证、OAuth 数据及证书会保留。[y/N] " answer
|
||||
[[ "${answer}" == "y" || "${answer}" == "Y" ]] || exit 0
|
||||
fi
|
||||
|
||||
systemctl --user disable --now "${service_name}" >/dev/null 2>&1 || true
|
||||
rm -f "${unit_file}"
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user reset-failed "${service_name}" >/dev/null 2>&1 || true
|
||||
|
||||
sudo rm -f "${nginx_site}"
|
||||
sudo rm -rf "${web_root}"
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "卸载完成。"
|
||||
echo "已保留:${project_root}/.secrets、${project_root}/.runtime 和 ${domain} 的证书。"
|
||||
Reference in New Issue
Block a user