Skip to content

Commit 915e7e4

Browse files
Merge pull request #165 from marcschaeferger/ghcr
feat(actions): Sync Images from Docker to GHCR
2 parents ddc3765 + a729b91 commit 915e7e4

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

.github/workflows/mirror.yaml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
name: Mirror & Sign (Docker Hub to GHCR)
2+
3+
on:
4+
workflow_dispatch: {}
5+
6+
permissions:
7+
contents: read
8+
packages: write
9+
id-token: write # for keyless OIDC
10+
11+
env:
12+
SOURCE_IMAGE: docker.io/fosrl/newt
13+
DEST_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
14+
15+
jobs:
16+
mirror-and-dual-sign:
17+
runs-on: amd64-runner
18+
steps:
19+
- name: Install skopeo + jq
20+
run: |
21+
sudo apt-get update -y
22+
sudo apt-get install -y skopeo jq
23+
skopeo --version
24+
25+
- name: Install cosign
26+
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
27+
28+
- name: Input check
29+
run: |
30+
test -n "${SOURCE_IMAGE}" || (echo "SOURCE_IMAGE is empty" && exit 1)
31+
echo "Source : ${SOURCE_IMAGE}"
32+
echo "Target : ${DEST_IMAGE}"
33+
34+
# Auth for skopeo (containers-auth)
35+
- name: Skopeo login to GHCR
36+
run: |
37+
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
38+
39+
# Auth for cosign (docker-config)
40+
- name: Docker login to GHCR (for cosign)
41+
run: |
42+
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
43+
44+
- name: List source tags
45+
run: |
46+
set -euo pipefail
47+
skopeo list-tags --retry-times 3 docker://"${SOURCE_IMAGE}" \
48+
| jq -r '.Tags[]' | sort -u > src-tags.txt
49+
echo "Found source tags: $(wc -l < src-tags.txt)"
50+
head -n 20 src-tags.txt || true
51+
52+
- name: List destination tags (skip existing)
53+
run: |
54+
set -euo pipefail
55+
if skopeo list-tags --retry-times 3 docker://"${DEST_IMAGE}" >/tmp/dst.json 2>/dev/null; then
56+
jq -r '.Tags[]' /tmp/dst.json | sort -u > dst-tags.txt
57+
else
58+
: > dst-tags.txt
59+
fi
60+
echo "Existing destination tags: $(wc -l < dst-tags.txt)"
61+
62+
- name: Mirror, dual-sign, and verify
63+
env:
64+
# keyless
65+
COSIGN_YES: "true"
66+
# key-based
67+
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
68+
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
69+
# verify
70+
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
71+
run: |
72+
set -euo pipefail
73+
copied=0; skipped=0; v_ok=0; errs=0
74+
75+
issuer="https://token.actions.githubusercontent.com"
76+
id_regex="^https://github.com/${{ github.repository }}/.+"
77+
78+
while read -r tag; do
79+
[ -z "$tag" ] && continue
80+
81+
if grep -Fxq "$tag" dst-tags.txt; then
82+
echo "::notice ::Skip (exists) ${DEST_IMAGE}:${tag}"
83+
skipped=$((skipped+1))
84+
continue
85+
fi
86+
87+
echo "==> Copy ${SOURCE_IMAGE}:${tag} → ${DEST_IMAGE}:${tag}"
88+
if ! skopeo copy --all --retry-times 3 \
89+
docker://"${SOURCE_IMAGE}:${tag}" docker://"${DEST_IMAGE}:${tag}"; then
90+
echo "::warning title=Copy failed::${SOURCE_IMAGE}:${tag}"
91+
errs=$((errs+1)); continue
92+
fi
93+
copied=$((copied+1))
94+
95+
digest="$(skopeo inspect --retry-times 3 docker://"${DEST_IMAGE}:${tag}" | jq -r '.Digest')"
96+
ref="${DEST_IMAGE}@${digest}"
97+
98+
echo "==> cosign sign (keyless) --recursive ${ref}"
99+
if ! cosign sign --recursive "${ref}"; then
100+
echo "::warning title=Keyless sign failed::${ref}"
101+
errs=$((errs+1))
102+
fi
103+
104+
echo "==> cosign sign (key) --recursive ${ref}"
105+
if ! cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${ref}"; then
106+
echo "::warning title=Key sign failed::${ref}"
107+
errs=$((errs+1))
108+
fi
109+
110+
echo "==> cosign verify (public key) ${ref}"
111+
if ! cosign verify --key env://COSIGN_PUBLIC_KEY "${ref}" -o text; then
112+
echo "::warning title=Verify(pubkey) failed::${ref}"
113+
errs=$((errs+1))
114+
fi
115+
116+
echo "==> cosign verify (keyless policy) ${ref}"
117+
if ! cosign verify \
118+
--certificate-oidc-issuer "${issuer}" \
119+
--certificate-identity-regexp "${id_regex}" \
120+
"${ref}" -o text; then
121+
echo "::warning title=Verify(keyless) failed::${ref}"
122+
errs=$((errs+1))
123+
else
124+
v_ok=$((v_ok+1))
125+
fi
126+
done < src-tags.txt
127+
128+
echo "---- Summary ----"
129+
echo "Copied : $copied"
130+
echo "Skipped : $skipped"
131+
echo "Verified OK : $v_ok"
132+
echo "Errors : $errs"

0 commit comments

Comments
 (0)