Personal configuration references and standalone maintenance tools for macOS
and Linux hosts. This repository reports drift and missing capabilities. It
does not automatically overwrite $HOME or rebuild an entire machine.
reference/contains the intended examples compared with live files under$HOME.mackup/declares which application paths belong to each reference.scripts/implements typed inspection tasks and explicit maintenance tasks; every mutating task previews by default and writes only with--apply.modules/bin/contains independent user commands.modules/<tool>/contains substantial tools that own their installation and removal.generated/is optional shell runtime state. A missing generated directory is a host-health warning, not a reason to mutate the repository.inventory/contains per-host software snapshots written only bymise run inventory.
Agent-specific rules live in AGENTS.md. Files under modules/ also inherit
modules/AGENTS.md.
The human who owns the target host and decides which changes are acceptable is the Operator. A person or AI carrying out the delegated task is the Executor.
This repository has two operating modes:
- In authoring mode, the Executor changes this repository, runs
mise run verify, and presents the result to the Operator for review. - In operations mode, the Executor inspects or previews the target host, reports the facts, impact, options, and recommendation, obtains the Operator's decision when needed, applies only the selected operation, and reruns the relevant inspection.
A finding's suggested action is not authorization to mutate the host. Ambiguous ownership, unclear scope, destructive effects that were not already authorized, or multiple legitimate outcomes require an Operator decision before mutation.
Git and mise are the only bootstrap requirements. On a machine without mise,
install the latest release from the official installer only when Dotfiles will
manage that host. If the operating system or another platform already owns
Mise or global configuration, retain that owner, create the
audit-only policy before running any Dotfiles
operation, and skip mise-sync --apply.
The managed-host bootstrap is:
curl -fsSL https://mise.run | sh
export PATH="$HOME/.local/bin:$PATH"
git clone https://github.com/ipruning/dotfiles.git ~/dotfiles
cd~/dotfiles
mise trust
mise install --locked fd jq python ripgrep shellcheck uv
mise exec -- uv sync --locked
mise tasks
mise run mise-sync
mise run mise-sync -- --applyManaged hosts default to the standalone executable at ~/.local/bin/mise. Do
not install a second copy with Homebrew, apt, or another package manager:
multiple owners may leave generated shell activation bound to the wrong binary.
The upstream installer uses this standalone path by default, and this
installation supports mise self-update.
A host whose operating system owns configuration or Mise can opt out of
repository mutation without changing the shared reference. Create the
untracked host-local file ~/.config/dotfiles/policy.toml:
mode = "audit-only"mise_path = "/usr/bin/mise"audit-only keeps previews plus diff, check, lint, and verification
available, but rejects every --apply operation before it writes files or runs
updaters. Previews still show the counterfactual managed-host plan, but omit an
inapplicable --apply next step. Apply-safety differences such as host-only
global Mise tools remain visible facts and do not fail an audit-only preview;
an incomplete inspection, unreadable configuration, or failed command probe
still fails. The same mutation guard protects the underlying apply_* and
execute_* Python operations. mise_path selects the host's canonical Mise
owner, so a package-managed binary such as Omarchy's /usr/bin/mise is not
treated as an extra installation. An absent policy preserves the managed
default above. An invalid policy, including unknown fields, fails closed and is
diagnosed by mise run check; it never falls back silently to the standalone
owner.
Under this policy, check marks Dotfiles-owned shell integration, shared Mise
capabilities, and generated runtime readiness as not applicable. Canonical Mise
health, duplicate owners, Skillshare health, permissions, and other host-owned
problems remain applicable and continue to gate strict inspection.
The installer intentionally has no version argument: host and Orb bootstrap
take the latest mise release available from https://mise.run rather than
pinning the mise binary. The hard min_version in both project and global
configuration is only the compatibility floor (currently 2026.8.3), not the
version bootstrap should install.
mise trust is required because this repository declares a project virtual
environment. The explicit mise install --locked ... command requires every
project tool to have a URL and checksum for the current platform in
mise.lock. mise exec -- uv sync --locked then materializes the locked Python
dependencies, including the pinned Mackup fork, before shell activation makes
uv directly available. Mise auto-install is disabled by the master
settings.auto_install = false gate, covering task, exec, and command-not-found
paths: these two commands form the explicit bootstrap boundary, and later
commands fail instead of quietly downloading a missing tool or project
dependency. Do not split the master gate into narrower task or exec settings.
Do not replace the explicit tool list with a bare
mise install --locked: Mise also loads the global configuration and would
install host-wide tools during repository bootstrap.
mise tasks is the authoritative command list. The main inspection interface
is:
mise run diff
mise run check
mise run lint
mise run verifyOn Linux, diff and check automatically select the small linux-lite
profile. macOS selects macos. Use --profile full when deliberately auditing
every stored reference:
mise run diff -- --profile full
mise run check -- --profile fullChoose the operation by intent; mise tasks and each task's --help remain the
authoritative command interfaces:
| Intent | Documentation |
|---|---|
| Bootstrap a small Linux host | Linux Lite setup |
| Compare, restore, or adopt configuration | Configuration drift |
| Converge global mise tools across hosts | Global mise convergence |
| Inspect host and repository health | Host health |
| Update installed tools | Host updates |
| Refresh generated shell state | Generated runtime |
| Record installed software | Host inventory |
| Operate a self-installing module | Standalone tools |
| Inspect Skillshare ownership | Skillshare |
Inspection remains read-only. The explicit setup task is the only repository task that edits shell and Git bootstrap configuration in place; like every mutating task it previews by default:
mise run setup -- --profile linux-lite
mise run setup -- --profile linux-lite --apply
exec bashIt preserves the existing ~/.bashrc, adds one marked block that loads
modules/bash/init.bash, and adds ~/.private.gitconfig to Git's includes. It
does not create an identity, install optional tools, clone private repositories,
or synchronize Skillshare extras. The Bash module adds ~/.local/bin and mise's
shim directory to PATH, then activates mise and guarded Starship, Atuin, and
Zoxide integrations in interactive shells. It provides .., ..., a Ctrl-W
binding that deletes the same path segment as the macOS Zsh configuration,
reserves Ctrl-R for Atuin when later startup files initialize FZF, and provides
Zoxide's j command while leaving the ordinary up arrow to Bash. Interactive
SSH logins remain ordinary shells; start Herdr explicitly when needed. It does
not expose modules/bin on Linux. The managed block is placed before Ubuntu's
non-interactive early return, so direct SSH commands also receive the user and
mise paths without interactive shell initialization.
The Linux Lite drift profile observes Git, Mise, portable Atuin and Btop
configuration, and the availability of Btop, Starship, Atuin, Zoxide, Herdr,
Skillshare, Hunk, Lazygit, and Lazydocker. Optional capabilities remain
warnings: inspection never installs a missing tool or restores configuration
automatically. mise run check -- --strict treats those warnings as failures.
Linux Lite targets Bash, but the full Zsh reference is deliberately safe to
restore on a Linux host as well, so mise run restore -- zsh is supported
there. The contract is a split between experience and commands:
- The Zsh experience loads on Linux.
.zshrcsourcesmodules/zsh/(aliases, environment, completions, plugins) and the generated completion directory regardless of platform; the Homebrew block is macOS-only and the clash block is Linux-only. - The user bin and mise shim directories load on every platform. The repository
command directory does not:
.zshenvprependsmodules/bintoPATHonly underdarwin*, so a Linux host never gains repository commands and can never shadow a system tool — most importantly iproute2ss, whose repository namesake isskillshare-source. - The repository mise configuration carries no repository bins. Its committed
lockfile contains URLs and checksums for both
macos-arm64andlinux-x64, so bootstrap uses the same reviewed tool artifacts on both platforms.
What is not promised: an identical experience when optional tools are absent.
Their integrations degrade gracefully, and mise run check reports
profile-scoped missing capabilities. Starship, Atuin, and Zoxide are declared in
the shared global Mise configuration for a consistent personal shell;
Television remains host-managed. mise run shell reflects the same asymmetry —
it syntax-checks Zsh files only where zsh is installed and marks them
not-applicable elsewhere rather than failing.
mise run diff compares profile-relevant files under reference/ with the
corresponding paths under the current $HOME. It reports locations and file
kinds, never file contents. Ordinary drift exits successfully; an incomplete
inspection, such as an unreadable path or invalid Mackup response, exits
non-zero.
mise run diff
mise run diff -- git
mise run diff -- --json
mise run diff -- --profile fullThe command runs the immutable Mackup fork pinned as a locked git dependency in
pyproject.toml and recorded in uv.lock. The bootstrap command mise exec -- uv sync --locked provisions it, so no Mackup fetch happens during inspection.
It loads application definitions directly from mackup/applications/ and does
not install ~/.mackup links or change either side.
Restore is deliberately application-scoped. It defaults to the same read-only
plan; --apply first moves each changed live path into
~/.local/state/dotfiles/restore-backups/, then links the live path to its
reference. There is no implicit restore-all operation.
mise run restore -- git
mise run restore -- git --json
mise run restore -- git --applyWhen a live configuration should become the reference, adopt is the mirror of
restore: it is application-scoped, defaults to the same read-only plan, and
--apply copies the live truth into reference/ (and removes reference files
whose live counterpart is gone). It writes only inside the repository, never
under $HOME, and refuses to run while the affected reference paths have
uncommitted changes, so Git can always revert an adoption.
mise run adopt -- mise
mise run adopt -- mise --json
mise run adopt -- mise --applyGit protects repository changes; restore backups protect the replaced live paths.
The tracked global config.toml and multi-platform mise.lock are the shared
personal interactive baseline for macOS and Linux hosts. They do not own
systemd services, cron jobs, workers, or project runtimes: those must use a
system package or invoke the owning project's Mise config explicitly. Installed
tools, download caches, shims, and generated shell functions remain local
runtime state and are rebuilt from the shared declaration rather than
synchronized through Git.
This convergence operation belongs to managed hosts. On an audit-only host its preview is a counterfactual comparison, not a health verdict: host-owned tools and alias differences remain in the safety report, but do not fail the preview or produce an apply handoff.
Preview the complete convergence first:
mise run mise-sync
mise run mise-sync -- --jsonAn apply backs up and links the live mise configuration to reference/, first
installs the locked Go, Node, Ruby, and Rust runtimes, then installs the complete
locked tool set against $HOME. Staging the runtimes prevents package-manager
backends such as cargo: from racing an older compiler already on the host.
Finally, it replaces all shims with a canonical rebuild while ~/.local/bin is
forced to the front of PATH:
mise run mise-sync -- --applyOn macOS 27, the complete tool step temporarily disables Cargo release stripping. Rust stable currently produces stripped proc-macro libraries that macOS 27 refuses to load; the override can be removed after stable Rust carries LLVM 22.1.8 or newer and the locked Atuin build passes without it.
Before replacing the live declaration, mise-sync compares its [tools]
entries with the tracked baseline. Any live-only tool blocks apply and is named
in the report. Decide whether to add it to the shared baseline, move it to its
project or service owner, or remove it explicitly; mise-sync never makes that
destructive choice. An unreadable or invalid live configuration also blocks
apply because ownership cannot be established safely.
This operation deliberately does not pull Git, self-update mise, update the
lockfile, remove an alternate package-managed mise, or install tools during
shell startup. A canonical binary older than the repository's hard minimum
must still be updated directly before any repository task can launch. That
minimum is a compatibility floor; bootstrap and self-update continue to select
the latest official release rather than pinning mise to the floor.
Backends that support lockfile URLs are artifact-locked; package-manager
backends such as gem:, go:, and npm: are version-locked but retain their
upstream package manager's artifact and integrity semantics.
Global tools should be interactive commands wanted on every personal host.
Project tools belong in that project's Mise configuration. Long-running Linux
services should use the distribution package when appropriate, or an explicit
<canonical-mise> -C <project> exec -- <tool> command; they must not depend on
global shims. A genuinely host-specific tool is an explicit exception, not a
second global configuration truth.
Herdr, Hunk, Lazygit, and Lazydocker are part of this shared baseline on macOS and Linux. Mise owns their installs and updates; do not combine them with Homebrew, a platform package, or a direct installer. Hunk supplies Git's pager and difftool plus Lazygit's static diff renderer, while Lazygit retains responsibility for staging. Herdr's portable configuration is restored independently:
mise run restore -- herdr
mise run restore -- herdr --applyAtuin and Zoxide follow the same Mise ownership rule. Atuin is compiled on each
host from the custom fork commit pinned in the shared declaration; the lockfile
records that exact revision. Only ~/.config/atuin/config.toml is portable and
restored through Mackup. Atuin history, login and encryption state, and
Zoxide's database stay local to each host:
mise run restore -- atuin
mise run restore -- atuin --applySkillshare is also part of the shared Mise baseline; mise upgrade owns its CLI
updates on every host. Do not combine that install with Homebrew or
skillshare upgrade --cli. GitHub CLI and Codex are host-owned instead of part
of the shared baseline. The capability is shared, but its installer is not:
Omarchy provisions both through its own Mise wrappers, Debian can use the
official APT package, and macOS can use Homebrew. The shared Mise configuration
therefore declares neither gh nor Codex, and version differences between
those host owners are not Dotfiles drift.
Btop is also platform-owned: Homebrew owns it on macOS, while each Linux
distribution's package manager owns it. The repository restores only Btop's
portable configuration and theme; check reports a missing binary but never
installs it. Distribution package versions can therefore differ without
creating configuration drift.
mise run check reports required executables and optional capabilities
separately. The selected profile determines the current check set, which the
plain or JSON report lists directly. On macOS, the report also consumes
macos-session-health status --format json to detect an unavailable collector
or notification path.
mise run check
mise run check -- --json
mise run check -- --strictWarnings do not fail the normal command because different hosts intentionally
have different capabilities. --strict treats warnings as failures.
Skillshare inspection parses the global YAML configuration and checks its source
and target locations without invoking the Skillshare CLI. Installation ownership
remains a separate check: Mise's JSON inventory distinguishes an inactive Mise
install from the active executable and warns only when independent owners
coexist. Run skillshare status --global --json or skillshare doctor
explicitly when tracked-repository, synchronization, or deeper target health is
needed: those commands may migrate legacy configuration or write diagnostic
state and therefore do not belong in read-only check.
Missing or empty generated shell directories are reported as not ready. The
report also verifies that the host's canonical Mise path is a real executable,
warns when another Mise installation exists on PATH or at a common system
location, verifies that Mise-owned shims target the canonical executable, and
checks that generated Zsh activation names only the canonical executable.
mise run lint inspects repository paths, Mackup mappings, and dangling
symlinks. Its path.* findings are host-relative: a machine-specific path
referenced by tracked configuration is reported OK where it exists and as a
warning on hosts where it does not, so warning counts legitimately differ
between machines running the same commit. mise run verify adds Python formatting, type checking, behavior
tests, the standalone module suites on macOS, and the shell gate: mise run shell
runs Bash syntax checks and ShellCheck for Bash files. Zsh files receive syntax
checking when zsh is available.
Updating installed tools is an explicit mutation, separate from configuration inspection. Preview the exact commands first:
mise run update
mise run update -- --json
mise run update -- --applyThe task discovers most supported updaters on PATH, reports missing tools as
skipped, applies available updates in a stable order, and continues independent
steps after a failure. Mise is the exception: its tool upgrade and reshim steps
always invoke the canonical Mise path explicitly. The standalone canonical
Mise install also self-updates without the unrelated plugin update side effect;
a host-selected Mise path is updated by its host owner instead. The preview is
the authoritative list of supported updaters and the exact commands available
on the current host. PLANNED means the updater command is available; the
updater determines whether a newer version exists during apply. Any failed
step makes the command exit non-zero. It deliberately does not run brew cleanup, brew autoremove, or mise prune;
the Homebrew package step also sets HOMEBREW_NO_INSTALL_CLEANUP=1 so
brew upgrade cannot trigger cleanup implicitly. Removal and pruning require a
separate, explicit operation. Because Sprite's updater treats a closed upgrade
prompt as a successful no-op, --apply supplies its affirmative response;
preview output and JSON expose that stdin behavior. Claude receives a 30-minute
outer timeout so its updater can report its own download failure. A failed
Claude update is not cleaned automatically: retry claude update, then inspect
its staging and versions directories before any explicit cleanup.
During a JSON apply, stdout remains one machine-readable document while stderr
reports RUN, 30-second STILL RUNNING, DONE, and contextual failure
records. Child output is discarded so it cannot corrupt JSON or hold the task
open through inherited pipes. An agent can still distinguish active work from
a stalled command.
For mise, the preview records the active installed tool versions. An apply
updates a standalone Mise CLI first; a host-selected Mise binary is left to its
host owner. It then passes the explicit installed-tool list to mise upgrade;
a configured but missing mise tool is not installed. Other missing CLIs are
skipped rather than bootstrapped. Package managers may still replace package
dependencies as part of an ordinary upgrade.
When the live global mise files are linked to reference/, the mise tool
upgrade may refresh the tracked lockfile. Run update --apply on a checkout
where that declaration change will be reviewed and committed. Other hosts use
mise-sync --apply to consume the committed lock without bumping it.
git diff -- reference/.config/miseA hard min_version failure happens before mise can launch this repository's
update task. In that bootstrap case, update the canonical binary directly
with <canonical-mise> self-update when its owner supports that operation, then
run the task normally. The hard minimum is only the oldest compatible release,
not a mise binary pin.
update does not pull this repository, synchronize Skillshare content, or
converge live configuration from reference/. Its Mise step may update the
tracked global declaration as described above. Inspect the resulting host state
separately:
mise run runtimeAfter updating tools, refresh the generated runtime. Restart each affected
shell to load generated integrations; for Zsh, open a new shell or run
exec zsh:
mise run runtime -- --apply
exec zshFrom the refreshed shell, inspect the resulting host and configuration state:
mise run check
mise run diffShell runtime is maintained separately from tool updates and configuration restore. Preview the owned operations, then apply them explicitly:
mise run runtime
mise run runtime -- --json
mise run runtime -- --applyThe runtime task owns generated shell functions, completions, plugins, caches,
and other declared runtime artifacts. Its preview is the authoritative list of
planned operations on the current host. --offline limits an apply to local
generation and cache maintenance. It never runs Skillshare or writes host
inventory; snapshots have their own explicit task (see Host inventory).
Generated files are a cache: shell startup sources them but does not regenerate
them. The Mise generator always invokes the host's canonical Mise binary by
absolute path, so cached activation remains bound to the selected owner.
Inventory snapshots record which software a host had installed at one point in
time. They are tracked under inventory/<host>/ so history stays reviewable,
and they are only ever written by the explicit task:
mise run inventory
mise run inventory -- --json
mise run inventory -- --applyThe preview is the authoritative list of collectors, destinations, and skipped host capabilities. A failed collector or an invalid empty result makes the command exit non-zero and leaves the existing snapshot untouched. Collectors whose domain permits zero items instead write an empty snapshot, replacing any stale non-empty result. Nothing regenerates these files implicitly — a snapshot is as old as its last committed run, never a claim about current host truth.
bag-mode, macos-session-health, macos-maxfiles, and
cursoruiviewservice-watchdog are self-installing modules. Each executable owns
its command interface, installation, removal, tests, and generated launchd
configuration. Their local runbooks own the module-specific operating and
recovery decisions:
bag-mode: closed-lid operation, restoration, and safe removal;macos-session-health: incident diagnosis and guarded recovery;macos-maxfiles: machine-wide limit lifecycle, rollback, and reboot behavior;cursoruiviewservice-watchdog: bounded recovery from Apple's text-cursor service memory growth.
Ordinary commands under modules/bin/ are independent of the dotfiles
inspection tasks. Linux Lite deliberately does not add them to PATH. The macOS
shell configuration currently expects this repository at ~/dotfiles; mise run lint reports when that assumption is false.
Global harness prompts and AI skills remain owned by the Skillshare source
repository. This repository stores only the portable Skillshare configuration;
its source and default extras directories live under ~/.config/skillshare/,
while their contents remain untracked here. Adopt or restore that configuration
explicitly with mise run adopt -- skillshare --apply or mise run restore -- skillshare --apply. The host check reports when the executable, configuration,
source directory, targets, or known installation ownership is unhealthy. This
repository does not install or synchronize Skillshare automatically; mise run update -- --apply only refreshes an already installed CLI.
Use one authoring host to run skillshare update --all, review the resulting
source changes, and publish them. Consumer hosts pull that reviewed Skillshare
source, rehydrate missing tracked repositories, and run skillshare sync; they
do not independently update every external Skill. This single-writer boundary
keeps .metadata.json and copied external Skills from diverging by host.
The default skillshare sync operation synchronizes skills. The tracked
configuration also declares opt-in extras targets under ~/.codex and
~/.claude; only an explicit extras sync writes those global harness
directories. Amp global guidance is configured in Amp's personal or workspace
settings and is not synchronized by this repository. Skill targets use merge
mode, so target-local non-symlink Skill directories are healthy and preserved.
In
skillshare diff --json, an action of remove with is_sync: false
describes the direction of the difference; it is not a planned sync deletion.
Before any external write, preview the complete synchronization and inspect each
target's local and pruned counts:
skillshare sync --all --global --dry-run --force --jsonlocal > 0 with pruned == 0 means synchronization preserves the local
entries. Only pruned > 0 means the preview plans deletion; list those entries
and request explicit confirmation. A failed preview leaves inspection
incomplete and must not be reported as safe. After reviewing the complete
preview, an extras-only preview remains available with skillshare sync extras --dry-run --force --json.
Installing Skillshare, choosing a source repository, and synchronizing skills are separate operator decisions. Linux Lite reports the missing capability and stops there; use the Skillshare source repository's current instructions if the host should gain that capability.
See LICENSE.