Artifact 67246789a5887dfe05171f755f10b2266ec45605b5826ae1ffe8a3a4becc40e9:
- File
hub/clone.go
— part of check-in
[001f82d3d2]
at
2026-05-12 20:59:53
on branch feat/cli-project-code-and-seed-from-upstream
— feat(cli): expose hub --project-code and --seed-from-upstream flags
The hub.Config.ProjectCode and hub.Config.SeedFromUpstream fields landed in #157 but were unreachable from the `edgesync hub serve` CLI. Wires them through and fixes a latent auth bug in seedFromUpstream that prevented it from cloning against the standard nobody-caps=gio deployment.
Changes: * cli/hub_serve.go — adds --project-code (40-char lowercase hex, format pre-validated for a friendlier error than libfossil's) and --seed-from-upstream (http(s) URL of upstream hub xfer endpoint). * hub/clone.go — drop bootstrapUser from the clone CloneOpts. Passing a local-creation user as the upstream-auth identity caused libfossil's clone to send a login card for a user the upstream had never heard of ("authentication failed"). Anonymous clone falls through to the upstream's "nobody" caps, matching the typical NobodyCaps:"gio" deploy. * hub/cross_leaf_test.go — adds TestCrossLeaf_SeedFromUpstream_PropagatesCommit covering the seed-then-sync path end-to-end (clone over HTTP, leaf-link, observe A→B commit propagation via NATS). (user: 41898282+github-actions[bot]@users.noreply.github.com, size: 2106) [annotate] [blame] [check-ins using] [more...]
package hub import ( "context" "fmt" "net/url" "os" libfossil "github.com/danmestas/libfossil" ) // seedFromUpstream clones a fossil repo at path from the given HTTP xfer // endpoint URL. The cloned repo carries the upstream's project-code, so // it can join the upstream's NATS fossil-sync subject after open. // // Validates that the URL is well-formed and has an http(s) scheme. // NATS-native clone is deferred until libfossil HandleSync supports the // clone protocol upstream (CDG-148). // // If expectedProjectCode is non-empty, asserts the cloned repo's // project-code matches it. The cloned repo is removed before returning // the mismatch error so the caller can retry or give up cleanly. // // Authentication: clones anonymously (no login card sent). The upstream // hub must grant clone caps to its "nobody" user — i.e. NobodyCaps must // include "g" (the standard NobodyCaps:"gio"). The local bootstrapUser // is a Create-time concept for the local repo, not an upstream-auth // identity, so it's intentionally not threaded through here. func seedFromUpstream(ctx context.Context, path, upstreamURL, expectedProjectCode string) error { u, err := url.Parse(upstreamURL) if err != nil { return fmt.Errorf("hub: parse SeedFromUpstream %q: %w", upstreamURL, err) } if u.Scheme != "http" && u.Scheme != "https" { return fmt.Errorf("hub: SeedFromUpstream %q: only http(s) is supported (NATS-clone deferred to libfossil HandleSync support)", upstreamURL) } transport := libfossil.NewHTTPTransport(upstreamURL) repo, result, err := libfossil.Clone(ctx, path, transport, libfossil.CloneOpts{ ProjectCode: expectedProjectCode, }) if err != nil { return fmt.Errorf("hub: clone from %s: %w", upstreamURL, err) } if err := repo.Close(); err != nil { return fmt.Errorf("hub: close cloned repo: %w", err) } if expectedProjectCode != "" && result.ProjectCode != expectedProjectCode { _ = os.Remove(path) return fmt.Errorf("hub: cloned repo has project-code %q, want %q (config drift between hub and upstream)", result.ProjectCode, expectedProjectCode) } return nil }