Skip to content

Commit 07ae251

Browse files
authored
Add GitLab webhook script for automated webhook setup (#446)
* Add GitLab webhook script for automated webhook setup * Fix link to GitLab webhook script in documentation
1 parent 5aa268e commit 07ae251

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

docs/self-hosted/gitlab.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,33 @@ Consult official CodeRabbitAI documentation for a detailed [guide](https://docs.
4444
- Issues events
4545
- Merge request events
4646

47+
We have a convenient [script](/code/gitlab-webhook.sh) to help you add webhooks to all projects in a GitLab instance. You can run it with the following command:
48+
49+
```bash
50+
# Make sure the script is executable:
51+
chmod +x gitlab-webhook.sh
52+
53+
# PAT example (header auto-detected)
54+
export GITLAB_TOKEN="glpat-xxxxx"
55+
./gitlab-add-webhook.sh \
56+
-h "gitlab.example.com" -u "http://<coderabbit-agent-addr>/gitlab_webhooks" \
57+
-s "mySecret" -p 42
58+
59+
# PAT example (explicit header)
60+
./gitlab-add-webhook.sh \
61+
-h "gitlab.example.com" -u "http://<coderabbit-agent-addr>/gitlab_webhooks" \
62+
-s "mySecret" -g "mygroup/mysubgroup/myproject" \
63+
-t "glpat-xxxxx" \
64+
-A "PRIVATE-TOKEN"
65+
66+
# OAuth token with explicit header
67+
./gitlab-add-webhook.sh \
68+
-h "gitlab.example.com" -u "http://<coderabbit-agent-addr>/gitlab_webhooks" \
69+
-s "mySecret" -g "company/backend" \
70+
-t "eyJhbGciOi..." \
71+
-A "Authorization: Bearer"
72+
```
73+
4774
## Prepare a `.env` file
4875

4976
Create a `.env` file with the following content:

static/code/gitlab-webhook.sh

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env bash
2+
3+
## gitlab-webhook.sh
4+
# Add a webhook to one project, or every project in a subgroup tree.
5+
6+
## Example usage:
7+
# Make sure the script is executable:
8+
# chmod +x gitlab-webhook.sh
9+
10+
# PAT example (header auto-detected)
11+
# export GITLAB_TOKEN="glpat-xxxxx"
12+
# ./gitlab-add-webhook.sh \
13+
# -h "gitlab.example.com" -u "https://ci.example.com/gitlab-hook" \
14+
# -s "mySecret" -p 42
15+
16+
# PAT example (explicit header)
17+
# ./gitlab-add-webhook.sh \
18+
# -h "gitlab.example.com" -u "https://ci.example.com/gitlab-hook" \
19+
# -s "mySecret" -g "mygroup/mysubgroup/myproject" \
20+
# -t "glpat-qj5s..." \
21+
# -A "PRIVATE-TOKEN"
22+
23+
# OAuth token with explicit header
24+
# ./gitlab-add-webhook.sh \
25+
# -h "gitlab.example.com" -u "https://ci.example.com/gitlab-hook" \
26+
# -s "mySecret" -g "company/backend" \
27+
# -t "eyJhbGciOi..." \
28+
# -A "Authorization: Bearer"
29+
30+
31+
set -euo pipefail
32+
33+
usage() {
34+
cat <<EOF
35+
Usage:
36+
$0 -h <gitlab-host> -u <webhook-url> -s <webhook-secret> \\
37+
[-t <access-token>] [-A <auth-header>] [-p <project> | -g <group>]
38+
39+
Required:
40+
-h GitLab host (e.g. gitlab.example.com)
41+
-u Webhook endpoint URL to receive POSTs
42+
-s Webhook secret token (used for signature verification)
43+
44+
Authentication (one of):
45+
-t Access token (PAT, project, group or OAuth). If omitted, \$GITLAB_TOKEN is used
46+
-A Auth header to use. Default detects:
47+
PAT → "PRIVATE-TOKEN"
48+
anything else → "Authorization: Bearer"
49+
50+
Scope (choose one):
51+
-p Project ID or full path (e.g. 42 or group/app)
52+
-g Group ID or full path, recurse through all subgroups & projects
53+
EOF
54+
exit 1
55+
}
56+
57+
HOST="" HOOK_URL="" HOOK_SECRET=""
58+
TOKEN="${GITLAB_TOKEN:-}" AUTH_HEADER=""
59+
PROJECT="" GROUP=""
60+
61+
while getopts "h:u:s:t:A:p:g:" opt; do
62+
case "$opt" in
63+
h) HOST=$OPTARG ;;
64+
u) HOOK_URL=$OPTARG ;;
65+
s) HOOK_SECRET=$OPTARG ;;
66+
t) TOKEN=$OPTARG ;;
67+
A) AUTH_HEADER=$OPTARG ;;
68+
p) PROJECT=$OPTARG ;;
69+
g) GROUP=$OPTARG ;;
70+
*) usage ;;
71+
esac
72+
done
73+
74+
# Mandatory checks
75+
[[ -z $HOST || -z $HOOK_URL || -z $HOOK_SECRET ]] && usage
76+
[[ -n $PROJECT && -n $GROUP ]] && usage
77+
[[ -z $PROJECT && -z $GROUP ]] && usage
78+
79+
# Token handling
80+
if [[ -z $TOKEN ]]; then
81+
echo "❌ No access token provided. Use -t or set \$GITLAB_TOKEN" >&2
82+
exit 1
83+
fi
84+
85+
# Choose header if not forced
86+
if [[ -z $AUTH_HEADER ]]; then
87+
if [[ $TOKEN == glpat-* || $TOKEN == "PAT-"* ]]; then
88+
AUTH_HEADER="PRIVATE-TOKEN"
89+
else
90+
AUTH_HEADER="Authorization: Bearer"
91+
fi
92+
fi
93+
94+
API="https://${HOST}/api/v4"
95+
CURL_BASE=(curl -sSf --header "${AUTH_HEADER}: ${TOKEN}")
96+
97+
# Track processed projects to avoid duplicates
98+
declare -A PROCESSED_PROJECTS
99+
# Track projects where webhooks were successfully added
100+
WEBHOOK_PROJECTS=()
101+
102+
##############################################################################
103+
# Helpers
104+
##############################################################################
105+
url_encode() {
106+
local string="$1"
107+
# URL encode the string using printf and sed
108+
printf '%s' "$string" | sed 's/\//%2F/g; s/ /%20/g; s/@/%40/g; s/:/%3A/g; s/#/%23/g; s/?/%3F/g; s/&/%26/g; s/=/%3D/g; s/+/%2B/g'
109+
}
110+
111+
create_hook() {
112+
local pid=$1
113+
114+
# Skip if already processed
115+
if [[ -n "${PROCESSED_PROJECTS[$pid]:-}" ]]; then
116+
return 0
117+
fi
118+
119+
# Mark as processed
120+
PROCESSED_PROJECTS[$pid]=1
121+
122+
local encoded_pid
123+
# URL encode if pid is not purely numeric
124+
if [[ $pid =~ ^[0-9]+$ ]]; then
125+
encoded_pid=$pid
126+
else
127+
encoded_pid=$(url_encode "$pid")
128+
fi
129+
130+
"${CURL_BASE[@]}" --request POST \
131+
--data-urlencode "url=${HOOK_URL}" \
132+
--data "token=${HOOK_SECRET}" \
133+
--data "push_events=true" \
134+
--data "note_events=true" \
135+
--data "issues_events=true" \
136+
--data "merge_requests_events=true" \
137+
--data "enable_ssl_verification=true" \
138+
"${API}/projects/${encoded_pid}/hooks" \
139+
>/dev/null
140+
141+
# Track successful webhook creation
142+
WEBHOOK_PROJECTS+=("$pid")
143+
}
144+
145+
traverse_group() {
146+
local gid=$1
147+
local encoded_gid
148+
# URL encode if gid is not purely numeric
149+
if [[ $gid =~ ^[0-9]+$ ]]; then
150+
encoded_gid=$gid
151+
else
152+
encoded_gid=$(url_encode "$gid")
153+
fi
154+
# projects (includes nested sub-groups)
155+
while IFS= read -r pid; do
156+
[[ -n "$pid" ]] && create_hook "$pid"
157+
done < <(
158+
"${CURL_BASE[@]}" \
159+
"${API}/groups/${encoded_gid}/projects?include_subgroups=true&per_page=100" |
160+
jq -r '.[].id'
161+
)
162+
# recurse explicit subgroups (older GitLab)
163+
while IFS= read -r sg; do
164+
[[ -n "$sg" ]] && traverse_group "$sg"
165+
done < <(
166+
"${CURL_BASE[@]}" "${API}/groups/${encoded_gid}/subgroups?per_page=100" |
167+
jq -r '.[].id'
168+
)
169+
}
170+
171+
##############################################################################
172+
# Main
173+
##############################################################################
174+
if [[ -n $PROJECT ]]; then
175+
create_hook "$PROJECT"
176+
else
177+
traverse_group "$GROUP"
178+
fi
179+
180+
# Print final summary
181+
if [[ ${#WEBHOOK_PROJECTS[@]} -eq 0 ]]; then
182+
echo "❌ No webhooks were installed."
183+
else
184+
echo "✅ Webhooks installed successfully on ${#WEBHOOK_PROJECTS[@]} project(s):"
185+
for pid in "${WEBHOOK_PROJECTS[@]}"; do
186+
echo " - Project ID: $pid"
187+
done
188+
fi

0 commit comments

Comments
 (0)