with one click
server-deploy
// 通用项目部署到远程服务器。自动识别项目类型(Node.js/Python/Rust/Go/静态站),SSH 配置、环境安装、项目上传、进程管理、Nginx 反向代理、Cloudflare SSL、安全加固。当用户需要部署项目、上线服务、配置域名时使用
// 通用项目部署到远程服务器。自动识别项目类型(Node.js/Python/Rust/Go/静态站),SSH 配置、环境安装、项目上传、进程管理、Nginx 反向代理、Cloudflare SSL、安全加固。当用户需要部署项目、上线服务、配置域名时使用
| name | server-deploy |
| description | 通用项目部署到远程服务器。自动识别项目类型(Node.js/Python/Rust/Go/静态站),SSH 配置、环境安装、项目上传、进程管理、Nginx 反向代理、Cloudflare SSL、安全加固。当用户需要部署项目、上线服务、配置域名时使用 |
| allowed-tools | Bash, Read, Write, Edit, AskUserQuestion, Task, Glob, Grep |
| metadata | {"argument-hint":"[SSH连接信息,如 root@host]"} |
将本地项目一键部署到远程 Linux 服务器。自动识别项目类型,适配对应的构建、运行和进程管理方案。
扫描项目根目录,按以下规则判断类型:
| 标志文件 | 项目类型 | 运行时 | 进程管理 |
|---|---|---|---|
package.json | Node.js | node | PM2 |
Cargo.toml | Rust | 编译产物 | systemd |
go.mod | Go | 编译产物 | systemd |
pyproject.toml / requirements.txt / setup.py | Python | python3 | systemd / gunicorn |
Dockerfile / docker-compose.yml | Docker | docker | docker compose |
index.html(无其他标志) | 静态站点 | 无 | Nginx 直接托管 |
如果检测到多个标志文件(如 package.json + Dockerfile),用 AskUserQuestion 让用户选择部署方式。
用 AskUserQuestion 收集以下信息(上下文已有的跳过):
SSH 连接(header: "服务器")
项目路径(header: "项目")
域名(header: "域名")
运行端口(header: "端口")
| 变量 | 说明 |
|---|---|
$SSH_TARGET | SSH 连接串(如 root@192.168.1.1) |
$PROJECT_DIR | 本地项目绝对路径 |
$PROJECT_NAME | 项目名称 |
$PROJECT_TYPE | 项目类型:nodejs / python / rust / go / docker / static |
$REMOTE_DIR | 远程部署目录(默认 /opt/$PROJECT_NAME) |
$PORT | 应用运行端口 |
$DOMAIN | 域名(可选) |
cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_ed25519.pub 2>/dev/null
如果没有公钥,生成一个:ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
ssh -o ConnectTimeout=5 -o BatchMode=yes $SSH_TARGET "echo ok" 2>/dev/null
如果失败,用密码登录并添加公钥:
sshpass -p '<PASSWORD>' ssh -o StrictHostKeyChecking=no $SSH_TARGET \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '<PUBLIC_KEY>' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
ssh -o ConnectTimeout=5 $SSH_TARGET "echo '免密登录成功' && uname -a"
单条命令获取全部信息:
ssh $SSH_TARGET "echo '=== 系统 ===' && cat /etc/os-release | grep PRETTY_NAME && uname -m && echo '=== CPU ===' && nproc && echo '=== 内存 ===' && free -h && echo '=== 磁盘 ===' && df -h / && echo '=== 包管理器 ===' && which apt yum dnf pacman 2>/dev/null && echo '=== Node.js ===' && node -v 2>/dev/null || echo '未安装' && echo '=== Python ===' && python3 --version 2>/dev/null || echo '未安装' && echo '=== Rust ===' && rustc --version 2>/dev/null || echo '未安装' && echo '=== Go ===' && go version 2>/dev/null || echo '未安装' && echo '=== Docker ===' && docker --version 2>/dev/null || echo '未安装' && echo '=== Nginx ===' && nginx -v 2>&1 || echo '未安装'"
检查要点:
x86_64 / aarch64(Rust/Go 需匹配编译目标)根据 $PROJECT_TYPE 安装对应运行时。仅安装缺少的组件。
# 安装 Node.js 20(如未安装)
ssh $SSH_TARGET "curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs"
# 安装 PM2
ssh $SSH_TARGET "npm install -g pm2"
# 安装 Python3 + pip + venv(如未安装)
ssh $SSH_TARGET "apt-get install -y python3 python3-pip python3-venv"
两种方案,根据服务器资源选择:
方案 A:服务器编译(内存 ≥ 1GB)
ssh $SSH_TARGET "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source ~/.cargo/env"
方案 B:本地交叉编译(推荐低配服务器)
# 本地编译(需安装对应 target)
rustup target add x86_64-unknown-linux-gnu # 或 aarch64-unknown-linux-gnu
cargo build --release --target x86_64-unknown-linux-gnu
# 仅上传编译产物
同样两种方案:
方案 A:服务器编译
ssh $SSH_TARGET "wget -q https://go.dev/dl/go1.22.0.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz && echo 'export PATH=\$PATH:/usr/local/go/bin' >> ~/.bashrc"
方案 B:本地交叉编译(推荐)
GOOS=linux GOARCH=amd64 go build -o $PROJECT_NAME .
# 仅上传二进制文件
# 安装 Docker(如未安装)
ssh $SSH_TARGET "curl -fsSL https://get.docker.com | sh && systemctl enable docker && systemctl start docker"
ssh $SSH_TARGET "apt-get install -y nginx && systemctl enable nginx && systemctl start nginx"
| 项目类型 | 排除目录/文件 |
|---|---|
| Node.js | node_modules/, .next/, dist/, .git/ |
| Python | __pycache__/, .venv/, venv/, *.pyc, .git/ |
| Rust | target/, .git/(方案 B 仅上传 target/release/$BINARY) |
| Go | vendor/(如有), .git/(方案 B 仅上传二进制) |
| Docker | .git/(需要上传 Dockerfile) |
| 静态站 | .git/, node_modules/ |
注意:大文件传输时 SSH 可能断连。使用保活参数:
rsync -avz --timeout=300 \
-e "ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=10" \
--exclude='.git' --exclude='<TYPE_SPECIFIC_EXCLUDES>' \
$PROJECT_DIR/ $SSH_TARGET:$REMOTE_DIR/
如果文件很大(>500MB):先上传代码,再单独上传大文件。
如果 SSH 频繁断连:调整服务器 SSH 配置:
ssh $SSH_TARGET "sed -i 's/#ClientAliveInterval.*/ClientAliveInterval 60/' /etc/ssh/sshd_config && sed -i 's/#ClientAliveCountMax.*/ClientAliveCountMax 120/' /etc/ssh/sshd_config && systemctl reload ssh || systemctl reload sshd"
用 nohup 后台执行,防止断连中断:
| 项目类型 | 命令 |
|---|---|
| Node.js | nohup bash -c 'npm install && npm run build' > /tmp/build.log 2>&1 & |
| Python | nohup bash -c 'python3 -m venv .venv && .venv/bin/pip install -r requirements.txt' > /tmp/build.log 2>&1 & |
| Rust (方案A) | nohup bash -c 'source ~/.cargo/env && cargo build --release' > /tmp/build.log 2>&1 & |
| Go (方案A) | nohup bash -c 'go build -o $PROJECT_NAME .' > /tmp/build.log 2>&1 & |
| Docker | nohup bash -c 'docker compose build' > /tmp/build.log 2>&1 & |
| 静态站 | 无需构建 |
等待后查看构建结果:ssh $SSH_TARGET "tail -20 /tmp/build.log"
根据项目类型选择对应的进程管理方案。
ssh $SSH_TARGET "cd $REMOTE_DIR && pm2 start npm --name $PROJECT_NAME -- start && pm2 startup && pm2 save"
验证:ssh $SSH_TARGET "pm2 list && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
ssh $SSH_TARGET "cat > /etc/systemd/system/$PROJECT_NAME.service << EOF
[Unit]
Description=$PROJECT_NAME
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$REMOTE_DIR
ExecStart=$REMOTE_DIR/.venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port $PORT
# 或 gunicorn: ExecStart=$REMOTE_DIR/.venv/bin/gunicorn -w 4 -b 0.0.0.0:$PORT main:app
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable $PROJECT_NAME && systemctl start $PROJECT_NAME"
注意:ExecStart 根据框架调整:
- FastAPI/Starlette:
uvicorn main:app- Flask:
gunicorn -w 4 main:app- Django:
gunicorn -w 4 project.wsgi:application- 纯脚本:
python main.py
验证:ssh $SSH_TARGET "systemctl status $PROJECT_NAME && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
ssh $SSH_TARGET "cat > /etc/systemd/system/$PROJECT_NAME.service << EOF
[Unit]
Description=$PROJECT_NAME
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$REMOTE_DIR
ExecStart=$REMOTE_DIR/$BINARY_NAME
Restart=on-failure
RestartSec=5
Environment=PORT=$PORT
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable $PROJECT_NAME && systemctl start $PROJECT_NAME"
Rust 二进制路径:
$REMOTE_DIR/target/release/$PROJECT_NAME(方案 A)或$REMOTE_DIR/$PROJECT_NAME(方案 B) Go 二进制路径:$REMOTE_DIR/$PROJECT_NAME
验证:ssh $SSH_TARGET "systemctl status $PROJECT_NAME && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
ssh $SSH_TARGET "cd $REMOTE_DIR && docker compose up -d"
配置开机自启:Docker 服务默认随 systemd 启动,docker compose 使用 restart: unless-stopped 即可。
验证:ssh $SSH_TARGET "docker compose ps && curl -s -o /dev/null -w '%{http_code}' http://localhost:$PORT"
无需进程管理,Nginx 直接指向静态文件目录(见第六步)。
如果用户选择"仅用 IP 访问",跳过此步和后续 SSL 步骤。
ssh $SSH_TARGET "cat > /etc/nginx/sites-available/$PROJECT_NAME << 'NGINX'
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:$PORT;
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 \$scheme;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection \"upgrade\";
}
}
NGINX
ln -sf /etc/nginx/sites-available/$PROJECT_NAME /etc/nginx/sites-enabled/$PROJECT_NAME
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx"
ssh $SSH_TARGET "cat > /etc/nginx/sites-available/$PROJECT_NAME << 'NGINX'
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
root $REMOTE_DIR;
index index.html;
location / {
try_files \$uri \$uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control \"public, immutable\";
}
}
NGINX
ln -sf /etc/nginx/sites-available/$PROJECT_NAME /etc/nginx/sites-enabled/$PROJECT_NAME
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx"
在对应的 server 块基础上:
return 301 https://$host$request_uri;server {
listen 443 ssl;
server_name $DOMAIN www.$DOMAIN;
ssl_certificate /etc/ssl/cloudflare/fullchain.pem;
ssl_certificate_key /etc/ssl/cloudflare/key.pem;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# ... location 块同上 ...
}
ssh $SSH_TARGET "sed -i 's/# server_tokens off;/server_tokens off;/' /etc/nginx/nginx.conf && nginx -t && systemctl reload nginx"
仅当用户有域名时执行。
告知用户在域名注册商后台添加 DNS 记录:
| 记录类型 | 名称 | 内容 |
|---|---|---|
| A | @ | <服务器IP> |
| A | www | <服务器IP> |
如果使用 Cloudflare,建议开启橙色云朵(代理模式)。
用 AskUserQuestion 询问:
告知用户操作:Cloudflare → SSL/TLS → 源服务器 → 创建证书
$DOMAIN 和 *.$DOMAIN用户提供证书和私钥后,写入服务器并拼接证书链:
ssh $SSH_TARGET "mkdir -p /etc/ssl/cloudflare"
# 写入 cert.pem 和 key.pem(chmod 600)
# 下载 Origin CA 根证书并拼接
ssh $SSH_TARGET "curl -s -o /etc/ssl/cloudflare/origin-ca-rsa-root.pem https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem && cat /etc/ssl/cloudflare/cert.pem /etc/ssl/cloudflare/origin-ca-rsa-root.pem > /etc/ssl/cloudflare/fullchain.pem"
ssh $SSH_TARGET "openssl x509 -in /etc/ssl/cloudflare/cert.pem -noout -text | grep -E 'DNS:|Subject:|Issuer:|Extended'"
DNS: 包含域名Extended Key Usage 包含 TLS Web Server Authenticationssh $SSH_TARGET "apt-get install -y certbot python3-certbot-nginx && certbot --nginx -d $DOMAIN -d www.$DOMAIN --non-interactive --agree-tos --email <EMAIL>"
需要临时关闭 Cloudflare 代理(橙色→灰色),完成后可重新开启。
部署完成后,调用 /server-security skill 进行完整安全审计。
如果用户不想跑完整审计,至少执行最小安全加固:
ssh $SSH_TARGET "ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw default deny incoming && ufw default allow outgoing && echo 'y' | ufw enable"
关键:先放通 22 端口再 enable。启用后立即验证 SSH 连通性。
ssh $SSH_TARGET "apt-get install -y -qq fail2ban && cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 86400
EOF
systemctl enable fail2ban && systemctl restart fail2ban"
ssh $SSH_TARGET "find $REMOTE_DIR -name '.env*' -exec chmod 600 {} \; 2>/dev/null"
ssh $SSH_TARGET "find $REMOTE_DIR \( -name '*.db' -o -name '*.sqlite' \) -exec chmod 600 {} \; 2>/dev/null"
并行验证所有组件:
# 应用响应
curl -s -o /dev/null -w '%{http_code}' https://$DOMAIN 2>/dev/null || curl -s -o /dev/null -w '%{http_code}' http://<IP>:$PORT
# 进程状态(按项目类型)
ssh $SSH_TARGET "pm2 list 2>/dev/null; systemctl status $PROJECT_NAME 2>/dev/null; docker compose ps 2>/dev/null"
# 安全
ssh $SSH_TARGET "ufw status && fail2ban-client status sshd 2>/dev/null"
全部通过后输出总结:
## 部署完成
| 项目 | 状态 |
|------|------|
| 项目 | $PROJECT_NAME ($PROJECT_TYPE) |
| 服务器 | $SSH_TARGET |
| 部署路径 | $REMOTE_DIR |
| 运行端口 | $PORT |
| 进程管理 | PM2 / systemd / docker(状态 online) |
| 开机自启 | 已配置 |
| 域名 | $DOMAIN(或"仅 IP") |
| SSL | Cloudflare Origin / Let's Encrypt / 无 |
| 防火墙 | UFW 启用 |
| fail2ban | SSH 防护已启用 |
| 访问地址 | https://$DOMAIN 或 http://<IP>:$PORT |
| # | 坑 | 原因 | 正确做法 |
|---|---|---|---|
| 1 | rsync 大文件传输 SSH 断连 | 服务器 SSH 默认超时短 | 调整 ClientAliveInterval=60,rsync 加 ServerAliveInterval=30 |
| 2 | rsync 断连不丢进度 | 增量传输 | 重试即可,自动跳过已传文件 |
| 3 | 构建超时断连 | SSH 会话中断终止前台进程 | 用 nohup 后台执行,日志写 /tmp |
| 4 | Cloudflare 521 错误 | 服务器只有 HTTP,CF 用 HTTPS 连 | 配 SSL 或 CF 改 Flexible |
| 5 | Cloudflare 526 错误 | Origin Certificate 无效 | 检查 SAN 包含域名 + Key Usage 包含 Server Auth |
| 6 | Origin Certificate 缺域名 | 创建时未填主机名 | 重新创建,包含 domain 和 *.domain |
| 7 | Full (Strict) 526 | 证书链不完整 | cert.pem + origin-ca-rsa-root.pem → fullchain.pem |
| 8 | UFW 启用后锁死 | 忘记先放通 22 端口 | 先 ufw allow 22/tcp 再 ufw enable |
| 9 | Nginx 多 server 块冲突 | 多配置监听同端口 | 删 default,每项目独立配置 |
| 10 | PM2 重启丢进程 | 未 save | pm2 startup && pm2 save |
| 11 | Rust 服务器编译 OOM | 小内存 VPS 编译大项目 | 本地交叉编译,仅上传二进制 |
| 12 | Go 二进制架构不匹配 | 本地 arm64 编译的放到 x86 服务器 | GOOS=linux GOARCH=amd64 交叉编译 |
| 13 | Python venv 路径硬编码 | 本地 venv 上传到服务器路径不同 | 远程重新创建 venv,不上传本地 venv |
| 14 | systemd 服务启动失败无日志 | 没看 journalctl | journalctl -u $PROJECT_NAME -f 排查 |
| 15 | Docker 端口映射冲突 | 主机端口已占用 | lsof -i:$PORT 先检查 |
| 16 | .env 权限 644 泄露密钥 | rsync 保留权限 | 部署后 chmod 600 |
| 17 | Nginx 暴露版本号 | 默认配置 | server_tokens off |
| 18 | 服务器连接频率限制 | 短时间多次 SSH 触发保护 | 等 30-60 秒后重试 |
| 19 | Cloudflare 缓存命中率低 | Next.js 默认不设 Cache-Control | next.config.ts 配置 headers:静态资源 immutable、uploads s-maxage=30d、API no-store |
| 20 | ISR 页面不被 CDN 缓存 | 页面没有 revalidate 导致每次回源 | 按变化频率设 revalidate:首页 1h、详情页 24h |
| 21 | CI/CD 构建缺环境变量 | NEXT_PUBLIC_* 变量需构建时注入 | GitHub Secrets 添加变量,workflow build 步骤 env 注入 |
| 22 | 外链图片在国内加载失败 | Steam CDN / SteamGridDB 被墙 | 批量下载到 public/uploads/,数据库改本地路径,rsync 同步到服务器 |
| 23 | 服务器无 sqlite3 CLI | 精简系统未安装 | 用 node + better-sqlite3 执行 SQL,或安装 sqlite3 |
查看指定进程的代理线路。通过 Mihomo API 查询当前活跃连接,显示进程匹配的规则和代理链路。用于确认某个进程(如 claude、chrome)走的是哪条订阅线路
Orchestrator-driven planning and execution workflow. Use when coordinating Claude memory-backed planning, make-plan/do flows, or preserving execution context across multi-step work.
扫描磁盘空间占用,找出可安全删除的缓存、编译产物、安装包等,交互式清理释放空间
在远程服务器上一键部署 OpenClaw。当用户需要安装 OpenClaw、部署 OpenClaw、配置 OpenClaw 到服务器时使用
Comprehensive toolkit for product managers including RICE prioritization, customer interview analysis, PRD templates, discovery frameworks, and go-to-market strategies. Use for feature prioritization, user research synthesis, requirement documentation, and product strategy development.
诊断 RustDesk 连接问题。当用户说 RustDesk 连不上、无法远程、ID 不显示、注册失败时使用