update

package
v0.32.1 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package update backs `jenticctl update`: it inspects what is installed (via the manifest and build-time metadata) and compares its version against the latest release tag to report whether a newer build is available, then fetches and swaps in the rebuilt binaries.

Index

Constants

View Source
const BrewUpgradeCommand = "brew upgrade jentic"

BrewUpgradeCommand is the user-facing command that updates a Homebrew-managed install of the CLIs (both binaries ship in the `jentic` cask).

Variables

This section is empty.

Functions

func AssetName added in v0.32.0

func AssetName(binary, version, goos, goarch string) string

AssetName returns the release archive filename for a binary, matching the per-binary goreleaser name_template introduced by the binary-distribution work (cli/.goreleaser.yaml: `jentic_{{.Version}}_{{.Os}}_{{.Arch}}`).

GOOS/GOARCH already use goreleaser's os/arch vocabulary (linux/darwin/windows, amd64/arm64), so no mapping is needed beyond the windows→.zip switch that the `jentic` archive's format_overrides applies (jenticctl is not shipped for windows). goreleaser's {{.Version}} is the tag WITHOUT the leading "v", so we strip it here to stay byte-identical with the published asset — the installer's shell name construction and this helper are kept in lockstep and reviewed together (a golden test pins this to the YAML template).

binary is "jentic" or "jenticctl"; version may be "v0.31.0" or "0.31.0".

func BrewCaskVersion added in v0.21.0

func BrewCaskVersion(ctx context.Context) string

BrewCaskVersion returns the installed version of the jentic cask as reported by brew (`brew list --cask --versions jentic`), or "" when it cannot be determined. Callers use it to verify that an upgrade actually moved the cask: brew can only install what the cask ships, and the cask bump lags the GitHub release tag, so a `brew upgrade` inside that window is a no-op.

func BrewManaged added in v0.21.0

func BrewManaged(target string) bool

BrewManaged reports whether the binary at target is managed by Homebrew.

Self-updating a brew-managed binary desyncs brew's bookkeeping (`brew outdated` keeps reporting the old version and the next `brew upgrade` clobbers the swapped binary), so `update` never swaps the CLI in place and instead delegates the CLI half to BrewUpgrade — the policy family flyctl, gh, and friends use: let brew do the swap rather than going behind its back.

Two signals mark a brew-managed install:

  • the symlink-resolved path has a Cellar or Caskroom segment (brew keeps the real files there and links them into <prefix>/bin), or
  • the resolved path sits directly in `$(brew --prefix)/bin` *and* the jentic cask is present in the Caskroom — a regular file there is then a brew link that an older self-update overwrote in place. The cask check keeps a deliberate `JENTIC_INSTALL_DIR=/usr/local/bin` source install on an Intel mac (where /usr/local is the brew prefix but not brew-owned) from being misdetected.

The decision deliberately uses the *resolved* path: a source install linked onto PATH by tools/install.sh (e.g. /usr/local/bin/jenticctl -> ~/.jentic/bin/jenticctl) resolves outside brew's tree and must not be misdetected just because /usr/local happens to be the brew prefix.

func BrewUpgrade added in v0.21.0

func BrewUpgrade(ctx context.Context, out, errW io.Writer) error

BrewUpgrade runs `brew upgrade jentic`, streaming its output, to refresh a Homebrew-managed install. It is the CLI half of `jenticctl update` for brew-managed binaries (flyctl-style delegation): brew performs the swap so its bookkeeping stays consistent.

func FetchInstaller

func FetchInstaller(ctx context.Context, repo, ref, token string) ([]byte, error)

FetchInstaller downloads tools/install.sh for ref from repo's raw content, authenticating with a bearer token when one is given (required while the repo is private). The script is returned verbatim so the caller can run it.

func IsReleaseTag added in v0.32.0

func IsReleaseTag(ref string) bool

IsReleaseTag reports whether ref is a canonical published-release tag (vMAJOR.MINOR.PATCH). The download-and-swap updater only engages for these — a branch/SHA/dev ref has no published assets and must build from source.

func IsVerificationError added in v0.32.0

func IsVerificationError(err error) bool

IsVerificationError reports whether err is a fail-closed verification failure (as opposed to a transport/asset-missing error a caller may fall back from).

func LatestReleaseTag added in v0.16.0

func LatestReleaseTag(ctx context.Context, repo, token string) (string, error)

LatestReleaseTag resolves the highest canonical release tag (vMAJOR.MINOR.PATCH) in repo via `git ls-remote --tags`, returning it with its `v` prefix (e.g. "v0.15.3"). A token (GitHub PAT) authenticates against private repositories, mirroring lsRemote. It errors when git is unavailable or no release tag exists.

func MigrateWithRollback added in v0.32.0

func MigrateWithRollback(dataDir string, sqlite bool, migrate func() error, onSnapshot, onRollback func()) error

MigrateWithRollback runs a forward-only SQLite migration inside a snapshot/restore net: it backs up the *.db files under dataDir, runs migrate, and on failure rolls the database back to its pre-migration bytes before returning the (wrapped) migration error. On success the snapshot is discarded. It is the single source of truth shared by both first-`install` and `update` so the two can never drift (install-flow review P1-E). onSnapshot/onRollback are optional progress hooks (nil-safe) so callers can print their own status lines. For a non-SQLite backend it just runs migrate (Postgres backup/restore is out of scope — see the SQLiteBackup doc), so callers pass sqlite=false.

func NewerAvailable added in v0.16.0

func NewerAvailable(installed, latest string) bool

NewerAvailable reports whether latest is a newer release than installed.

When installed does not parse as a clean semver — e.g. it is "dev", a branch name, or a SHA (an unreleased/source build) — we cannot meaningfully compare, so we conservatively report true so such builds are offered the latest release. When latest itself does not parse we report false: there is nothing sensible to update to.

func ReplaceBinary

func ReplaceBinary(target, staged string) (string, error)

ReplaceBinary atomically swaps the file at target with the freshly built binary at staged, after backing up the current target to "<target>.bak". staged is copied into target's directory first so the final rename is on the same filesystem (atomic), avoiding a cross-device rename error when the build was staged under a temp dir. Returns the backup path (empty if target did not previously exist).

func RestoreBinary added in v0.32.0

func RestoreBinary(target, backup string) error

RestoreBinary copies a .bak backup produced by ReplaceBinary back over the target, undoing a swap. Used to roll back a multi-binary update when a later binary in the set fails to swap, so a half-updated pair can't persist.

Types

type DownloadResult added in v0.32.0

type DownloadResult struct {
	BinaryPath string
	Warning    string
}

DownloadResult is the outcome of a verified download: the extracted binary path in stageDir and any non-fatal warning (e.g. cosign absent).

func DownloadAndVerify added in v0.32.0

func DownloadAndVerify(ctx context.Context, repo, tag, binary, goos, goarch, token, stageDir string) (*DownloadResult, error)

DownloadAndVerify fetches the release archive for `binary` at `tag`, enforces the sha256 gate against the release checksums.txt (fail-closed), verifies the cosign signature when cosign is present (a bad signature aborts; absence warns), extracts the archive into stageDir, and returns the path to the extracted binary. It is the runtime analogue of the installer's download mode and shares the AssetName construction so the two never diverge.

type SQLiteBackup added in v0.32.0

type SQLiteBackup struct {
	// contains filtered or unexported fields
}

SQLiteBackup is a snapshot of the local SQLite database files taken before a forward-only migration, so a failed migration can be rolled back to a known-good state (CLI-V2 Phase 6 lifecycle hardening: "DB backup before migration, rollback path"). It only covers the SQLite backend — the files live under ~/.jentic/data and the CLI owns them outright. The Postgres/Docker backend keeps the documented `pg_dump` warning: an in-CLI dump/restore of an operator-managed database is out of scope and would give false assurance.

func BackupSQLite added in v0.32.0

func BackupSQLite(dataDir string) (*SQLiteBackup, error)

BackupSQLite snapshots every SQLite file directly under dataDir — the main *.db AND its WAL sidecars (*.db-wal, *.db-shm) — into a temp directory, returning a handle that can Restore them if the migration fails or be Discarded on success. A dataDir with no SQLite files (a fresh install whose migration will create them) yields an empty, harmless backup.

The WAL sidecars matter (F1, review round-3 #7): a live WAL-mode database keeps uncheckpointed committed pages in the -wal file, so snapshotting only the .db would restore a main file whose committed state lived partly in a WAL that is then stale or gone — an inconsistent restore presented as a successful rollback. Capturing all three keeps the restore self-consistent. Callers MUST still stop the app before snapshotting (see MigrateWithRollback callers) so nothing is mid-transaction; the file copy is otherwise a crash-consistency bet.

func (*SQLiteBackup) Discard added in v0.32.0

func (b *SQLiteBackup) Discard()

Discard removes the snapshot directory once it is no longer needed (a successful migration). Best-effort: a leftover temp dir is harmless.

func (*SQLiteBackup) Empty added in v0.32.0

func (b *SQLiteBackup) Empty() bool

Empty reports whether the backup captured no files (nothing to roll back to).

func (*SQLiteBackup) Restore added in v0.32.0

func (b *SQLiteBackup) Restore() error

Restore copies each snapshot back over its live file, undoing a failed migration. It restores as many files as it can and joins any errors so one bad file does not silently strand the rest half-rolled-back.

It also removes any live WAL/SHM sidecar that was NOT part of the snapshot (F1, review round-3 #7): a failed migration may have started a fresh WAL that didn't exist when we snapshotted; leaving it in place could replay stale frames on top of the restored .db. Deleting the un-snapshotted sidecars forces SQLite to treat the restored .db as authoritative on next open.

Jump to

Keyboard shortcuts

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