fix: load git mirror scripts from files

This commit is contained in:
Codex
2026-07-03 16:02:41 +00:00
parent e973b73dbc
commit edb6fe7485
5 changed files with 299 additions and 285 deletions
@@ -0,0 +1,65 @@
#!/bin/sh
set -eu
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
__GIT_MIRROR_PROXY_PRELUDE__
test -d "$repo/objects"
git --git-dir="$repo" remote set-url origin "$remote" || git --git-dir="$repo" remote add origin "$remote"
local_gitops=$(git --git-dir="$repo" rev-parse --verify "refs/heads/${gitops_branch}^{commit}" 2>/dev/null || true)
push_status=skipped
push_exit=0
fetch_status=skipped
fetch_exit=0
fetch_attempt=0
fetch_max_attempts=5
if [ -n "$local_gitops" ]; then
set +e
timeout 240 git --git-dir="$repo" -c remote.origin.mirror=false push origin "refs/heads/${gitops_branch}:refs/heads/${gitops_branch}"
push_exit=$?
set -e
if [ "$push_exit" = "0" ]; then
push_status=succeeded
fetch_retry_delay=1
while [ "$fetch_attempt" -lt "$fetch_max_attempts" ]; do
fetch_attempt=$((fetch_attempt + 1))
echo "git-mirror post-push fetch attempt ${fetch_attempt}/${fetch_max_attempts}" >&2
set +e
timeout 240 git --git-dir="$repo" fetch origin "+refs/heads/${gitops_branch}:refs/mirror-stage/heads/${gitops_branch}"
fetch_exit=$?
set -e
if [ "$fetch_exit" = "0" ]; then fetch_status=succeeded; break; fi
fetch_status=failed
if [ "$fetch_attempt" -lt "$fetch_max_attempts" ]; then
echo "git-mirror post-push fetch retry ${fetch_attempt}/${fetch_max_attempts} failed exit=${fetch_exit}; backoff=${fetch_retry_delay}s" >&2
sleep "$fetch_retry_delay"
if [ "$fetch_retry_delay" -lt 16 ]; then fetch_retry_delay=$((fetch_retry_delay * 2)); fi
fi
done
else
push_status=failed
fi
fi
github_gitops=$(git --git-dir="$repo" rev-parse --verify "refs/mirror-stage/heads/${gitops_branch}^{commit}" 2>/dev/null || true)
pending=false; if [ -n "$local_gitops" ] && { [ -z "$github_gitops" ] || [ "$local_gitops" != "$github_gitops" ]; }; then pending=true; fi
status=succeeded
partial_success=
degraded_reason=
exit_code=0
if [ "$push_status" = "failed" ]; then
status=failed
degraded_reason=git-mirror-push-failed
exit_code=$push_exit
elif [ "$push_status" = "succeeded" ] && [ "$fetch_status" = "failed" ]; then
status=partial-success
partial_success=push-succeeded-fetch-failed
degraded_reason=git-mirror-post-push-fetch-failed
exit_code=44
fi
export repository gitops_branch started_at local_gitops github_gitops pending push_status push_exit fetch_status fetch_exit fetch_attempt fetch_max_attempts status partial_success degraded_reason
summary_tmp=/tmp/HWLAB.last-flush.json.$$
node <<'NODE' > "$summary_tmp"
const payload = { event: 'git-mirror-flush', repo: process.env.repository, status: process.env.status || 'failed', partialSuccess: process.env.partial_success || null, degradedReason: process.env.degraded_reason || null, startedAt: process.env.started_at, flushedAt: new Date().toISOString(), gitopsBranch: process.env.gitops_branch, localGitops: process.env.local_gitops || null, githubGitops: process.env.github_gitops || null, pendingFlush: process.env.pending === 'true', stages: { push: process.env.push_status || null, pushExitCode: Number.parseInt(process.env.push_exit || '0', 10), postPushFetch: process.env.fetch_status || null, postPushFetchExitCode: Number.parseInt(process.env.fetch_exit || '0', 10), postPushFetchAttempts: Number.parseInt(process.env.fetch_attempt || '0', 10), postPushFetchMaxAttempts: Number.parseInt(process.env.fetch_max_attempts || '0', 10) } };
console.log(JSON.stringify(payload));
NODE
tee /cache/HWLAB.last-flush.json < "$summary_tmp"
rm -f "$summary_tmp"
if [ "$exit_code" != "0" ]; then exit "$exit_code"; fi
@@ -0,0 +1,48 @@
#!/usr/bin/env node
const net = require('node:net');
const [proxyHost, proxyPortRaw, targetHost, targetPortRaw] = process.argv.slice(2);
const proxyPort = Number.parseInt(proxyPortRaw || '', 10);
const targetPort = Number.parseInt(targetPortRaw || '', 10);
if (!proxyHost || !Number.isInteger(proxyPort) || !targetHost || !Number.isInteger(targetPort)) {
console.error('hwlab git-mirror proxy-connect: invalid ProxyCommand arguments');
process.exit(64);
}
let settled = false;
let tunnelEstablished = false;
function finish(code, message) {
if (settled) return;
settled = true;
if (message) console.error('hwlab git-mirror proxy-connect: ' + message);
process.exit(code);
}
const socket = net.createConnection({ host: proxyHost, port: proxyPort });
let buffer = Buffer.alloc(0);
socket.setTimeout(15000, () => { socket.destroy(); finish(65, 'timeout connecting via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort); });
socket.on('connect', () => socket.write('CONNECT ' + targetHost + ':' + targetPort + ' HTTP/1.1\r\nHost: ' + targetHost + ':' + targetPort + '\r\nProxy-Connection: Keep-Alive\r\n\r\n'));
socket.on('error', (error) => finish(tunnelEstablished ? 69 : 66, (tunnelEstablished ? 'tunnel socket error: ' : 'tcp error connecting to proxy: ') + (error && error.message ? error.message : String(error))));
socket.on('close', () => { if (!tunnelEstablished) finish(68, 'proxy closed before CONNECT completed via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort); else finish(0); });
function onData(chunk) {
buffer = Buffer.concat([buffer, chunk]);
const headerEnd = buffer.indexOf('\r\n\r\n');
if (headerEnd === -1 && buffer.length < 8192) return;
if (headerEnd === -1) { socket.destroy(); finish(68, 'proxy response header exceeded 8192 bytes before CONNECT status via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort); return; }
const head = buffer.slice(0, headerEnd + 4).toString('latin1');
const statusLine = head.split('\r\n', 1)[0] || '';
const statusCode = Number.parseInt(statusLine.split(' ')[1] || '', 10);
if (!statusLine.startsWith('HTTP/1.') || !Number.isInteger(statusCode) || statusCode < 200 || statusCode > 299) {
const safeStatus = statusLine.replace(/[^\x20-\x7e]/g, '?').slice(0, 160);
socket.destroy();
finish(67, 'proxy CONNECT failed via ' + proxyHost + ':' + proxyPort + ' to ' + targetHost + ':' + targetPort + ': ' + safeStatus);
return;
}
socket.off('data', onData);
socket.setTimeout(0);
tunnelEstablished = true;
const rest = buffer.slice(headerEnd + 4);
if (rest.length) process.stdout.write(rest);
process.stdin.on('error', () => {});
process.stdout.on('error', () => {});
process.stdin.pipe(socket);
socket.pipe(process.stdout);
}
socket.on('data', onData);
@@ -0,0 +1,56 @@
#!/bin/sh
set -eu
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
__GIT_MIRROR_PROXY_PRELUDE__
mkdir -p "$(dirname "$repo")"
if [ -d "$repo/objects" ] && [ -f "$repo/HEAD" ]; then
git --git-dir="$repo" remote set-url origin "$remote" || git --git-dir="$repo" remote add origin "$remote"
else
rm -rf "$repo"
git init --bare "$repo"
git --git-dir="$repo" remote add origin "$remote"
fi
git --git-dir="$repo" config uploadpack.allowReachableSHA1InWant true
git --git-dir="$repo" config uploadpack.allowAnySHA1InWant true
git --git-dir="$repo" config http.uploadpack true
git --git-dir="$repo" config http.receivepack true
timeout 240 git --git-dir="$repo" fetch origin "+refs/heads/${source_branch}:refs/mirror-stage/heads/${source_branch}"
source_sha=$(git --git-dir="$repo" rev-parse --verify "refs/mirror-stage/heads/${source_branch}^{commit}")
source_stage_ref="${source_stage_ref_prefix%/}/$source_sha"
git --git-dir="$repo" update-ref "$source_stage_ref" "$source_sha"
git --git-dir="$repo" update-ref "refs/heads/${source_branch}" "$source_sha"
discarded_stale_gitops=false
if timeout 240 git --git-dir="$repo" fetch origin "+refs/heads/${gitops_branch}:refs/mirror-stage/heads/${gitops_branch}"; then
github_gitops=$(git --git-dir="$repo" rev-parse --verify "refs/mirror-stage/heads/${gitops_branch}^{commit}" 2>/dev/null || true)
local_gitops=$(git --git-dir="$repo" rev-parse --verify "refs/heads/${gitops_branch}^{commit}" 2>/dev/null || true)
if [ -z "$local_gitops" ] && [ -n "$github_gitops" ]; then
git --git-dir="$repo" update-ref "refs/heads/${gitops_branch}" "$github_gitops"
elif [ -n "$local_gitops" ] && [ -n "$github_gitops" ] && [ "$local_gitops" != "$github_gitops" ] && git --git-dir="$repo" merge-base --is-ancestor "$local_gitops" "$github_gitops"; then
git --git-dir="$repo" update-ref "refs/heads/${gitops_branch}" "$github_gitops"
elif [ -n "$local_gitops" ] && [ -n "$github_gitops" ] && [ "$local_gitops" != "$github_gitops" ] && [ "${UNIDESK_GIT_MIRROR_DISCARD_STALE_GITOPS:-false}" = "true" ]; then
git --git-dir="$repo" update-ref "refs/heads/${gitops_branch}" "$github_gitops"
discarded_stale_gitops=true
printf '%s\n' "git-mirror sync: discarded stale local gitops ref local=${local_gitops} github=${github_gitops}" >&2
fi
fi
git --git-dir="$repo" update-server-info
export repository source_branch source_stage_ref gitops_branch started_at discarded_stale_gitops
summary_tmp=/tmp/HWLAB.last-sync.json.$$
node <<'NODE' > "$summary_tmp"
const { execFileSync } = require('node:child_process');
const repository = process.env.repository;
const sourceBranch = process.env.source_branch;
const gitopsBranch = process.env.gitops_branch;
const repoPath = `/cache/${repository}.git`;
function rev(ref) { try { return execFileSync('git', ['--git-dir=' + repoPath, 'rev-parse', '--verify', ref + '^{commit}'], { encoding: 'utf8' }).trim(); } catch { return null; } }
const localSource = rev(`refs/heads/${sourceBranch}`);
const githubSource = rev(`refs/mirror-stage/heads/${sourceBranch}`);
const sourceStageRef = process.env.source_stage_ref;
const sourceSnapshot = sourceStageRef ? rev(sourceStageRef) : null;
const localGitops = rev(`refs/heads/${gitopsBranch}`);
const githubGitops = rev(`refs/mirror-stage/heads/${gitopsBranch}`);
const pendingFlush = Boolean(localGitops && (!githubGitops || localGitops !== githubGitops));
console.log(JSON.stringify({ event: 'git-mirror-sync', repo: repository, status: 'succeeded', startedAt: process.env.started_at, syncedAt: new Date().toISOString(), sourceAuthority: 'git-mirror-snapshot', localSource, githubSource, sourceStageRef, sourceSnapshot, gitopsBranch, localGitops, githubGitops, sourceInSync: Boolean(localSource && githubSource && localSource === githubSource && sourceSnapshot === githubSource), gitopsInSync: Boolean(localGitops && githubGitops && localGitops === githubGitops), pendingFlush, discardedStaleGitops: process.env.discarded_stale_gitops === 'true' }));
NODE
tee /cache/HWLAB.last-sync.json < "$summary_tmp"
rm -f "$summary_tmp"