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/wizard.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""The interactive selection flow.
|
|
2
|
+
|
|
3
|
+
Only ever *decides* things -- it returns a MachineState and touches nothing
|
|
4
|
+
else. Installing is apply's job, planning is planner's. Keeping the wizard
|
|
5
|
+
free of side effects is what lets every answer also be expressible as a flag.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import socket
|
|
11
|
+
|
|
12
|
+
import questionary
|
|
13
|
+
from questionary import Choice
|
|
14
|
+
|
|
15
|
+
from .detect import System
|
|
16
|
+
from .manifest import Catalog, Module
|
|
17
|
+
from .runner import Runner
|
|
18
|
+
from .state import MachineState
|
|
19
|
+
|
|
20
|
+
CUSTOM = "custom"
|
|
21
|
+
|
|
22
|
+
_STYLE = questionary.Style(
|
|
23
|
+
[
|
|
24
|
+
("qmark", "fg:cyan bold"),
|
|
25
|
+
("question", "bold"),
|
|
26
|
+
("answer", "fg:cyan"),
|
|
27
|
+
("pointer", "fg:cyan bold"),
|
|
28
|
+
("highlighted", "fg:cyan bold"),
|
|
29
|
+
("selected", "fg:green"),
|
|
30
|
+
("instruction", "fg:#888888"),
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WizardAborted(RuntimeError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ask(question):
|
|
40
|
+
answer = question.ask()
|
|
41
|
+
if answer is None:
|
|
42
|
+
raise WizardAborted("cancelled")
|
|
43
|
+
return answer
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def default_machine_name(runner: Runner) -> str:
|
|
47
|
+
result = runner.run(["scutil", "--get", "ComputerName"])
|
|
48
|
+
if result.ok and result.stdout.strip():
|
|
49
|
+
return result.stdout.strip()
|
|
50
|
+
return socket.gethostname().removesuffix(".local")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _module_label(module: Module, system: System) -> str:
|
|
54
|
+
total = len(module.packages)
|
|
55
|
+
installed = sum(1 for p in module.packages if system.has(p.name, cask=p.cask))
|
|
56
|
+
suffix = f"{installed}/{total} installed" if installed else f"{total} pkg"
|
|
57
|
+
detail = f" — {module.description}" if module.description else ""
|
|
58
|
+
return f"{module.id:<14} [{suffix}]{detail}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def run(
|
|
62
|
+
catalog: Catalog,
|
|
63
|
+
system: System,
|
|
64
|
+
runner: Runner,
|
|
65
|
+
*,
|
|
66
|
+
current: MachineState | None = None,
|
|
67
|
+
) -> MachineState:
|
|
68
|
+
"""Ask the questions. Returns the chosen state; saving is the caller's job."""
|
|
69
|
+
|
|
70
|
+
machine = _ask(
|
|
71
|
+
questionary.text(
|
|
72
|
+
"Name this machine",
|
|
73
|
+
default=current.machine if current else default_machine_name(runner),
|
|
74
|
+
style=_STYLE,
|
|
75
|
+
)
|
|
76
|
+
).strip()
|
|
77
|
+
|
|
78
|
+
profile_choices = [
|
|
79
|
+
Choice(
|
|
80
|
+
title=f"{name:<10} {'everything' if entries == ['*'] else f'{len(entries)} modules'}",
|
|
81
|
+
value=name,
|
|
82
|
+
)
|
|
83
|
+
for name, entries in catalog.profiles.items()
|
|
84
|
+
]
|
|
85
|
+
profile_choices.append(Choice(title="custom pick modules yourself", value=CUSTOM))
|
|
86
|
+
|
|
87
|
+
profile = _ask(
|
|
88
|
+
questionary.select(
|
|
89
|
+
"Profile",
|
|
90
|
+
choices=profile_choices,
|
|
91
|
+
default=next(
|
|
92
|
+
(c for c in profile_choices if c.value == (current.profile if current else "work")),
|
|
93
|
+
None,
|
|
94
|
+
),
|
|
95
|
+
style=_STYLE,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Pre-tick from the current machine if reconfiguring, otherwise the profile.
|
|
100
|
+
# An empty saved selection falls through to the profile on purpose: pre-ticking
|
|
101
|
+
# nothing makes plain Enter save nothing, so an empty machine.toml would
|
|
102
|
+
# otherwise re-empty itself every time the wizard runs.
|
|
103
|
+
if current and current.modules and profile == current.profile:
|
|
104
|
+
preselected = set(current.modules)
|
|
105
|
+
elif profile == CUSTOM:
|
|
106
|
+
preselected = set()
|
|
107
|
+
else:
|
|
108
|
+
preselected = {m.id for m in catalog.resolve(profile) if not m.always}
|
|
109
|
+
|
|
110
|
+
optional = [m for m in catalog.modules.values() if not m.always]
|
|
111
|
+
always = [m for m in catalog.modules.values() if m.always]
|
|
112
|
+
if always:
|
|
113
|
+
names = ", ".join(m.id for m in always)
|
|
114
|
+
questionary.print(f" always included: {names}", style="fg:#888888")
|
|
115
|
+
|
|
116
|
+
chosen = _ask(
|
|
117
|
+
questionary.checkbox(
|
|
118
|
+
"Modules for this machine",
|
|
119
|
+
choices=[
|
|
120
|
+
Choice(
|
|
121
|
+
title=_module_label(m, system),
|
|
122
|
+
value=m.id,
|
|
123
|
+
checked=m.id in preselected,
|
|
124
|
+
)
|
|
125
|
+
for m in optional
|
|
126
|
+
],
|
|
127
|
+
style=_STYLE,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# requires may pull in modules the user did not tick; surface that rather
|
|
132
|
+
# than silently installing something they did not choose.
|
|
133
|
+
resolved = catalog.resolve(only=chosen)
|
|
134
|
+
pulled = [m.id for m in resolved if m.id not in chosen and not m.always]
|
|
135
|
+
if pulled:
|
|
136
|
+
questionary.print(f" + required by your selection: {', '.join(pulled)}", style="fg:yellow")
|
|
137
|
+
|
|
138
|
+
selected_ids = [m.id for m in resolved if not m.always]
|
|
139
|
+
|
|
140
|
+
picks: dict[str, list[str]] = {}
|
|
141
|
+
for module in resolved:
|
|
142
|
+
if module.select != "pick":
|
|
143
|
+
continue
|
|
144
|
+
if current and module.id in current.picks:
|
|
145
|
+
checked = set(current.picks[module.id])
|
|
146
|
+
else:
|
|
147
|
+
checked = set(module.default)
|
|
148
|
+
choices = []
|
|
149
|
+
for package in module.packages:
|
|
150
|
+
mark = " (installed)" if system.has(package.name, cask=package.cask) else ""
|
|
151
|
+
choices.append(
|
|
152
|
+
Choice(
|
|
153
|
+
title=f"{package.name}{mark}",
|
|
154
|
+
value=package.name,
|
|
155
|
+
checked=package.name in checked,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
answer = _ask(
|
|
159
|
+
questionary.checkbox(f"{module.name} — choose packages", choices=choices, style=_STYLE)
|
|
160
|
+
)
|
|
161
|
+
picks[module.id] = list(answer)
|
|
162
|
+
|
|
163
|
+
return MachineState(
|
|
164
|
+
machine=machine,
|
|
165
|
+
profile=profile,
|
|
166
|
+
modules=selected_ids,
|
|
167
|
+
picks=picks,
|
|
168
|
+
vars=dict(current.vars) if current else {},
|
|
169
|
+
)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: macsetup
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Declarative provisioning for macOS: packages and dotfiles, with a preview
|
|
5
|
+
Project-URL: Homepage, https://github.com/neosergio/macsetup
|
|
6
|
+
Project-URL: Issues, https://github.com/neosergio/macsetup/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: brew-bundle,dotfiles,homebrew,macos,provisioning
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: jinja2>=3.1.6
|
|
18
|
+
Requires-Dist: pydantic>=2.9
|
|
19
|
+
Requires-Dist: pyyaml>=6.0
|
|
20
|
+
Requires-Dist: questionary>=2.1.1
|
|
21
|
+
Requires-Dist: rich>=13.7
|
|
22
|
+
Requires-Dist: tomli-w>=1.0
|
|
23
|
+
Requires-Dist: typer>=0.15
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# macsetup
|
|
27
|
+
|
|
28
|
+
[](https://github.com/neosergio/macsetup/actions/workflows/ci.yml)
|
|
29
|
+
|
|
30
|
+
Declarative provisioning for macOS. Describe what a machine should have in
|
|
31
|
+
YAML; `macsetup plan` shows you the difference against reality, and
|
|
32
|
+
`macsetup apply` closes it.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ macsetup plan
|
|
36
|
+
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
|
|
37
|
+
┃ Module ┃ Package ┃ State ┃
|
|
38
|
+
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
|
|
39
|
+
│ core │ git, gh │ satisfied │
|
|
40
|
+
│ cli │ ripgrep │ install │
|
|
41
|
+
│ cli │ ~/.config/macsetup/zsh/90-… │ update │
|
|
42
|
+
│ lang-python │ uv │ satisfied │
|
|
43
|
+
└──────────────┴──────────────────────────────┴───────────┘
|
|
44
|
+
1 to install, 1 file to update, 6 satisfied
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Adding software is a YAML edit, never a code change. Re-running on a converged
|
|
48
|
+
machine is a no-op, and `plan` and `apply` share the same planner, so the
|
|
49
|
+
preview is exactly what apply acts on.
|
|
50
|
+
|
|
51
|
+
Built for the case where you have **several** Macs that should share an
|
|
52
|
+
environment without every machine getting every package.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
One line, on a Mac with nothing on it yet:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/neosergio/macsetup/main/install.sh)"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
[`install.sh`](install.sh) installs Homebrew if it is missing, then uv, then
|
|
63
|
+
macsetup, and ends in the `init` wizard. Each step checks first, so it is safe
|
|
64
|
+
to re-run. Arguments replace the wizard, for unattended machines:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
curl -fsSL https://raw.githubusercontent.com/neosergio/macsetup/main/install.sh \
|
|
68
|
+
| bash -s -- apply --profile work --yes
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If you already have Homebrew, the manual route is three commands:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
brew install uv
|
|
75
|
+
uv tool install macsetup
|
|
76
|
+
macsetup init
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`init` runs a wizard (machine name → profile → per-module choices), saves the
|
|
80
|
+
result to `~/.config/macsetup/machine.toml`, and applies it. After that no
|
|
81
|
+
flags are needed.
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
macsetup plan # what apply would do; changes nothing
|
|
85
|
+
macsetup apply # do it
|
|
86
|
+
macsetup status # drift: manifest vs this machine
|
|
87
|
+
macsetup untracked # installed packages no selected module accounts for
|
|
88
|
+
macsetup modules # the whole catalog and its profiles
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Every wizard answer has a flag equivalent, so a machine can be provisioned
|
|
92
|
+
unattended: `macsetup apply --profile work --yes`.
|
|
93
|
+
|
|
94
|
+
## Your own catalog
|
|
95
|
+
|
|
96
|
+
macsetup ships a small example catalog so a fresh install has something to run
|
|
97
|
+
against. It is an example — replace it with yours:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
export MACSETUP_MANIFESTS=~/dotfiles/manifests
|
|
101
|
+
export MACSETUP_FILES=~/dotfiles/files
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Both are resolved in that order: the environment variable, then a `manifests/`
|
|
105
|
+
or `files/` directory in the current working directory, then the copy baked
|
|
106
|
+
into the wheel. Keeping your catalog in a private repo and the engine public
|
|
107
|
+
is the intended arrangement.
|
|
108
|
+
|
|
109
|
+
### Modules
|
|
110
|
+
|
|
111
|
+
One file per module in `manifests/modules/`, where `id` matches the filename
|
|
112
|
+
stem:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
id: cli
|
|
116
|
+
name: CLI tools
|
|
117
|
+
requires: [fonts] # dependency-closed at resolve time
|
|
118
|
+
packages:
|
|
119
|
+
- ripgrep # formula
|
|
120
|
+
- {name: ghostty, cask: true} # cask
|
|
121
|
+
files:
|
|
122
|
+
- copy: zsh/cli.zsh
|
|
123
|
+
to: ~/.config/macsetup/zsh/90-cli.zsh
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Casks must be marked explicitly.** Several names exist in both namespaces —
|
|
127
|
+
`docker` is the CLI formula *and* Docker Desktop the cask — and a bare
|
|
128
|
+
`brew install docker` silently picks the formula. Marking casks is the single
|
|
129
|
+
biggest class of bug the manifest format exists to prevent.
|
|
130
|
+
|
|
131
|
+
`always: true` puts a module on every machine regardless of profile.
|
|
132
|
+
`select: pick` turns a module into a per-package menu in the wizard, with
|
|
133
|
+
`default:` pre-ticked. Pydantic forbids unknown keys, so a typo fails at load
|
|
134
|
+
rather than halfway through an install.
|
|
135
|
+
|
|
136
|
+
### Profiles
|
|
137
|
+
|
|
138
|
+
`manifests/profiles.yaml` bundles modules into named sets; `"*"` means
|
|
139
|
+
everything.
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
profiles:
|
|
143
|
+
work: [cli, lang-python, editors]
|
|
144
|
+
minimal: [cli]
|
|
145
|
+
full: ["*"]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`machine.toml` stores the **resolved module list**, not just the profile name,
|
|
149
|
+
so a module added to a profile upstream cannot silently install itself on
|
|
150
|
+
every machine the next time they sync. `status` reports unadopted modules;
|
|
151
|
+
`configure` adopts them.
|
|
152
|
+
|
|
153
|
+
### Files
|
|
154
|
+
|
|
155
|
+
Three actions:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
files:
|
|
159
|
+
- copy: zsh/python.zsh # verbatim
|
|
160
|
+
to: ~/.config/macsetup/zsh/20-python.zsh
|
|
161
|
+
- render: git/gitconfig.j2 # Jinja2 + machine vars
|
|
162
|
+
to: ~/.gitconfig
|
|
163
|
+
mode: "600"
|
|
164
|
+
- block: shell/zshrc-block.zsh # region in a file you don't own
|
|
165
|
+
into: ~/.zshrc
|
|
166
|
+
position: top # default bottom
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`block:` maintains a marker-delimited region. Lines outside the markers are
|
|
170
|
+
preserved; re-applying rewrites only what's between them. `position: top`
|
|
171
|
+
exists because some formats are order-sensitive — an `Include` in `ssh_config`
|
|
172
|
+
is ignored if it lands after a matching block.
|
|
173
|
+
|
|
174
|
+
`render:` substitutes variables from `manifests/vars.yaml`, overridden per
|
|
175
|
+
machine by the `[vars]` table in `machine.toml`, plus `machine` and `home`
|
|
176
|
+
which are always supplied. Rendering uses `StrictUndefined`: a template
|
|
177
|
+
referencing a variable with no value is a hard error, not a silently empty
|
|
178
|
+
setting.
|
|
179
|
+
|
|
180
|
+
Before overwriting a file it did not create, `copy`/`render` leave a
|
|
181
|
+
`.macsetup-bak` copy alongside it.
|
|
182
|
+
|
|
183
|
+
**Nothing is symlinked.** A symlink would point into the installed wheel and
|
|
184
|
+
break on the next upgrade, so files are copied and you re-run `apply` after
|
|
185
|
+
editing a fragment.
|
|
186
|
+
|
|
187
|
+
## Design notes
|
|
188
|
+
|
|
189
|
+
A few decisions that are load-bearing, in case you're reading the source:
|
|
190
|
+
|
|
191
|
+
**Data is separate from code.** `manifests/` says what to install;
|
|
192
|
+
`src/macsetup/` only knows how. If adding software requires touching Python,
|
|
193
|
+
something is wrong.
|
|
194
|
+
|
|
195
|
+
**`brew list` runs exactly twice per invocation**, cached in `detect.py` —
|
|
196
|
+
never once per package. Checking 60 packages costs two subprocess calls, so a
|
|
197
|
+
converged machine plans in well under a second regardless of catalog size.
|
|
198
|
+
There is a test asserting the call count.
|
|
199
|
+
|
|
200
|
+
**Installs delegate to `brew bundle`** against a generated Brewfile rather than
|
|
201
|
+
shelling out per package. Homebrew already knows about retries, cask
|
|
202
|
+
quarantine and sudo prompts. Generated Brewfiles stay in
|
|
203
|
+
`~/.cache/macsetup/brewfiles/` so a failure can be inspected.
|
|
204
|
+
|
|
205
|
+
**`apply` never upgrades.** `brew bundle install` upgrades by default; macsetup
|
|
206
|
+
passes `--no-upgrade` so converging a machine cannot turn into a 40-minute
|
|
207
|
+
upgrade run. `--upgrade` is opt-in.
|
|
208
|
+
|
|
209
|
+
**Subprocess calls go through `runner.py`.** That protocol is the test seam —
|
|
210
|
+
the suite runs without touching the machine.
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
uv sync
|
|
216
|
+
uv run macsetup plan -p work
|
|
217
|
+
uv run pytest # no machine access required
|
|
218
|
+
uv run ruff check . && uv run ruff format .
|
|
219
|
+
uv tool install -e . # put `macsetup` on PATH, tracking this checkout
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Tests never mutate the machine: `FakeRunner` stands in for subprocesses, and
|
|
223
|
+
file steps run against a sandboxed `HOME`. To try the file actions safely:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
SB=/tmp/ms-try; mkdir -p $SB
|
|
227
|
+
HOME=$SB MACSETUP_CONFIG=$SB/.config uv run macsetup apply --only shell --yes
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`shell` has no packages, so that never invokes brew.
|
|
231
|
+
|
|
232
|
+
CI runs the same two things on every push. Lint, the suite and a check that
|
|
233
|
+
the wheel still carries the catalog go on Linux; `plan` and `apply` shell out
|
|
234
|
+
to `brew list`, so every profile is planned and dry-run on a macOS runner,
|
|
235
|
+
along with the file appliers against a throwaway `HOME` and a second apply to
|
|
236
|
+
prove re-running converges to nothing.
|
|
237
|
+
|
|
238
|
+
## Not built yet
|
|
239
|
+
|
|
240
|
+
- `sync` (git pull + apply), `add`, `prune`, `upgrade`, `doctor`
|
|
241
|
+
- macOS `defaults write` tweaks as a step type
|
|
242
|
+
- Mac App Store apps (`brew bundle` handles `mas` entries natively; no manifest
|
|
243
|
+
uses them yet)
|
|
244
|
+
|
|
245
|
+
## Prior art
|
|
246
|
+
|
|
247
|
+
[chezmoi](https://www.chezmoi.io/) does dotfiles thoroughly but not packages.
|
|
248
|
+
`brew bundle` does packages but has no preview, no drift report and no notion
|
|
249
|
+
of per-machine subsets. [nix-darwin](https://github.com/LnL7/nix-darwin) does
|
|
250
|
+
all of it and more, if you want to learn Nix. macsetup is the middle: plain
|
|
251
|
+
YAML, Homebrew underneath, a plan you read before anything happens.
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
macsetup/__init__.py,sha256=XaUMTRQDMeOi9KFq2M2WfVSq87BbjxQ88uF3kl6t5Gs,81
|
|
2
|
+
macsetup/brewfile.py,sha256=BXg17LSbtfDHW0o5hArYpxxD-ZZx_p2JmWUtBtsbuWo,2322
|
|
3
|
+
macsetup/cli.py,sha256=JRGKk8aEOdmgqLocj_fFgbFmwYtNNHfwNh9K9liDDP8,17699
|
|
4
|
+
macsetup/detect.py,sha256=tNids4Cv-9C77c1CGEC2m8_9Bbzu-9ZKns-o9ln11ww,1563
|
|
5
|
+
macsetup/files.py,sha256=S0oOVbCl2Ns-SkxwRgVU1xS_Evq0RqnzgVU4k8awZqw,5194
|
|
6
|
+
macsetup/manifest.py,sha256=zkoNYoBE23gKcYPHMc0uAUtjbc9YmAiRlySHvndyY-w,12036
|
|
7
|
+
macsetup/planner.py,sha256=473VxyYYeTtzmdaZspbmrbdQ1a4ZhtbNW3LoM0t_VfQ,4241
|
|
8
|
+
macsetup/runner.py,sha256=Cq7_W1sit5zg6MguLvBkFM2Px54c90fVFr89Tzdle8c,2233
|
|
9
|
+
macsetup/state.py,sha256=jlKiOxxBOpNcMBtekZTpq_3himlFzojXCEJcpi1LOXY,3422
|
|
10
|
+
macsetup/wizard.py,sha256=oOl8h485T4XPqMbAvLB9O5Bu0b85LhDVKZU5SaS5MTw,5351
|
|
11
|
+
macsetup/_data/files/git/gitconfig.j2,sha256=gpeUG8-sNpehJ9EXntoLwdlsJNCrIiPHvvG5zrOmeeY,500
|
|
12
|
+
macsetup/_data/files/shell/zshrc-block.zsh,sha256=yHXw5U9Vyp3DB9zma59k4Oo3Dj90zxU6qKARcKBfE1g,234
|
|
13
|
+
macsetup/_data/files/zsh/00-homebrew.zsh,sha256=PBJkPfefy40uB2415t4DXeGuXyTHdjMqOrxgS-9u4w8,474
|
|
14
|
+
macsetup/_data/files/zsh/10-completion.zsh,sha256=VnPCobzCCFwtpvDqe-xfrWRTuQTa-xxyQmxMBdlm2Z0,938
|
|
15
|
+
macsetup/_data/files/zsh/cli.zsh,sha256=zq-UujtIOiacPX40pvtJzkeWMqRVx9Qw6zUoEaHk-7Q,916
|
|
16
|
+
macsetup/_data/files/zsh/node.zsh,sha256=XISalK4-90fBmNYSlgzsMaGkFCyKFL4XwAkGv92QzjE,377
|
|
17
|
+
macsetup/_data/files/zsh/python.zsh,sha256=W8v5_bYgYCT5TEIr4p8g2zTqWIupLXYjhrDaDKShJ8g,489
|
|
18
|
+
macsetup/_data/manifests/profiles.yaml,sha256=hfiJ3_KCRh7PvApdzCl7xHDgjeFn0-A_1cs8bO76mNg,596
|
|
19
|
+
macsetup/_data/manifests/vars.yaml,sha256=ltXckqGmtD7HoYCicl5KXdCCCscdwvek-LSIdqhXu5Q,439
|
|
20
|
+
macsetup/_data/manifests/modules/cli.yaml,sha256=WaC8bF-tcCJ5CceAidDV6hJiEYW2yvghRhmw50ixJgo,469
|
|
21
|
+
macsetup/_data/manifests/modules/core.yaml,sha256=OeLHEYB5nZ7gXEJPK31P3RDGgslqJXqya1j0rZkGXo0,118
|
|
22
|
+
macsetup/_data/manifests/modules/editors.yaml,sha256=vnzxbWIz1X29L5NlRef_Wi3ntyOjT-SCuRLcWPmFmQQ,621
|
|
23
|
+
macsetup/_data/manifests/modules/fonts.yaml,sha256=AIBIIuPz0Y1zELGIdbaeCecyBy1zYJMEfgq4k3sDfz8,147
|
|
24
|
+
macsetup/_data/manifests/modules/git-identity.yaml,sha256=BjUHAXIyVJC-a5RIBo4Eh0YQIluGuWgYHuMSfze2L1Y,741
|
|
25
|
+
macsetup/_data/manifests/modules/lang-node.yaml,sha256=g64UiC0F025RxlIkrFoVCFR-tp2anCNetvcSck9lQ0g,171
|
|
26
|
+
macsetup/_data/manifests/modules/lang-python.yaml,sha256=2PJXLw4qibmEnriBABwTt-8de4ZhSlOJ96pwbNaxiDI,193
|
|
27
|
+
macsetup/_data/manifests/modules/shell.yaml,sha256=i-ccXMNyF1-JNG3BXM--m045_Aq3UeEOScV_hi8k5q8,766
|
|
28
|
+
macsetup-0.1.0.dist-info/METADATA,sha256=oc-NKvHcBLFUhU0aM8MG_LgyKmabQIyrGXznvIWd3tc,9597
|
|
29
|
+
macsetup-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
30
|
+
macsetup-0.1.0.dist-info/entry_points.txt,sha256=aqEEFnIkOEgSnrbNChMkdVQf37CJ5NaCh6N53OMPNG8,46
|
|
31
|
+
macsetup-0.1.0.dist-info/licenses/LICENSE,sha256=fC6gbDhxVx-OgjDtdsNFmtniABmXidQ3-nhlXcPqs0w,1071
|
|
32
|
+
macsetup-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergio Infante
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|