sysup
Configurable, dotfiles-shareable system updater. sysup runs every updater
that applies to the current host, in order. What runs is defined in
configuration, not code, so adding, removing, or reordering a simple updater is
a config edit rather than a release.
Usage
sysup # run every applicable updater, in order
sysup --only brew,helm # run only these entries (comma list or repeated)
sysup --tag python # run only entries carrying this tag
sysup --list # print the resolved plan for this host, run nothing
sysup --dry-run # show what would run without running it
Applicability is decided per entry by its when guard (OS + a binary that
must exist), so there are no platform subcommands: a macOS-only entry simply
carries when.os: darwin and is skipped on other hosts.
Configuration
Updaters live in buvis-sysup.yaml in the buvis config stack
($BUVIS_CONFIG_DIR → ~/.config/buvis → cwd), deep-merged over a bundled
default. The updater collection is a flat map keyed by name, so a machine
layer adds an entry with a new key and overrides a field of an existing entry
by that key — no list-append syntax, and every base key survives unless
explicitly disabled.
# buvis-sysup.yaml
commands:
brew:
order: 10 # entries run in ascending order
when: { os: darwin, check: brew }
steps: # run entry: argv arrays, early-abort
- [brew, update]
- [brew, upgrade]
- [brew, cleanup]
npm-check:
order: 20
when: { check: npm-check }
interactive: true # inherit stdio instead of capturing
steps: [[npm-check, -gu]]
python-packages:
order: 30
use: pip-outdated # use entry: a named built-in capability
nvim:
order: 50
when: { check: nvim }
use: nvim-mason
with: { timeout: 600 } # capability inputs
prime: # session capabilities run first
- sudo-prime
Per-entry envelope
Every entry is exactly one of a run entry (steps: a list of argv arrays,
executed in order with early-abort, no shell) or a use entry (use: a
named built-in capability, with optional with inputs). Common fields:
Field |
Meaning |
|---|---|
|
integer; entries run ascending (default 100) |
|
set |
|
|
|
binary name; skipped (reported) if |
|
inherit stdio instead of capturing (default false) |
|
seconds; |
|
if true, a failed step does not abort the entry (default false) |
|
list of strings for |
Only $${VAR} / ${VAR} substitution applies to steps values (from the
config loader); there is no shell, so no other expansion happens. Use
$${VAR} to pass a literal ${VAR} through.
Built-in capabilities
The stateful updaters that config cannot express as plain argv ship as code-owned capabilities, referenced by name:
Capability |
Behaviour / inputs |
|---|---|
|
|
|
headless Mason update probe; input |
|
per-interpreter (mise-managed, else PATH |
|
caches sudo credentials with a background refresher ( |
An unknown capability name, or an unknown with input, is a config error
reported clearly on load — never a stack trace.
Migration from the subcommands
The former sysup mac / sysup pip / sysup nvim / sysup wsl
subcommands are removed. With no user config, plain sysup reproduces
what sysup mac did on macOS (brew → npm-check → pip → uv → helm → mise, mise
last) and what sysup wsl did on Linux (apt → snap), host-selected via
when. Replace sysup mac with sysup; to run a subset use --only
or --tag (e.g. sysup --only brew to run just Homebrew).