Introdução

Uma das coisas mais disruptivas que eu já vi em distribuições linux desktop foi o Nix.

Nix é uma linguagem de programação funcional + gerenciador de pacotes que tem como objetivo criar ambientes reproduzíveis, e sinceramente, de todas as que eu conheço e ouvi falar foi a que chegou mais perto.

A primeira coisa que me ganhou no Nix foi o nix-shell, um componente que baixa os pacotes e fornece um shell com eles disponíveis para uso. Depois que você fecha o shell só fica o cache dos pacotes baixado, coisa que pode ser removida em um comando se esse espaço fizer falta. O programa usado nesse shell não fica disponível necessariamente no ambiente global.

Experiências iniciais

O Nix em distros não Nixos me deu problema usando aplicações gráficas pelo Nix e não administra serviços e programas mais acoplados com o sistema, pode ser que já tenham resolvido isso.

Três dias depois de instalar o Nix no xubuntu 20.04 no meu notebook daily driver eu me aventurei para instalar o NixOS. A instalação na live fornecida começa com você montando seu sistema de arquivos na /mnt, mais ou menos como se faz no arch só que ao invés do pacstrap você usa o nixos-generate para gerar os arquivos de configuração básicos. Dalí você adapta as suas necessidades, bootloader (se vai usar grub ou systemd-boot), programas básicos, docker e tudo mais usando o configuration.nix. Se você configurou o bootloader certin (o que no meu caso deu meia duzia de linha) ele vai dar boot de primeira. No meu caso em uma instalação UEFI esse trecho ficou o seguinte:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 boot.supportedFilesystems = [ "ntfs" ];
  boot.loader = {
    efi = {
      canTouchEfiVariables = true;
    };
    grub = {
      efiSupport = true;
      #efiInstallAsRemovable = true; # in case canTouchEfiVariables doesn't work for your system
      device = "nodev";
      useOSProber = true;
    };
  };

  # limpar tmp no boot
  boot.cleanTmpDir = true;

Com esse arquivo de configuração mexido você executa um nixos-install e o Nix se encarrega de configurar o sistema do jeito que você descreveu. Existem jeitos de fazer o Nix entender que é pra procurar esse configuration.nix em outras pastas usando uma variável de ambiente chamada NIX_PATH ou flakes, que eu vou falar posteriormente sobre.

Um gerenciador de pacotes e uma linguagem de programação? Hann?

Sim, o Nix gerenciador de pacotes e o Nix linguagem de programação trabalham juntos.

Todo esse ferramental usa os princípios da programação funcional como:

  • Imutabilidade: Uma entrada não vai ser modificada inplace pelo Nix, ele simplesmente não permite. Quando A vira B, A ainda vai existir depois da computação ser finalizada
  • Currying: Assim como, por exemplo, no Haskell, se uma função tem dois parâmetros e você especificou só o primeiro o retorno vai ser uma função que tem um parâmetro, que é o segundo da função inicial.image-20210113151912458
  • Funções de alta ordem: funções podem ser passadas como valores por ai

E diferente de outras linguagens que possuem alguns tipos classicos como objetos (attrs), listas, strings, números e funções o Nix tem alguns tipos extra como:

  • Derivations: representam computações entre arquivos e pastas. Um exemplo é uma função que recebe um arquivo e retorna uma derivação para extrair esse arquivo para uma pasta. Todas as derivações emitem suas saídas na nix-store e geralmente podem ser convertidas em caminhos e strings, que são os locais das suas saídas na nix-store.
  • Paths: representam caminhos absolutos ou relativos a pasta atual. O fato de isso existir exige que o usuário tome cuidado em operações de divisão. image-20210113152515812

O jeito Nix de ser faz citar dependências bem fácil fazendo uma árvore de dependências reproduzível uma realidade bem mais viável. A reproducibilidade das compilações possibilita o uso de compilação distribuída usando builders remotos e cache de saídas das derivações, o que já é uma realidade. Em breve provavelmente teremos cache distribuido usando IPFS, o que é uma questão complicada por que sistemas distribuídos, ainda mais lidando com nós não confiáveis, não são tão simples quanto puxar de um servidor que já se confia.

O Nix usa uma estratégia diferente dos makefiles para saber quando recompilar coisas, aliás, Nix não é necessariamente um substituto de makefiles mas sim um orquestrador pois um sistema pode ter vários pacotes que usam makefiles e o Nix pode orquestrar essas compilações para acontecer em paralelo com outras, inclusive em máquinas remotas.

Ao buscar dados externos é recomendável, se não obrigatório, especificar os hashes sha256 dos arquivos a serem baixados, isso permite comparar com o arquivo esperado de ser recebido sem ter que ter o arquivo antes para comparar depois de baixar. Existem meios de se organizar novas versões dessas dependências, o que será abordado mais para frente.

O tipo de derivação mais comum é o que a derivação tem uma dependência de entrada, geralmente o código fonte do programa a ser compilado e a saída da derivação é o binário desse programa pkgs.mkDerivation. A derivação tem suas entradas de tempo de compilação, que são dependências necessárias para compilar esse programa, e dependências de execução que são as que o programa precisa para executar. Esse é o tipo mais comum mas existe por exemplo o pkgs.writeShellScriptBin que recebe o nome do binário do script e o corpo do script em Shell Script e a derivação é uma pasta com uma pasta bin e um script com o nome especificado, shebang de Shell Script e o conteúdo que o usuário apresentou no script. A saída não pode ser modificada nem com acesso root na nix-store.

Nix-store?

Nix-store é onde todos os dados do Nix ficam salvos. Um NixOS só precisa da /boot e da /Nix para subir, o resto ou ele constroi no boot ou é dado do usuário (estado).

Cada derivation vira uma pasta ou arquivo dentro da /nix/store. O Nix mantêm uma lista de raizes de GC e de dependências entre caminhos da nix-store assim ele sabe o que apagar quando você executar um GC.

Cada modificação de configuração ou rebuild de um programa cria pelo menos um caminho na nix-store, que pode ser removido pelo processo de GC.

GC é garbage collection, um processo bastante popular em linguagens de programação dinâmica como Javascript, Java e Python. Consiste em remover elementos da memória que não estão sendo mais referenciados. No Nix é a mesma ideia só que as raízes do GC são as gerações do seu sistema NixOS e os profiles criados pela instalação imperativa de programas usando o comando nix-env. Todas as dependências dessas raízes são mantidas e o resto marcado para exclusão.

Resumidamente o Nix primeiro enumera o que excluir, manda os elementos para a /nix/store/.trash e depois vai apagando as pastas dessa pasta uma por uma, esse processo intermediário é necessário para saber quais links podem ser apagados. Esses links são criados em processos de otimização da nix-store onde arquivos dentro da nix-store com o mesmo hash são movidos para uma pasta especial e o caminho original tem um hard-link apontando para esses arquivos. Isso pode salvar alguns GBs e é possível configurar para ser feito automaticamente, assim como o GC.

Qualquer modificação, por menor que seja, em uma descrição ou no código vai ocasionar a necessidade de recompilar o pacote sendo alterado e todos os pacotes que dependem dele então atualizações, que no meu caso são mensais, podem pesar vários GBs o que faz o Nix comer o armazenamento com farofa. Não é recomendável para quem usa a máquina em internet com franquia limitada e essa é minha maior reclamação com o sistema. Felizmente não é a máquina do usuário final que compila esses pacotes mas sim um servidor, ou um grupo de servidores, que são orquestrados por um sistema chamado Hydra (bem sugestivo o nome, não?) que compila as novas versões e disponibiliza em um cache público imenso. Os pacotes ficam lá por até um ano se eu não me engano.

Existe também a possibilidade de pré compilar gerações e pacotes no GitHub Actions e usar um cache privado como o Cachix, o que é bem conveniente e com os 10GB que eles fornecem no plano gratuito dá para fazer bastante coisa por que quase tudo acaba apontando o cache global, que o Cachix não conta como usado na cota.

Flakes? (wiki)

Quem já mexeu com dependências externas em pacotes já se deparou com a preguiça de ficar bumpando versão dos repositórios dependentes. Bumpar é o processo de atualizar uma dependência, ou um pacote, pelo menos é esse o sentido que eu quero dar para essa palavra :p.

Assim como o npm tem o package.lock, yarn tem o yarn.lock, cargo tem o Cargo.lock and so on, o Nix tem um lockfile global, tá em fase beta mas funciona muito bem.

Para importar outros repositórios geralmente é usada a função builtins.fetchGit que é uma função interna que recebe uma attr/objeto com o endereço do repositório git, o commit que você quer buscar e o hash da saída retornando uma derivation com os dados desse repositório. O flakes te abstrai disso. Você pode especificar cada um dos repositórios e ele se encarrega de puxar a ultima revisão da branch que você tem interesse ou da ultima que foi buscada, a menos que você atualize o input da flake com o nix flake update. Dentro da flake.nix que você especifica as saídas do repositório, lembrando que flakes não funcionam fora de repositórios git e vai ter problema de arquivo faltando se você não der git add no arquivo que você acabou de adicionar na pasta antes de buildar.

A estrutura de um flake é basicamente um attr que tem:

  • description: descrição ou nome do repositório
  • os inputs com suas devidas urls e flake = false se o repositório não tiver um flake.nix na raiz
  • outputs: uma função que recebe um attr com self e todos os inputs resolvidos em derivations, é possivel descrever sistemas NixOS que podem ser buildados e aplicados na maquina atual ou em uma máquina em instalação (essa segunda opção eu ainda não usei).

Nixpkgs (link)

Nixpkgs é onde todos os pacotes e módulos ficam, tem MUITA coisa. O repositório completo tem coisa de 1.3GB e subindo. Se você baixar em zip a ultima versão vai dar menos de 50MB.

Quem bota a cara no Nix vai passar muitas vezes por esse repositório, principalmente quem empacota por que lá tem alguns utilitários que evitam de você ter que fazer as coisas na mão, como uma função para criar pacotes de projetos em Go (pkgs.buildGoPackage).

Cada vez que eu vejo que vou ter que baixar o Nixpkgs para algum PR me da preguiça por que o repositório é gigante mesmo hehehe. Meus ultimos PRs eu não precisei por que o primeiro eu usei o GitHub Codespaces e o segundo eu editei local como um outro pacote e depois adaptei pelo GitHub web mesmo para ficar no formato do que já tá lá mas recomendo que faça o processo de fork do github pra clonar o repositório para sua máquina, commitar e mandar o PR. Minhas alterações foram bem triviais até agora.

Outras ferramentas que quebram um galho

  • Manix: Pesquisa pacotes e options de módulos no Nix/NixOS
  • nix-index: Procura quais pacotes tem o arquivo que você especificou
  • rnix-lsp: Servidor de linguagem que fornece o procotolo LSP usado em IDEs e editores de texto como o VS Code e o neovim (que eu uso com LanguageClient-neovim)
  • home-manager: É quem administra dotfiles e configurações para cada usuário. É um modulo para NixOS que aceita configurações de arquivos que devem existir no ambiente de usuário, configurações e programas que são acessíveis apenas para aquele usuário. Dá pra usar NixOS sem ele mas com ele é mais conveniente.
  • Tem mais no nix-community [off]

Nem tudo são flores

Existem algumas coisas que não funcionam out of the box em Nix mas que funcionariam em outras distros como AppImages e binários pré-compilados com dependências carregadas dinâmicamente. A questão é que a hierarquia de pastas do sistema é diferente de outros sistemas então o programa da problema por que não acha o que ele necessita onde ele espera encontrar. Para isso existem dois jeitos, o primeiro é o patchelf que busca essas dependências nativas como bibliotecas C pré compiladas e modifica o binário do programa para usar essas bibliotecas. A segunda é um ambiente FHS que tapeia o programa fazendo entender que tá tudo onde ele espera. Essa segunda opção é usada para fazer funcionar a Steam e AppImages.

Ainda não existe um jeito de empacotar programas Wine, por depender de estado e os bottles não tem um esquema de arquivos .d para configurar o registro. Se você depende do OnlyOffice esquece, perdi uma semana tentando empacotar isso. Talvez me falta manha mas não consegui fazer funcionar. Levei uma semana não por que o sistema é ruim mas sim por que é uma coisa que fere a alma. Como assim não vou empacotar saporra? Depois de apanhar muito e tentar de tudo quanto é jeito eu chutei o balde, peguei, chutei, peguei, chutei e na ultima vez foi de vez hehehe.

Como dito o NixOS em uma atualização completa baixa bastante coisa e come espaço com farofa (meu / tem 100GB), mas pelo menos ele não te exige de reiniciar para qualquer coisa e se der qualquer problema você pode dar rollback para uma versão anterior do sistema seja pelo nixos-rebuild rollback ou pelo menu do bootloader podendo subir o sistema sem compromisso em qualquer geração que ainda não foi apagada. Isso é perfeito para poder testar ambientes de desktop sem medo de ser feliz por que voltar para o ambiente anterior é bem tranquilo.

Compilar uma nova geração usava bastante RAM, tipo uns 2GB, no Nix stable. Não sei por que. No nixFlakes parece não existir mais esse problema porém o nixFlakes é uma versão mais atualizada, e beta.

Como contribuir

Meu primeiro pull request foi participação de um projeto de conversão de documentação do texlive de DocBook para Common Mark. Resumidamente converter XML para Markdown. O segundo, que atualmente está em andamento, é adicionar uma opção ao pacote do VLC para que seja possível usar skins nele. Por padrão essa opção nem existe na definição de pacote e como eu já tinha conseguido fazer localmente resolvi contibuir upstream.

O processo de review de um pull request, no meu ponto de vista, é bem organizado. Existem diversas checagens automatizadas que acotnecem. Uma delas só para checar se o código tá dentro dos padrões, tem outra que checa se o código compila e a discussão só acontece depois que todas as checagens acontecem, inclusive a ofborg que é a mais demorada delas.

De todos os repositórios que eu já fiz pull request o Nixpkgs é o que mais rola discussão, não no mal sentido, é mais pessoas sugerindo melhorias, as pessoas são muito gente fina por lá e eu acho que é uma experiência de code review mais próxima a que você teria numa empresa dos sonhos. Inclusive naquele primeiro pull request que eu mandei tive menção de honra/agradecimento na postagem que o cara que tava organizando o projeto fez no Discourse . Tudo acontece em inglês e se você tiver interesse em ajudar mas estiver meio assim vem para o NixOS Brasil no Telegram que a gente ajuda a desenrolar. O código de lá tem seus padrões e o máximo que vai acontecer se você não seguir os padrões é seu código não ser fagocitado/mergeado na master para todo mundo em breve poder tirar proveito.

Uma contribuição pode ser um programa novo empacotado, uma atualização para um programa que você percebeu estar desatualizado, documentação para novos componentes, posts em blogs, avaliações e feedback em outros pull requests e issues e se for o bixão memo até mesmo resolver eventuais problemas de segurança e performance no Nix e seus componentes se houver alguma mudança que melhore.

Open source é assim, cada um ajuda um pouco e todo mundo sai ganhando.