【Git】GitLab 18.9 全局服务器钩子(Server Hooks)官方规范与落地实践

文档来源https://archives.docs.gitlab.com/18.9/administration/server_hooks/?tab=Linux+package+(Omnibus)


一、概述

Git Server Hooks 是运行在 GitLab 服务端的自定义逻辑,可用于提交规范校验、分支权限控制、状态触发任务等。

支持三种标准 Git 钩子:

  • pre-receive:接收推送前执行,可拒绝整个推送
  • update:每个分支更新前执行一次,可拒绝单分支
  • post-receive:全部更新完成后执行,不可拒绝推送

适用版本:GitLab Free/Premium/Ultimate(自托管)

部署方式:Linux package (Omnibus)


二、前置条件

  1. GitLab 服务器 root 权限
  2. Gitaly 配置文件路径(默认):
    /var/opt/gitlab/gitaly/config.toml
  3. 钩子脚本必须可执行 ,属主为 git:git
  4. 脚本不匹配备份文件后缀(如 *~

三、全局服务器钩子配置(官方标准)

1. 配置全局钩子目录

/etc/gitlab/gitlab.rb 中配置:

ruby 复制代码
gitaly['configuration'][:hooks][:custom_hooks_dir] = '/var/opt/gitlab/gitaly/custom_hooks'

官方默认推荐目录:
/var/opt/gitlab/gitaly/custom_hooks

2. 创建目录结构

必须按钩子类型创建 .d 目录:

bash 复制代码
mkdir -p /var/opt/gitlab/gitaly/custom_hooks/pre-receive.d

3. 放置钩子脚本

  • 目录:pre-receive.d/
  • 文件名:任意可执行文件(无后缀要求)
  • 首行必须指定解释器:#!/bin/bash

4. 权限配置(关键)

bash 复制代码
chmod +x /var/opt/gitlab/gitaly/custom_hooks/pre-receive.d/*.sh
chown -R git:git /var/opt/gitlab/gitaly/custom_hooks
chmod -R 755 /var/opt/gitlab/gitaly/custom_hooks

5. 重载生效

bash 复制代码
gitlab-ctl reconfigure
gitlab-ctl restart gitaly

四、执行顺序与规则

  1. 优先执行 GitLab 内置钩子
  2. 执行项目级钩子:repo.git/custom_hooks/pre-receive.d/*
  3. 执行全局钩子:custom_hooks_dir/pre-receive.d/*
  4. 文件名字母序执行
  5. 任意钩子 exit 1 即中断推送

五、支持的环境变量

GitLab 内置变量

变量 说明
GL_ID 操作用户/key 标识(user-123 / key-456)
GL_USERNAME GitLab 用户名
GL_PROJECT_PATH 项目路径
GL_REPOSITORY 项目 ID(project-1234)
GL_PROTOCOL 协议:ssh / http / web

Git 内置变量

  • GIT_OBJECT_DIRECTORY
  • GIT_ALTERNATE_OBJECT_DIRECTORIES
  • GIT_PUSH_OPTION_COUNT

六、自定义错误信息规范

为在终端与 Web 端清晰展示错误,必须使用官方前缀:

bash 复制代码
echo "GL-HOOK-ERR: 仅允许创建 main 和 dev 分支!"

七、最佳实践:分支硬限制脚本(生产可用)

目标

全局强制仓库仅允许 main + dev 两个分支,禁止创建任何其他分支。

脚本路径

/var/opt/gitlab/gitaly/custom_hooks/pre-receive.d/branch-restrict.sh

脚本内容

bash 复制代码
#!/bin/bash
while read oldrev newrev refname; do
    if [[ "$oldrev" == "0000000000000000000000000000000000000000" ]]; then
        if [[ "$refname" != "refs/heads/main" && "$refname" != "refs/heads/dev" ]]; then
            echo "GL-HOOK-ERR: 禁止创建分支!仅允许 main 和 dev 分支。"
            exit 1
        fi
    fi
done
exit 0
群组限制
shell 复制代码
cat > /var/opt/gitlab/gitaly/custom_hooks/pre-receive.d/branch-group-restrict.sh << 'EOF'
#!/bin/bash

# GitLab 全局分支限制策略
# 规则:
# 1. 个人命名空间(username/*):不限制,可自由创建分支
# 2. 群组命名空间(group/*):仅允许 main 和 dev 分支

# 获取环境变量(GitLab 自动注入)
PROJECT_PATH="$GL_PROJECT_PATH"
USERNAME="$GL_USERNAME"

# ===================== 核心逻辑 =====================
# 如果是【个人仓库】 → 直接放行
# 逻辑:判断路径是否以 "用户名/" 开头
if [[ "$PROJECT_PATH" == "${USERNAME}/"* ]]; then
    exit 0
fi

# ===================== 群组仓库分支限制 =====================
while read -r oldrev newrev refname; do
    # 仅拦截:创建新分支(oldrev 为全 0)
    if [[ "$oldrev" == "0000000000000000000000000000000000000000" ]]; then
        # 分支白名单校验
        if [[ "$refname" != "refs/heads/main" && "$refname" != "refs/heads/dev" ]]; then
            echo "GL-HOOK-ERR: [组织合规] 群组仓库仅允许使用 main / dev 分支,禁止创建自定义分支!"
            exit 1
        fi
    fi
done

exit 0
EOF
白名单模式
shell 复制代码
#!/bin/bash

# ==============================================================================
# 配置区
# ==============================================================================

# 使用正则表达式匹配顶级群组,更高效且易读
# 匹配 ros, hardware, tools, web, algo 下的所有子路径
LIMIT_GROUPS_REGEX="^(ros|hardware|tools|web|algo)/"

# 允许的分支(使用数组配合正则判断)
ALLOWED_BRANCHES_REGEX="^(main|dev)$"

# ==============================================================================
# 核心逻辑
# ==============================================================================

# 获取环境变量
PROJECT="$GL_PROJECT_PATH"

# 1. 快速判定:是否在限制名单内
if [[ ! "$PROJECT" =~ $LIMIT_GROUPS_REGEX ]]; then
    exit 0
fi

# 2. 循环处理推送流(标准输入获取 oldrev, newrev, refname)
while read -r oldrev newrev refname; do
    
    # 仅处理【分支】引用 (refs/heads/),忽略 Tags 或其他
    if [[ "$refname" != refs/heads/* ]]; then
        continue
    fi

    # 3. 判定是否为【新建分支】动作 (oldrev 为全 0)
    if [[ "$oldrev" == "0000000000000000000000000000000000000000" ]]; then
        
        # 提取分支短名称
        branch="${refname#refs/heads/}"

        # 4. 校验分支名是否符合允许的正则
        if [[ ! "$branch" =~ $ALLOWED_BRANCHES_REGEX ]]; then
            echo "--------------------------------------------------------"
            echo "GL-HOOK-ERR: ❌ [组织合规性拦截]"
            echo "项目路径: $PROJECT"
            echo "错误原因: 该业务群组受限,禁止创建自定义分支 '$branch'"
            echo "允许分支: main, dev"
            echo "--------------------------------------------------------"
            exit 1
        fi
    fi
done

exit 0

八、常见问题排查

1. 钩子不生效

  • 目录是否为 custom_hooks/pre-receive.d
  • 权限是否为 git:git + chmod +x
  • 是否重启 gitaly
  • 是否在 gitlab.rb 正确配置 custom_hooks_dir

2. pre-receive hook declined

  • 查看钩子输出错误信息

  • 查看 Gitaly 日志:

    bash 复制代码
    grep PreReceiveHook /var/log/gitlab/gitaly/current

3. SSH 不触发、HTTP 触发

  • 新版 GitLab 统一走 Gitaly Hooks,SSH/HTTP 表现一致
  • 确认使用全局 Gitaly 钩子,而非旧版 git-shell 目录

九、总结(可直接用于技术规范文档)

  1. GitLab 18.9 全局服务端钩子唯一标准目录
    /var/opt/gitlab/gitaly/custom_hooks
  2. 必须使用 .d 目录结构存放钩子
  3. 权限必须是 git:git + 可执行
  4. 分支限制推荐使用白名单模式(仅允许 main/dev)
  5. 错误信息必须以 GL-HOOK-ERR: 开头
  6. 配置完成需重启 gitaly 生效
相关推荐
司悠1 天前
【解决在vscode里开服务器登录codeX后发消息会一直reconnecting】
服务器·ide·vscode
倔强的石头1061 天前
Fooocus开源神器+cpolarAI让绘画告别服务器依赖
运维·服务器·cpolar
Ajie'Blog1 天前
Copilot Agent Tasks API 开放:AI 编程开始进入后台任务时代
服务器·前端·javascript·人工智能·copilot·ai编程
wei_shuo1 天前
服务器挂了等用户投诉才发现?我用Beszel搭了轻量监控系统,宕机第一时间通知我
运维·服务器
王码码20351 天前
多台服务器怎么统一看状态?Beszel 轻量监控,搭起来不费事
运维·服务器·后端·安全·阿里云·接口·web
_codemonster1 天前
git 容易混淆的点
git
剑神一笑1 天前
Linux ls 命令深度解析:从目录遍历到颜色输出的实现原理
linux·服务器·数据库
_codemonster1 天前
Git 最常用操作和原理
大数据·git·elasticsearch
Jinkxs1 天前
Python基础 - 文件的写入操作 write与writelines方法
android·服务器·python
兮动人1 天前
服务器流量监控与性能优化实战
服务器·网络·性能优化·服务器流量监控与性能优化实战