fix(license): Standardize SPDX headers and license fields to MPL-2.0 #160
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: MPL-2.0 | ||
|
Check failure on line 1 in .github/workflows/mirror-reusable.yml
|
||
| # mirror-reusable.yml — Reusable git-forge mirror bundle. | ||
| # | ||
| # Consolidates the per-repo `mirror.yml` workflow (estate-wide: 289 | ||
| # deployments with 76% SHA drift across a 100-repo sample, top SHA | ||
| # covering only 16% of sampled repos). All sampled variants carried the | ||
| # same 7 forge jobs; drift was almost entirely SHA-pin / whitespace | ||
| # churn, not feature variance — exactly the shape that consolidates | ||
| # cleanly behind a `workflow_call` reusable. | ||
| # | ||
| # Each mirror job is gated on the per-repo variable | ||
| # `vars.<FORGE>_MIRROR_ENABLED == 'true'`, so forge selection is | ||
| # configured per-repo via Actions vars — no per-call inputs required. | ||
| # | ||
| # Caller (one-line wrapper) MUST use `secrets: inherit` so the reusable | ||
| # can read the per-forge SSH keys (GITLAB_SSH_KEY, BITBUCKET_SSH_KEY, | ||
| # CODEBERG_SSH_KEY, SOURCEHUT_SSH_KEY, DISROOT_SSH_KEY, GITEA_SSH_KEY) | ||
| # and RADICLE_KEY from the caller repo. Without `secrets: inherit`, | ||
| # `${{ secrets.X }}` inside the reusable evaluates to empty. | ||
| # | ||
| # Caller example (wrapper): | ||
| # # SPDX-License-Identifier: MPL-2.0 | ||
| # name: Mirror to Git Forges | ||
| # on: | ||
| # push: | ||
| # branches: [main] | ||
| # workflow_dispatch: | ||
| # permissions: | ||
| # contents: read | ||
| # jobs: | ||
| # mirror: | ||
| # uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@<sha> | ||
| # secrets: inherit | ||
| name: Mirror to Git Forges (reusable) | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| runs-on: | ||
| description: Runner label for all mirror jobs | ||
| type: string | ||
| required: false | ||
| default: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| jobs: | ||
| mirror-gitlab: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.GITLAB_MIRROR_ENABLED == 'true' | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 | ||
| if: ${{ secrets.GITLAB_SSH_KEY != '' }} | ||
| with: | ||
| ssh-private-key: ${{ secrets.GITLAB_SSH_KEY }} | ||
| - name: Mirror to GitLab | ||
| if: ${{ secrets.GITLAB_SSH_KEY != '' }} | ||
| # continue-on-error: GitLab branch protection on the mirror repo may block | ||
| # force-push even for a deploy key. Owner action required: in GitLab go to | ||
| # Settings → Repository → Protected branches → main and either allow force-push | ||
| # for Maintainers/Developers or remove the protection on the mirror repo. | ||
| # Until then this step is advisory-only; failures do not red main. | ||
| continue-on-error: true | ||
| if: ${{ secrets.GITLAB_SSH_KEY != '' }} | ||
| run: | | ||
| ssh-keyscan -t ed25519 gitlab.com >> ~/.ssh/known_hosts | ||
| git remote add gitlab git@gitlab.com:hyperpolymath/${{ github.event.repository.name }}.git || true | ||
| git push --force gitlab main | ||
| - name: Skipped (GITLAB_SSH_KEY not configured) | ||
| if: ${{ secrets.GITLAB_SSH_KEY == '' }} | ||
| run: | | ||
| echo "::notice::GITLAB_MIRROR_ENABLED=true but secrets.GITLAB_SSH_KEY is empty. Skipping GitLab mirror. Configure the GITLAB_SSH_KEY org/repo secret to enable." | ||
| mirror-bitbucket: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.BITBUCKET_MIRROR_ENABLED == 'true' | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 | ||
| if: ${{ secrets.BITBUCKET_SSH_KEY != '' }} | ||
| with: | ||
| ssh-private-key: ${{ secrets.BITBUCKET_SSH_KEY }} | ||
| - name: Mirror to Bitbucket | ||
| if: ${{ secrets.BITBUCKET_SSH_KEY != '' }} | ||
| run: | | ||
| ssh-keyscan -t ed25519 bitbucket.org >> ~/.ssh/known_hosts | ||
| git remote add bitbucket git@bitbucket.org:hyperpolymath/${{ github.event.repository.name }}.git || true | ||
| git push --force bitbucket main | ||
| - name: Skipped (BITBUCKET_SSH_KEY not configured) | ||
| if: ${{ secrets.BITBUCKET_SSH_KEY == '' }} | ||
| run: | | ||
| echo "::notice::BITBUCKET_MIRROR_ENABLED=true but secrets.BITBUCKET_SSH_KEY is empty. Skipping Bitbucket mirror. Configure the BITBUCKET_SSH_KEY org/repo secret to enable." | ||
| mirror-codeberg: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.CODEBERG_MIRROR_ENABLED == 'true' | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 | ||
| if: ${{ secrets.CODEBERG_SSH_KEY != '' }} | ||
| with: | ||
| ssh-private-key: ${{ secrets.CODEBERG_SSH_KEY }} | ||
| - name: Mirror to Codeberg | ||
| if: ${{ secrets.CODEBERG_SSH_KEY != '' }} | ||
| run: | | ||
| ssh-keyscan -t ed25519 codeberg.org >> ~/.ssh/known_hosts | ||
| git remote add codeberg git@codeberg.org:hyperpolymath/${{ github.event.repository.name }}.git || true | ||
| git push --force codeberg main | ||
| - name: Skipped (CODEBERG_SSH_KEY not configured) | ||
| if: ${{ secrets.CODEBERG_SSH_KEY == '' }} | ||
| run: | | ||
| echo "::notice::CODEBERG_MIRROR_ENABLED=true but secrets.CODEBERG_SSH_KEY is empty. Skipping Codeberg mirror. Configure the CODEBERG_SSH_KEY org/repo secret to enable." | ||
| mirror-sourcehut: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.SOURCEHUT_MIRROR_ENABLED == 'true' | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 | ||
| if: ${{ secrets.SOURCEHUT_SSH_KEY != '' }} | ||
| with: | ||
| ssh-private-key: ${{ secrets.SOURCEHUT_SSH_KEY }} | ||
| - name: Mirror to SourceHut | ||
| if: ${{ secrets.SOURCEHUT_SSH_KEY != '' }} | ||
| run: | | ||
| ssh-keyscan -t ed25519 git.sr.ht >> ~/.ssh/known_hosts | ||
| git remote add sourcehut git@git.sr.ht:~hyperpolymath/${{ github.event.repository.name }} || true | ||
| git push --force sourcehut main | ||
| - name: Skipped (SOURCEHUT_SSH_KEY not configured) | ||
| if: ${{ secrets.SOURCEHUT_SSH_KEY == '' }} | ||
| run: | | ||
| echo "::notice::SOURCEHUT_MIRROR_ENABLED=true but secrets.SOURCEHUT_SSH_KEY is empty. Skipping SourceHut mirror. Configure the SOURCEHUT_SSH_KEY org/repo secret to enable." | ||
| mirror-disroot: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.DISROOT_MIRROR_ENABLED == 'true' | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 | ||
| if: ${{ secrets.DISROOT_SSH_KEY != '' }} | ||
| with: | ||
| ssh-private-key: ${{ secrets.DISROOT_SSH_KEY }} | ||
| - name: Mirror to Disroot | ||
| if: ${{ secrets.DISROOT_SSH_KEY != '' }} | ||
| run: | | ||
| ssh-keyscan -t ed25519 git.disroot.org >> ~/.ssh/known_hosts | ||
| git remote add disroot git@git.disroot.org:hyperpolymath/${{ github.event.repository.name }}.git || true | ||
| git push --force disroot main | ||
| - name: Skipped (DISROOT_SSH_KEY not configured) | ||
| if: ${{ secrets.DISROOT_SSH_KEY == '' }} | ||
| run: | | ||
| echo "::notice::DISROOT_MIRROR_ENABLED=true but secrets.DISROOT_SSH_KEY is empty. Skipping Disroot mirror. Configure the DISROOT_SSH_KEY org/repo secret to enable." | ||
| mirror-gitea: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.GITEA_MIRROR_ENABLED == 'true' | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 | ||
| if: ${{ secrets.GITEA_SSH_KEY != '' }} | ||
| with: | ||
| ssh-private-key: ${{ secrets.GITEA_SSH_KEY }} | ||
| - name: Mirror to Gitea | ||
| if: ${{ secrets.GITEA_SSH_KEY != '' }} | ||
| run: | | ||
| ssh-keyscan -t ed25519 ${{ vars.GITEA_HOST }} >> ~/.ssh/known_hosts | ||
| git remote add gitea git@${{ vars.GITEA_HOST }}:hyperpolymath/${{ github.event.repository.name }}.git || true | ||
| git push --force gitea main | ||
| - name: Skipped (GITEA_SSH_KEY not configured) | ||
| if: ${{ secrets.GITEA_SSH_KEY == '' }} | ||
| run: | | ||
| echo "::notice::GITEA_MIRROR_ENABLED=true but secrets.GITEA_SSH_KEY is empty. Skipping Gitea mirror. Configure the GITEA_SSH_KEY org/repo secret to enable." | ||
| mirror-radicle: | ||
| timeout-minutes: 20 | ||
| runs-on: ${{ inputs.runs-on }} | ||
| if: vars.RADICLE_MIRROR_ENABLED == 'true' | ||
| # Map the secret to env so step `if:`s can gate on its presence: the | ||
| # `secrets` context is NOT available in `if:` (using it is an | ||
| # "Unrecognized named-value: 'secrets'" startup failure). `env` IS | ||
| # available in step `if:`, and secrets are valid in job-level `env`. | ||
| env: | ||
| RADICLE_KEY: ${{ secrets.RADICLE_KEY }} | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| fetch-depth: 0 | ||
| # All Radicle steps gate on secrets.RADICLE_KEY being set on the | ||
| # caller repo (resolved via `secrets: inherit`). Without this gate | ||
| # the workflow burned ~3 minutes of Rust+Radicle install on every | ||
| # push to every RADICLE_MIRROR_ENABLED repo only to fail at | ||
| # `~/.radicle/keys/radicle: No such file or directory` because the | ||
| # `echo "" > ...` write into a non-existent dir errors out — and | ||
| # even if the dir existed, the empty-key write would never sync. | ||
| # Caught 26 estate repos on the 2026-05-30 audit. The vars gate | ||
| # answers "is Radicle mirror desired here?"; the secret gate | ||
| # answers "are we configured to actually do it?". | ||
| - name: Setup Rust | ||
| if: ${{ env.RADICLE_KEY != '' }} | ||
| uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable | ||
| with: | ||
| toolchain: stable | ||
| - name: Install Radicle | ||
| if: ${{ env.RADICLE_KEY != '' }} | ||
| run: | | ||
| cargo install radicle-cli --locked | ||
| echo "$HOME/.cargo/bin" >> $GITHUB_PATH | ||
| - name: Mirror to Radicle | ||
| if: ${{ env.RADICLE_KEY != '' }} | ||
| run: | | ||
| mkdir -p ~/.radicle/keys | ||
| echo "${{ secrets.RADICLE_KEY }}" > ~/.radicle/keys/radicle | ||
| chmod 600 ~/.radicle/keys/radicle | ||
| rad sync --announce || echo "Radicle sync attempted" | ||
| - name: Skipped (RADICLE_KEY not configured) | ||
| if: ${{ env.RADICLE_KEY == '' }} | ||
| run: | | ||
| echo "::notice::RADICLE_MIRROR_ENABLED=true but secrets.RADICLE_KEY is empty. Skipping Radicle mirror. Configure the RADICLE_KEY org/repo secret to enable." | ||