macsetup 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- macsetup/__init__.py +1 -0
- macsetup/_data/files/git/gitconfig.j2 +17 -0
- macsetup/_data/files/shell/zshrc-block.zsh +6 -0
- macsetup/_data/files/zsh/00-homebrew.zsh +10 -0
- macsetup/_data/files/zsh/10-completion.zsh +22 -0
- macsetup/_data/files/zsh/cli.zsh +18 -0
- macsetup/_data/files/zsh/node.zsh +8 -0
- macsetup/_data/files/zsh/python.zsh +12 -0
- macsetup/_data/manifests/modules/cli.yaml +16 -0
- macsetup/_data/manifests/modules/core.yaml +7 -0
- macsetup/_data/manifests/modules/editors.yaml +16 -0
- macsetup/_data/manifests/modules/fonts.yaml +5 -0
- macsetup/_data/manifests/modules/git-identity.yaml +15 -0
- macsetup/_data/manifests/modules/lang-node.yaml +8 -0
- macsetup/_data/manifests/modules/lang-python.yaml +8 -0
- macsetup/_data/manifests/modules/shell.yaml +18 -0
- macsetup/_data/manifests/profiles.yaml +26 -0
- macsetup/_data/manifests/vars.yaml +10 -0
- macsetup/brewfile.py +59 -0
- macsetup/cli.py +481 -0
- macsetup/detect.py +53 -0
- macsetup/files.py +150 -0
- macsetup/manifest.py +321 -0
- macsetup/planner.py +141 -0
- macsetup/runner.py +71 -0
- macsetup/state.py +102 -0
- macsetup/wizard.py +169 -0
- macsetup-0.1.0.dist-info/METADATA +255 -0
- macsetup-0.1.0.dist-info/RECORD +32 -0
- macsetup-0.1.0.dist-info/WHEEL +4 -0
- macsetup-0.1.0.dist-info/entry_points.txt +2 -0
- macsetup-0.1.0.dist-info/licenses/LICENSE +21 -0
macsetup/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Declarative provisioning for macOS: packages and dotfiles, with a preview."""
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Managed by macsetup. Edits here are overwritten on the next `macsetup apply`;
|
|
2
|
+
# the source is files/git/gitconfig.j2 in the manifests repo.
|
|
3
|
+
#
|
|
4
|
+
# Rendered on {{ machine }}.
|
|
5
|
+
|
|
6
|
+
[user]
|
|
7
|
+
name = {{ git_name }}
|
|
8
|
+
email = {{ git_email }}
|
|
9
|
+
|
|
10
|
+
[init]
|
|
11
|
+
defaultBranch = main
|
|
12
|
+
|
|
13
|
+
# Refuse to invent an identity. Without this, an unset user.email makes git
|
|
14
|
+
# silently guess you@<hostname>.local and stamp it on the commit -- a wrong
|
|
15
|
+
# address you only discover later, in a published history.
|
|
16
|
+
[user]
|
|
17
|
+
useConfigOnly = true
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# HOMEBREW_PREFIX, without shelling out to `brew --prefix` on every startup.
|
|
2
|
+
# `brew shellenv` would set this, but it costs a subprocess and PATH is already
|
|
3
|
+
# handled by /etc/paths.d, so just resolve the two possible locations.
|
|
4
|
+
if [[ -z ${HOMEBREW_PREFIX} ]]; then
|
|
5
|
+
if [[ -x /opt/homebrew/bin/brew ]]; then
|
|
6
|
+
export HOMEBREW_PREFIX=/opt/homebrew # Apple Silicon
|
|
7
|
+
elif [[ -x /usr/local/bin/brew ]]; then
|
|
8
|
+
export HOMEBREW_PREFIX=/usr/local # Intel
|
|
9
|
+
fi
|
|
10
|
+
fi
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# zsh completion. Must run before any fragment that calls compdef -- tool
|
|
2
|
+
# completion scripts fail with "command not found: compdef" otherwise.
|
|
3
|
+
#
|
|
4
|
+
# Homebrew drops completions in share/zsh/site-functions (_brew, _git, _gh,
|
|
5
|
+
# _docker, _uv, _uvx, _wezterm, _zed, _aws), so adding it to fpath is all that
|
|
6
|
+
# is needed; no per-tool `eval` at startup.
|
|
7
|
+
if [[ -n ${HOMEBREW_PREFIX} && -d ${HOMEBREW_PREFIX}/share/zsh/site-functions ]]; then
|
|
8
|
+
fpath=("${HOMEBREW_PREFIX}/share/zsh/site-functions" $fpath)
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
autoload -Uz compinit
|
|
12
|
+
|
|
13
|
+
# compinit's security audit is slow, so run it in full at most once a day and
|
|
14
|
+
# use the cached dump otherwise. -i skips the prompt about Homebrew's
|
|
15
|
+
# group-writable directories, which are expected rather than suspicious.
|
|
16
|
+
_ms_zcompdump="${ZDOTDIR:-$HOME}/.zcompdump"
|
|
17
|
+
if [[ -n ${_ms_zcompdump}(#qN.mh+24) ]]; then
|
|
18
|
+
compinit -i -d "${_ms_zcompdump}"
|
|
19
|
+
else
|
|
20
|
+
compinit -C -i -d "${_ms_zcompdump}"
|
|
21
|
+
fi
|
|
22
|
+
unset _ms_zcompdump
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# The two `cli` tools that need shell wiring rather than just a binary on PATH.
|
|
2
|
+
#
|
|
3
|
+
# Installed as 90-cli.zsh so zoxide initialises after every other fragment:
|
|
4
|
+
# zoxide's own doctor check wants its init at the end of the shell config, and
|
|
5
|
+
# it says so on stderr when it is not. The gap to 40-p10k leaves room for
|
|
6
|
+
# fragments that have to load earlier.
|
|
7
|
+
|
|
8
|
+
# `always` rather than the default `auto`, which drops the icons as soon as the
|
|
9
|
+
# output is not a terminal -- `ls | less` should still show them. The glyphs come
|
|
10
|
+
# from the Nerd Font, which is why the cli module requires the fonts module.
|
|
11
|
+
alias ls="eza --icons=always"
|
|
12
|
+
|
|
13
|
+
# zoxide tracks visited directories and jumps to a match on a partial name.
|
|
14
|
+
# Aliasing cd to z keeps `cd some/real/path` working, since z falls back to a
|
|
15
|
+
# plain cd when the argument resolves to a real directory. `zi` still opens the
|
|
16
|
+
# interactive picker.
|
|
17
|
+
eval "$(zoxide init zsh)"
|
|
18
|
+
alias cd="z"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Node — nvm, installed via Homebrew. HOMEBREW_PREFIX comes from
|
|
2
|
+
# 00-homebrew.zsh; the literal fallback keeps this fragment usable standalone.
|
|
3
|
+
export NVM_DIR="$HOME/.nvm"
|
|
4
|
+
|
|
5
|
+
_ms_nvm="${HOMEBREW_PREFIX:-/opt/homebrew}/opt/nvm"
|
|
6
|
+
[ -s "$_ms_nvm/nvm.sh" ] && \. "$_ms_nvm/nvm.sh"
|
|
7
|
+
[ -s "$_ms_nvm/etc/bash_completion.d/nvm" ] && \. "$_ms_nvm/etc/bash_completion.d/nvm"
|
|
8
|
+
unset _ms_nvm
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Python — uv only. pyenv and poetry were dropped on 2026-07-26.
|
|
2
|
+
#
|
|
3
|
+
# No completion setup here: Homebrew already installs _uv and _uvx into
|
|
4
|
+
# site-functions, which 10-completion.zsh puts on fpath. Calling
|
|
5
|
+
# `uv generate-shell-completion` at startup would duplicate that and cost two
|
|
6
|
+
# subprocesses per shell.
|
|
7
|
+
|
|
8
|
+
# `uv tool install` puts shims here.
|
|
9
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
10
|
+
|
|
11
|
+
# Prefer uv's managed interpreters over whatever the system ships.
|
|
12
|
+
export UV_PYTHON_PREFERENCE=managed
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id: cli
|
|
2
|
+
name: CLI tools
|
|
3
|
+
description: Terminal utilities
|
|
4
|
+
# eza renders Nerd Font glyphs, so the font is a hard dependency rather than
|
|
5
|
+
# something the wizard could leave unticked. Anything with hard dependencies
|
|
6
|
+
# belongs in its own module for exactly this reason.
|
|
7
|
+
requires: [fonts]
|
|
8
|
+
packages:
|
|
9
|
+
- tree
|
|
10
|
+
- ripgrep
|
|
11
|
+
- eza
|
|
12
|
+
- zoxide
|
|
13
|
+
files:
|
|
14
|
+
# 90 so zoxide's init is the last fragment to run; see the file.
|
|
15
|
+
- copy: zsh/cli.zsh
|
|
16
|
+
to: ~/.config/macsetup/zsh/90-cli.zsh
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id: editors
|
|
2
|
+
name: Editors & IDEs
|
|
3
|
+
description: Pick the ones this machine needs
|
|
4
|
+
# `select: pick` turns this into a per-package menu in the wizard, with
|
|
5
|
+
# `default:` pre-ticked.
|
|
6
|
+
#
|
|
7
|
+
# Picks gate packages, not files: a module's `files` are applied whenever the
|
|
8
|
+
# module is selected, whichever packages you tick. So anything that ships
|
|
9
|
+
# config cannot live as a pick -- its config would land on machines that
|
|
10
|
+
# unticked it. Give those their own module instead.
|
|
11
|
+
select: pick
|
|
12
|
+
default: [visual-studio-code]
|
|
13
|
+
packages:
|
|
14
|
+
- {name: visual-studio-code, cask: true}
|
|
15
|
+
- {name: zed, cask: true}
|
|
16
|
+
- {name: jetbrains-toolbox, cask: true}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
id: git-identity
|
|
2
|
+
name: Git identity
|
|
3
|
+
description: A rendered gitconfig, as a demonstration of `render:`
|
|
4
|
+
# Rendered rather than copied: identity is the one part that may differ per
|
|
5
|
+
# machine. Override git_name / git_email in the [vars] table of machine.toml;
|
|
6
|
+
# the catalog defaults are in manifests/vars.yaml.
|
|
7
|
+
#
|
|
8
|
+
# The destination is deliberately inert. This is the catalog that ships inside
|
|
9
|
+
# the wheel, so `macsetup init` on a machine that has not written its own
|
|
10
|
+
# manifests yet will run it -- and an example that overwrites a real
|
|
11
|
+
# ~/.gitconfig on first run is a trap, not a demonstration. Point this at
|
|
12
|
+
# ~/.gitconfig in your own catalog, once you mean it.
|
|
13
|
+
files:
|
|
14
|
+
- render: git/gitconfig.j2
|
|
15
|
+
to: ~/.config/macsetup/example-gitconfig
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
id: shell
|
|
2
|
+
name: Shell
|
|
3
|
+
description: The managed ~/.zshrc block, Homebrew prefix and zsh completion
|
|
4
|
+
always: true
|
|
5
|
+
# No packages: this module exists purely to place files. `apply --only shell`
|
|
6
|
+
# therefore never invokes brew, which makes it the safest module to try
|
|
7
|
+
# against a sandboxed HOME.
|
|
8
|
+
files:
|
|
9
|
+
# One managed block in a file macsetup does not own. Lines outside the
|
|
10
|
+
# markers are left alone; re-applying rewrites only between them.
|
|
11
|
+
- block: shell/zshrc-block.zsh
|
|
12
|
+
into: ~/.zshrc
|
|
13
|
+
# Numbered so they load before the tool fragments: completion must be
|
|
14
|
+
# initialised before anything calls compdef.
|
|
15
|
+
- copy: zsh/00-homebrew.zsh
|
|
16
|
+
to: ~/.config/macsetup/zsh/00-homebrew.zsh
|
|
17
|
+
- copy: zsh/10-completion.zsh
|
|
18
|
+
to: ~/.config/macsetup/zsh/10-completion.zsh
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Profiles bundle modules. Modules marked `always: true` are folded in
|
|
2
|
+
# automatically and do not need listing here.
|
|
3
|
+
#
|
|
4
|
+
# These are an example. Replace them with the way you actually use your
|
|
5
|
+
# machines -- `brew bundle dump` on each one and diffing the results is a
|
|
6
|
+
# good way to turn guesswork into data.
|
|
7
|
+
profiles:
|
|
8
|
+
work:
|
|
9
|
+
- cli
|
|
10
|
+
- git-identity
|
|
11
|
+
- lang-python
|
|
12
|
+
- lang-node
|
|
13
|
+
- editors
|
|
14
|
+
|
|
15
|
+
personal:
|
|
16
|
+
- cli
|
|
17
|
+
- git-identity
|
|
18
|
+
- lang-python
|
|
19
|
+
- editors
|
|
20
|
+
|
|
21
|
+
# A loaner or travel machine: enough to be useful in a terminal, nothing else.
|
|
22
|
+
minimal:
|
|
23
|
+
- cli
|
|
24
|
+
|
|
25
|
+
full:
|
|
26
|
+
- "*"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Default values for `render:` template variables. The engine ships none of
|
|
2
|
+
# its own -- identity is data, so it lives here with the manifests that use
|
|
3
|
+
# it. A machine overrides any of these in the [vars] table of
|
|
4
|
+
# ~/.config/macsetup/machine.toml.
|
|
5
|
+
#
|
|
6
|
+
# Rendering uses StrictUndefined: a template referencing a variable with no
|
|
7
|
+
# value is a hard error, not a silently empty setting.
|
|
8
|
+
vars:
|
|
9
|
+
git_name: Your Name
|
|
10
|
+
git_email: you@example.com
|
macsetup/brewfile.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Brewfile generation and the `brew bundle` driver.
|
|
2
|
+
|
|
3
|
+
macsetup does not install anything itself. It decides *what* should be
|
|
4
|
+
installed and hands a generated Brewfile to Homebrew's bundler, which already
|
|
5
|
+
knows about retries, cask quarantine, sudo prompts and upgrade semantics.
|
|
6
|
+
|
|
7
|
+
Generated files are written to a predictable cache dir and deliberately left
|
|
8
|
+
behind after a run, so a failure can be inspected.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from .manifest import Package
|
|
17
|
+
from .runner import Runner
|
|
18
|
+
|
|
19
|
+
CACHE_DIR = Path.home() / ".cache" / "macsetup" / "brewfiles"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def render(packages: Iterable[Package], *, header: str | None = None) -> str:
|
|
23
|
+
lines: list[str] = []
|
|
24
|
+
if header:
|
|
25
|
+
lines += [f"# {header}", "# generated by macsetup -- edits will be overwritten", ""]
|
|
26
|
+
# Taps and formulae before casks; brew bundle is order-insensitive but this
|
|
27
|
+
# reads far better when you open the file to debug something.
|
|
28
|
+
packages = list(packages)
|
|
29
|
+
lines += [p.brewfile_line() for p in packages if not p.cask and p.mas is None]
|
|
30
|
+
lines += [p.brewfile_line() for p in packages if p.cask]
|
|
31
|
+
lines += [p.brewfile_line() for p in packages if p.mas is not None]
|
|
32
|
+
return "\n".join(lines) + "\n"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def write(module_id: str, packages: Iterable[Package], *, cache_dir: Path | None = None) -> Path:
|
|
36
|
+
directory = cache_dir or CACHE_DIR
|
|
37
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
path = directory / f"{module_id}.Brewfile"
|
|
39
|
+
path.write_text(render(packages, header=f"module: {module_id}"), encoding="utf-8")
|
|
40
|
+
return path
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def install(runner: Runner, brewfile: Path, *, upgrade: bool = False, jobs: int | None = None):
|
|
44
|
+
"""Run `brew bundle install` against a generated Brewfile.
|
|
45
|
+
|
|
46
|
+
Homebrew upgrades outdated dependencies by default, which is wrong for
|
|
47
|
+
convergence -- adding one package should not turn into a 40 minute upgrade
|
|
48
|
+
of everything. So `--no-upgrade` unless explicitly asked.
|
|
49
|
+
"""
|
|
50
|
+
cmd = ["brew", "bundle", "install", f"--file={brewfile}"]
|
|
51
|
+
if not upgrade:
|
|
52
|
+
cmd.append("--no-upgrade")
|
|
53
|
+
if jobs:
|
|
54
|
+
cmd.append(f"--jobs={jobs}")
|
|
55
|
+
return runner.run(cmd, capture=False)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check(runner: Runner, brewfile: Path):
|
|
59
|
+
return runner.run(["brew", "bundle", "check", f"--file={brewfile}"])
|