|
|
@@ -0,0 +1,225 @@
|
|
|
+### 步骤 1: 准备部署脚本 (deploy.sh)
|
|
|
+
|
|
|
+首先,确保你已经有一个可以正常工作的部署脚本,就像上一个回答中提到的那样。我们假设它位于 `/home/myuser/deploy.sh`。
|
|
|
+
|
|
|
+```sh
|
|
|
+#!/bin/bash
|
|
|
+# /home/myuser/deploy.sh
|
|
|
+
|
|
|
+set -e
|
|
|
+
|
|
|
+PROJECT_DIR="/usr/local/nginx/html/huahuibao"
|
|
|
+GIT_BRANCH="dev"
|
|
|
+
|
|
|
+echo "======== [Webhook GO] 部署开始 `date` ========"
|
|
|
+cd $PROJECT_DIR
|
|
|
+unset GIT_DIR
|
|
|
+/usr/local/git/bin/git reset --hard
|
|
|
+/usr/local/git/bin/git pull origin $GIT_BRANCH
|
|
|
+
|
|
|
+# ... 其他构建或重启命令 ...
|
|
|
+# 例如:
|
|
|
+# echo "正在重启服务..."
|
|
|
+# sudo systemctl restart my-app
|
|
|
+
|
|
|
+echo "======== [Webhook dev分支] 部署成功 `date` ========"
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+### 步骤 2: Go 语言 Webhook 监听服务代码
|
|
|
+
|
|
|
+此代码放在测试服 /root/Code/go/gogs-webhook-listener 目录下
|
|
|
+
|
|
|
+创建一个名为 `gogs-webhook-listener` 的文件夹,并在其中创建一个 `main.go` 文件。
|
|
|
+```Go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "crypto/hmac"
|
|
|
+ "crypto/sha256"
|
|
|
+ "encoding/hex"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "log"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
+ "os/exec"
|
|
|
+)
|
|
|
+
|
|
|
+// --- 配置项 ---
|
|
|
+// 这些值可以硬编码,也可以通过环境变量或配置文件传入
|
|
|
+const (
|
|
|
+ // Webhook 监听地址和端口
|
|
|
+ listenAddr = "0.0.0.0:8000"
|
|
|
+ // Gogs Webhook 中设置的密钥,!!务必修改为你的密钥!!
|
|
|
+ gogsSecret = "gogs_secret_key-xhb*2025"
|
|
|
+ // 要执行的部署脚本路径
|
|
|
+ deployScriptPath = "/root/sh/gogs_deploy.sh"
|
|
|
+ // 只对这个分支的 push 事件做出反应
|
|
|
+ targetBranch = "refs/heads/dev"
|
|
|
+ // Webhook 访问路径
|
|
|
+ webhookPath = "/webhook"
|
|
|
+)
|
|
|
+
|
|
|
+// GogsPushPayload 定义了我们关心的 Gogs push 事件的 JSON 结构体字段
|
|
|
+type GogsPushPayload struct {
|
|
|
+ Ref string `json:"ref"` // The full git ref that was pushed. Example: "refs/heads/master".
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ http.HandleFunc(webhookPath, handleWebhook)
|
|
|
+
|
|
|
+ log.Printf("Gogs Webhook 监听服务已启动,地址: http://%s%s\n", listenAddr, webhookPath)
|
|
|
+ if err := http.ListenAndServe(listenAddr, nil); err != nil {
|
|
|
+ log.Fatalf("启动 HTTP 服务失败: %v", err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// handleWebhook 是处理所有进入 webhookPath 的请求的函数
|
|
|
+func handleWebhook(w http.ResponseWriter, r *http.Request) {
|
|
|
+ // 1. 检查请求方法是否为 POST
|
|
|
+ if r.Method != http.MethodPost {
|
|
|
+ log.Printf("无效的请求方法: %s", r.Method)
|
|
|
+ http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 读取请求体
|
|
|
+ body, err := io.ReadAll(r.Body)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("读取请求体失败: %v", err)
|
|
|
+ http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ defer r.Body.Close()
|
|
|
+
|
|
|
+ // 3. 验证签名 (关键安全步骤)
|
|
|
+ gogsSignature := r.Header.Get("X-Gogs-Signature")
|
|
|
+ if !isValidSignature(body, gogsSecret, gogsSignature) {
|
|
|
+ log.Println("签名验证失败!请求来源可疑。")
|
|
|
+ http.Error(w, "Invalid signature", http.StatusUnauthorized)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 检查是否为 'push' 事件
|
|
|
+ if r.Header.Get("X-Gogs-Event") != "push" {
|
|
|
+ log.Println("接收到非 push 事件,已忽略。")
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
+ fmt.Fprint(w, "Not a push event, ignored.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 解析 JSON 并检查分支
|
|
|
+ var payload GogsPushPayload
|
|
|
+ if err := json.Unmarshal(body, &payload); err != nil {
|
|
|
+ log.Printf("解析 JSON payload 失败: %v", err)
|
|
|
+ http.Error(w, "Invalid payload", http.StatusBadRequest)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if payload.Ref != targetBranch {
|
|
|
+ log.Printf("接收到对分支 '%s' 的 push,已忽略。目标分支为 '%s'。", payload.Ref, targetBranch)
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
+ fmt.Fprintf(w, "Push to branch %s ignored.", payload.Ref)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 执行部署脚本
|
|
|
+ log.Printf("验证通过,开始执行部署脚本: %s", deployScriptPath)
|
|
|
+ cmd := exec.Command("sh", deployScriptPath)
|
|
|
+ // 将脚本的标准输出和标准错误输出重定向到 Go 程序的日志
|
|
|
+ cmd.Stdout = os.Stdout
|
|
|
+ cmd.Stderr = os.Stderr
|
|
|
+
|
|
|
+ if err := cmd.Run(); err != nil {
|
|
|
+ log.Printf("执行部署脚本失败: %v", err)
|
|
|
+ http.Error(w, "Deployment script execution failed", http.StatusInternalServerError)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ log.Println("部署脚本执行成功。")
|
|
|
+ w.WriteHeader(http.StatusOK)
|
|
|
+ fmt.Fprint(w, "Deployment triggered successfully.")
|
|
|
+}
|
|
|
+
|
|
|
+// isValidSignature 验证 Gogs 的 HMAC-SHA256 签名
|
|
|
+func isValidSignature(body []byte, secret, signature string) bool {
|
|
|
+ if signature == "" {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ mac := hmac.New(sha256.New, []byte(secret))
|
|
|
+ mac.Write(body)
|
|
|
+ expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
|
|
+ return hmac.Equal([]byte(signature), []byte(expectedMAC))
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
+### 步骤 3: 编译和运行
|
|
|
+
|
|
|
+代码放在测试服 /root 目录下
|
|
|
+
|
|
|
+1. **初始化 Go Module** (如果你还没有的话)
|
|
|
+ 在 `gogs-webhook-listener` 目录下执行:
|
|
|
+```Bash
|
|
|
+go mod init gogs-webhook-listener
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+2. **构建二进制文件** 这个命令会生成一个名为 `gogs-webhook-listener` 的可执行文件。
|
|
|
+```Bash
|
|
|
+go build .
|
|
|
+```
|
|
|
+
|
|
|
+3. **运行服务** 可以直接在前台运行进行测试:
|
|
|
+```Bash
|
|
|
+./gogs-webhook-listener
|
|
|
+```
|
|
|
+你应该会看到日志输出: `Gogs Webhook 监听服务已启动,地址: http://0.0.0.0:8000/webhook`
|
|
|
+
|
|
|
+
|
|
|
+### 步骤 4: 设置为后台服务 (推荐使用 Systemd)
|
|
|
+
|
|
|
+为了让这个服务在服务器上长期稳定地运行,并能开机自启,最好将它配置成一个 `systemd` 服务。
|
|
|
+
|
|
|
+1. 创建 service 文件:
|
|
|
+```
|
|
|
+vim /etc/systemd/system/gogs-webhook.service
|
|
|
+```
|
|
|
+
|
|
|
+2. 写入以下内容。注意修改 `User` 和 `ExecStart` 的路径。
|
|
|
+```
|
|
|
+[Unit]
|
|
|
+Description=Gogs Webhook Listener for Auto Deployment
|
|
|
+After=network.target
|
|
|
+
|
|
|
+[Service]
|
|
|
+Type=simple
|
|
|
+# 建议创建一个专门的用户来运行此服务
|
|
|
+User=myuser
|
|
|
+# ExecStart 指向你编译好的二进制文件的绝对路径
|
|
|
+ExecStart=/home/myuser/gogs-webhook-listener/gogs-webhook-listener
|
|
|
+Restart=on-failure
|
|
|
+RestartSec=5s
|
|
|
+
|
|
|
+[Install]
|
|
|
+WantedBy=multi-user.target
|
|
|
+```
|
|
|
+
|
|
|
+3. 重载 `systemd` 并启动服务:
|
|
|
+```
|
|
|
+systemctl daemon-reload
|
|
|
+systemctl start gogs-webhook.service
|
|
|
+```
|
|
|
+
|
|
|
+4. 检查服务状态:
|
|
|
+```
|
|
|
+systemctl status gogs-webhook.service
|
|
|
+```
|
|
|
+ 如果一切正常,它应该显示 `active (running)`。
|
|
|
+
|
|
|
+5. 设置开机自启:
|
|
|
+```
|
|
|
+systemctl enable gogs-webhook.service
|
|
|
+```
|