diff --git a/.github/workflows/settings-drift.yml b/.github/workflows/settings-drift.yml new file mode 100644 index 0000000..c37c939 --- /dev/null +++ b/.github/workflows/settings-drift.yml @@ -0,0 +1,48 @@ +name: Settings drift + +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + drift: + name: Settings drift + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check out + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: 24 + + - name: List the repositories to check + id: repos + run: echo "names=$(node -p "Object.keys(require('./settings/repos.json')).map((name) => name.split('/')[1]).join(',')")" >> "$GITHUB_OUTPUT" + + - name: Create a read-only token + id: token + uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 + with: + client-id: ${{ vars.SETTINGS_APP_CLIENT_ID }} + private-key: ${{ secrets.SETTINGS_APP_PRIVATE_KEY }} + owner: BSStudio + repositories: ${{ steps.repos.outputs.names }} + permission-administration: read + + - name: Compare settings with the baseline + env: + GH_TOKEN: ${{ steps.token.outputs.token }} + run: node scripts/apply-settings.mjs --dry-run diff --git a/README.md b/README.md index 872e1a0..e32785b 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ node scripts/apply-settings.mjs # fix them in ever node scripts/apply-settings.mjs --dry-run BSStudio/backstage # one repo ``` +Settings must not drift unnoticed: `.github/workflows/settings-drift.yml` runs the dry run every Monday morning and fails when anything differs. It reads the repos with a token from the read-only "BSStudio settings check" GitHub App (Administration: read), which is installed on the repos in `settings/repos.json`; a repo added to that file needs the App installed too. Fixing drift stays a local run of the script. GitHub turns scheduled workflows off after 60 days without activity in the repo and emails about it; Renovate's monthly PRs normally keep it active. + A named repo must be listed in `settings/repos.json`. A run writes only what differs, then reads the repo again and reports anything GitHub did not take. Run it as an admin of the listed repos: without admin rights GitHub leaves some settings out of its answers (they show as `missing`) and refuses the writes. A failed call is reported with the `gh` command that failed. Security features (secret scanning, push protection, Dependabot alerts, CodeQL default setup, private vulnerability reporting) come from the organisation's "GitHub recommended" code security configuration, which is the default for new public repos. The script checks that it is attached and never attaches it.