Push mirroring with on-push fan-out + optional schedule (Fix #623) #624
No reviewers
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub!624
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/issue-623"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements Forgejo #623 end to end: post-hoc push-mirror config (upstream + password/token/SSH-key incl. walhub-generated ed25519 keypair), write-only credentials + scrubbed surfaces, on-push enqueue after landed pushes (new mirror-push-sync task kind), optional preset-cron scheduled sync (default off), git push --mirror transfer, server-publish exclusion (structural), Settings Push mirror tab + badge + SDK, summary push_mirror + ~p ETag.
Decisions: stdlib keygen (no openssh-client in runtime), accept-new without pinned known_hosts, no first-sync on create, schedule OFF default, config+secret sidecars with frozen-list amendment. Full spec: docs/features/13_push_mirror.md.
E2E (scratch stack, file:// upstream, zero auth): push lands upstream refs+objects (ls-remote + clone with history); scheduled daily repo fired via loop; keygen/redaction/failure-recording/~p ETag verified live. Coverage: pushmirror 96.0%, api 95.3%, server 98.4%, store 95.0% (-race green); web 1611 node tests green; vite+esbuild green.
Review: PR #624 (Forgejo #623) — verdict: APPROVE with review fixes landed
Reviewed
origin/fix/issue-623(69c001d) in/tmp/walhub-623, hard pass over all 10 areas. Found 1 high + 1 medium-high + 3 small defects, all fixed directly in follow-up commitc91803f(pushed tofix/issue-623). No large rework needed. Remaining notes are accepted-risks/observations, not blockers.Fix commit
c91803f(review fixes, 15 files)[HIGH] R1 —
git push --mirrorshipped forge-internal refs.refs/pull/N/headis ordinary WAL ref state (published server-side via the WAL funnel,internal/pulls/service.go:438), materialized into the serving copy by every Serve sync — so bare--mirrorpushed walhub's own PR heads upstream on every PR-carrying repo (leak, and hosts like GitHub refuse writes torefs/pull/*, which would wedge such repos in permanent "failed"). Transfer is now forced namespace wildcard refspecs +--prune(+refs/heads/*,+refs/tags/*always so emptied namespaces prune; other surviving namespaces when populated; bare two-segment names exact), droppingreplace/meta/keep-aroundalways andpull/changes/review/notesby default — the pull direction's FilterRefs discipline reversed (per-namespace wildcards scale; per-ref refspecs would not). Pinned argv + rationale updated indocs/go/04_git.md,13_push_mirror.md§3, decision (j) appended (law 12; (e) kept, marked superseded-in-shape). New tests:TestPushSkipsInternalRefsAndPrunes,TestPushRefspecsRender.[MED-HIGH] R2 — shell injection via username in credential helper.
credentialArgvinterpolated the user-controlled username into the!shell helper (echo username=<user>); metachars/newlines/$()execute as the server user (import/mirror precedents hardcodex-access-token, so this surface is new here). Both halves now ride child env (WALHUB_PUSHMIRROR_USER/_TOKEN); argv carries only env names. Pinned byTestPushHostileUsernameRidesEnvOnly(fake git binary asserts argv-vs-env separation + no marker execution).[MED] R3 — keygen bricked non-SSH configs.
POST keygenflipped any config toauth_kind=sshwithout checking the stored URL scheme (https+ssh fails ValidateTarget at sync time → bricked behind a 200). Now 400 unless upstream is ssh/scp; config untouched. Pinned byTestKeygenRefusesNonSSHUpstream.[MED] R4 — update wrote secret before validating. Kind-change path
SaveSecret'd first, then re-validated kind-vs-URL — a refused update left secret material for a kind the config no longer names. Validation hoisted before any secret write. Pinned byTestUpdateKindMismatchKeepsSecret(fails on old code: hint flips to the new key's).[SMALL] R5 — ETag missed display fields.
pushMirrorHashcovered only 6 fields; username/secret-rotation (hint) and repeated identical failures (counter-only change) went stale behind 304. Hash now covers username, has_secret, secret_hint, consecutive_failures;PushMirrorViewgainsconsecutive_failures(additive field, §14.12; mapped incmd/walhub/pushmirror.go).Area-by-area (post-fix)
go.mod/go.sumdiff empty; nox/crypto/sshclient use (only a doc.go comment restating the prohibition); transfer argv pinned in04_git.md; nopackage.jsonchange; UI is shared Tailwind classes only (noui.cssdiff, no<style>).meta/pushmirror-secret.json; write-only API (presence+last-4);scrubTexton runner stderr,fail(),RecordAttempt, HTTP errors, sanitize-on-write; per-fire 0600 key dirs alwaysRemoveAlld via deferred cleanup (incl. write-error paths); http-token refused (ValidateTarget); missing material is a failed outcome viaresolveAuth(never silent-anonymous). Upstream URLs cannot smuggle userinfo (NormalizeSource400s embedded credentials), so task params/notices/views are safe.--prunewithin live namespaces). Ref reconstruction = Serve-sync materialization (pull-refmap reversed). Bulk/control: Runner owns its pool (never the control-plane lane); no LIST (probe-only); on-push enqueue is one exact-key GET off the response (go notify(id)post-report inpushPipeline).Publish/PublishRefs, never enterpushPipeline) + pinned byTestServerPublishBypassExclusion. Kindmirror-push-syncdistinct (own single-flight). Schedule default""off; preset cron reuse (bundle.ParseSchedule); 1-min loop, own cadence. Note: loop uses blockingSyncNowper due repo — matches the pull-mirror precedent (mirror/sync.go:883); serializing both loops to async is a joint future change, not this PR.api+api-browserinHandler.Handle); no top-level twins by design (post-hoc only — the issue's "three twins" note applies to top-level endpoints, of which there are none);ExposedTemplates+api.RegisterExposedfrom composition (14 §14.12/#272);api.Env.PushMirrorSummaryhook (core never imports feature); frozen-list amendment present in14_extensibility.md:632-653.go list -depsshows only the leafserver/authtypes package (same asinternal/mirror) — no upward import.push_mirrorprojection +~psuffix; pull/push independent (separate sidecars/hooks,TestSummaryPushMirrorIndependentOfPull).ssh-keygen -y -fderives the exact public line;ssh-keygen -lfingerprint matchesSHA256:…on both key and pub file. (Existing test only did the Go-side round-trip; live verification done in review.)pushmirror95.7% (-racegreen);api,server,store,cmd/walhubgreen incl.-race. Zeropushmirrorrefs onorigin/main(new subsystem — tests fail pre-fix by absence). file:// zero-auth e2e exercised in tests (TestRunPushFileEndToEnd,TestPushFileBare, loop/on-push tests); SSH/HTTPS transfers are shape-covered without network — the PR's live-server claims are manual, plausible, and consistent with the covered shapes.chip,muted,grid gap-3,flex flex-wrap,pill, shared data-table); status table shows upstream/auth+hint/fingerprint/sync phrasing/last result/next fire; Sync-now + force + removal; SDK carries no credentials. No mobile-specific breakage by construction (wrap/grid, no fixed widths) — but I did NOT drive a browser (no running server in this env); recommend the author attach the desktop+~390px screenshots before merge per AGENTS.md ladder step 8.StrictHostKeyChecking=accept-newwith the default user known_hosts file: trust lands in ephemeral local disk (law 4 — wiped on restart/redeploy), so each fresh host re-TOFUs with no out-of-band verification surface (no UI shows the accepted host key). MITM window reopens per fresh host. The issue left this to the implementer and decision (i) documents it; for production upstreams recommend pinningknown_hosts(field exists). Suggest a follow-up: surface the observed host key/fingerprint in the status view after first sync.Bottom line
Approve. All acceptance criteria met: post-hoc Settings config (all three auth kinds + keygen), write-only/redacted credentials, on-push enqueue → upstream receives refs+objects (file:// proven in tests), schedule opt-in default-off, status + summary/
~psurface, structural publish exclusion, pull/push independence, coverage green. Review fixes inc91803f; only asks: browser screenshots (ladder step 8) + consider the TOFU host-key surface as follow-up.Rendered verification (orchestrator, headless Chromium over CDP against a scratch stack serving this branch): Settings Push-mirror tab at 1280px and 390px, zero console errors on both. Desktop shows upstream/status pill/auth/sync cadence/last synced (relative time)/last result ok, schedule select + username/save, sync-now + force, remove button with explanatory copy. Mobile stacks with no page overflow (tab strip scrolls internally per .repo-tabs). Live on-push sync also confirmed end to end on the rig (push to walhub arrived in a file:// upstream bare repo; status flipped to ok). One investigation note: an apparent frozen last_synced_at during the run turned out to be second-precision truncation on a sub-second sync plus a git-log-on-unborn-master misread — server log confirms push landed and sync stamped in the same second; no defect.