diff --git a/.github/workflows/mirror-reusable.yml b/.github/workflows/mirror-reusable.yml new file mode 100644 index 00000000..66f5258c --- /dev/null +++ b/.github/workflows/mirror-reusable.yml @@ -0,0 +1,165 @@ +# SPDX-License-Identifier: PMPL-1.0-or-later +# 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._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: PMPL-1.0-or-later +# name: Mirror to Git Forges +# on: +# push: +# branches: [main] +# workflow_dispatch: +# permissions: +# contents: read +# jobs: +# mirror: +# uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@ +# 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: + runs-on: ${{ inputs.runs-on }} + if: vars.GITLAB_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 + with: + ssh-private-key: ${{ secrets.GITLAB_SSH_KEY }} + - name: Mirror to GitLab + 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 + + mirror-bitbucket: + runs-on: ${{ inputs.runs-on }} + if: vars.BITBUCKET_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 + with: + ssh-private-key: ${{ secrets.BITBUCKET_SSH_KEY }} + - name: Mirror to Bitbucket + 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 + + mirror-codeberg: + runs-on: ${{ inputs.runs-on }} + if: vars.CODEBERG_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 + with: + ssh-private-key: ${{ secrets.CODEBERG_SSH_KEY }} + - name: Mirror to Codeberg + 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 + + mirror-sourcehut: + runs-on: ${{ inputs.runs-on }} + if: vars.SOURCEHUT_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 + with: + ssh-private-key: ${{ secrets.SOURCEHUT_SSH_KEY }} + - name: Mirror to SourceHut + 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 + + mirror-disroot: + runs-on: ${{ inputs.runs-on }} + if: vars.DISROOT_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 + with: + ssh-private-key: ${{ secrets.DISROOT_SSH_KEY }} + - name: Mirror to Disroot + 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 + + mirror-gitea: + runs-on: ${{ inputs.runs-on }} + if: vars.GITEA_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 + with: + ssh-private-key: ${{ secrets.GITEA_SSH_KEY }} + - name: Mirror to Gitea + 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 + + mirror-radicle: + runs-on: ${{ inputs.runs-on }} + if: vars.RADICLE_MIRROR_ENABLED == 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - name: Setup Rust + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # stable + with: + toolchain: stable + - name: Install Radicle + run: | + cargo install radicle-cli --locked + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - name: Mirror to Radicle + run: | + echo "${{ secrets.RADICLE_KEY }}" > ~/.radicle/keys/radicle + chmod 600 ~/.radicle/keys/radicle + rad sync --announce || echo "Radicle sync attempted"