bundle

package
v0.0.0-...-01cc07e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 20, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package bundle owns the per-tenant static-content bundle lifecycle: HMAC-verified ingest webhook → authenticated GH release tarball fetch → un-tar into versioned directory → atomic swap of `current` symlink.

Per gocodealone-multisite SPEC.md:

V8: ingest webhook → HMAC sig ! valid; mismatched req → 401.
V9: bundle version monotonic per tenant; replay attempt → 409.
V24: content-repo tarball fetch ! authenticated; ⊥ public-anon GET.

Index

Constants

View Source
const SignatureHeader = "X-Multisite-Sig"

SignatureHeader is the canonical HMAC-bearing header.

Variables

View Source
var ErrInvalidSignature = errors.New("ingest: invalid HMAC signature")

ErrInvalidSignature is returned when the HMAC header does not match.

View Source
var ErrSignatureHeaderMissing = errors.New("ingest: signature header missing")

ErrSignatureHeaderMissing is returned when no signature header was supplied.

View Source
var ErrVersionRegression = errors.New("ingest: version regression")

ErrVersionRegression is returned by OnPayload when the incoming bundle version is not monotonic (V9).

Functions

func ComputeSignature

func ComputeSignature(body []byte, secret string) string

ComputeSignature is the inverse of VerifyHMAC — used by the content- repo release.yml to sign outgoing requests. Returned header value is "sha256=<hex>".

func VerifyHMAC

func VerifyHMAC(header string, body []byte, secret string) error

VerifyHMAC returns nil iff `header` matches the HMAC-SHA256 of `body` using `secret`.

The header value must be of the form "sha256=<hex>". Constant-time comparison protects against timing side-channels.

Types

type Fetcher

type Fetcher struct {
	// BundleRoot is the directory under which tenants are stored.
	BundleRoot string

	// Token is the GH PAT or App token used as `Authorization: Bearer`.
	// Required (V24) — never make an anonymous request.
	Token string

	// HTTPClient lets tests inject a recording client. Default is
	// http.DefaultClient with a 30s timeout if nil.
	HTTPClient *http.Client

	// Now is a clock injection point for tests.
	Now func() time.Time

	// MaxTarballBytes caps the downloaded size (defence against
	// resource exhaustion). Default 200 MiB.
	MaxTarballBytes int64
}

Fetcher fetches a tarball from a GH release URL using a Bearer token, streams it through an integrity hash, un-tars into a versioned dir, and atomically swaps the `current` symlink.

Layout on disk:

<root>/<tenant_slug>/<version>/...
<root>/<tenant_slug>/current → <version>

`current` swap uses rename(2) semantics — readers see either the old target or the new target, never a partial state (V9 monotonic activation).

func (*Fetcher) Activate

func (f *Fetcher) Activate(ctx context.Context, tenantSlug, version, tarballURL string) (string, error)

Activate fetches the tarball, hashes it, extracts it under <BundleRoot>/<tenantSlug>/<version>/, then atomically swaps the `current` symlink. Returns the SHA-256 hex of the tarball for integrity recording.

type IngestHandler

type IngestHandler struct {
	Secret    string
	OnPayload func(IngestPayload) error // host-supplied; usually queues a fetch
}

IngestHandler is an http.Handler that verifies the HMAC + parses the payload, then hands off to fn for the actual fetch + activate work.

The handler itself does NO fetching — that's the bundle fetcher's job (see Fetcher in fetcher.go) — so this handler stays trivially testable.

func (*IngestHandler) ServeHTTP

func (h *IngestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type IngestPayload

type IngestPayload struct {
	Tag        string `json:"tag"`         // e.g. "v0.3.0"
	Repo       string `json:"repo"`        // "GoCodeAlone/gocodealone-website"
	TarballURL string `json:"tarball_url"` // GH release asset URL
}

IngestPayload is the body of POST /api/v1/ingest/release. Sent by the content-repo's release.yml GH Action after a tag is published; the host fetches the actual tarball.

func (*IngestPayload) Validate

func (p *IngestPayload) Validate() error

Validate returns nil iff the payload satisfies basic shape rules.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL