cron-images-mirror-all.ymlΒΆ

name: "πŸͺž Mirror all images"
run-name: "${{ github.event_name == 'schedule' && '⏰ ' || '' }}πŸͺž Mirror all images"

on:
  schedule:
    - cron: "0 0 * * *"

  workflow_dispatch:
    inputs:
      ghcr_namespace:
        description: "πŸ›οΈ GHCR namespace/org (default: repository owner)"
        required: false
        type: string
      ghcr_prefix:
        description: "🏷️ GHCR repo prefix (default: mirror)"
        required: false
        default: "mirror"
        type: string
      repo_root:
        description: "πŸ“ Repository root path (default: .)"
        required: false
        default: "."
        type: string
      images_per_hour:
        description: "⏱️ Optional throttle: max images mirrored per hour"
        required: false
        type: string

  workflow_call:
    inputs:
      ghcr_namespace:
        description: "GHCR namespace/org (default: repository owner)"
        required: false
        type: string
      ghcr_prefix:
        description: "GHCR repo prefix (default: mirror)"
        required: false
        type: string
        default: "mirror"
      repo_root:
        description: "Repository root path (default: .)"
        required: false
        type: string
        default: "."
      images_per_hour:
        description: "Optional throttle: max images mirrored per hour"
        required: false
        type: string
    secrets:
      DOCKERHUB_USERNAME:
        required: false
      DOCKERHUB_TOKEN:
        required: false

permissions:
  contents: read
concurrency:
  group: mirror-images-${{ github.repository }}-${{ github.ref_name }}
  cancel-in-progress: false

jobs:
  sync:
    name: πŸͺž Mirror
    runs-on: ubuntu-26.04
    permissions:
      contents: read
      packages: write

    env:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
      IMAGES_PER_HOUR: ${{ github.event.inputs.images_per_hour || inputs.images_per_hour || '50' }}

    steps:
      - name: ⬇️ Checkout
        uses: actions/checkout@v7.0.0

      - name: πŸ” Resolve GHCR namespace and repository
        id: vars
        shell: bash
        env:
          GHCR_NAMESPACE_INPUT: ${{ inputs.ghcr_namespace || '' }}
        run: bash scripts/meta/resolve/ghcr/namespace_and_repository.sh

      - name: πŸ”§ Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.12"
          cache: pip
          cache-dependency-path: pyproject.toml

      - name: πŸ“¦ Install Python deps
        run: PYTHON=python bash scripts/install/python.sh

      - name: πŸ“¦ Install skopeo
        run: ./scripts/install/apt.sh skopeo

      - name: πŸ”‘ Login to Docker Hub (optional)
        if: ${{ env.DOCKERHUB_USERNAME != '' && env.DOCKERHUB_TOKEN != '' }}
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          username: ${{ env.DOCKERHUB_USERNAME }}
          password: ${{ env.DOCKERHUB_TOKEN }}

      - name: πŸ”‘ Login to GHCR
        uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: πŸͺž Mirror images to GHCR (best-effort)
        shell: bash
        env:
          GHCR_NAMESPACE: ${{ steps.vars.outputs.ghcr_namespace }}
          GHCR_REPOSITORY: ${{ steps.vars.outputs.ghcr_repository }}
          GHCR_PREFIX: ${{ inputs.ghcr_prefix || 'mirror' }}
          REPO_ROOT: ${{ inputs.repo_root || '.' }}
        run: bash scripts/meta/mirror/sync/all.sh