Documentation
¶
Overview ¶
Package selfupdate owns the on-disk install layout and the self-update mechanics behind the `self install|update|uninstall|doctor` commands. It is a plain internal package (like internal/agentskill): the cli layer (cli/selfcmd) builds a Config from the invocation environment and calls the operations here, which do the file/network work and return result structs; no cli-facing formatting lives here.
The model is a single installed binary on PATH plus a manifest. The binary sits at a stable name on PATH (~/.local/bin/korbit on unix, %LOCALAPPDATA%\bin\korbit.exe on windows) — a real file, not a symlink (Windows file symlinks need admin/Developer Mode). <home>/install.json records what was installed (version, sha256, provenance) and is what gates self update. self update replaces the binary in place with github.com/minio/selfupdate, which also handles the Windows running-exe swap.
Trust is TLS + sha256 + a release signature: self update fetches the release checksums.txt and verifies the downloaded archive's sha256 against it before the swap, and — gated by a release-signing certificate published on the trusted docs host (a different host than the GitHub release, so it defends against a compromised release) — verifies the detached RSA signature over checksums.txt. See verify.go for the full policy (including the empty-cert kill switch and why any fetch failure is fatal).
Index ¶
- Constants
- type Config
- func (c Config) AssertManaged() error
- func (c Config) Doctor() (*DoctorReport, error)
- func (c Config) Install() (*InstallResult, error)
- func (c Config) Layout() Layout
- func (c Config) OnPath() bool
- func (c Config) PathAdditions() []PathAddition
- func (c Config) PathEdits() []PathEdit
- func (c Config) Uninstall(opts UninstallOptions) (*UninstallResult, error)
- func (c Config) Update(ctx context.Context, targetVersion string, dryRun bool) (*UpdateResult, error)
- type DiffLine
- type DoctorProblem
- type DoctorReport
- type Doer
- type FailedRemoval
- type InstallResult
- type Layout
- type Manifest
- type PathAddition
- type PathEdit
- type PathResult
- type ProvenanceError
- type UninstallOptions
- type UninstallResult
- type UpdateResult
Constants ¶
const ( FieldManaged = "managed" // the managed-install / manifest check FieldBinary = "binary" // the installed-binary check FieldPath = "path" // the on-PATH check )
Diagnosis field names, one per line `self doctor` reports. A DoctorProblem carries the same tag as the line it belongs to, so the text view can print the problem + fix inline under its diagnosis and an agent can key off it.
const ( SigVerified = "verified" SigDisabled = "disabled" )
SigVerified / SigDisabled are the outcomes verifyChecksumsSignature reports (surfaced in UpdateResult.SignatureCheck): the release signature was verified against the published cert, or verification was disabled by an empty cert.
const DefaultReleaseCertURL = "https://docs.korbit.co.kr/release-signing-cert.pem"
DefaultReleaseCertURL is where self update fetches the release-signing certificate to verify a release's checksums.txt signature. It is served from the managed docs host — deliberately a different, access-controlled host than the GitHub release it authenticates — so the pin defends against a compromised release even though the archive, its checksums, and the signature all come from GitHub. See verify.go for the fetch/verify policy.
const DefaultRepo = "korbit-official/korbit-cli"
DefaultRepo is the public release repository self update resolves against.
const MethodManagedScript = "managed-script"
MethodManagedScript is the manifest `method` for an install created by the managed install script / `self install`. It is the only method self update operates on; any other provenance (go install, Homebrew, a hand-downloaded archive, a dev build) is refused with guidance.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// Getenv reads the process environment (KORBIT_CLI_HOME, HOME/USERPROFILE,
// LOCALAPPDATA, PATH). Injectable so tests point it at a temp dir.
Getenv func(string) string
// Now is the local clock in unix-ms, stamped into the manifest's installedAt.
Now func() int64
// Doer is the HTTP client self update / version resolution run through
// (nil is fine for install/uninstall/doctor, which touch no network).
Doer Doer
// GOOS / GOARCH are the target platform (runtime.GOOS/GOARCH in production;
// injectable so the path layout can be tested for another OS on this host).
GOOS string
GOARCH string
// Version is the running binary's version (internal/version.Version). A "dev"
// value marks an unmanaged source build and is refused.
Version string
// Repo is the release repository (owner/name); empty means DefaultRepo.
Repo string
// ReleaseCertURL is the URL of the release-signing certificate on the trusted
// docs host; empty means DefaultReleaseCertURL. Injectable so tests point it
// at a stub endpoint.
ReleaseCertURL string
// PathConfirm is asked once per PATH location that needs the installer's entry
// (each shell rc file on unix; the User PATH on windows) and returns whether to
// apply it. nil means non-interactive (no TTY reachable) — PATH wiring then only
// prints guidance instead of editing dotfiles (unix) / keeps the silent write
// (windows). The cli layer wires a /dev/tty-backed confirm that renders the
// addition's diff preview and defaults an empty answer to yes.
PathConfirm func(add PathAddition) (bool, error)
// Progress is the human progress/instruction sink (stderr). It is never the
// result: everything an agent needs is in the returned struct.
Progress io.Writer
// Logger is the optional operational logger for the fragile network/filesystem
// path (release resolution, each fetch, the signature/checksum verdicts, the
// in-place binary swap, manifest writes, PATH edits). nil = silent (resolved
// via logging.Or in log). It is distinct from Progress: Progress is program
// output, Logger is the level-gated --debug/--log-level telemetry.
Logger *slog.Logger
// contains filtered or unexported fields
}
Config is the injected environment one self-management operation runs against, so the whole package is unit-testable against a temp home, a stub HTTP client, and a fixed clock.
func (Config) AssertManaged ¶
AssertManaged reports whether this is a managed install self uninstall can operate on (the running binary is the one the managed install script placed, with a matching manifest). The interactive front-end calls it BEFORE asking any removal question or clearing keys, so a non-managed install is refused before any destructive work — Uninstall re-checks it under the lock as a backstop.
func (Config) Doctor ¶
func (c Config) Doctor() (*DoctorReport, error)
Doctor inspects the install and reports its health without changing anything.
func (Config) Install ¶
func (c Config) Install() (*InstallResult, error)
Install copies the running binary to the stable PATH location, wires PATH, and writes the manifest. It is RECONCILING, not linear: re-running it converges a broken install to healthy (a deleted binary, a lost PATH entry, a corrupt manifest, a leftover temp file, …) and tolerates any subset already correct. It is the binary side of the `curl … | sh` repair path, so it assumes nothing about the prior on-disk state. Because the install script is version-pinned, Install records THIS binary's version.
func (Config) OnPath ¶
OnPath reports whether the install dir is already effectively on PATH, so PATH wiring would be a no-op. Read-only; the dry-run report uses it to tell "already on PATH" apart from "wired on disk but not yet in this shell" (both of which leave PathAdditions empty).
func (Config) PathAdditions ¶
func (c Config) PathAdditions() []PathAddition
PathAdditions returns the pending "add the installer's PATH line" edits, one per location that does not yet carry the installer's entry (each shell rc file lacking the managed block on unix; the User PATH on windows), each with a diff preview. Empty when the install dir is already on PATH or every location already has the entry. It edits nothing — the interactive front-end renders the previews, asks per location, and applies only what the user accepts.
func (Config) PathEdits ¶
PathEdits returns the pending "undo the installer's PATH change" edits, one per location that currently carries the installer's entry (each shell rc file with the managed block on unix; the User PATH on windows), each with a diff preview. Empty when there is nothing to undo. It edits nothing.
func (Config) Uninstall ¶
func (c Config) Uninstall(opts UninstallOptions) (*UninstallResult, error)
Uninstall removes the parts of a managed install the caller selected, all under one lock. It refuses on a non-managed install before touching anything. Once past the lock it never returns an error — every problem is recorded in the result (Failed/Warnings) so a partial removal is always fully reported.
func (Config) Update ¶
func (c Config) Update(ctx context.Context, targetVersion string, dryRun bool) (*UpdateResult, error)
Update resolves the target release (latest, or targetVersion when set), and — unless dryRun — downloads it, verifies the release checksums.txt against the release signature (verify.go), verifies the archive's sha256 against that checksums.txt, and replaces the installed binary in place (minio/selfupdate, which re-verifies the exact bytes against the passed checksum and handles the Windows running-exe swap), then updates the manifest. It refuses on a non-managed or dev build (ProvenanceError).
type DiffLine ¶
DiffLine is one line of a pending PATH-undo edit shown to the user before it is applied. Num is the 1-based line number in the file, or 0 for a location that is not a file (the Windows User PATH), where only Text (the entry) is shown.
type DoctorProblem ¶
DoctorProblem is one thing wrong with the install: which diagnosis it belongs to (Field) and a message that states the problem AND its fix.
type DoctorReport ¶
type DoctorReport struct {
Managed bool `json:"managed"`
Method string `json:"method,omitempty"`
RunningVersion string `json:"runningVersion"`
InstalledVersion string `json:"installedVersion,omitempty"`
RunningFrom string `json:"runningFrom"`
Executable string `json:"executable"`
ExecutableValid bool `json:"executableValid"`
ExecutableDir string `json:"executableDir"`
OnPath bool `json:"onPath"`
Problems []DoctorProblem `json:"problems"`
}
DoctorReport is the read-only install-health report from `self doctor`. It diagnoses the same states self install repairs, but never mutates. Each Problem is attached to the diagnosis it concerns and states both what is wrong and how to fix it.
func (DoctorReport) OK ¶
func (r DoctorReport) OK() bool
OK reports whether the install is healthy (managed, executable valid, on PATH, no problems). The cli layer maps a false to a non-zero exit.
func (DoctorReport) Problem ¶
func (r DoctorReport) Problem(field string) string
Problem returns the message for the given diagnosis field, or "" if that diagnosis is healthy.
type Doer ¶
Doer performs an HTTP request. *http.Client satisfies it, as does the CLI's shared korbit.Doer; tests inject a stub. Declared locally so the package does not depend on internal/korbit.
type FailedRemoval ¶
FailedRemoval is one thing uninstall could not remove or edit, with why — so the summary tells the user exactly what is left and what to do about it.
type InstallResult ¶
type InstallResult struct {
Version string `json:"version"`
Executable string `json:"executable"`
SHA256 string `json:"sha256"`
Repaired []string `json:"repaired"` // healed states (always present; [] when clean)
Path PathResult `json:"path"`
Warnings []string `json:"warnings,omitempty"`
}
InstallResult is what `self install` reports: the version installed, the binary path it settled, what it had to repair (empty on a clean first install), and the PATH outcome. Everything the caller needs is here — nothing important is only on stderr.
type Layout ¶
type Layout struct {
// contains filtered or unexported fields
}
Layout computes the install paths from the environment. Paths derive from two roots: the CLI home (config.Home — the manifest + lock) and the OS user home (the installed binary dir), so $KORBIT_CLI_HOME relocates the manifest without moving the installed binary.
func (Layout) BinName ¶
BinName is the installed binary/stored binary's filename: korbit, or korbit.exe on Windows. It is the fixed canonical name (not the possibly-renamed program basename) so the installed binary is predictable.
func (Layout) ExecutableDir ¶
ExecutableDir is the directory on PATH the active binary is copied into: ~/.local/bin on unix, %LOCALAPPDATA%\bin (= %USERPROFILE%\AppData\Local\bin) on Windows.
func (Layout) ExecutablePath ¶
ExecutablePath is the active binary's stable path on PATH.
func (Layout) Home ¶
Home is the CLI home (config.Home): $KORBIT_CLI_HOME else ~/.korbit-cli. The manifest and the lock live under it.
func (Layout) LockPath ¶
LockPath is the advisory lock serializing install/update/uninstall against a concurrent korbit-cli process mutating the same store.
func (Layout) ManifestPath ¶
ManifestPath is the install manifest: <home>/install.json.
type Manifest ¶
type Manifest struct {
Method string `json:"method"` // MethodManagedScript
Executable string `json:"executable"`
Version string `json:"version"`
OS string `json:"os"`
Arch string `json:"arch"`
SHA256 string `json:"sha256"`
Repo string `json:"repo"`
InstalledAt string `json:"installedAt"` // unix-ms as a string
}
Manifest is the durable record of a managed install, written to <home>/install.json by self install / self update. Its presence (plus the running binary being the installed binary on PATH) is what gates self update to managed installs; when it is absent the install was done some other way (Homebrew, go install, a hand-downloaded archive) and self update refuses with method-specific guidance.
type PathAddition ¶
type PathAddition struct {
Location string `json:"location"`
Context []DiffLine `json:"context,omitempty"`
Added []DiffLine `json:"added"`
NewFile bool `json:"newFile,omitempty"`
}
PathAddition is a pending "add the installer's PATH line" at one location: a shell rc file that will receive the managed block (unix), or the User PATH (windows). Context are unchanged trailing lines of the current file, shown for orientation above the change; Added are the lines that would be appended, for a diff preview before applying (the inverse of PathEdit's removal). NewFile is true when the rc file does not exist yet, so the block creates it. For the Windows User PATH there is no file: Context is empty and Added is the single entry to add (Num 0).
type PathEdit ¶
type PathEdit struct {
// Location is the rc file path (unix) or a human label for the User PATH
// (windows).
Location string `json:"location"`
// Lines are the removed lines, most-to-least specific as they appear; for a
// file each carries its line number, for the User PATH a single entry (Num 0).
Lines []DiffLine `json:"lines"`
}
PathEdit is a pending "undo the installer's PATH change" at one location: a shell rc file that carries the managed block (unix), or the User PATH (windows). Lines are the parts that would be removed, for a diff preview before applying.
type PathResult ¶
type PathResult struct {
Dir string `json:"dir"`
OnPath bool `json:"onPath"`
// Action is what was done: "already-on-path", "edited-shell-rc" (unix, on a
// yes prompt), "added-user-path" (windows), or "instructions" (non-interactive
// or declined — the user must add it themselves; see Hint).
Action string `json:"action"`
Files []string `json:"files,omitempty"` // shell rc files edited (unix)
Hint string `json:"hint,omitempty"` // the manual step / follow-up
}
PathResult reports how PATH was handled for the installed binary directory during install. It rides the install result so a stdout-only consumer learns whether a manual step is still needed.
type ProvenanceError ¶
ProvenanceError marks a self update refused because the running binary is not a managed-script install (Homebrew, go install, a hand-downloaded archive, a dev build). Message is the reason; Guidance is the method-specific fix. The cli layer maps it to a config-class error so an agent branches on it.
func (*ProvenanceError) Error ¶
func (e *ProvenanceError) Error() string
type UninstallOptions ¶
type UninstallOptions struct {
// RemoveBinary removes the installed binary, the install manifest, and any
// leftover swap file. A binary that cannot delete itself (the locked running
// korbit.exe on Windows) is reported in Failed instead.
RemoveBinary bool
// RemoveData removes the CLI-home data files in DataPaths (config.json, the
// journal) and, when the home is then empty, the home dir. Key material is
// cleared first via ClearKeys.
RemoveData bool
// RemoveCaches removes the regenerable sandbox artifacts in Artifacts.
RemoveCaches bool
// Artifacts are the regenerable sandbox artifact dirs (resolved by the caller).
// Removed only under RemoveCaches; non-existent entries ignored.
Artifacts []string
// DataPaths are the CLI-home data files to remove under RemoveData (config +
// journal + sidecars). The key registry/vault are removed by ClearKeys instead.
DataPaths []string
// EditPaths are the PATH locations (from PathEdits) the user accepted undoing.
EditPaths []string
// ClearKeys clears every key's material from its backend and removes the key
// files, returning removed lines + warnings. Called under the lock before
// deleting DataPaths (RemoveData only); nil = skip. It lives in the cli layer
// because it needs the key manager; running it here keeps every destructive
// step under the one lock and in one returned result.
ClearKeys func() (removed, warnings []string)
// StopSandbox stops a sandbox running under the default state dir before its
// caches are removed (RemoveCaches only); nil = skip. A stop error is a warning.
StopSandbox func() error
}
UninstallOptions selects what Uninstall removes. The interactive front-end (the cli layer) resolves the candidate paths, shows the previews, collects the user's choices, and passes them here; this layer does the filesystem work under one lock and reports the outcome.
type UninstallResult ¶
type UninstallResult struct {
// Removed lists files/dirs actually deleted (binary, manifest, data files,
// caches, the emptied CLI home) plus a line per cleared API key.
Removed []string `json:"removed,omitempty"`
// Edited lists the PATH locations whose installer change was undone (a shell rc
// file's managed block, or the User PATH entry).
Edited []string `json:"edited,omitempty"`
// Failed lists things that could not be removed/edited, with a reason (a locked
// binary, a permission error, a non-empty dir, a failed edit).
Failed []FailedRemoval `json:"failed,omitempty"`
// KeptPaths lists PATH locations left as-is (the user declined, or none was
// selected) so the summary can tell the user to edit them by hand. Set by the
// cli layer, which knows what was offered vs accepted.
KeptPaths []string `json:"keptPaths,omitempty"`
// Warnings are non-fatal problems that aren't a specific failed removal (a
// sandbox that could not be stopped, a home-containing cache refused).
Warnings []string `json:"warnings,omitempty"`
}
UninstallResult reports what `self uninstall` did. Each piece is chosen by the caller (the interactive selection lives in the cli layer); an empty field means that piece was not selected or nothing of it existed.
type UpdateResult ¶
type UpdateResult struct {
PreviousVersion string `json:"previousVersion"`
LatestVersion string `json:"latestVersion"`
Updated bool `json:"updated"`
CheckedOnly bool `json:"checkedOnly"`
Executable string `json:"executable,omitempty"`
SHA256 string `json:"sha256,omitempty"`
// SignatureCheck records how the release signature was handled for an applied
// update: SigVerified (verified against the published cert) or SigDisabled
// (the docs host served an empty cert — the kill switch). Empty when no update
// was applied (dry-run / already-latest), so an agent can audit from --json
// output whether the binary it now runs was signature-verified.
SignatureCheck string `json:"signatureCheck,omitempty"`
}
UpdateResult reports the outcome of `self update`. Updated is false when already current or when only checking (--dry-run); CheckedOnly marks the dry-run case.