MacOS + Nix = moins de pomme, plus de manchot
Table des matières
La semaine dernière, j’ai débuté un nouveau job 🎉 Et qui dit nouveau job dit nouvelle machine de travail.
Si j’ai passé plus des dix dernières années de ma carrière sur ArchLinux
, je n’ai cette fois-ci pas eu le luxe de rester sur mon OS de prédilection. J’ai eu le choix entre macOS
et Windows
. Autant dire que je n’ai pas eu à réfléchir très longtemps.
J’ai parlé il y a peu de Nix
, un gestionnaire de paquets pas comme les autres. Et aussi de NixOS
, une distribution Linux
équipé de Nix
pour seul moyen de gestion.
Hé bien Nix
fonctionne sur macOS
. Et Nix-Darwin
porte une bonne partie des fonctionnalités de NixOS
sur la plateforme d’Apple.
Cet article est un condensé de mon expérience avec tous ces outils pour tenter de me rapprocher le plus possible d’une expérience agréable sur ce nouvel ordinateur.
Le problème
Je ne suis par réellement néophyte sur macOS
. Notamment, je me suis déjà frotté à l’absence de gestionnaire de paquets natif sur cette plateforme.
Et d’ailleurs, même les OS avec un gestionnaire de paquets digne de ce nom ont un défaut : il faut tout réinstaller et reconfigurer à chaque installation. Alors, oui, il y a bien des solutions pour aller plus vite. Je pense notamment aux dotfiles et l’usage de stow
. Couplé à git
, il devient possible d’automatiser presque toute la configuration.
Mais quid de l’installation ? Oui, il est possible de lister les paquets installés sur une machine et de s’en servir sur une autre. Personnellement, je n’affectionne pas cette approche, car chaque machine a son usage et ne requiert pas les mêmes programmes installés. Je préfère n’avoir que ce qui est nécessaire là où c’est nécessaire. Ni plus, ni moins.
La solution
Only a Sith deals in absolutes
Oui, LA solution. Nan, il doit y en avoir d’autres . Mais c’est celle que j’ai choisie.
Je veux avoir mes outils en ligne de commande habituels à portée de main. Et je les veux configurés.
Je veux aussi un certain nombre de logiciels avec interface graphique. Oui, je sais, stupéfaction etc, mais je ne suis pas encore prêt à passer sur un navigateur web 100% CLI à tout hasard.
Et par-dessus le marché, je veux tout ça vite. Hé bien. Voilà, vite et bien 👌
Et c’est ce que permet le combo suivant :
Nix
: le gestionnaire de paquet qui secoue les traditions des systèmesposix
en remettant en cause la façon de ranger et d’accéder aux binaires, bibliothèques et réglages,NixDarwin
: la surcouche qui sait éplucher la pomme dans le sens des plumes pour des réglages à l’échelle du système d’exploitationmacOS
,HomeManager
: la surcouche permettant d’automatiser installations et réglages à l’échelle d’une session utilisateur,HomeBrew
: le gestionnaire de paquets capable d’apporter les outils avec interfaces graphiques.
J’ai fait l’erreur de foncer tête baissée sur ces outils dans le désordre. J’ai bien cru avoir endommagé la machine suite à des manipulations assurément hasardeuses.
Move fast, break things comme disait l’autre. Mais attention, qui casse paie comme disait l’autre autre.
Les manipulations décrites par la suite sont à effectuer à vos risques et périls. Surtout s’il s’agit d’une machine de travail 😨
Nix
Le procédé d’installation de nix
est décrit directement sur le site dédié. Je ne préfère pas copier ces commandes ici, car je pense qu’il faut toujours aller les prendre à la source de vérité au moment de l’installation. Il y a plus de détails sur ce que l’installateur effectue dans la documentation. Et il existe aussi cette page concernant la désinstallation si besoin.
À la fin, il doit être possible d’obtenir les informations sur l’état du système:
% nix-shell -p nix-info --run "nix-info -m"
- system: `"aarch64-darwin"`
- host os: `Darwin 22.5.0, macOS 13.4.1`
- multi-user?: `yes`
- sandbox: `no`
- version: `nix-env (Nix) 2.15.1`
- channels(root): `"nixpkgs"`
- nixpkgs: `/nix/var/nix/profiles/per-user/root/channels/nixpkgs`
Nix Darwin
Comme dit un peu plus haut, Nix Darwin
est une surcouche. Le projet repose sur Nix
est apporte à macOS
l’approche déclarative de Nix
pour l’installation et la configuration des logiciels.
Il y a deux façons de l’utiliser : la bonne, et la mauvaise via un installateur ou via Flakes
. J’ai choisi la bonne de passer par Flakes
.
Les Flakes
sont une façon de rendre les builds et switchs de Nix
et NixDarwin
plus déclaratif et plus répétables. Un flake n’est rien d’autre qu’un dossier contenant un fichier flake.nix
décrivant les entrées passées à Nix
(et Nix Darwin
ici) et les sorties de ce(s) dernier(s) qui doivent dépendre uniquement des entrées en questions. Pas de magie, de sources d’informations cachées ni de builds/switchs non répétables.
Voici celui que j’utilise au moment d’écrire ces lignes :
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
darwin = {
url = "github:lnl7/nix-darwin/master";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, darwin }: {
darwinConfigurations."__HOSTNAME__" = darwin.lib.darwinSystem {
system = "aarch64-darwin";
modules = [
home-manager.darwinModules.home-manager
./work
];
};
};
}
Je pense que c’est le bout de code le plus simple de l’article :
- Les entrées
inputs
sont au nombre de trois :nixpkgs
contenant la liste des paquets queNix
pourra installer pour tous les utilisateurs de la machine,home-manager
reposant surnixpkgs
pour l’installation et la configuration à la discrétion de chaque utilisateur,darwin
, lui aussi reposant surnixpkgs
et apportant donc les petits bonus parmacOS
.
- Les sorties
outputs
au nombre de une : la configurationNix Darwin
pour la machine__HOSTNAME__
à remplacer par le nom de votre machine bien entendu.
Les autres sorties pourraient par exemple être les configurations pour d’autres machines, d’autres systèmes d’exploitation ou simple des réglages de home-manager
pour différents utilisateurs.
La suite, la partie plus complexe à mes yeux, c’est ce qui est appelé dans les modules
. Honnêtement, je n’ai pour le moment aucune idée de ce qui se trouve dans celui fourni par home-manager
. Je vais me concentrer sur celui que j’ai rédigé et appelé habillement work
pour ma machine de travail. Ce qu’il faut noter ici c’est qu’il est possible de séparer les configurations dans des fichiers/dossiers en blocs minimaux à réutiliser au détail pour les besoins des différentes machines.
Work
Par convention, Nix
voyant le module work
, va aller chercher le dossier éponyme et charger le fichier default.nix
. Dont voici le contenu, là encore au moment d’écrire ces lignes :
1 { pkgs, ... }:
2 {
3 services.nix-daemon.enable = true;
4
5 system.keyboard = {
6 enableKeyMapping = true;
7 remapCapsLockToEscape = true;
8 };
9
10 users.users."pcoves".home = "/Users/pcoves";
11
12 environment.systemPackages = with pkgs; [
13 ansible
14 awscli2
15 colima
16 docker
17 neovim
18 packer
19 stow
20 terraform
21 tree
22 ];
23
24 programs = {
25 zsh.enable = true;
26 };
27
28 fonts.fontDir.enable = true;
29 fonts.fonts = with pkgs; [
30 (nerdfonts.override { fonts = [ "FiraCode" ]; })
31 ];
32
33 homebrew = {
34 enable = true;
35 onActivation.upgrade = true;
36 casks = [
37 "alacritty"
38 "firefox"
39 ];
40 };
41
42 home-manager.useGlobalPkgs = true;
43 home-manager.useUserPackages = true;
44 home-manager.users.pcoves = { pkgs, ... }: {
45 home = {
46 stateVersion = "23.05";
47 file = {
48 ".gnupg/gpg-agent.conf".text = ''
49 default-cache-ttl 600
50 max-cache-ttl 7200
51 enable-ssh-support
52 '';
53 };
54 };
55
56 programs = {
57 gpg = {
58 enable = true;
59 settings = {
60 use-agent = true;
61 };
62 };
63
64 password-store = {
65 enable = true;
66 package = pkgs.pass.withExtensions (exts: [ exts.pass-otp ]);
67 };
68
69 tmux = {
70 enable = true;
71
72 clock24 = true;
73 disableConfirmationPrompt = true;
74 keyMode = "vi";
75 shortcut = "s";
76 mouse = false;
77
78 extraConfig = ''
79 #
80 # Update the split keys from " and % to - and |.
81 #
82 unbind '"'
83 bind - split-window -v -c "#{pane_current_path}"
84 unbind %
85 bind | split-window -h -c "#{pane_current_path}"
86
87 #
88 # Update the copy/paste buffes from [ and ] to ( and )
89 #
90 unbind [
91 unbind ]
92 bind ( copy-mode
93 bind ) paste-buffer
94
95 bind-key -T copy-mode-vi 'v' send -X begin-selection
96 bind-key -T copy-mode-vi 'C-v' send -X rectangle-toggle
97 bind-key -T copy-mode-vi 'y' send -X copy-selection
98
99 bind C-l send-keys 'C-l' # Enable the clear screen shortcut.
100
101 set-option -g renumber-windows on # Keep windows's number up to date.
102 set-option -g allow-rename off # Do not update window's name accoring to current task.
103
104 set -g @resurrect-save 'o'
105 set -g @resurrect-restore 'O'
106 set -g @resurrect-capture-pane-contents 'on'
107 set -g @resurrect-strategy-vim 'session'
108 set -g @continuum-restore 'on'
109 '';
110
111 plugins = with pkgs; [
112 tmuxPlugins.resurrect
113 tmuxPlugins.continuum
114 tmuxPlugins.vim-tmux-navigator
115 ];
116 };
117 };
118 };
119 }
Oui, ça fait beaucoup d’un coup. Mais le découpage est assez simple dans le cas présent :
- Tout d’abord, un réglage de
Nix
lui-même : oui, je veux utiliser le daemon deNix
. Autrement il faudrait le lancer manuellement. Peut-être que les gourous deNix
saurait mieux expliquer, mais je ne vois pas encore de cas d’usage à désactiver ce réglage. - Ensuite quelques réglages concernant le comportement du clavier. Étant donné que
CapsLock
ne devrait pas exister, autant s’en servir pour quelque chose d’utile. - Je renseigne le chemin vers mon utilisateur. J’ai cru comprendre que c’était automatique auparavant. Plus maintenant et une erreur sensiblement cryptique apparaît sans cette ligne.
- Je décris l’ensemble des applications que je veux utiliser sur la machine. Tous les utilisateurs pourront lancer ces commandes. Alors, oui, c’est une machine mono-utilisateur. Mais ce n’est pas toujours le cas, notamment dans le cas de serveurs. Il existe des pages pour rechercher les paquets et options disponibles.
- Un petit bonus purement esthétique, j’aime beaucoup cette police d’écriture
FiraCode
alors pourquoi se priver ? - Je reviendrai un peu plus tard sur le bloc
Homebrew
. - Toute la fin concerne
HomeManager
, la surcouche concernant les utilisateurs et je vais la détailler juste après.
Simple non ?
Home-Manager
Contrairement aux réglages précédents qui concernaient la machine toute entière (__HOSTNAME__
), home-manager
s’occupe des utilisateurs. C’est là que se range notamment la configuration des outils utilisés.
Pour le moment, ce fichier est assez sommaire, j’ai le strict minimum pour pouvoir travailler. Je suppose que cette partie va très vite être séparée en blocs réutilisables et indépendants.
Pour le moment, on y retrouve un peu de configuration pour gnupg
et un gros bloc pour tmux
. Il faut que je farfouine un peu plus pour savoir si je peux obtenir le meilleur des deux mondes : la configuration de l’OS via Nix
et la configuration des outils via mes dotfiles, car je tiens tout de même à pouvoir utiliser mes configurations à la volée, sans Nix
si besoin.
Dans l’état actuel des choses, gpg-agent
n’est pas accessible via Nix
sans Nix Darwin
.
Il existe bon nombre de services spécifiques à macOS
qui sont rendus disponibles seulement ainsi. Par chance, les messages d’erreur si l’on essaie d’utiliser les versions unix
sont assez explicites et la solution est rapide à trouver.
Pour savoir ce qu’il est possible d’installer et comment le configurer, la meilleure ressource à ma connaissance est cette page. Je trouve que l’outil manque de flexibilité sur certain point, mais je suis à peu près sûr que c’est parce que je n’ai pas encore acquis suffisamment d’expertise. J’ai hâte que le moi-du-futur® se moque du moi-du-présent à ce propos.
Homebrew
Homebrew
se présente comme The Missing Package Manager for macOS (or Linux). Honnêtement, je n’en ai jamais eu besoin sur Linux
. Mais c’est vrai qu’il m’a été indispensable en bien des occasions avant que je ne découvre Nix
.
Il s’agit d’un gestionnaire de paquets tout à fait classique à l’usage. Une commande, brew
, des sous commandes pour installer/désinstaller des paquets passés en arguments. L’outil classique, la méthode impérative classique.
Pourquoi rajouter Homebrew
alors que Nix
, Nix Darwin
et Home Manager
font déjà un si bon travail ? Simplement parce que seul Homebrew
permet d’installer des applications avec interfaces graphiques. Les trois autres ne le permettent pas sur macOS
pour des questions de droits. Qu’à cela ne tienne, ce petit bloc de configuration installe mon terminal favori et mon navigateur habituel.
Usage
C’est bien beau cette approche déclarative, mais on fait quoi de tout ça ?
Hé bien on le donne à manger à Nix
, on se rend compte que ça échoue, on lance la paire de commande que Nix
nous indique, on retente, on constate que ça marche nickel, on est contents. Dans cet ordre bien précis.
1 % nix build .#darwinConfigurations.__HOSTNAME__.system --extra-experimental-features "nix-command flakes"
2 % ./result/sw/bin/darwin-rebuild switch --flake .
3 % printf 'run\tprivate/var/run\n' | sudo tee -a /etc/synthetic.conf
4 % /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -t
5 % ./result/sw/bin/darwin-rebuild switch --flake .
- La première ligne va calculer la dérivation décrite dans la configuration. C’est-à-dire qu’elle va, étant donné les entrées et l’état du système, calculer l’ensemble des modifications à effectuer pour arriver dans l’état désiré.
- La seconde ligne va appliquer les modifications. Enfin, essayer.
- Les lignes trois et quatre sont indiquées par
Nix
. C’est pour faire face à une limitation demacOS
concernant l’écriture de fichiers/dossiers vers la racine du système. - La dernière ligne applique les modifications demandées. Les logiciels sont installés et configurés, prêts à l’emploi !
Une fois fait, Nix Darwin
est installé et les commandes seront plus simples pour calculer les dérivations et les appliquer :
% darwin-rebuild build --flake .
% darwin-rebuild switch --flake .
Conclusion
L’expérience n’est pas des plus aisées. J’ai vraiment cru avoir mis la machine en l’air deux jours après l’avoir reçue. Au final, plus de peur que de mal avec une quasi-nuit blanche à réparer mes bêtises.
Mais désormais, je peux installer et configurer tout un OS en quelques minutes. Je compte bien intégrer ces codes à un dépôt déjà existant contenant les configurations de mes ordinateurs sur Linux
et n’avoir plus qu’une seule source de vérité pour toutes mes machines.
Avec le peu de recul dont je dispose sur ces outils, je conseillerais de ne surtout pas les utiliser. À moins de faire un métier similaire au mien, nécessitant l’usage de N
logiciels dans M
versions différentes et de devoir passer d’une configuration à l’autre très souvent, je suis convaincu que c’est sortir une le grand jeu pour pas grand-chose. Ceci étant dit, j’en ai fait l’expérience auparavant lors de nombreuses installations de serveurs “identiques” sur une courte période de temps, c’est extrêmement efficace.