Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aaa7cd4
refactor: harden ruleset synchronization
vitormattos Sep 15, 2026
aef5967
fix: compare effective ruleset configuration
vitormattos Sep 15, 2026
097994a
fix: fail closed on repository API errors
vitormattos Sep 15, 2026
12d17ce
test: cover ruleset synchronization behavior
vitormattos Sep 15, 2026
9bd7972
test: assert ruleset policy invariants
vitormattos Sep 15, 2026
2e39dd3
fix: propagate synchronization failures explicitly
vitormattos Sep 15, 2026
69e2ce1
fix: preserve command failure status
vitormattos Sep 15, 2026
e32c4bd
ci: add tests and security checks
vitormattos Sep 15, 2026
15375df
ci: serialize privileged ruleset syncs
vitormattos Sep 15, 2026
d21b3fa
ci: enable Dependabot for GitHub Actions
vitormattos Sep 15, 2026
e3877bc
docs: document ruleset automation
vitormattos Sep 15, 2026
9ad4218
security: scope ruleset tokens per repository
vitormattos Sep 15, 2026
abb801a
security: add Dependabot update cooldown
vitormattos Sep 15, 2026
3241a21
security: pin workflow analysis tool versions
vitormattos Sep 15, 2026
8763497
ci: split quality checks by concern
vitormattos Sep 15, 2026
00f1e68
ci: add dedicated test workflow
vitormattos Sep 15, 2026
88bef9a
ci: add dedicated shellcheck workflow
vitormattos Sep 15, 2026
61d29b1
ci: add dedicated actionlint workflow
vitormattos Sep 15, 2026
53500d5
ci: add dedicated zizmor workflow
vitormattos Sep 15, 2026
bda38d8
docs: move ruleset sync details out of README
vitormattos Sep 15, 2026
56009d9
docs: document testing and validation
vitormattos Sep 15, 2026
ebaf413
docs: keep README focused on repository purpose
vitormattos Sep 15, 2026
af00b69
ci: name Bats ruleset sync workflow explicitly
vitormattos Sep 15, 2026
744d894
ci: replace generic test workflow name
vitormattos Sep 15, 2026
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
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
github-actions:
patterns:
- "*"
28 changes: 28 additions & 0 deletions .github/workflows/actionlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: actionlint

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
actionlint:
name: actionlint
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Run actionlint
uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2.2.0
with:
version: 1.7.12
shellcheck: true
30 changes: 30 additions & 0 deletions .github/workflows/bats-ruleset-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Bats ruleset sync tests

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
bats:
name: Ruleset sync behavior and policy
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Install Bats
run: |
sudo apt-get update
sudo apt-get install --yes bats

- name: Run Bats
run: bats tests
25 changes: 25 additions & 0 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: ShellCheck

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Run ShellCheck
run: shellcheck scripts/*.sh
51 changes: 44 additions & 7 deletions .github/workflows/sync-rulesets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,73 @@ on:
permissions:
contents: read

concurrency:
group: sync-rulesets
cancel-in-progress: false

jobs:
discover:
name: Discover public repositories
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
repositories: ${{ steps.repositories.outputs.repositories }}

steps:
- name: List repositories
id: repositories
env:
GH_TOKEN: ${{ github.token }}
run: |
repositories="$(
for organization in LibreSign LibreCodeCoop; do
gh api \
--paginate \
"orgs/$organization/repos?type=public&per_page=100" \
--jq '.[] | select(.archived == false) | [.owner.login, .name] | @tsv'
done |
jq -Rsc '
split("\n") |
map(
select(length > 0) |
split("\t") |
{organization: .[0], repository: .[1]}
)
'
)"
echo "repositories=$repositories" >> "$GITHUB_OUTPUT"

sync:
name: Sync rulesets for ${{ matrix.organization }}
name: Sync ${{ matrix.organization }}/${{ matrix.repository }}
needs: discover
runs-on: ubuntu-latest
timeout-minutes: 10
environment: ruleset-sync

strategy:
fail-fast: false
matrix:
organization:
- LibreSign
- LibreCodeCoop
include: ${{ fromJSON(needs.discover.outputs.repositories) }}

steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Generate GitHub App token
- name: Generate repository-scoped GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
client-id: ${{ vars.RULESET_APP_CLIENT_ID }}
private-key: ${{ secrets.RULESET_APP_PRIVATE_KEY }}
owner: ${{ matrix.organization }}
repositories: ${{ matrix.repository }}
permission-administration: write

- name: Sync rulesets
- name: Sync ruleset
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
ORG: ${{ matrix.organization }}
run: ./scripts/sync-rulesets.sh
TARGET_REPOSITORY: ${{ format('{0}/{1}', matrix.organization, matrix.repository) }}
run: ./scripts/sync-rulesets.sh --repo "$TARGET_REPOSITORY"
29 changes: 29 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: zizmor

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
zizmor:
name: zizmor
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Run zizmor
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
version: 1.30.1
advanced-security: false
online-audits: false
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# .github
# LibreSign organization automation

This repository centralizes shared GitHub organization automation for LibreSign and LibreCodeCoop.

It helps keep repository governance consistent as the project grows, including branch protection policies, controlled exceptions required by project workflows, and automated validation of the configuration that manages those policies.

The repository currently delivers:

- consistent branch protection rules across public repositories;
- automatic support for Nextcloud translation workflows where required;
- reduced administrative access scope for automation;
- automated checks that help prevent regressions in repository governance.

Technical and operational details are kept in [`docs/`](docs/):

- [Ruleset synchronization](docs/ruleset-sync.md)
- [Testing and validation](docs/testing.md)
45 changes: 45 additions & 0 deletions docs/ruleset-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Ruleset synchronization

This repository keeps branch protection rulesets consistent across public repositories in the LibreSign and LibreCodeCoop organizations.

## Source of truth

`.github/rulesets/default-branches.json` defines the desired ruleset for default branches and `stable*` branches.

The synchronization script applies this policy to public, non-archived repositories available to the ruleset GitHub App installation.

## Nextcloud apps

A repository is treated as a Nextcloud app when `appinfo/info.xml` exists in its default branch.

For these repositories, the synchronization adds `nextcloud-bot` as a bypass actor with `bypass_mode: always`. The GitHub actor is pinned by user ID `20296731`.

A `404` while checking `appinfo/info.xml` means the repository is not a Nextcloud app. Other API errors abort synchronization so transient failures cannot silently remove the bot bypass.

## Running locally

Synchronize all public, non-archived repositories in an organization:

```bash
ORG=LibreSign ./scripts/sync-rulesets.sh
```

Check for drift without modifying repositories:

```bash
ORG=LibreSign ./scripts/sync-rulesets.sh --check
```

Limit synchronization to one repository:

```bash
ORG=LibreSign ./scripts/sync-rulesets.sh --repo LibreSign/libresign
```

The command requires `gh`, `jq`, and a GitHub token with repository administration permission.

## Privileged workflow

`.github/workflows/sync-rulesets.yml` discovers public repositories with the read-only workflow token. It then creates one short-lived GitHub App token per repository, scoped to that repository with `administration: write`, and runs the sync using `--repo`.

The workflow serializes synchronization runs and has execution timeouts to reduce the risk of concurrent administrative writes or stuck privileged jobs.
35 changes: 35 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Testing and validation

The repository uses focused checks so each validation type has its own setup and failure signal.

## Bats

Bats tests cover synchronization behavior and ruleset policy invariants.

Run locally with:

```bash
bats tests
```

The test suite covers Nextcloud and non-Nextcloud repositories, API error handling, bypass idempotency, repository targeting, ruleset normalization, and policy invariants.

## ShellCheck

Shell scripts are analyzed independently with ShellCheck:

```bash
shellcheck scripts/*.sh
```

## actionlint

GitHub Actions workflow syntax and expressions are validated by the dedicated `actionlint` workflow.

## zizmor

GitHub Actions security is audited by the dedicated `zizmor` workflow. Analysis runs without access to the ruleset GitHub App credentials.

## Dependency updates

Third-party Actions are pinned to full commit SHAs. Dependabot checks GitHub Actions dependencies weekly and applies a seven-day cooldown before proposing updates.
Loading