1919 permissions : {}
2020 runs-on : ubuntu-latest
2121 steps :
22- # Keep private repository metadata out of a matrix because matrix values are visible in public workflow runs .
22+ # Process repositories in one job so project creation, privacy checks, and mirroring stay together .
2323 - name : Get repositories
2424 uses : actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
2525 with :
@@ -70,12 +70,15 @@ jobs:
7070 GITHUB_TOKEN : ${{ secrets.GH_BOT_TOKEN }}
7171 GITLAB_API_URL : https://gitlab.com/api/v4
7272 GITLAB_GROUP : lizardbyte
73+ GITLAB_SSH_PRIVATE_KEY : ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
7374 GITLAB_TOKEN : ${{ secrets.GITLAB_TOKEN }}
7475 run : |
7576 set -euo pipefail
7677
77- if [[ -z "${GITHUB_TOKEN}" ]] || [[ -z "${GITLAB_TOKEN}" ]]; then
78- echo "::error::GH_BOT_TOKEN and GITLAB_TOKEN must both be configured."
78+ if [[ -z "${GITHUB_TOKEN}" ]] || \
79+ [[ -z "${GITLAB_TOKEN}" ]] || \
80+ [[ -z "${GITLAB_SSH_PRIVATE_KEY}" ]]; then
81+ echo "::error::GH_BOT_TOKEN, GITLAB_TOKEN, and GITLAB_SSH_PRIVATE_KEY must all be configured."
7982 exit 1
8083 fi
8184
8487 temp_root="$(mktemp -d)"
8588 trap 'rm -f "${response_file}" "${repository_file}"; rm -rf "${temp_root}"' EXIT
8689
90+ ssh_dir="${temp_root}/ssh"
91+ ssh_key="${ssh_dir}/id_ed25519"
92+ ssh_known_hosts="${ssh_dir}/known_hosts"
93+ mkdir -m 700 "${ssh_dir}"
94+ printf '%s\n' "${GITLAB_SSH_PRIVATE_KEY}" > "${ssh_key}"
95+ chmod 600 "${ssh_key}"
96+ if ! ssh-keygen -y -P '' -f "${ssh_key}" > /dev/null; then
97+ echo "::error::GITLAB_SSH_PRIVATE_KEY is not a valid unencrypted SSH private key."
98+ exit 1
99+ fi
100+
101+ # Published at https://docs.gitlab.com/user/gitlab_com/#ssh-known_hosts-entries.
102+ gitlab_host_key='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf'
103+ printf 'gitlab.com %s\n' "${gitlab_host_key}" > "${ssh_known_hosts}"
104+ chmod 600 "${ssh_known_hosts}"
105+
106+ gitlab_host_fingerprint="$(ssh-keygen -lf "${ssh_known_hosts}" -E sha256 | awk '{print $2}')"
107+ if [[ "${gitlab_host_fingerprint}" != \
108+ 'SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8' ]]; then
109+ echo "::error::The configured GitLab SSH host key has an unexpected fingerprint."
110+ exit 1
111+ fi
112+
113+ gitlab_ssh_args=(
114+ -i "${ssh_key}"
115+ -o BatchMode=yes
116+ -o IdentitiesOnly=yes
117+ -o StrictHostKeyChecking=yes
118+ -o "UserKnownHostsFile=${ssh_known_hosts}"
119+ -o ConnectTimeout=30
120+ )
121+ printf -v gitlab_ssh_command '%q ' ssh "${gitlab_ssh_args[@]}"
122+
123+ echo "Verifying GitLab SSH authentication..."
124+ if ! ssh "${gitlab_ssh_args[@]}" -T git@gitlab.com; then
125+ echo "::error::Unable to authenticate to GitLab with GITLAB_SSH_PRIVATE_KEY."
126+ exit 1
127+ fi
128+
87129 gitlab_request() {
88130 local method="$1"
89131 local url="$2"
@@ -119,42 +161,32 @@ jobs:
119161 group_id="$(jq -er '.id' "${response_file}")"
120162
121163 github_auth="$(printf 'x-access-token:%s' "${GITHUB_TOKEN}" | base64 --wrap=0)"
122- gitlab_auth="$(printf 'oauth2:%s' "${GITLAB_TOKEN}" | base64 --wrap=0)"
123164 echo "::add-mask::${github_auth}"
124- echo "::add-mask::${gitlab_auth}"
125165
126166 repository_count="$(jq -er 'length' "${repository_file}")"
127167 for ((index = 0; index < repository_count; index++)); do
128168 repository="$(jq -ec ".[${index}]" "${repository_file}")"
169+ repository_number="$((index + 1))"
129170 source_name="$(jq -er '.name' <<< "${repository}")"
130171 source_clone_url="$(jq -er '.cloneUrl' <<< "${repository}")"
131172 target_name="$(jq -er '.targetName' <<< "${repository}")"
132173 target_path="$(jq -er '.targetPath' <<< "${repository}")"
133174 target_visibility="$(jq -er '.targetVisibility' <<< "${repository}")"
134175 description="Mirror of ${source_clone_url}"
135-
136- if [[ "${target_visibility}" == "private" ]]; then
137- echo "::add-mask::${source_name}"
138- echo "::add-mask::${source_clone_url}"
139- echo "::add-mask::${target_name}"
140- echo "::add-mask::${target_path}"
141- echo "::add-mask::${description}"
142- fi
143-
144- echo "Mirroring repository $((index + 1)) of ${repository_count}."
145176 project_path="${GITLAB_GROUP}/${target_path}"
146177 encoded_project_path="$(jq -rn --arg value "${project_path}" '$value | @uri')"
147- if [[ "${target_visibility}" == "private" ]]; then
148- echo "::add-mask:: ${project_path}"
149- echo "::add-mask::${encoded_project_path }"
150- fi
178+
179+ echo "::group::[${repository_number}/${repository_count}] ${source_name} -> ${project_path}"
180+ echo "Visibility: ${target_visibility }"
181+ echo "Checking for the GitLab project..."
151182
152183 if ! status="$(gitlab_request GET "${GITLAB_API_URL}/projects/${encoded_project_path}")"; then
153- echo "::error:: Unable to query the GitLab project for repository $((index + 1)) ."
184+ echo "::error title=Mirror failed: ${source_name}:: Unable to query the GitLab project."
154185 exit 1
155186 fi
156187
157188 if [[ "${status}" == "404" ]]; then
189+ echo "Project does not exist; creating ${project_path}..."
158190 payload="$(
159191 jq -nc \
160192 --arg name "${target_name}" \
@@ -170,36 +202,41 @@ jobs:
170202 }'
171203 )"
172204 if ! status="$(gitlab_request POST "${GITLAB_API_URL}/projects" "${payload}")"; then
173- echo "::error:: Unable to create the GitLab project for repository $((index + 1)) ."
205+ echo "::error title=Mirror failed: ${source_name}:: Unable to create the GitLab project."
174206 exit 1
175207 fi
176208 if [[ "${status}" != "201" ]]; then
177- echo "::error::Unable to create the GitLab project for repository $((index + 1)) (HTTP ${status})."
209+ error_message="Unable to create the GitLab project (HTTP ${status})."
210+ echo "::error title=Mirror failed: ${source_name}::${error_message}"
178211 exit 1
179212 fi
180213 elif [[ "${status}" != "200" ]]; then
181- echo "::error:: Unable to query the GitLab project for repository $((index + 1)) (HTTP ${status})."
214+ echo "::error title=Mirror failed: ${source_name}:: Unable to query the GitLab project (HTTP ${status})."
182215 exit 1
216+ else
217+ echo "Found existing GitLab project ${project_path}."
183218 fi
184219
185220 project_id="$(jq -er '.id' "${response_file}")"
186221
187222 # Make a private source private before changing metadata or pushing any Git data.
188223 if [[ "${target_visibility}" == "private" ]]; then
224+ echo "Securing ${project_path} as private before mirroring..."
189225 privacy_payload="$(jq -nc '{visibility: "private"}')"
190226 if ! status="$(
191227 gitlab_request PUT "${GITLAB_API_URL}/projects/${project_id}" "${privacy_payload}"
192228 )"; then
193- echo "::error:: Unable to secure the GitLab project for repository $((index + 1)) ."
229+ echo "::error title=Mirror failed: ${source_name}:: Unable to secure the GitLab project."
194230 exit 1
195231 fi
196232 if [[ "${status}" != "200" ]] || \
197233 [[ "$(jq -er '.visibility' "${response_file}")" != "private" ]]; then
198- echo "::error:: GitLab privacy verification failed for repository $((index + 1)) ."
234+ echo "::error title=Mirror failed: ${source_name}:: GitLab privacy verification failed."
199235 exit 1
200236 fi
201237 fi
202238
239+ echo "Updating and verifying the GitLab project metadata..."
203240 payload="$(
204241 jq -nc \
205242 --arg description "${description}" \
@@ -209,45 +246,46 @@ jobs:
209246 if ! status="$(
210247 gitlab_request PUT "${GITLAB_API_URL}/projects/${project_id}" "${payload}"
211248 )"; then
212- echo "::error:: Unable to update the GitLab project for repository $((index + 1)) ."
249+ echo "::error title=Mirror failed: ${source_name}:: Unable to update the GitLab project."
213250 exit 1
214251 fi
215252 if [[ "${status}" != "200" ]]; then
216- echo "::error:: Unable to update the GitLab project for repository $((index + 1)) (HTTP ${status})."
253+ echo "::error title=Mirror failed: ${source_name}:: Unable to update the GitLab project (HTTP ${status})."
217254 exit 1
218255 fi
219256
220257 actual_description="$(jq -er '.description // ""' "${response_file}")"
221258 actual_visibility="$(jq -er '.visibility' "${response_file}")"
222259 if [[ "${actual_description}" != "${description}" ]]; then
223- echo "::error:: GitLab description verification failed for repository $((index + 1)) ."
260+ echo "::error title=Mirror failed: ${source_name}:: GitLab description verification failed."
224261 exit 1
225262 fi
226263 if [[ "${actual_visibility}" != "${target_visibility}" ]]; then
227- echo "::error:: GitLab visibility verification failed for repository $((index + 1)) ."
264+ echo "::error title=Mirror failed: ${source_name}:: GitLab visibility verification failed."
228265 exit 1
229266 fi
230267
231- target_clone_url="$(jq -er '.http_url_to_repo' "${response_file}")"
232- if [[ "${target_visibility}" == "private" ]]; then
233- echo "::add-mask::${target_clone_url}"
234- fi
268+ target_clone_url="$(jq -er '.ssh_url_to_repo' "${response_file}")"
235269
236270 mirror_dir="${temp_root}/repository.git"
271+ echo "Cloning ${source_clone_url} as a mirror..."
237272 if ! git \
238273 -c http.https://github.com/.extraheader="AUTHORIZATION: basic ${github_auth}" \
239- clone --mirror --quiet "${source_clone_url}" "${mirror_dir}"; then
240- echo "::error:: Unable to clone GitHub repository $((index + 1)) ."
274+ clone --mirror --progress "${source_clone_url}" "${mirror_dir}"; then
275+ echo "::error title=Mirror failed: ${source_name}:: Unable to clone the GitHub repository."
241276 exit 1
242277 fi
278+ echo "Pushing the mirror to ${target_clone_url}..."
243279 if ! git \
244280 -C "${mirror_dir}" \
245- -c http.https://gitlab.com/.extraheader="AUTHORIZATION: basic ${gitlab_auth }" \
246- push --mirror --quiet "${target_clone_url}"; then
247- echo "::error:: Unable to push GitLab mirror $((index + 1)) ."
281+ -c core.sshCommand="${gitlab_ssh_command }" \
282+ push --mirror --progress "${target_clone_url}"; then
283+ echo "::error title=Mirror failed: ${source_name}:: Unable to push the GitLab mirror."
248284 exit 1
249285 fi
250286 rm -rf "${mirror_dir}"
287+ echo "Completed mirror for ${source_name}."
288+ echo "::endgroup::"
251289 done
252290
253291 echo "Mirrored ${repository_count} repositories to GitLab."
0 commit comments