Varias Macs y una carpeta de scripts: por qué necesitaba macsetup
Tengo varias MacBooks que deberían compartir un entorno sin que todas tengan lo mismo. Durante más de un año lo resolví con scripts de Homebrew, y los scripts fallaban justo en eso: no sabían qué había en la máquina, no sabían qué le tocaba y se equivocaban sin avisar.
Tengo MacBooks. No todas son iguales y no deberían serlo: la del trabajo lleva Slack y Docker, la personal lleva WhatsApp y Discord, y una que uso para viajar solo necesita una terminal decente. Pero todas deberían sentirse como la misma computadora: el mismo shell, los mismos atajos, el mismo git.
Cada vez que estreno o formateo una, la pregunta es la misma: ¿qué le instalo a esta?
Lo que tenía
Desde abril de 2025 la respuesta vivía en un repo privado: una carpeta brew/ con cuatro scripts (install_dev_tools.sh, install_productivity_tools.sh, install_cli_tools.sh, install_content_editor_tools.sh) y una carpeta zshrc/ con fragmentos para pegar en el .zshrc. Cada script era una lista de brew install, uno por línea:
brew install awscli
brew install clickup
brew install docker
brew install gh
...
Funcionaba en el sentido de que instalaba cosas. Pero cada vez que lo usaba en una Mac distinta me encontraba con el mismo puñado de problemas.
Por qué los scripts no alcanzaban
No sabían qué había en la máquina. Un script de brew install no te dice qué va a hacer antes de hacerlo. No hay forma de preguntarle “¿qué le falta a esta Mac?” sin correrlo. Y en una Mac que ya llevaba meses en uso, tampoco había forma de saber qué había instalado yo a mano y nunca anoté en ningún script.
Estaban agrupados por lo que la app es, no por dónde la quiero. “Productividad” metía en el mismo archivo Slack, que es del trabajo, WhatsApp, que es personal, y 1Password, que va en todas. Nunca quise ese grupo completo en ninguna máquina. Así que terminaba corriendo el script y desinstalando después, o copiando líneas a mano a la terminal, que es justo lo que el script tenía que evitar.
Se equivocaban sin avisar. Cuando por fin revisé cada paquete contra brew info, descubrí que brew install docker nunca había instalado Docker Desktop. Instala la fórmula: el CLI de Docker, sin daemon y sin app. La app es el cask docker-desktop. Ese script llevaba más de un año en el repo, y en cada Mac donde lo corrí asumí que tenía Docker Desktop. No tenía. Hay más nombres así en Homebrew, que existen como fórmula y como cask, y un brew install sin --cask elige la fórmula en silencio.
La configuración se pegaba a mano. Los fragmentos de zshrc/ había que copiarlos al .zshrc de cada máquina. Si cambiaba uno en una Mac, las demás se quedaban con la versión vieja hasta que me acordara. Casi nunca me acordaba.
La primera Mac no tiene nada. Una MacBook recién sacada de la caja no tiene Homebrew, ni un Python moderno (trae un 3.9 de las Command Line Tools), ni git hasta que instalas esas herramientas. Cualquier solución tenía que empezar desde ahí, con un solo curl.
Lo que necesitaba de verdad
Cuando escribí la lista, no era “un script mejor”. Era otra cosa:
- Ver antes de tocar. Un comando que diga qué va a instalar, qué archivo va a cambiar y qué ya está bien, sin cambiar nada.
- Que cada Mac lleve lo suyo. Grupos pequeños, del tamaño de una decisión (“¿quiero el chat del trabajo en esta máquina?”), y perfiles que los junten. Y que cada máquina recuerde su propia selección, para que agregar algo a un perfil no lo instale solo en todas.
- Que agregar software sea editar una lista, no un programa. Y que esa lista me obligue a decir qué es un cask, para no repetir lo de Docker.
- Que la configuración llegue sola. Los fragmentos del shell y de git copiados a su lugar, y que si toco un archivo que no es mío, como el
.zshrc, solo toque mi parte. - Correrlo dos veces no hace nada. Si la máquina ya está al día, no instala nada ni actualiza nada. Volver a correrlo tiene que ser seguro, no un riesgo.
- Una línea en una Mac vacía. Que instale lo que falte para poder empezar y termine preguntándome qué quiero.
Por qué no usé lo que ya existía
Lo busqué antes de escribirlo.
chezmoi es muy bueno con los dotfiles, pero no se ocupa de los paquetes.
brew bundle hace los paquetes, y de hecho macsetup lo usa por debajo para instalar. Pero un Brewfile es una lista para una máquina: no tiene vista previa, no tiene reporte de qué se desvió y no tiene idea de que varias máquinas comparten una parte y difieren en otra. Un Brewfile por Mac se desincroniza, y uno solo para todas instala de más.
nix-darwin hace todo esto y más, si estás dispuesto a aprender Nix. Para mí era cambiar un problema de varias Macs por un lenguaje nuevo.
Quería algo en el medio: YAML simple, Homebrew por debajo y un plan que leo antes de que pase nada.
macsetup
Así que lo construí. macsetup empezó dentro de ese mismo repo privado y después lo separé en uno público, con el motor por un lado y mi catálogo por otro. Describes en YAML lo que lleva cada módulo, eliges en un asistente qué módulos van en esta Mac, y después:
macsetup plan # qué haría, sin cambiar nada
macsetup apply # hacerlo
macsetup status # qué se movió desde la última vez
Los cuatro scripts se convirtieron en módulos, el docker mal escrito se convirtió en un cask: true explícito, y los fragmentos del .zshrc ahora se copian solos.
En otro post voy a contar cómo está hecho por dentro. Este era para explicar por qué hacía falta. Si tienes más de una Mac y te reconoces en alguno de estos problemas, está en GitHub y en PyPI, con licencia MIT. En una Mac vacía se prueba con una línea:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/neosergio/macsetup/main/install.sh)"
Comentarios