me.nix
Acho que a melhor maneira de começar esse texto é falando um pouco do que me motivou a pesquisar sobre Nix.
Sempre fui uma pessoa chata com minhas configurações e sempre gostei de testar coisas novas. Além disso, por um breve período tive que ficar saltando entre máquinas. Então era sempre a mesma problemática: eu estava num ponto em que já havia configurado tudo num repositório chamado dotfiles
, mas ter que refazer a instalação dos pacotes e mover os arquivos pros seus lugares era um saco.
Lembro que fiquei encantado quando vi uma apresentação de um colega sobre Ansible e como ele conseguia recriar as máquinas dele caso fosse necessário.
O que é?
Acho que a melhor maneira de explicar o que é Nix é com um diagrama1 feito pela Xe:
![Diagrama de Nix e seus elementos. Criado por Xe Iaso.](/_astro/xe-iaso-nix-diagram.BAT83Rnb_Z3tPs1.webp)
Por que utilizar?
-
Reprodutibilidade:
As derivações garantem que todo mundo conseguirá o mesmo resultado quando tiverem o mesmo input, ou seja, é fácil garantir que o ambiente que está rodando na etapa de desenvolvimento é o mesmo que está sendo utilizado no seu CI/CD.
-
Declarativo:
Defina o estado do sistema sem se preocupar em como o resultado será obtido.
Vamos para a prática
Confesso que não tenho experiência com Nix além dos flakes. Apesar de ser experimental, é estável2.
E me pareceu uma abordagem bem interessante: ser auto-contido3.
Iniciando um flake
Essa é a parte fácil. A parte mais complicada é a habilitação do flake
.
A maneira que eu preferi habilitar, foi editando o ~/.config/nix/nix.conf
.
1experimental-features = nix-command flakes
Viu como foi difícil?
Finalizada esta árdua etapa, podemos seguir para a criação o nosso flake
:
$ mkdir primeiro-flake$ cd primeiro-flake/$ nix flake initwrote: /tmp/primeiro-flake/flake.nix
Conseguimos confirmar se está tudo certo usando o comando nix flake show
.
$ lsflake.nix$ nix flake showwarning: creating lock file '/tmp/primeiro-flake/flake.lock':• Added input 'nixpkgs': 'github:nixos/nixpkgs/d8fe5e6c92d0d190646fb9f1056741a229980089?narHash=sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk%3D' (2024-03-29)path:/tmp/primeiro-flake?lastModified=1712097883&narHash=sha256-xFmRxTzUU6rX6AuIOHVJHqy5yvWIjMLuQsy9RHeKw3A%3D└───packages └───x86_64-linux ├───default: package 'hello-2.12.1' └───hello: package 'hello-2.12.1'$ ls -lflake.lock flake.nix
Tudo certo por aqui. Temos o arquivo de definição do flake, flake.nix
e o lockfile, um arquivo responsável por definir as versões exatas do que estamos referenciando.
Analisando os miúdos
O flake.nix
ficou assim:
1{2 description = "A very basic flake";3
4 inputs = {5 nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";6 };7
8 outputs = { self, nixpkgs }: {9
10 packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;11
12 packages.x86_64-linux.default = self.packages.x86_64-linux.hello;13
14 };15}
Nesse flake básico, temos 3 dos 4 atributos — não precisamos do nixConfig
. Então vamos dar uma analisada no que temos por agora:
description
: é apenas uma descrição do flake
2 description = "A very basic flake";
inputs
: são as dependências do nosso flake. Por enquanto temos apenas onixpkgs
na branchnixos-unstable
.
4 inputs = {5 nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";6 };
outputs
: são, de forma bem simplificada, os resultados do nosso flake com os inputs determinados. Se você for fã de formalidades, são as realizações4 dos nossos inputs. Por enquanto, nosso flake possui o programahello
, com suporte parax86_64-linux
. Que é definido como o resultado padrão na linha 12.
8 outputs = { self, nixpkgs }: {9
10 packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;11
12 packages.x86_64-linux.default = self.packages.x86_64-linux.hello;13
14 };
Só pra não passar batido, o arquivo de trava ficou assim:
1{2 "nodes": {3 "nixpkgs": {4 "locked": {5 "lastModified": 1711703276,6 "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",7 "owner": "nixos",8 "repo": "nixpkgs",9 "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",10 "type": "github"11 },12 "original": {13 "owner": "nixos",14 "ref": "nixos-unstable",15 "repo": "nixpkgs",16 "type": "github"17 }18 },19 "root": {20 "inputs": {21 "nixpkgs": "nixpkgs"22 }23 }24 },25 "root": "root",26 "version": 727}
A gente consegue ver que para a trava acontecer, ele especifica qual é o commit em que buscou os pacotes no nixpkgs
, pelo rev
. Ou seja, no momento em que ele travou as versões, o último commit era este aqui. Quando as mágicas acontecerem, ele vai buscar o repositório nesta versão exata.
No
github
podemos navegar por um repositório em qualquer ponto (leia-se commit).No terminal, seria equivalente ao
git checkout <commit-hash>
.É o que está acontecendo aqui agora.
Tornando nossos inputs uma realidade
Foi mal, não pude evitar esse trocadilho. Mas isso é realmente uma pergunta pertinente: como geramos algum produto com nosso flake?
A palavra mágica é build
. E apesar de estarmos lidando com flakes desde o início, esse é um conceito geral do Nix. Como já vimos antes (linha 12 do nosso flake), nosso pacote padrão é o hello
.
Fazemos a mágica acontecer assim:
$ nix build$ lsflake.lock flake.nix result$ file resultresult: symbolic link to /nix/store/rnxji3jf6fb0nx2v0svdqpj9ml53gyqh-hello-2.12.1
Opa, temos novidades! Surgiu um diretório result/
agora.
Ok, ok. É um link simbólico de um diretório. Eu sei, mas agora não precisamos nos preocupar com isso!!!
A gente consegue ter uma noção melhor do que está acontecendo nesse diretório novo usando o tree
ou erdtree
:
$ erd -H -L 2 result/ 56.0 KiB ┌─ hello 56.0 KiB ┌─ bin 4.0 KiB │ ┌─ man 36.0 KiB │ ├─ info228.0 KiB │ ├─ locale268.0 KiB ├─ share324.0 KiB rnxji3jf6fb0nx2v0svdqpj9ml53gyqh-hello-2.12.1
94 directories, 47 files$ file result/bin/helloresult/bin/hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /nix/store/1rm6sr6ixxzipv5358x0cmaw8rs84g2j-glibc-2.38-44/lib/ld-linux-x86-64.so.2, for GNU/Linux 3.10.0, not stripped$ result/bin/helloHello, world!
Quer dizer que concluímos? Sim e não. Nós já temos um flake capaz de produzir um resultado que é reprodutível: todo mundo com o mesmo flake.nix
e flake.lock
é capaz de construir exatamente o mesmo pacote, sem qualquer dor de cabeça.
Por outro lado, sabemos que por mais educado que seja, um ‘Hello, world!’ não é a coisa mais útil do mundo. Precisamos de algum problema mais complexo.
Maaaaaaas isso vai ficar pra um próximo post.
Preciso me atentar pra escrever menos e com mais frequência, senão vai tudo pro esquecimento de novo! Agora é exercitar isso. :)
Recapitulando
Nesse post nós vimos:
- O porquê utilizar Nix;
- Como criar um flake com
nix flake init
; - O que existe num flake (mesmo que bem simples);
- Como construir algo com flake utilizando
nix build
;
Próximos passos
Num futuro próximo, quero escrever sobre algumas melhorias que podemos fazer e aumentar a complexidade do nosso flake. Caso você seja uma pessoa ansiosa e queira saber sobre algumas coisas que pretendo abordar, lá vai:
Cachix
para reduzir o tempo alguns processos utilizando cache- Criação de imagens
docker
do nosso projeto - Criação de
devShells
pra garantir que todos tenham as mesmas versões direnv
para automatizar o acesso aos binários de nossadevShell
node2nix
para buildar os projetos em node- Instalação de CLI via PyPI que não está disponível no
nixpkgs
(e ainda não acho que tenho capacidade de me tornar mantenedor)
Se vou conseguir falar disso tudo no próximo, eu não sei. Mas fica por aí que alguma coisa sai!
Até!
Referências
-
Baseado no diagrama disponível em https://xeiaso.net/talks/nixos-pain-2021-11-10/ ↩
-
Determinant Systems: Experimental does not mean unstable, em https://determinate.systems/posts/experimental-does-not-mean-unstable/ ↩
-
Julia Evans: Some notes on nix flakes, em https://jvns.ca/blog/2023/11/11/notes-on-nix-flakes/ ↩
-
NixOS Wiki — Flakes, em https://nixos.wiki/wiki/Flakes ↩