#!/bin/bash
# Set the same Secret token on the deploy webhook of every project in a GitLab group.
#
# Dry run (shows what would change, changes nothing):
#   GITLAB_TOKEN=glpat-xxx WEBHOOK_SECRET=yyy ./set-webhook-secret.sh
# Apply:
#   GITLAB_TOKEN=glpat-xxx WEBHOOK_SECRET=yyy ./set-webhook-secret.sh --apply
#
# GITLAB_TOKEN:   personal access token with the "api" scope, from a user who is
#                 Maintainer (or higher) on the projects.
# WEBHOOK_SECRET: the same value as 'webhook_secret' in /etc/gitlab-webhook/config.php.
#
# Only hooks whose URL contains HOOK_MATCH (default: webgitlab.php) are touched.
# Needs: bash, curl, jq.
set -euo pipefail

for tool in curl jq; do
    command -v "$tool" >/dev/null || { echo "This script needs $tool (e.g. sudo apt install $tool)"; exit 1; }
done

GITLAB_URL="${GITLAB_URL:-https://amgit.altametrics.com}"
GROUP="${GROUP:-web-development-team}"
HOOK_MATCH="${HOOK_MATCH:-webgitlab.php}"
BRANCH="${BRANCH:-master}"
: "${GITLAB_TOKEN:?Set GITLAB_TOKEN (personal access token with api scope)}"
: "${WEBHOOK_SECRET:?Set WEBHOOK_SECRET (same value as webhook_secret in config.php)}"

APPLY=0
[ "${1:-}" = "--apply" ] && APPLY=1

api() {
    curl -sSf -H "PRIVATE-TOKEN: $GITLAB_TOKEN" "$@"
}

group_enc=$(jq -rn --arg g "$GROUP" '$g | @uri')
projects=$(mktemp)
trap 'rm -f "$projects"' EXIT

# All non-archived projects in the group, including subgroups.
page=1
while :; do
    batch=$(api "$GITLAB_URL/api/v4/groups/$group_enc/projects?include_subgroups=true&archived=false&simple=true&per_page=100&page=$page")
    [ "$(jq length <<<"$batch")" -eq 0 ] && break
    jq -r '.[] | "\(.id)\t\(.path_with_namespace)"' <<<"$batch" >> "$projects"
    page=$((page + 1))
done

total=$(wc -l < "$projects")
echo "Found $total projects in $GROUP. Mode: $([ $APPLY = 1 ] && echo APPLY || echo 'dry run')"
echo

updated=0; missing=0; failed=0
while IFS=$'\t' read -r id path; do
    hooks=$(api "$GITLAB_URL/api/v4/projects/$id/hooks") || { echo "FAIL     $path (cannot read hooks: token needs Maintainer on this project)"; failed=$((failed + 1)); continue; }
    matches=$(jq -c --arg m "$HOOK_MATCH" '[.[] | select(.url | contains($m))]' <<<"$hooks")

    if [ "$(jq length <<<"$matches")" -eq 0 ]; then
        echo "MISSING  $path (no webhook pointing at $HOOK_MATCH)"
        missing=$((missing + 1))
        continue
    fi

    while read -r hook; do
        hook_id=$(jq -r .id <<<"$hook")
        hook_url=$(jq -r .url <<<"$hook")
        if [ $APPLY = 0 ]; then
            echo "WOULD    $path  hook $hook_id  $hook_url"
            continue
        fi
        # url is required by the edit endpoint; the other fields enforce push-to-master only.
        if api -X PUT "$GITLAB_URL/api/v4/projects/$id/hooks/$hook_id" \
            --data-urlencode "url=$hook_url" \
            --data-urlencode "token=$WEBHOOK_SECRET" \
            --data-urlencode "push_events_branch_filter=$BRANCH" \
            -d "push_events=true" -o /dev/null; then
            echo "UPDATED  $path  hook $hook_id"
            updated=$((updated + 1))
        else
            echo "FAIL     $path  hook $hook_id (update rejected)"
            failed=$((failed + 1))
        fi
    done < <(jq -c '.[]' <<<"$matches")
done < "$projects"

echo
if [ $APPLY = 1 ]; then
    echo "Updated $updated hook(s). Missing webhook: $missing project(s). Failed: $failed."
else
    echo "Dry run only. Re-run with --apply to change them. Missing webhook: $missing project(s). Failed: $failed."
fi
[ "$failed" -eq 0 ]
