Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 106 additions & 25 deletions scripts/apply-repo-settings.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,69 @@
#!/usr/bin/env bash
# Apply repository-level security settings via the GitHub API.
# Apply repository-level standard settings via the GitHub API.
#
# Called by the apply-repo-settings workflow on every push to main so that
# settings documented in .github/settings.yml stay in effect even if they
# are reset manually.
# Applies security_and_analysis settings and disables check-suite auto-trigger
# for apps that queue suites on every push without completing them (Claude,
# CodeRabbit), which permanently blocks GitHub auto-merge.
#
# Required token scope: administration:write
# Usage (local): GH_TOKEN=<token> GITHUB_REPOSITORY=petry-projects/TalkTerm \
# ./scripts/apply-repo-settings.sh
set -euo pipefail
# Standard: petry-projects/.github/standards/github-settings.md
# #check-suite-auto-trigger-preferences
#
# Usage:
# bash scripts/apply-repo-settings.sh <repo-name> # e.g. TalkTerm
# bash scripts/apply-repo-settings.sh <owner/repo>
# GITHUB_REPOSITORY=owner/repo bash scripts/apply-repo-settings.sh # CI form
#
# Environment:
# GH_TOKEN GitHub token. The check-suites API rejects OAuth app
# tokens — use a classic PAT with `repo` scope (or admin).
# ORG GitHub org used to expand a bare repo name (default:
# petry-projects).
# GITHUB_REPOSITORY owner/repo, used when no positional argument is given
# (set automatically by GitHub Actions).
#
# The helpers below are pure and side-effect-free so they can be sourced and
# unit-tested; main only runs when the script is executed directly.

REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY must be set (e.g. petry-projects/TalkTerm)}"
# App IDs whose check-suite auto-trigger must be disabled. GitHub creates a
# queued suite on every push when auto-trigger is on; these apps never complete
# those suites, permanently blocking auto-merge.
readonly -a CHECK_SUITE_APP_IDS=(1236702 347564) # Claude, CodeRabbit

echo "Applying security_and_analysis settings to ${REPO} ..."
# resolve_repo <arg>
# Resolves the target "owner/repo". Precedence: positional arg, then
# GITHUB_REPOSITORY, then REPO. A bare name is expanded to "<ORG>/<name>".
# Returns non-zero if no repo can be determined.
resolve_repo() {
local repo="${1:-}"
[ -z "$repo" ] && repo="${GITHUB_REPOSITORY:-}"

Check failure on line 38 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG1&open=AZ7gpgnG8lbwoM_tWWG1&pullRequest=300
[ -z "$repo" ] && repo="${REPO:-}"

Check failure on line 39 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG2&open=AZ7gpgnG8lbwoM_tWWG2&pullRequest=300
[ -z "$repo" ] && return 1

Check failure on line 40 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG3&open=AZ7gpgnG8lbwoM_tWWG3&pullRequest=300
case "$repo" in
*/*) printf '%s' "$repo" ;;
*) printf '%s/%s' "${ORG:-petry-projects}" "$repo" ;;
esac
}

# auto_trigger_status <prefs_json> <app_id>
# Echoes the current auto_trigger setting for app_id: "true", "false", or
# "missing" (app absent from preferences — never run in repo, so compliant).
auto_trigger_status() {
local json="${1:-}" app_id="$2"
if [ -z "$json" ]; then

Check failure on line 52 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG4&open=AZ7gpgnG8lbwoM_tWWG4&pullRequest=300
printf 'missing'
return 0
fi
printf '%s' "$json" | jq -r --argjson id "$app_id" \
'.preferences.auto_trigger_checks // []
| map(select(.app_id == $id))
| if length == 0 then "missing" else (.[0].setting | tostring) end'
}

gh api -X PATCH "repos/${REPO}" \
--input - <<'JSON'
# apply_security_and_analysis <owner/repo>
apply_security_and_analysis() {

Check warning on line 63 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG5&open=AZ7gpgnG8lbwoM_tWWG5&pullRequest=300
local repo="$1"
echo "Applying security_and_analysis settings to ${repo} ..."
gh api -X PATCH "repos/${repo}" --input - <<'JSON'
{
"security_and_analysis": {
"secret_scanning": { "status": "enabled" },
Expand All @@ -26,20 +74,53 @@
}
}
JSON
}

echo "Disabling CodeRabbit (app 347564) and Claude (app 1236702) check-suite auto-trigger ..."
# apply_check_suite_prefs <owner/repo>
# Disables auto-trigger for any configured app that currently has it enabled.
# Apps that are "missing" (never run) or already "false" are compliant and left
# untouched. The check-suites/preferences GET endpoint is PATCH-only on GitHub
# and returns 404, so when preferences cannot be read we conservatively apply
# the disabling PATCH for every configured app.
apply_check_suite_prefs() {
local repo="$1"
echo "Configuring check-suite auto-trigger preferences for ${repo} ..."

# These apps create queued check suites on every push that are never completed,
# which permanently blocks auto-merge. Disabling auto-trigger prevents GitHub
# from creating those ghost check suites.
gh api -X PATCH "repos/${REPO}/check-suites/preferences" \
--input - <<'JSON'
{
"auto_trigger_checks": [
{ "app_id": 347564, "setting": false },
{ "app_id": 1236702, "setting": false }
]
local prefs status app_id
local -a to_disable=()
if prefs=$(gh api "repos/${repo}/check-suites/preferences" 2>/dev/null) && [ -n "$prefs" ]; then

Check failure on line 91 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG6&open=AZ7gpgnG8lbwoM_tWWG6&pullRequest=300
for app_id in "${CHECK_SUITE_APP_IDS[@]}"; do
status=$(auto_trigger_status "$prefs" "$app_id")
case "$status" in
missing) echo " app ${app_id}: never run in repo — compliant, skipping" ;;
false) echo " app ${app_id}: already disabled — skipping" ;;
*) echo " app ${app_id}: auto-trigger enabled — disabling"; to_disable+=("$app_id") ;;
esac
done
else
echo " could not read current preferences — applying disable for all configured apps"
to_disable=("${CHECK_SUITE_APP_IDS[@]}")
fi

if [ "${#to_disable[@]}" -eq 0 ]; then

Check failure on line 105 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG7&open=AZ7gpgnG8lbwoM_tWWG7&pullRequest=300
echo " already compliant — nothing to do"
return 0
fi

local payload
payload=$(printf '%s\n' "${to_disable[@]}" |
jq -Rcn '[inputs | tonumber] | map({app_id: ., setting: false}) | {auto_trigger_checks: .}')
gh api -X PATCH "repos/${repo}/check-suites/preferences" --input - <<<"$payload"
}
JSON

echo "Done."
# Run main only when executed directly, so tests can source the helpers.
if [ "${BASH_SOURCE[0]:-$0}" = "$0" ]; then

Check failure on line 117 in scripts/apply-repo-settings.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgnG8lbwoM_tWWG8&open=AZ7gpgnG8lbwoM_tWWG8&pullRequest=300
set -euo pipefail
repo="$(resolve_repo "${1:-}")" || {
echo "Usage: $0 <repo-name|owner/repo> (or set GITHUB_REPOSITORY)" >&2
exit 1
}
apply_security_and_analysis "$repo"
apply_check_suite_prefs "$repo"
echo "Done."
fi
58 changes: 58 additions & 0 deletions scripts/apply-repo-settings.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# apply-repo-settings.test.sh — portable unit tests for the pure helpers in
# apply-repo-settings.sh. No bats dependency: sourcing the script must not run
# its main body (guarded by the BASH_SOURCE check), so the helpers can be tested
# in isolation. Run: bash scripts/apply-repo-settings.test.sh
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Sourcing must not execute main; if it errors the whole test run aborts here.
# shellcheck source=scripts/apply-repo-settings.sh
source "${SCRIPT_DIR}/apply-repo-settings.sh"

fails=0
pass() { echo "ok - $1"; }

Check warning on line 15 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWG9&open=AZ7gpgqd8lbwoM_tWWG9&pullRequest=300

Check warning on line 15 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWG-&open=AZ7gpgqd8lbwoM_tWWG-&pullRequest=300
fail() { echo "FAIL - $1"; fails=$((fails + 1)); }

Check warning on line 16 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWG_&open=AZ7gpgqd8lbwoM_tWWG_&pullRequest=300

Check warning on line 16 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWHA&open=AZ7gpgqd8lbwoM_tWWHA&pullRequest=300

assert_eq() {

Check warning on line 18 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWHB&open=AZ7gpgqd8lbwoM_tWWHB&pullRequest=300
local desc="$1" expected="$2" actual="$3"
if [ "$expected" = "$actual" ]; then

Check failure on line 20 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWHC&open=AZ7gpgqd8lbwoM_tWWHC&pullRequest=300
pass "$desc"
else
fail "$desc (expected '$expected', got '$actual')"
fi
}

# ── auto_trigger_status ───────────────────────────────────────────────────────
prefs_true='{"preferences":{"auto_trigger_checks":[{"app_id":1236702,"setting":true},{"app_id":347564,"setting":false}]}}'
prefs_missing='{"preferences":{"auto_trigger_checks":[{"app_id":999,"setting":true}]}}'
prefs_empty='{"preferences":{"auto_trigger_checks":[]}}'

assert_eq "auto_trigger_status: enabled app -> true" \
"true" "$(auto_trigger_status "$prefs_true" 1236702)"
assert_eq "auto_trigger_status: disabled app -> false" \
"false" "$(auto_trigger_status "$prefs_true" 347564)"
assert_eq "auto_trigger_status: app absent -> missing" \
"missing" "$(auto_trigger_status "$prefs_missing" 1236702)"
assert_eq "auto_trigger_status: empty list -> missing" \
"missing" "$(auto_trigger_status "$prefs_empty" 1236702)"
assert_eq "auto_trigger_status: empty string -> missing" \
"missing" "$(auto_trigger_status "" 1236702)"

# ── resolve_repo ──────────────────────────────────────────────────────────────
assert_eq "resolve_repo: bare name -> org/name" \
"petry-projects/TalkTerm" "$(ORG=petry-projects resolve_repo "TalkTerm")"
assert_eq "resolve_repo: owner/repo passthrough" \
"acme/widgets" "$(ORG=petry-projects resolve_repo "acme/widgets")"
assert_eq "resolve_repo: GITHUB_REPOSITORY fallback" \
"petry-projects/TalkTerm" "$(ORG=petry-projects GITHUB_REPOSITORY=petry-projects/TalkTerm resolve_repo "")"
assert_eq "resolve_repo: REPO env fallback" \
"petry-projects/TalkTerm" "$(ORG=petry-projects REPO=petry-projects/TalkTerm resolve_repo "")"

if [ "$fails" -eq 0 ]; then

Check failure on line 53 in scripts/apply-repo-settings.test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=petry-projects_TalkTerm&issues=AZ7gpgqd8lbwoM_tWWHD&open=AZ7gpgqd8lbwoM_tWWHD&pullRequest=300
echo "All tests passed."
exit 0
fi
echo "$fails test(s) failed." >&2
exit 1
Loading