fix(migration): multiplex operator-window SSH pushes through one master connection

Forgejo rate-limits rapid port-22 handshakes. The bare per-target
`git push --all` + `git push --tags` opened ~22 SSH connections in rapid
succession; the server refused around the 6th (voyage's tag push) with
"port 22: Connection refused" (TCP-level, not auth), deterministically, on
every run. Export GIT_SSH_COMMAND with ControlMaster/ControlPath/ControlPersist
so all git-over-SSH reuses ONE persistent master connection (one TCP handshake
for the whole rollout).

Verified: 8 rapid multiplexed connections all succeed where the 6th bare
connection is refused; the full 11-target rollout then completed clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 09:23:36 +02:00
commit f35e4ec46d

View file

@ -56,6 +56,15 @@ command -v curl >/dev/null 2>&1 || die "curl not found"
BR="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD)"
[ "$BR" = "main" ] || die "catalog repo not on main (on '$BR')"
# ---- SSH connection multiplexing (REQUIRED — Forgejo rate-limits rapid port-22 handshakes) ----
# The bare per-target `git push --all` + `git push --tags` opens 2 SSH connections per target (22 total)
# in rapid succession; Forgejo refuses around the 6th handshake ("ssh: connect to host ... port 22:
# Connection refused" — TCP-level, not auth), which killed voyage's tag push on every run. Route ALL
# git-over-SSH through ONE persistent master connection so the whole rollout costs a single TCP handshake.
# Verified: 8 rapid multiplexed connections all succeed where the 6th bare connection is refused.
# ControlPersist keeps the master alive across the inter-target validate/clone gaps.
export GIT_SSH_COMMAND="ssh -o ControlMaster=auto -o ControlPath=/tmp/polyrepo-ssh-%C -o ControlPersist=600"
mapget() { python3 -c "import json;print(json.load(open('$MAP'))['targets']['$1'].get('$2',''))"; }
entry_present() {