一个基于Go和Echo框架的自动化部署系统,支持通过Webhook触发部署流程,自动下载文件并在多台服务器上执行部署脚本。
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Webhook API │───▶│ 流程引擎 │───▶│ SSH管理器 │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 日志系统 │ │ OSS下载器 │ │ 脚本执行器 │
└─────────────────┘ └──────────────────┘ └─────────────────┘
go mod download
创建 config/config.yaml 配置文件:
# OSS配置
oss:
endpoint: "oss-cn-hangzhou.aliyuncs.com"
access_key_id: "your_access_key_id"
access_key_secret: "your_access_key_secret"
bucket: "your-deployment-bucket"
# 服务器配置
servers:
server-1:
host: "192.168.1.10"
auth_type: "key"
server-2:
host: "192.168.1.11"
auth_type: "password"
username: "deploy"
password: "your_password"
# 工作流配置
workflows:
frontend-deploy:
download_path: "/tmp/frontend"
steps:
- name: "deploy_to_servers"
servers: ["server-1", "server-2"]
scp:
remote_path: "/var/www/"
scripts: |
cd /var/www
unzip -o *.zip
systemctl reload nginx
# 开发模式
go run main.go
# 编译后运行
go build
./deploy-box
# 构建镜像
docker build -t deploy-box .
# 使用Docker Compose
docker-compose up -d
当deploy-box在Docker容器中运行,需要在宿主机执行部署任务时,只需配置宿主机的SSH免密登录即可:
servers:
docker-host:
host: "宿主机IP" # Docker Desktop
# host: "172.17.0.1" # Linux Docker
auth_type: "key"
ssh_key: "/app/.ssh/id_rsa.pub" # 容器内SSH密钥路径
配置步骤:
ssh-keygen -t rsa -b 4096 -C "deploy-box" -f ./ssh/host_key
# 将公钥添加到宿主机authorized_keys
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
docker run -d \
-v ./ssh:/app/.ssh:ro \
-v ./config:/app/config \
deploy-box
这样deploy-box容器就能通过SSH连接到宿主机执行部署命令了。
POST /webhook/{workflow_id}?token={token}
Content-Type: application/json
# 前端部署(需要文件下载)
{
"version": "v1.2.3",
"download_url": "https://bucket.oss-cn-hangzhou.aliyuncs.com/app-v1.2.3.zip"
}
# Docker部署(不需要文件下载)
{
"version": "v1.2.3",
"image": "myapp:v1.2.3"
}
注意:
timestamp 字段会在服务器端自动生成,无需客户端提供download_path 或步骤中包含 scp,则 download_url 为必需参数download_url 参数,服务器会返回 400 Bad Request 错误将本地文件直接上传到工作流的下载目录(download.path),不会触发部署脚本。
# POST 表单上传
curl -X POST "http://localhost:8080/upload/{workflow_id}?token={token}" \
-F "[email protected]"
# PUT 直接上传(curl -T,推荐)
curl -T dist.zip "http://localhost:8080/upload/{workflow_id}?token={token}&filename=dist.zip"
上传后需再调用 /webhook(用 filename 指向刚上传的文件)才会执行部署。
PUT /deploy/{workflow_id} 在一次调用中完成「保存文件 + 触发工作流」:先把文件写入 download.path,随后按工作流步骤 SCP 到目标服务器 → 执行 scripts 脚本(顺序执行、任一步失败即中止)。
# 单行、纯英文引号、使用 -T(PUT 原始上传)
curl --http2 -v -A "Mozilla/5.0" -T dist.zip "http://localhost:8080/deploy/{workflow_id}?token={token}&filename=dist.zip"
# 部署完成后自动清理上传的文件(默认不清理)
curl -T dist.zip "http://localhost:8080/deploy/{workflow_id}?token={token}&filename=dist.zip&cleanup=true"
⚠️ 常见坑(务必注意):
-T(PUT),不要用 -F:/deploy 只注册了 PUT,-F 会发 POST(返回 405);且服务端是把整个请求体当原始文件写盘的,-F 的 multipart 分隔头会被写进文件,导致 zip 损坏。&filename=xxx:否则文件名解析会落到默认值 upload_<时间戳>.tar,可能与脚本预期的文件名不符。\ 续行或全角引号 "” 会导致 curl: (3) URL rejected: Malformed input to a URL function(curl 解析 URL 阶段就失败,尚未发出请求)。dist.zip 在当前目录:-T 用相对路径,文件不存在会报 Can't open 'dist.zip'。https://jd.xingyunka.com/ayouok-rehu90/deploy/{workflow_id}?...,前缀 /ayouok-rehu90 由 nginx 转发到后端 /deploy/{workflow_id}。文件名解析优先级(/deploy 与 PUT /upload):
filenameContent-Disposition: filename="..."download.rename/deploy 兜底默认 upload_<时间戳>.tar;PUT /upload 无法确定时返回 400也可用路径形式携带文件名:
PUT /deploy/{workflow_id}/dist.zip(对应/deploy/:id/*路由)。
接口对比:
| 接口 | 保存文件 | 执行部署脚本 |
|---|---|---|
POST/PUT /upload/{id} | ✅ | ❌ |
POST /webhook/{id} | ❌ | ✅ |
PUT /deploy/{id} | ✅ | ✅ |
# 获取执行日志
GET /api/v1/logs/webhook/{execution_id}
# 获取最新日志
GET /api/v1/logs/latest
# 获取工作流列表
GET /api/v1/workflows
# 生成curl命令
GET /gen/curl/{workflow_name}
# 健康检查
GET /health
获取指定工作流的curl命令示例:
# 获取工作流的curl命令
GET /gen/curl/vue3-admin-frontend
# 响应示例:
{
"workflow_name": "vue3-admin-frontend",
"workflow_id": "wf_a1b2c3d4e5f6",
"webhook_url": "http://localhost:8080/webhook/wf_a1b2c3d4e5f6?token=tk_abc123def456",
"curl_command": "curl -X POST \"http://localhost:8080/webhook/wf_a1b2c3d4e5f6?token=tk_abc123def456\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"version\": \"v1.2.3\",\n \"download_url\": \"https://your-bucket.oss-cn-hangzhou.aliyuncs.com/app-v1.2.3.zip\",\n \"timestamp\": \"2024-01-01T10:00:00Z\"\n}'",
"curl_simple": "curl -X POST \"http://localhost:8080/webhook/wf_a1b2c3d4e5f6?token=tk_abc123def456\" -H \"Content-Type: application/json\" -d '{\"version\":\"v1.2.3\",\"timestamp\":\"2024-01-01T10:00:00Z\",\"download_url\":\"https://your-bucket.oss-cn-hangzhou.aliyuncs.com/app-v1.2.3.zip\"}'",
"example_payload": {
"version": "v1.2.3",
"download_url": "https://your-bucket.oss-cn-hangzhou.aliyuncs.com/app-v1.2.3.zip"
},
"instructions": {
"description": "Use this curl command to trigger the webhook",
"notes": [
"Replace the version with your actual version",
"For frontend deployments, update the download_url",
"For Docker deployments, update the image tag",
"timestamp will be auto-generated by the server"
]
}
}
支持两种认证方式:
servers:
# 密钥认证(默认)
server-key:
host: "192.168.1.10"
auth_type: "key"
ssh_key: "/path/to/private/key" # 可选,默认~/.ssh/id_rsa
# 密码认证
server-pwd:
host: "192.168.1.11"
auth_type: "password"
username: "deploy"
password: "your_password"
每个步骤支持以下配置:
steps:
# 本地执行
- name: "local_step"
scripts: "echo 'local command'"
# 远程执行
- name: "remote_step"
servers: ["server-1", "server-2"]
scripts: |
echo 'multi-line'
echo 'commands'
# OSS下载+文件传输+远程执行
- name: "oss_download_deploy"
download:
type: "oss" # OSS下载
path: "./downloads" # 下载到的本地目录
rename: "frontend.zip" # 重命名文件
servers: ["server-1"]
scp:
remote_path: "/var/www/" # SCP会自动传输 ./downloads/frontend.zip
scripts: |
cd /var/www
unzip -o frontend.zip
# HTTP下载+文件传输+远程执行
- name: "http_download_deploy"
download:
type: "default" # HTTP下载(默认值)
path: "./downloads" # 下载到的本地目录
rename: "app.tar.gz" # 重命名文件
servers: ["server-1"]
scp:
remote_path: "/opt/app/" # SCP会自动传输 ./downloads/app.tar.gz
scripts: |
cd /opt/app
tar -xzf app.tar.gz
workflows:
vue-frontend:
steps:
- name: "download_and_deploy"
download:
type: "oss"
path: "./downloads"
rename: "frontend.zip"
servers: ["web-server-1", "web-server-2"]
scp:
remote_path: "/var/www/app/" # SCP会自动传输 ./downloads/frontend.zip
scripts: |
cd /var/www/app
unzip -o frontend.zip -d current/
chown -R www-data:www-data current/
systemctl reload nginx
Webhook调用:
curl -X POST "http://localhost:8080/webhook/wf_abc123?token=tk_def456" \
-H "Content-Type: application/json" \
-d '{"version": "v1.2.3", "download_url": "frontend/releases/v1.2.3/dist.zip"}'
workflows:
external-app:
steps:
- name: "download_and_deploy"
download:
type: "default"
path: "./downloads"
rename: "app.tar.gz"
servers: ["app-server"]
scp:
remote_path: "/opt/app/" # SCP会自动传输 ./downloads/app.tar.gz
scripts: |
cd /opt/app
tar -xzf app.tar.gz
./install.sh
Webhook调用:
curl -X POST "http://localhost:8080/webhook/wf_abc123?token=tk_def456" \
-H "Content-Type: application/json" \
-d '{"version": "v1.2.3", "download_url": "https://github.com/user/repo/releases/download/v1.2.3/app.tar.gz"}'
workflows:
api-service:
steps:
- name: "update_containers"
servers: ["app-server-1", "app-server-2"]
scripts: |
docker pull myapp:latest
docker stop myapp || true
docker run -d --name myapp \
--restart=always \
-p 8080:8080 \
myapp:latest
go test ./tests/...
# 本地构建
go build
# 交叉编译
GOOS=linux GOARCH=amd64 go build
deploy-box/
├── api/handlers/ # API处理器
├── cmd/ # 命令行入口
├── config/ # 配置文件
├── internal/
│ ├── config/ # 配置管理
│ ├── logger/ # 日志系统
│ ├── oss/ # OSS下载
│ ├── ssh/ # SSH客户端
│ └── workflow/ # 工作流引擎
├── pkg/models/ # 数据模型
├── tests/ # 测试用例
└── main.go # 主程序
SSH连接失败
OSS下载失败
脚本执行失败
# 查看应用日志
docker logs deploy-box
# 查看执行日志
curl http://localhost:8080/api/v1/logs/latest
MIT License
Content type
Image
Digest
sha256:2d96b649d…
Size
15.7 MB
Last updated
about 2 months ago
docker pull buzzxu/deploy-box