diff --git a/.gitea/workflows/license.yml b/.gitea/workflows/license.yml index 0ae4acc..77792f7 100644 --- a/.gitea/workflows/license.yml +++ b/.gitea/workflows/license.yml @@ -1,13 +1,30 @@ name: license-headers +# Enforce AGPL-3.0 headers on every file ADDED by a pull request: amend the PR +# head commit with the headers and comment the new commit hash on the PR. +# +# `runs-on` must match a registered runner label. The org's only runner offers +# `alpine-latest` (container `node:alpine`), so jobs asking for anything else +# (e.g. `alpine-3.24`) queue forever — that is why this bot never ran before. +# +# No long-lived secret is needed or wanted here: Gitea mints a per-run token +# (`secrets.GITHUB_TOKEN`) scoped to this repository, which is enough to amend +# the PR branch and comment. Never add a PAT/SSH key — an action that runs on +# PRs (including forks) must stay keyless, and Gitea withholds repo/org secrets +# from fork PRs anyway. + on: - push: - branches: ['**'] pull_request: + types: [opened, synchronize, reopened, ready_for_review] permissions: contents: write issues: write + pull-requests: read + +concurrency: + group: license-headers-${{ gitea.event.pull_request.number }} + cancel-in-progress: true defaults: run: @@ -15,104 +32,143 @@ defaults: jobs: addlicense: - runs-on: alpine-3.24 + runs-on: alpine-latest steps: - name: Install prerequisites run: apk add --no-cache git curl ca-certificates jq - - name: Checkout + - name: Checkout pull request head branch env: TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -e - REF="${{ gitea.event.pull_request.head.ref || gitea.ref_name }}" + REF="${{ gitea.event.pull_request.head.ref }}" + HEAD_REPO="${{ gitea.event.pull_request.head.repo.full_name }}" + BASE_REPO="${{ gitea.repository }}" + if [ "$HEAD_REPO" != "$BASE_REPO" ]; then + echo "Fork PR ($HEAD_REPO -> $BASE_REPO): the bot cannot push to a fork; skipping." + echo "fork_pr=true" >> "$GITHUB_ENV" + exit 0 + fi + # Keep the scheme: the dev Gitea is HTTP-only, so a hardcoded https:// + # here fails with "TLS connect error: wrong version number". SERVER="${{ gitea.server_url }}" - SERVER="${SERVER#https://}"; SERVER="${SERVER#http://}" + SERVER="${SERVER%/}" + SCHEME="${SERVER%%://*}" + HOST="${SERVER#*://}" git clone --depth 1 --branch "$REF" \ - "https://oauth2:${TOKEN}@${SERVER}/${{ gitea.repository }}.git" . + "${SCHEME}://oauth2:${TOKEN}@${HOST}/${BASE_REPO}.git" . + # Lease value for the later force push: the exact remote head we based on. + git ls-remote origin "refs/heads/${REF}" | cut -f1 > /tmp/head_before.txt + echo "checked out ${REF} at $(git rev-parse HEAD)" - name: Install addlicense + if: env.fork_pr != 'true' run: | set -e curl -sSL -o /tmp/addlicense.tar.gz \ https://github.com/google/addlicense/releases/download/v1.2.0/addlicense_v1.2.0_Linux_x86_64.tar.gz tar -xzf /tmp/addlicense.tar.gz -C /tmp - - name: Add AGPL-3.0 headers - run: | - set -e - set -- -f .license-header.tpl -c "Dcntrlized" -y "$(date +%Y)" - while IFS= read -r line; do - case "$line" in - "" | \#*) continue ;; - esac - set -- "$@" -ignore "$line" - done < .licenseignore - /tmp/addlicense "$@" . - - - name: Commit and push changes + - name: Add AGPL-3.0 headers to newly added files + if: env.fork_pr != 'true' env: TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -e - REF="${{ gitea.event.pull_request.head.ref || gitea.ref_name }}" SERVER="${{ gitea.server_url }}" - SERVER="${SERVER#https://}"; SERVER="${SERVER#http://}" + SERVER="${SERVER%/}" + REPO="${{ gitea.repository }}" + PR="${{ gitea.event.pull_request.number }}" + + # Files added by this PR, straight from the API (no base history needed). + : > /tmp/added.txt + page=1 + while : ; do + body="$(curl -sS -H "Authorization: token ${TOKEN}" \ + "${SERVER}/api/v1/repos/${REPO}/pulls/${PR}/files?limit=100&page=${page}")" + n="$(printf '%s' "$body" | jq 'length')" + [ "$n" = "0" ] && break + printf '%s' "$body" | jq -r '.[] | select(.status=="added") | .filename' >> /tmp/added.txt + [ "$n" -lt 100 ] && break + page=$((page + 1)) + done + + # addlicense validates -ignore with doublestar and silently skips file + # types it does not understand, so pass every added file and let + # .licenseignore be the only escape hatch. + set -- -v -f .license-header.tpl -c "Dcntrlized" -y "$(date +%Y)" + if [ -f .licenseignore ]; then + while IFS= read -r line; do + case "$line" in ""|\#*) continue ;; esac + set -- "$@" -ignore "$line" + done < .licenseignore + fi + found=0 + while IFS= read -r f; do + [ -n "$f" ] || continue + [ -f "$f" ] || continue + found=1 + /tmp/addlicense "$@" "$f" + done < /tmp/added.txt + [ "$found" = "1" ] || echo "No newly added files." + if git diff --quiet; then echo "no_changes=true" >> "$GITHUB_ENV" - echo "commit_sha=" >> "$GITHUB_ENV" - echo "No license header changes." - exit 0 + else + echo "no_changes=false" >> "$GITHUB_ENV" fi - git config user.name "Dcntrlized Auto CopyLeft Bot" - git config user.email "copyleft+bot@dcntrlized.org" - git remote set-url origin "https://oauth2:${TOKEN}@${SERVER}/${{ gitea.repository }}.git" - git add -A - git commit -m "chore: add AGPL-3.0 license headers" - SHA="$(git rev-parse HEAD)" - git push origin HEAD:refs/heads/$REF - echo "no_changes=false" >> "$GITHUB_ENV" - echo "commit_sha=$SHA" >> "$GITHUB_ENV" - - name: Comment on pull request - if: env.no_changes == 'false' + - name: Amend the commit and force-push + if: env.fork_pr != 'true' && env.no_changes == 'false' + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + REF="${{ gitea.event.pull_request.head.ref }}" + SERVER="${{ gitea.server_url }}" + SERVER="${SERVER%/}" + SCHEME="${SERVER%%://*}" + HOST="${SERVER#*://}" + git config user.name "Dcntrlized Auto CopyLeft Bot" + git config user.email "copyleft+bot@dcntrlized.org" + git add -A + git commit --amend --no-edit + SHA="$(git rev-parse HEAD)" + printf '%s\n' "$SHA" > /tmp/commit_sha.txt + echo "amended to $SHA" + BEFORE="$(cat /tmp/head_before.txt)" + git push --force-with-lease="refs/heads/${REF}:${BEFORE}" \ + "${SCHEME}://oauth2:${TOKEN}@${HOST}/${{ gitea.repository }}.git" \ + "HEAD:refs/heads/${REF}" + + - name: Comment on the pull request + if: env.fork_pr != 'true' && env.no_changes == 'false' env: TOKEN: ${{ secrets.GITHUB_TOKEN }} - COMMIT_SHA: ${{ env.commit_sha }} - COMMIT_URL: "${{ gitea.server_url }}/${{ gitea.repository }}/commit/${{ env.commit_sha }}" run: | set -e SERVER="${{ gitea.server_url }}" + SERVER="${SERVER%/}" REPO="${{ gitea.repository }}" - if [ "${{ gitea.event_name }}" = "pull_request" ]; then - PR_NUMBER="${{ gitea.event.pull_request.number }}" - else - OWNER="${REPO%%/*}" - BRANCH="${{ gitea.ref_name }}" - PR_NUMBER="$(curl -sS -H "Authorization: token ${TOKEN}" \ - "${SERVER}/api/v1/repos/${REPO}/pulls?state=open&limit=50" \ - | jq -r --arg b "$BRANCH" 'map(select(.head.ref == $b))[0].number // empty')" - if [ -z "$PR_NUMBER" ]; then - echo "No open pull request for branch ${BRANCH}; skipping comment." - exit 0 - fi - fi - cat > /tmp/pr-comment.md <<'EOF' + PR="${{ gitea.event.pull_request.number }}" + SHA="$(cat /tmp/commit_sha.txt)" + cat > /tmp/pr-comment.md <