installer

package
v0.1.0-beta.1 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package installer drives Ozy's setup and teardown as resumable, idempotent step state machines. Run is the install entrypoint called by cmd/ozy-install.

Package installer drives Ozy's setup and teardown as resumable, idempotent step state machines. This file holds the durable state store: the small JSON record that lets a rerun skip completed-and-valid steps and continue from the last safe point.

Index

Constants

View Source
const SchemaVersion = 1

SchemaVersion is the on-disk state schema version. Bump it on incompatible changes; a mismatch causes the store to start fresh rather than trust an unreadable record.

Variables

This section is empty.

Functions

func BackupConfig

func BackupConfig(path string) (string, error)

BackupConfig copies the config to a timestamped sibling and returns the backup path. It is the required first move before any in-place edit of an existing config. Callers invoke it only when a change is actually needed.

func Confirm

func Confirm(r io.Reader, def bool) bool

Confirm reads a yes/no answer from r, returning def for an empty or unrecognized line. It is the thin IO layer used when Decide returns AskUser.

func Redact

func Redact(s string) string

Redact masks obvious credential patterns in s. It is a defense-in-depth net, not a guarantee. ponytail: pattern-based masking; the real guarantee is structural — callers log structured fields and never hand raw config contents or env values to the logger. Tighten the patterns only if a concrete leak vector shows up.

func RenderPlan

func RenderPlan(w io.Writer, plan Plan)

RenderPlan writes the human-readable plan and dependency report to w. The same renderer serves TTY and non-TTY output (plain text, colour is layered elsewhere). It always ends with "Nothing has changed yet."

func Run

func Run(opts Options) error

Run executes the dry-run-first install flow: inspect, print the plan, and — unless this is a plan/dry-run — confirm before performing any mutation.

func Uninstall

func Uninstall(opts UninstallOptions) error

Uninstall runs the plan-first, consent-based removal flow. It mirrors the installer: detect, show the plan, confirm, then remove. Every removal is idempotent (a missing target is a no-op), so an interrupted run is safe to rerun.

ponytail: no persisted uninstall-state file. RemoveAll idempotency already makes reruns safe, and the state would otherwise live inside the very directory the run deletes. Add one only if a non-idempotent removal appears.

Types

type ConfigManager

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

ConfigManager creates or safely preserves the user's ozy.jsonc. It never clobbers an existing config: a fresh file is written only when none exists, and any future edit goes through a timestamped backup first.

func NewConfigManager

func NewConfigManager(path string) ConfigManager

NewConfigManager manages the config at path (paths.ConfigFile).

func (ConfigManager) Ensure

func (m ConfigManager) Ensure(log *Logger) error

Ensure creates a default config when none exists, or validates and reports an existing one without overwriting it. It returns the (unchanged) config path.

type ConsentPolicy

type ConsentPolicy struct {
	AssumeYes   bool // --yes was passed
	Interactive bool // stdin is a TTY we can prompt on
}

ConsentPolicy decides whether an action needs a prompt given the run flags. It is pure so the consent boundary is testable without IO.

func (ConsentPolicy) Decide

func (p ConsentPolicy) Decide(r Risk) Decision

Decide resolves the consent outcome for an action of the given risk.

func (ConsentPolicy) NeedsPrompt

func (p ConsentPolicy) NeedsPrompt(r Risk) bool

NeedsPrompt reports whether the user must be asked before an action of the given risk. Risky actions always need a prompt, even under --yes.

type Decision

type Decision int

Decision is the resolved consent outcome for an action, computed before any IO.

const (
	// Proceed means the action is allowed without prompting (ordinary + --yes).
	Proceed Decision = iota
	// AskUser means the run is interactive and must prompt before acting.
	AskUser
	// SkipNoConsent means consent is required but cannot be obtained
	// (non-interactive), so the action is skipped and the caller prints a manual
	// instruction.
	SkipNoConsent
)

type DepChecker

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

DepChecker detects Ozy's build- and runtime dependencies. It never mutates anything; it only reports what it finds.

func NewDepChecker

func NewDepChecker() DepChecker

NewDepChecker returns a checker backed by the real exec.LookPath / exec.Command.

func (DepChecker) Check

func (d DepChecker) Check() []Dependency

Check runs every detection and returns the dependency report in display order.

type DepStatus

type DepStatus string

DepStatus is the detection outcome for one dependency.

const (
	DepOK        DepStatus = "ok"       // present and new enough
	DepMissing   DepStatus = "missing"  // not found
	DepOutOfDate DepStatus = "outdated" // present but below the required version
)

Dependency detection statuses.

type Dependency

type Dependency struct {
	Name      string    // human label, e.g. "Python"
	Required  bool      // required vs optional
	Detected  string    // detected version, "" when missing
	Wanted    string    // required version expression, "" when any version works
	Status    DepStatus // ok / missing / outdated
	Why       string    // why Ozy needs it
	CanManage bool      // whether Ozy can provision/manage it
	Fallback  string    // what happens if it stays missing
}

Dependency is one row of the dependency report: everything the user needs to understand what Ozy found and what it will do about it.

type Logger

type Logger struct {
	Verbose bool // when set, Logf detail also streams to the terminal (--verbose)
	// contains filtered or unexported fields
}

Logger writes a durable, redacted file log for one install/uninstall run and streams friendly lines to the terminal. The file log is detailed for debugging; the terminal stream is for humans. Secrets are never written.

func NewLogger

func NewLogger(dir, kind string, term io.Writer) (*Logger, error)

NewLogger opens a timestamped log file under dir (kind is "install" or "uninstall") and streams human-facing lines to term (may be nil).

func (*Logger) Close

func (l *Logger) Close() error

Close flushes and closes the log file.

func (*Logger) Log

func (l *Logger) Log(msg string)

Log satisfies the sidecar.Logger interface so the installer's logger captures provisioning output in the same file.

func (*Logger) Logf

func (l *Logger) Logf(format string, args ...any)

Logf writes a timestamped, redacted line to the file log. Under --verbose the same line also streams to the terminal.

func (*Logger) Path

func (l *Logger) Path() string

Path returns the log file path, printed at the end of every run.

func (*Logger) Sayf

func (l *Logger) Sayf(format string, args ...any)

Sayf writes a redacted line to both the terminal and the file log.

type Options

type Options struct {
	DryRun    bool // --dry-run / --plan: inspect and print only, never mutate
	AssumeYes bool // --yes: auto-accept ordinary confirmations (never risky)
	Manual    bool // --manual: print a guided checklist instead of installing
	Verbose   bool // --verbose: stream detailed log lines to the terminal
	NoColor   bool // --no-color: disable ANSI color
}

Options are the parsed bootstrap flags.

type Plan

type Plan struct {
	Platform       Platform
	Paths          paths.Paths
	OzyVersion     string
	IsUpdate       bool   // an existing binary or config was found
	ExistingBinary string // path of an existing ozy binary, "" if none
	ExistingConfig string // path of an existing config, "" if none
	Deps           []Dependency
	Steps          []string // ordered planned step names
	Downloads      []string // planned network fetches, human-readable
	EstDiskBytes   int64    // rough total, labelled an estimate when shown
	PathChange     string   // planned PATH change, "" when already reachable
	Warnings       []string
}

Plan is the full, transparent description of what an install run will do. It is built from detection only — constructing it never mutates the system.

func BuildPlan

func BuildPlan(plat Platform, p paths.Paths, deps DepChecker) Plan

BuildPlan inspects the host and produces the install plan. It performs only read-only detection (DetectPlatform, ResolveInstallDirs, CheckExistingInstall, dependency checks) — no step in here writes anything.

func (Plan) SemanticPlanned

func (p Plan) SemanticPlanned() bool

SemanticPlanned reports whether the plan provisions the semantic backend (a usable Python toolchain was detected). When false, the install is lexical-only.

type Platform

type Platform struct {
	OS    string // runtime.GOOS
	Arch  string // runtime.GOARCH
	TTY   bool   // stdout is an interactive terminal
	Width int    // terminal width in columns (best-effort; 80 default)
	Color bool   // ANSI color is safe to emit
}

Platform captures the host OS/arch and the terminal capabilities the plan renderer, progress dashboard, and prompts depend on. It is detected once at startup and is otherwise inert (no mutation).

func DetectPlatform

func DetectPlatform(stdout *os.File, noColor bool) Platform

DetectPlatform inspects the host and the given stdout stream. noColor forces color off regardless of the terminal (the --no-color flag).

func (Platform) Plain

func (p Platform) Plain() bool

Plain reports whether output should use the static, no-ANSI fallback: a non-terminal, a CI environment, or color explicitly disabled.

type Progress

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

Progress renders per-step status to the terminal. On a colour-capable TTY it uses coloured status glyphs; otherwise it emits the same lines without ANSI, preserving the step semantics for non-TTY / CI / --no-color output.

ponytail: static per-step lines, not an in-place repaint with an animated spinner. Chosen deliberately — consent prompts and copy-paste PATH instructions print mid-execution, and a repainting dashboard would corrupt scrollback and garble those. The plan already lists every step up front, so "every step visible" holds. Upgrade path: a repainting dashboard with all consent gathered up front, behind this same Start/Done/Skip/Fail interface.

func NewProgress

func NewProgress(w io.Writer, plat Platform) *Progress

NewProgress renders to w. Colour is enabled only when the platform reports a colour-capable interactive terminal.

func (*Progress) Done

func (p *Progress) Done(title string)

Done marks a step as completed.

func (*Progress) Fail

func (p *Progress) Fail(title string)

Fail keeps the failed step's line visible; the actionable error is printed by the caller immediately beneath it.

func (*Progress) Skip

func (p *Progress) Skip(title string)

Skip marks a step that was already complete on a rerun.

func (*Progress) Start

func (p *Progress) Start(title string)

Start marks a step as now running.

type Risk

type Risk int

Risk classifies how much consent an action needs.

const (
	// Ordinary actions (create a Ozy-managed directory, write a fresh config)
	// may be auto-accepted by --yes.
	Ordinary Risk = iota
	// Risky actions (downloads, dependency installs, PATH/shell-profile edits,
	// creating managed runtimes, deleting user config/data, purge, or anything
	// outside the Ozy-managed directory) always require an explicit answer.
	// --yes never auto-accepts them.
	Risky
)

type State

type State struct {
	SchemaVersion int                  `json:"schemaVersion"`
	OzyVersion    string               `json:"ozyVersion,omitempty"`
	UpdatedAt     time.Time            `json:"updatedAt"`
	Steps         map[string]StepState `json:"steps"`
}

State is the durable installer/uninstaller state persisted between runs.

func (*State) Done

func (s *State) Done(step string) bool

Done reports whether step is recorded complete.

func (*State) Mark

func (s *State) Mark(step string, status StepStatus, detail string)

Mark records status and optional detail for step.

type StateStore

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

StateStore reads and writes State at a fixed path.

func NewStateStore

func NewStateStore(path string) *StateStore

NewStateStore returns a store backed by the file at path.

func (*StateStore) Load

func (s *StateStore) Load() (*State, error)

Load reads the state file. A missing file yields a fresh empty State (not an error) so the first run starts clean. A schema mismatch is treated the same way: the recorded steps cannot be trusted, so the run starts fresh.

func (*StateStore) Save

func (s *StateStore) Save(st *State) error

Save atomically writes state to disk, creating parent directories. The state area is owner-private.

type StepError

type StepError struct {
	Step      string // the step name that failed
	Cause     error  // the underlying failure
	Impact    string // why the step matters / what is affected
	SafeRetry bool   // whether re-running is safe (steps are idempotent)
	Next      string // the exact command to run next
	LogPath   string // where the full log lives
}

StepError is the actionable error produced when a step fails. It carries everything the user needs to recover: what failed, why the step mattered, whether retry is safe, the next command, and the log path. Bare "install failed" messages are never surfaced.

func (*StepError) Error

func (e *StepError) Error() string

func (*StepError) Unwrap

func (e *StepError) Unwrap() error

type StepState

type StepState struct {
	Status    StepStatus `json:"status"`
	UpdatedAt time.Time  `json:"updatedAt"`
	Detail    string     `json:"detail,omitempty"`
}

StepState records one step's last outcome and any validation metadata (for example a failure reason, or a recorded version/checksum to revalidate on the next run).

type StepStatus

type StepStatus string

StepStatus is the recorded outcome of one step.

const (
	StepPending StepStatus = "pending"
	StepDone    StepStatus = "done"
	StepFailed  StepStatus = "failed"
)

Recorded step outcomes.

type UninstallOptions

type UninstallOptions struct {
	DryRun     bool // print the removal plan and exit
	AssumeYes  bool // auto-accept ordinary removals (never config/purge)
	KeepConfig bool // preserve config even under --purge
	KeepData   bool // preserve data/models
	Purge      bool // also remove config (requires a distinct confirmation)
	NoColor    bool
}

UninstallOptions are the parsed uninstall flags.

Jump to

Keyboard shortcuts

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