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
- func BackupConfig(path string) (string, error)
- func Confirm(r io.Reader, def bool) bool
- func Redact(s string) string
- func RenderPlan(w io.Writer, plan Plan)
- func Run(opts Options) error
- func Uninstall(opts UninstallOptions) error
- type ConfigManager
- type ConsentPolicy
- type Decision
- type DepChecker
- type DepStatus
- type Dependency
- type Logger
- type Options
- type Plan
- type Platform
- type Progress
- type Risk
- type State
- type StateStore
- type StepError
- type StepState
- type StepStatus
- type UninstallOptions
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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) Log ¶
Log satisfies the sidecar.Logger interface so the installer's logger captures provisioning output in the same file.
func (*Logger) Logf ¶
Logf writes a timestamped, redacted line to the file log. Under --verbose the same line also streams to the terminal.
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 ¶
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 ¶
DetectPlatform inspects the host and the given stdout stream. noColor forces color off regardless of the terminal (the --no-color flag).
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 ¶
NewProgress renders to w. Colour is enabled only when the platform reports a colour-capable interactive terminal.
func (*Progress) Fail ¶
Fail keeps the failed step's line visible; the actionable error is printed by the caller immediately beneath it.
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.
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.
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.