【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 生效
相关推荐
羊小蜜.2 小时前
Linux 03:僵死进程(Zombie Process)原理、危害与解决方案
linux·运维·服务器
cen__y2 小时前
Linux06(进程)
linux·运维·服务器·c语言·ubuntu
Gofarlic_oms12 小时前
Allegro高级功能模块许可证管理注意事项
运维·服务器·开发语言·matlab·负载均衡
IMPYLH2 小时前
Linux 的 sum 命令
linux·运维·服务器·chrome·python·bash
AIDF20263 小时前
我们看一份报告的时候主要看什么
运维·服务器·推理·vllm
用户11481867894843 小时前
Git Stash 丢失后的完整找回指南
前端·git
Ting.~3 小时前
GIT详解
java·笔记·git
Mrlxl.cn3 小时前
计算机网络——应用层
运维·服务器·计算机网络
计算机安禾3 小时前
【Linux从入门到精通】第23篇:条件判断——让脚本拥有“大脑”
linux·运维·服务器