From 0f5e6bb950833c69ad9dc182245132067889fe25 Mon Sep 17 00:00:00 2001 From: Daniel Maksymilian Syrnicki Date: Thu, 16 Apr 2026 12:02:10 +0200 Subject: [PATCH] ops(forgejo): apply-branch-protection script + main-branch rule Codifies the branch protection applied to main on 2026-04-16: no direct pushes, required checks = CI / {lint,test,validate-json}*, zero approvals (2-person team), admin bypass left on for emergencies. Script is idempotent (create-or-patch) and reads its token from \$FORGEJO_TOKEN or the local git remote URL as a fallback, so a clean re-run just reconciles the rule with branch-protection.json. Co-Authored-By: Claude Opus 4.6 (1M context) --- ops/forgejo/apply-branch-protection.sh | 56 ++++++++++++++++++++++++++ ops/forgejo/branch-protection.json | 13 ++++++ 2 files changed, 69 insertions(+) create mode 100755 ops/forgejo/apply-branch-protection.sh create mode 100644 ops/forgejo/branch-protection.json diff --git a/ops/forgejo/apply-branch-protection.sh b/ops/forgejo/apply-branch-protection.sh new file mode 100755 index 0000000..f3f8584 --- /dev/null +++ b/ops/forgejo/apply-branch-protection.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Apply branch protection rules to Furtka's main branch on Forgejo. +# Idempotent — re-runs safely (creates on first call, patches thereafter). +# +# Requires: jq, curl, and a Forgejo personal access token with repo write +# scope. Token is read from $FORGEJO_TOKEN, or extracted from the local +# git remote URL as a fallback. +# +# Usage: +# FORGEJO_TOKEN=... ./ops/forgejo/apply-branch-protection.sh + +set -euo pipefail + +HOST="${FORGEJO_HOST:-forgejo.sourcegate.online}" +REPO="${FORGEJO_REPO:-daniel/furtka}" +BRANCH="${FORGEJO_BRANCH:-main}" + +if [ -z "${FORGEJO_TOKEN:-}" ]; then + FORGEJO_TOKEN=$(git config --get remote.origin.url \ + | sed -nE 's|https://[^:]+:([^@]+)@.*|\1|p') +fi +if [ -z "${FORGEJO_TOKEN:-}" ]; then + echo "error: set FORGEJO_TOKEN or configure a token in remote.origin.url" >&2 + exit 1 +fi + +here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +config="$here/branch-protection.json" +base="https://${HOST}/api/v1/repos/${REPO}/branch_protections" + +api() { + curl --silent --show-error --fail-with-body \ + --header "Authorization: token ${FORGEJO_TOKEN}" \ + --header "Content-Type: application/json" \ + "$@" +} + +code=$(curl --silent --output /dev/null --write-out '%{http_code}' \ + --header "Authorization: token ${FORGEJO_TOKEN}" \ + "${base}/${BRANCH}") + +case "$code" in + 200) + echo "Updating existing protection on ${BRANCH}…" + api --request PATCH "${base}/${BRANCH}" --data @"$config" | jq . + ;; + 404) + echo "Creating protection on ${BRANCH}…" + body=$(jq --arg b "$BRANCH" '. + {branch_name: $b}' "$config") + api --request POST "${base}" --data "$body" | jq . + ;; + *) + echo "unexpected HTTP ${code} from ${base}/${BRANCH}" >&2 + exit 1 + ;; +esac diff --git a/ops/forgejo/branch-protection.json b/ops/forgejo/branch-protection.json new file mode 100644 index 0000000..0f34550 --- /dev/null +++ b/ops/forgejo/branch-protection.json @@ -0,0 +1,13 @@ +{ + "enable_push": false, + "enable_status_check": true, + "status_check_contexts": [ + "CI / lint*", + "CI / test*", + "CI / validate-json*" + ], + "required_approvals": 0, + "block_on_rejected_reviews": false, + "block_on_outdated_branch": false, + "require_signed_commits": false +}