Documentation
¶
Overview ¶
Package selfupdate implements `pgedge self update`: resolving a release tag from a fetched release list, verifying the downloaded asset, and swapping it into place.
Index ¶
- Constants
- Variables
- func ExtractBinary(archivePath, dstDir string) (string, error)
- func InstallMethodFrom(path string) string
- func IsCurrent(tag, current string) bool
- func ResolveTarget() (string, error)
- func SigstoreCacheDir() (string, error)
- func StageBeside(target, src string) (string, error)
- func Swap(target, newBinary string) error
- func VerifyChecksum(archivePath, archiveName string, checksums []byte) error
- func VerifySignature(checksums, bundleJSON []byte, trusted *TrustedMaterial) error
- type Asset
- type Chain
- type GHSource
- type HTTPSource
- type RefusalError
- type Release
- type Runner
- type Source
- type TrustedMaterial
Constants ¶
const ( // ListTimeout bounds a release-list fetch made by `self update`. ListTimeout = 30 * time.Second // DownloadTimeout bounds one asset download. Generous: it stops a // hang, not a slow link. DownloadTimeout = 10 * time.Minute // DoctorCheckTimeout bounds doctor's whole latest-version check, // both rungs together; doctor prints a row and moves on. DoctorCheckTimeout = 5 * time.Second // ProbeTimeout bounds the post-swap `pgedge version -o json` exec. ProbeTimeout = 30 * time.Second )
The deadlines every Source call runs under. The caller sets them, not a Source implementation, so both rungs share one budget.
const ( ChecksumsAsset = "checksums.txt" BundleAsset = ChecksumsAsset + ".sigstore.json" )
The release assets VerifySignature and VerifyChecksum read, as .goreleaser.yaml publishes them.
Variables ¶
var ErrGHUnauthenticated = errors.New("gh is not authenticated")
ErrGHUnauthenticated marks a gh failure classified as an unauthenticated session (gh auth status itself failing after a fetch failed), distinct from any other reason gh might fail. The caller uses errors.Is to pick exit 5 over the generic exit 1.
Functions ¶
func ExtractBinary ¶
ExtractBinary pulls the pgedge binary out of the archive at archivePath into dstDir and returns the extracted file's path.
Only the EXACT top-level member name is extracted. That is the path-traversal guard: "foo/pgedge" and "../evil" both fail it. A symlink member, even one named "pgedge", is skipped (tar by its Typeflag, zip by its mode bit), and no Linkname is ever followed or written.
func InstallMethodFrom ¶
InstallMethodFrom infers the install method from a resolved executable path. `pgedge doctor` and ResolveTarget's refusal checks share it, so the two cannot disagree on how pgedge was installed.
func IsCurrent ¶
IsCurrent reports whether tag names the running version, tolerant of a "v" prefix on either side.
func ResolveTarget ¶
ResolveTarget locates the running pgedge binary — os.Executable, resolved through any symlink — and applies the refusal checks before handing it back as a swap target.
func SigstoreCacheDir ¶
SigstoreCacheDir is where the refreshed TUF trust root is cached. sigstore-go's default, ~/.sigstore/root, is outside ~/.pgedge/cli, the only directory this CLI owns.
func StageBeside ¶
StageBeside copies src to "<target>.new", with target's permission bits, and returns that path. Same directory, so the rename that follows is atomic, and on Linux possible at all: renaming from $TMPDIR fails with EXDEV when /tmp is another filesystem, and a tmpfs /tmp with the binary in ~/.local/bin is the common non-root install.
A failure removes whatever was written, so an error leaves no path to clean up.
func Swap ¶
Swap replaces target with newBinary. Outside Windows an executing file can be renamed over, so this is a chmod to target's bits and one os.Rename. StageBeside put newBinary in target's directory, so the rename is atomic and target's path is never empty.
func VerifyChecksum ¶
VerifyChecksum guarantees the bytes on disk at archivePath are the bytes checksums.txt recorded for archiveName — so an archive swapped in transit, or a truncated download, cannot reach the extractor. It is the second half of the trust chain: VerifySignature proves checksums.txt is ours, this proves the archive matches it.
The match is on the FILENAME FIELD, never a substring: checksums.txt lists each archive twice over, once as `<name>` and once as `<name>.sbom.json`, and a substring scan takes whichever came first.
func VerifySignature ¶
func VerifySignature( checksums, bundleJSON []byte, trusted *TrustedMaterial, ) error
VerifySignature proves checksums.txt came from our release workflow, which is what makes the checksums worth comparing against. bundleJSON is the release's `checksums.txt.sigstore.json`, the Sigstore bundle cosign's --bundle writes: certificate, signature, log entry and timestamps in one file, so nothing is fetched here.
Failure is final: there is no checksum-only fallback, because a `self update` running unattended has no operator to read a warning.
Types ¶
type Asset ¶
type Asset struct {
Name string
}
Asset is one downloadable file attached to a release.
type Chain ¶
type Chain struct {
// contains filtered or unexported fields
}
Chain tries primary, falling back to fallback when primary fails. Production wires HTTPSource as primary and GHSource as fallback: the repository is public, so the unauthenticated HTTP rung serves a user without gh, and gh is tried only when it fails.
type GHSource ¶
type GHSource struct {
// contains filtered or unexported fields
}
GHSource fetches releases and downloads assets by shelling out to the gh CLI — the ladder's fallback rung, used when the unauthenticated HTTP rung fails.
func NewGHSource ¶
NewGHSource builds a GHSource that invokes commands through run.
func (*GHSource) Download ¶
Download execs `gh release download <tag> --repo pgEdge/pgedge-cli --pattern <assetName> --dir <dstDir>`; gh writes the file itself.
type HTTPSource ¶
type HTTPSource struct {
// contains filtered or unexported fields
}
HTTPSource fetches releases and assets from GitHub's unauthenticated REST API, the ladder's first rung. It carries no credentials: the repository is public, so the private-asset endpoint an authenticated transport would need is deliberately not built.
func NewHTTPSource ¶
func NewHTTPSource(baseAPI, baseDL string) *HTTPSource
NewHTTPSource builds an HTTPSource against baseAPI (a GitHub API origin, e.g. "https://api.github.com") and baseDL (a download origin, e.g. "https://github.com"). Splitting them lets tests point each at its own httptest server.
func (*HTTPSource) Download ¶
func (s *HTTPSource) Download( ctx context.Context, tag, assetName, dstDir string, ) (string, error)
Download GETs <baseDL>/pgEdge/pgedge-cli/releases/download/<tag>/<assetName> and writes the body to dstDir/<assetName>, returning that path.
func (*HTTPSource) Logging ¶
func (s *HTTPSource) Logging(out io.Writer, lvl httplog.Level) *HTTPSource
Logging renders this rung's traffic to out at lvl. The download client is capped at Verbose: dumping megabytes of binary archive would bury the line --debug was turned on to read.
type RefusalError ¶
type RefusalError struct{ Msg string }
RefusalError marks a target selfupdate must not touch. `self update` only ever manages a binary it fully owns in place; a Homebrew install and a copy inside a git working tree are each owned by something else instead, so ResolveTarget refuses rather than swapping over either.
func (*RefusalError) Error ¶
func (e *RefusalError) Error() string
type Runner ¶
Runner constructs the *exec.Cmd for one invocation of an external command. Production passes exec.CommandContext; tests inject a stub that re-execs the test binary, so no test runs a real gh.
The context is the deadline seam: a child process has no timeout of its own, so an unauthenticated `gh` sitting on a prompt would hang the command.
type Source ¶
type Source interface {
Releases(ctx context.Context) ([]Release, error)
Download(ctx context.Context, tag, assetName, dstDir string) (string, error)
}
Source fetches the release list and downloads a chosen asset. HTTPSource, GHSource and Chain all implement it.
type TrustedMaterial ¶
type TrustedMaterial struct {
// contains filtered or unexported fields
}
TrustedMaterial is the Sigstore roots a release's certificate must chain to, and whose logs and timestamp authorities must have dated and recorded its signature.
func NewTestTrustedMaterial ¶
func NewTestTrustedMaterial(roots root.TrustedMaterial) *TrustedMaterial
NewTestTrustedMaterial builds a TrustedMaterial from a caller-minted Sigstore root (e.g. sigstore-go's ca.NewVirtualSigstore), for tests outside this package that drive VerifySignature end to end without dialing public Sigstore. SCT verification is skipped: an ephemeral CA embeds no SCT.
func ProductionTrustedMaterial ¶
func ProductionTrustedMaterial() (*TrustedMaterial, error)
ProductionTrustedMaterial loads the public-good Sigstore trust root: sigstore-go's embedded TUF root, refreshed from the CDN and cached under SigstoreCacheDir.