Webhook.md 6.5 KB

如何使用 Gogs 设置 Git 钩子(Git Hooks)来实现项目的自动化部署

使用 Webhooks:这是 Gogs 官方推荐的、更现代、更灵活的方式。它通过 HTTP 请求通知你的服务器进行部署。这种方法的核心思想是:当 Gogs 收到 git push 时,它会向你配置的一个 URL 发送一个带有提交信息的 POST 请求。你的 Web 服务器上需要有一个小的应用一直监听这个 URL,收到请求后执行部署脚本。

步骤 1: 准备部署脚本 (deploy.sh)

首先,确保你已经有一个可以正常工作的部署脚本,就像上一个回答中提到的那样。我们假设它位于 /home/myuser/deploy.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 文件。

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 目录下执行:

    go mod init gogs-webhook-listener
    
  2. 构建二进制文件 这个命令会生成一个名为 gogs-webhook-listener 的可执行文件。

    go build .
    
  3. 运行服务 可以直接在前台运行进行测试:

    ./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. 写入以下内容。注意修改 UserExecStart 的路径。

    [Unit]
    Description=Gogs Webhook Listener for Auto Deployment
    After=network.target
    
    [Service]
    Type=simple
    # 建议创建一个专门的用户来运行此服务
    User=root
    # ExecStart 指向你编译好的二进制文件的绝对路径
    ExecStart=/root/Code/go/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