Skip to content

Commit f9ab704

Browse files
committed
Add dockerio mirror
1 parent 031822e commit f9ab704

1 file changed

Lines changed: 112 additions & 0 deletions

File tree

‎.github/workflows/dockerio.yaml‎

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
name: dockerio
2+
on:
3+
push:
4+
workflow_dispatch:
5+
inputs:
6+
image:
7+
description: 'Docker Hub image (e.g., nginx, library/alpine)'
8+
required: true
9+
type: string
10+
schedule:
11+
- cron: '0 0 * * *' # Daily at midnight UTC
12+
13+
env:
14+
# Define images to mirror (format: source -> mirrored to ghcr.io/$REPO/docker.io/source)
15+
IMAGES: |
16+
netbirdio/management
17+
netbirdio/dashboard
18+
netbirdio/relay
19+
netbirdio/signal
20+
21+
jobs:
22+
# Build matrix of images to mirror
23+
prepare:
24+
runs-on: ubuntu-latest
25+
outputs:
26+
matrix: ${{ steps.set-matrix.outputs.matrix }}
27+
steps:
28+
- id: set-matrix
29+
run: |
30+
if [ -n "${{ inputs.image }}" ]; then
31+
# Manual trigger - single image
32+
echo "matrix=[\"${{ inputs.image }}\"]" >> $GITHUB_OUTPUT
33+
else
34+
# Scheduled - all images from IMAGES env
35+
MATRIX=$(echo "$IMAGES" | grep -v '^$' | jq -R -s -c 'split("\n") | map(select(length > 0))')
36+
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
37+
fi
38+
39+
mirror:
40+
needs: prepare
41+
runs-on: ubuntu-latest
42+
permissions:
43+
packages: write
44+
strategy:
45+
fail-fast: false
46+
max-parallel: 4 # Parallel image mirroring
47+
matrix:
48+
image: ${{ fromJson(needs.prepare.outputs.matrix) }}
49+
steps:
50+
- name: Setup crane
51+
uses: imjasonh/setup-crane@v0.4
52+
53+
- name: Login to GHCR
54+
run: crane auth login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
55+
56+
- name: Login to Docker Hub (optional, increases rate limits)
57+
env:
58+
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
59+
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
60+
run: |
61+
if [ -n "$DOCKERHUB_TOKEN" ]; then
62+
crane auth login docker.io -u "$DOCKERHUB_USERNAME" -p "$DOCKERHUB_TOKEN"
63+
fi
64+
continue-on-error: true
65+
66+
- name: Mirror ${{ matrix.image }}
67+
run: |
68+
SOURCE="docker.io/${{ matrix.image }}"
69+
DEST="ghcr.io/${{ github.repository }}/docker.io/${{ matrix.image }}"
70+
71+
# Retry with exponential backoff
72+
retry() {
73+
local max=5 delay=30 attempt=1
74+
while [ $attempt -le $max ]; do
75+
if "$@" 2>&1; then return 0; fi
76+
[ $attempt -lt $max ] && echo "⏳ Retry $attempt/$max in ${delay}s..." && sleep $delay && delay=$((delay * 2))
77+
attempt=$((attempt + 1))
78+
done
79+
return 1
80+
}
81+
82+
# Copy single tag if digest differs
83+
copy_tag() {
84+
local tag="$1"
85+
local src_digest dst_digest
86+
87+
src_digest=$(retry crane digest "$SOURCE:$tag" 2>/dev/null) || return 1
88+
dst_digest=$(crane digest "$DEST:$tag" 2>/dev/null) || dst_digest=""
89+
90+
if [ "$src_digest" = "$dst_digest" ]; then
91+
echo "⏭️ $tag (unchanged)"
92+
else
93+
retry crane copy "$SOURCE:$tag" "$DEST:$tag" && echo "✅ $tag"
94+
fi
95+
}
96+
97+
echo "Mirroring: $SOURCE -> $DEST"
98+
99+
# Get last 10 tags + ensure latest is included first
100+
TAGS=$(retry crane ls "$SOURCE" | tail -10)
101+
if retry crane manifest "$SOURCE:latest" >/dev/null 2>&1; then
102+
TAGS=$(echo -e "latest\n$TAGS" | awk '!seen[$0]++')
103+
fi
104+
105+
echo "Tags: $TAGS"
106+
107+
# Export for subshells
108+
export SOURCE DEST
109+
export -f retry copy_tag
110+
111+
# Copy in parallel
112+
echo "$TAGS" | xargs -P4 -I{} bash -c 'copy_tag "{}"'

0 commit comments

Comments
 (0)