Reproduzindo meu Arch sem transformar tudo em Nix
Como uso Git, scripts idempotentes, Btrfs, Snapper, dois kernels e containers para reconstruir minha workstation Arch sem fingir que Bash virou NixOS.
Eu quase virei usuário de NixOS. Não por causa da sintaxe da linguagem, que dificilmente seria o argumento mais sedutor numa conversa honesta, mas porque várias ideias do ecossistema resolviam problemas que eu já tinha. Descrever uma máquina em arquivos versionados, reconstruí-la sem depender da memória, abrir um ambiente com as dependências exatas de um projeto e voltar para uma geração anterior quando alguma coisa desse errado eram propostas muito difíceis de ignorar.
Também gostei do NUR e, principalmente, do nix-shell. A possibilidade de entrar num shell com ferramentas específicas sem instalar cada uma delas no host ataca diretamente o clássico “funciona na minha máquina”. A documentação oficial do Nix mostra justamente esse modelo: o ambiente pode viver em um shell.nix, ser versionado e recriado em outra máquina. Com uma revisão do Nixpkgs fixada, a promessa é bem mais forte do que simplesmente instalar hoje os pacotes que têm os mesmos nomes.
Este texto, porém, não é uma tentativa de recriar NixOS em Bash, nem uma alternativa à ArchWiki. É um relato sobre quais propriedades eu queria, quais consegui aproximar no Arch e onde essa aproximação acaba. O “quase” no título está fazendo bastante trabalho.
O problema que me afastou não era técnico
Em 2024, o ecossistema Nix atravessou uma crise de governança que tornou difícil separar decisões técnicas de disputas sobre liderança, moderação, representação e poder de decisão. Houve também uma controvérsia prolongada sobre patrocínios de eventos. Não preciso reconstruir cada discussão aqui, até porque isso transformaria um texto sobre snapshots em uma ata de condomínio, mas a própria NixOS Foundation reconheceu publicamente problemas nessas áreas e anunciou uma transferência de poder. Eelco Dolstra, autor principal do Nix, deixou o conselho da fundação como parte desse processo.
É importante não resumir tudo como “mantenedores foram expulsos”. A história foi mais confusa. Houve pessoas que saíram voluntariamente de funções por desgaste, como Théophane Hufschmitt; houve disputas de moderação, suspensões e discussões sobre permissões em casos específicos; e houve críticas públicas à transparência de alguns processos. São categorias diferentes e misturá-las produz uma narrativa mais simples, porém menos verdadeira.
O projeto também não ficou parado. A fundação nomeou uma Constitutional Assembly, a comunidade adotou uma Governance Constitution e elegeu o primeiro Steering Committee no fim de 2024. Hoje existe uma separação formal entre o comitê eleito, responsável pela liderança técnica e comunitária, e o conselho da fundação, responsável por assuntos jurídicos, financeiros e de parcerias. Isso não apaga os conflitos anteriores, mas seria incorreto escrever como se nenhuma reforma tivesse acontecido.
Minha decisão foi bem menos grandiosa. Eu continuo achando a tecnologia excelente, mas, para uma workstation pessoal, meu receio de depender de decisões tomadas por um grupo pequeno ficou maior do que minha preguiça de gastar alguns minutos executando scripts que eu entendo. Isso é uma preferência pessoal, não um diagnóstico universal sobre NixOS.
Foi aí que a pergunta melhor apareceu. Eu não precisava necessariamente de NixOS. Precisava descobrir quais propriedades do Nix realmente importavam para mim.
A intenção da máquina como fonte de verdade
Eu queria reinstalar o sistema sem tentar lembrar o que havia feito seis meses antes. Queria declarar os pacotes que considero parte da máquina, versionar a configuração do desktop, rodar o setup novamente sem destruir o estado existente, ter um caminho de volta depois de uma atualização ruim e manter dependências de projetos fora do host. Acima de tudo, queria abrir um repositório Git e entender como aquela máquina nasceu.
O meu arch-post-install-scripts é a implementação ainda pequena dessa ideia. O repositório atual não instala o Arch do zero e não decide particionamento, criptografia ou firmware. Ele parte de uma instalação funcional com root em Btrfs, Snapper e Limine disponíveis, e cuida do pós-instalação: bootstrap, pacotes, Zsh e snapshots integrados ao bootloader.
A árvore é curta o bastante para caber na cabeça:
tree .
.
├── commands
│ ├── limine-snapshot-sync
│ └── snaplimine
├── dotfiles
│ └── zshrc
├── install.sh
├── LICENSE
├── packages
│ ├── aur.txt
│ └── pacman.txt
├── README.md
├── scripts
│ ├── bootstrap.sh
│ ├── packages.sh
│ ├── snapshots.sh
│ └── zsh.sh
└── systemd
├── arch-snapper-limine-sync.path
└── arch-snapper-limine-sync.service
6 directories, 14 files
O install.sh só orquestra etapas independentes:
"$root_dir/scripts/bootstrap.sh"
"$root_dir/scripts/packages.sh" "$root_dir"
"$root_dir/scripts/zsh.sh" "$root_dir"
"$root_dir/scripts/snapshots.sh" "$root_dir"
Essa separação parece detalhe enquanto tudo funciona. Quando a configuração do Snapper falha, porém, é melhor depurar snapshots.sh do que atravessar um instalador monolítico de mil linhas tentando descobrir onde o shell decidiu guardar a surpresa.
Idempotência sem cerimônia
Todos os scripts começam com set -euo pipefail, rejeitam execução como root e elevam apenas as operações que precisam de privilégio. Isso não torna Bash magicamente seguro, mas remove algumas classes comuns de erro: comando que falhou e foi ignorado, variável não definida e falha escondida no meio de um pipeline.
Idempotência, nesse contexto, significa que a segunda execução deve ser tediosa, não emocionante. O bootstrap usa pacman -Syu --needed; se paru já está no PATH, não tenta compilá-lo novamente. A instalação do Oh My Zsh aceita uma árvore válida que já existe. Antes de substituir o .zshrc, o script compara o conteúdo e, quando encontra uma configuração diferente, cria um backup com timestamp. A troca final é feita por um arquivo temporário seguido de mv, evitando deixar um arquivo pela metade.
O módulo de snapshots aplica a mesma ideia a arquivos de sistema. Ele gera a nova configuração em um temporário, compara com o destino, preserva um backup único e só então publica a substituição. As units são habilitadas com systemctl enable --now, que pode ser executado novamente. Diretórios usam install -d; dependências são verificadas antes da mutação; e o instalador inteiro exige ser iniciado por um usuário normal.
Nada disso entrega a pureza funcional do Nix. Entrega uma propriedade operacional mais modesta: repetir a intenção não deveria acumular dano.
Pacotes são configuração, mas não são um lockfile
Os pacotes dos repositórios oficiais ficam em packages/pacman.txt; os pacotes do AUR, em packages/aur.txt. O parser remove espaços e ignora linhas vazias, depois passa arrays para pacman e paru com --needed. Em vez de esconder uma linha gigantesca dentro de um script, o Git passa a responder uma pergunta útil: “o que eu considero instalado nesta máquina?”
sudo pacman -S --needed --noconfirm "${pacman_packages[@]}"
paru -S --needed --noconfirm "${aur_packages[@]}"
Isso reconstrói intenção, não estado bit a bit. O pacman instalará as versões disponíveis quando o script for executado. Um pacote pode mudar de dependências, sair dos repositórios ou quebrar no AUR. A receita de um pacote comunitário também pode ser alterada sem que o meu repositório mude uma linha. Até uma imagem de container apontada apenas por tag pode produzir outro resultado no mês seguinte.
Nix é estruturalmente mais forte aqui porque a composição das dependências e as entradas de build podem ser fixadas e endereçadas por conteúdo. Minha lista de nomes é deliberadamente mais fraca. Em compensação, consigo lê-la, editar uma linha e continuar usando o gerenciador de pacotes nativo do sistema. Para o meu caso, essa troca é aceitável desde que eu não minta para mim mesmo sobre o que ela garante.
Hyprism: o desktop também pode voltar do Git
O segundo repositório dessa estratégia é o Hyprism. Ele transforma boa parte da sessão gráfica em arquivos versionados: configuração do Hyprland em Lua, shell em Quickshell, temas derivados do wallpaper, integração com GTK e Qt, terminal, Neovim, tmux, scripts de sistema, lista de dependências e preferências do usuário em user.json.
O instalador cria uma cópia de runtime em ~/.local/share/hyprism, liga os caminhos gerenciados à configuração do usuário, preserva conflitos em ~/.local/state/hyprism/backups/ e inicializa o estado sem sobrescrever escolhas válidas. make update reaplica a instalação sem reinstalar pacotes; make uninstall remove os links gerenciados e arquiva o runtime e a configuração do usuário. A versão empacotada no AUR separa ainda melhor as coisas: arquivos imutáveis ficam em /usr/share/hyprism, enquanto preferências continuam em ~/.config/hyprism.
Isso não serve para vender o Hyprism. Serve para mostrar que “dotfiles” podem ser tratados como uma camada instalável do sistema, com migração, backup e remoção. Se o desktop inteiro vive num repositório, formatar / deixa de ser uma escavação arqueológica e passa a ser principalmente download, instalação e restauração dos dados que realmente são pessoais.
Esses dados não devem ir junto. Tokens, chaves SSH privadas, credenciais, cookies, caches e documentos ficam fora do Git. O repositório contém a forma da máquina; segredos e dados precisam de backup e de um mecanismo próprio de restauração. Um git clone não deveria recriar minha identidade digital por acidente.
Dois kernels compram um pouco de tranquilidade
Na minha workstation, o linux-zen é o kernel principal e o linux-lts é o fallback. A ArchWiki descreve o Zen como uma árvore ajustada para sistemas de uso cotidiano e o LTS como a árvore de suporte prolongado, especialmente útil quando módulos externos demoram a acompanhar o kernel mais novo. Eu uso o Zen porque faz sentido para desktop. Mantenho o LTS porque uma regressão recente não precisa ganhar o direito de estragar meu domingo.
sudo pacman -S linux-zen linux-zen-headers linux-lts linux-lts-headers
Os headers só são necessários se houver módulos externos, DKMS ou outra ferramenta que compile contra o kernel. Não são um imposto universal. No meu caso eles fazem parte do setup; em uma máquina sem módulos externos, instalar os dois pacotes de headers pode ser apenas coleção temática de arquivos.
Esta etapa ainda não foi incorporada ao arch-post-install-scripts; hoje os kernels são um pré-requisito mantido fora do bootstrap. É um dos módulos óbvios a adicionar. Até isso acontecer, o importante é não confundir “pacote instalado” com “fallback funcional”. Os dois kernels precisam de imagens initramfs próprias e entradas testadas no Limine.
Um exemplo mínimo, baseado no formato atual documentado pelo Limine, fica assim. UUID, subvolume, caminhos de microcode e localização dos arquivos precisam ser adaptados à instalação real:
/Arch Linux Zen
protocol: linux
path: boot():/vmlinuz-linux-zen
cmdline: root=UUID=SEU-UUID rw rootflags=subvol=/@
module_path: boot():/initramfs-linux-zen.img
/Arch Linux LTS
protocol: linux
path: boot():/vmlinuz-linux-lts
cmdline: root=UUID=SEU-UUID rw rootflags=subvol=/@
module_path: boot():/initramfs-linux-lts.img
Os parâmetros de root devem ser consistentes entre as entradas. Depois da instalação, eu inicializo o LTS de propósito e verifico rede, vídeo, áudio e módulos externos. Fallback que nunca foi bootado é só otimismo versionado.
Btrfs e Snapper protegem outra categoria de falha
Um segundo kernel ajuda quando o problema está no kernel ou em um módulo. Ele não desfaz uma biblioteca quebrada, uma remoção ruim ou uma configuração de sistema que deixou o userspace inutilizável. Essa é a camada do Btrfs e do Snapper.
O layout precisa ser pensado antes dos snapshots. Minha raiz está no subvolume @ e o /home em @home. Um layout maior poderia separar também /.snapshots, /var/log, /var/cache, /var/lib/containers, /var/lib/machines e /swap, mas isso é uma decisão de política, não uma árvore sagrada para copiar.
Subvolumes aninhados ou montados separadamente não entram automaticamente no snapshot da raiz. Isso é útil quando eu quero restaurar o sistema sem voltar também meus documentos, logs ou imagens de containers. O mesmo raciocínio vale para /var/lib/docker e /var/lib/containers: excluir esses dados do snapshot do root reduz tamanho e evita misturar rollback do host com estado de containers. /var/lib/machines merece atenção semelhante se eu usar systemd-nspawn.
Há duas exceções que não trato com casualidade. A base do pacman em /var/lib/pacman precisa permanecer coerente com os arquivos do sistema; separá-la do snapshot da raiz pode fazer o filesystem voltar no tempo enquanto o gerenciador de pacotes insiste que nada mudou. E swapfile em Btrfs tem restrições próprias. A ArchWiki recomenda um subvolume separado, porque um subvolume com swapfile ativo não pode ser snapshotado.
A configuração real do Snapper
Para uma instalação sem /.snapshots previamente montado, o ponto de partida comum é:
sudo snapper -c root create-config /
O comando cria /etc/snapper/configs/root e o subvolume /.snapshots. Se esse caminho já existe como subvolume montado, situação comum em layouts criados pelo archinstall, o comando pode falhar. O meu snapshots.sh trata os dois casos: usa create-config quando o caminho não existe e, quando encontra um /.snapshots válido, instala o template de configuração diretamente antes de validar que a configuração realmente gerencia /.
Depois, o script acrescenta meu usuário a ALLOW_USERS, ativa SYNC_ACL e configura retenção. Hoje ele mantém até 20 snapshots pelo algoritmo number, dez marcados como importantes, dez horários, sete diários, quatro semanais, seis mensais e dois anuais. Também remove pares pre/post vazios depois da idade mínima. Esses números não são recomendação universal; são uma política explícita que cabe no meu disco e pode ser alterada no repositório.
sudo systemctl enable --now snapper-timeline.timer
sudo systemctl enable --now snapper-cleanup.timer
snapper -c root list
snapper -c root create --description "antes de testar o driver"
O primeiro timer cria snapshots de timeline; o segundo aplica os algoritmos de limpeza. Sem limites, snapshots automáticos são apenas uma maneira sofisticada de descobrir que espaço em disco também acaba.
Pacman, snap-pac e o valor de não reinventar 80 linhas
O pacote snap-pac instala hooks da libalpm que criam snapshots antes e depois de operações de instalação, atualização e remoção. O hook real de pré-transação é essencialmente este:
[Trigger]
Operation = Upgrade
Operation = Install
Operation = Remove
Type = Package
Target = *
[Action]
When = PreTransaction
Exec = /usr/share/libalpm/scripts/snap-pac pre
NeedsTargets
AbortOnFail
Existe outro hook com When = PostTransaction. A distinção importa: hooks PreTransaction e PostTransaction pertencem ao pacman e são executados pela libalpm ao redor da transação, conforme o manual de alpm-hooks(5). Funções pre_install e post_install pertencem ao ciclo de vida de um pacote específico. Os nomes parecem parentes, mas resolvem problemas diferentes.
O instalador verifica se snap-pac está presente e garante uma seção para a configuração Snapper que controla / em /etc/snap-pac.ini. Não há um clone artesanal do pacote. Eu não quero um script de 80 linhas substituindo uma ferramenta existente só para poder dizer que fui eu quem escreveu.
Depois de uma transação, snapper list mostra o par pre/post, com a descrição e os pacotes envolvidos. Isso não torna qualquer atualização segura, mas cria um ponto de comparação imediatamente antes da mudança, que é exatamente quando costumo lembrar que teria sido bom criar um snapshot.
Snapshot no disco não é recuperação no boot
Possuir snapshots ajuda pouco quando o sistema não chega ao desktop e eu não lembro o número certo dentro de /.snapshots. Por isso o repositório instala uma integração própria com Limine. O script detecta a partição FAT montada, localiza o limine.conf ativo, identifica uma entrada Linux cujo cmdline contém rootflags=...subvol=, e usa essa entrada como modelo. Para cada snapshot do Snapper, ele troca apenas o caminho do subvolume e publica um menu chamado Snapshots (Hyprism generated).
Um systemd.path observa mudanças em /.snapshots e dispara a sincronização. O publicador usa lock, preserva entradas que não gerencia, cria um backup inicial e substitui o arquivo de configuração de forma atômica. O comando de usuário simplifica snapshots manuais:
snaplimine create "antes de mexer no driver"
snaplimine sync
O primeiro cria um snapshot read-write e sincroniza o menu; o segundo apenas reconstrói as entradas. O sincronizador também torna graváveis os snapshots encontrados, porque muitos serviços de desktop não funcionam com uma raiz read-only. Essa é uma escolha prática, mas reduz a imutabilidade que normalmente espero de um snapshot. A alternativa mais limpa é bootar read-only com uma camada temporária de OverlayFS, abordagem também descrita na documentação do Snapper.
Há uma limitação mais séria no código atual: ele replica a entrada ativa do Limine, portanto os snapshots apontam para o kernel e o initramfs que estão hoje na partição de boot. Como /boot está em FAT e fora do snapshot Btrfs, uma raiz antiga pode conter módulos que não correspondem ao kernel atual. A entrada existe, mas pode não inicializar depois de uma atualização de kernel. O repositório ainda não arquiva um conjunto correspondente de kernel, initramfs e módulos por snapshot, nem valida esse casamento.
Isso também significa que bootar um snapshot não é o mesmo que efetuar rollback. Posso usá-lo para recuperar arquivos, corrigir a raiz atual ou criar um novo snapshot gravável e promover esse estado manualmente. Um rollback definitivo exige decidir o que acontece com o subvolume @, com os subvolumes excluídos e com /boot. Snapshot que aparece no menu não é automaticamente um plano completo de recuperação. O projeto melhorou bastante a ergonomia, mas o “quase” continua vivo e saudável.
O meu nix-shell sem NixOS
nix-shell e nix develop continuam sendo algumas das melhores ideias do ecossistema Nix. A pergunta útil para mim, porém, não é qual ferramenta reproduz exatamente sua semântica. É como abrir um ambiente descartável, com dependências específicas, sem transformar o host numa lista de experimentos antigos.
Distrobox para ambientes próximos do desktop
O Distrobox usa Podman, Docker ou Lilipod para criar containers muito integrados ao host. Ele compartilha home, sockets gráficos, áudio, dispositivos e outros recursos, o que permite executar aplicações gráficas e ferramentas de outra distribuição sem uma VM. Essa integração é justamente o motivo para não chamá-lo de sandbox de segurança. A documentação é explícita: isolamento forte não é o objetivo.
Para reprodução, a parte interessante é o distrobox assemble. Um manifesto distrobox.ini pode declarar a imagem, pacotes adicionais, volumes, hooks e aplicações exportadas:
[projeto-cpp]
image=archlinux:latest
additional_packages="clang cmake ninja git"
pull=true
root=false
replace=false
start_now=true
distrobox assemble create --file distrobox.ini
distrobox enter projeto-cpp
O manifesto é versionável e recria a intenção do ambiente. Ainda assim, archlinux:latest e pacotes sem versões fixadas voltam a introduzir variação. Para um ambiente de desenvolvimento interativo isso pode ser suficiente. Para resultados mais estritos, a imagem precisa ser fixada por digest e as dependências também precisam de uma estratégia de pinning.
Containerfile quando o projeto precisa viajar
Para um ambiente que deve funcionar igual no notebook, na máquina de outra pessoa e no CI, prefiro um Containerfile simples. Podman aceita a mesma sintaxe de Dockerfile:
FROM archlinux:base
RUN pacman -Syu --noconfirm --needed \
clang \
cmake \
ninja \
git
WORKDIR /workspace
podman build -t meu-projeto-dev .
podman run --rm -it \
--userns=keep-id \
-v "$PWD:/workspace" \
meu-projeto-dev
É mais pesado que entrar em um shell do Nix. Em troca, a definição é explícita, o código do projeto fica montado em /workspace, o host não recebe o toolchain e praticamente todo serviço de CI sabe construir uma imagem OCI. Novamente, a tag archlinux:base não congela o resultado. A documentação do Docker recomenda fixar imagens por digest quando a reprodução exata importa.
Famílias parecidas, objetivos diferentes
Toolbx e Distrobox são containers de sistema integrados ao host. O Toolbx usa Podman e expõe home, Wayland, SSH agent, D-Bus, dispositivos e journal para oferecer um ambiente confortável de desenvolvimento. systemd-nspawn fica mais perto de um sistema Linux leve e completo; integrado a machinectl, é útil quando quero inicializar uma árvore de sistema com seu próprio systemd. Em Btrfs, ele também consegue criar containers a partir de subvolumes e snapshots temporários.
Docker e Podman são a família OCI tradicional. São melhores quando a unidade que quero compartilhar é uma imagem e quando isolamento e integração com CI pesam mais do que parecer que estou diretamente no host. Já os clean chroots do Arch, criados pelas ferramentas de devtools e usados por pkgctl build, têm um objetivo mais específico: compilar pacotes em um ambiente limpo, não substituir meu shell de projeto. arch-nspawn e mkarchroot pertencem mais a essa rotina de packaging.
Há ainda ferramentas que aproximam a experiência declarativa de shell. Devbox, Flox e devenv são opções atuais, mas todas usam Nix por baixo em graus diferentes. São interfaces mais amigáveis para o mesmo motor, não alternativas sem Nix. mise e direnv resolvem uma camada menor: versões de runtimes, variáveis e ativação por diretório. São leves e úteis, mas não entregam por conta própria um filesystem isolado nem o grafo de dependências do Nix.
Por fim, mkosi e systemd-nspawn podem construir e executar imagens completas de sistema. Isso é poderoso quando o artefato desejado é uma imagem de OS, mas seria um martelo bastante generoso para abrir um shell com clang e cmake.
O bootstrap como composição, não como ritual
O repositório atual tem quatro etapas. Se eu incorporar os kernels e os ambientes declarados, a evolução natural não é inflar install.sh, mas acrescentar módulos pequenos:
arch-post-install-scripts/
├── install.sh
├── packages/
├── scripts/
│ ├── bootstrap.sh
│ ├── packages.sh
│ ├── zsh.sh
│ ├── kernels.sh
│ ├── snapshots.sh
│ ├── containers.sh
│ └── desktop.sh
├── containers/
│ └── distrobox.ini
├── commands/
├── systemd/
└── dotfiles/
Essa segunda árvore é proposta, não o estado atual do Git. A diferença é importante. Um orquestrador curto permite executar, testar e corrigir cada parte isoladamente. Também deixa claro onde termina o bootstrap do host e começa a instalação do desktop, que no meu caso já pertence ao Hyprism.
Git funciona como fonte de verdade apenas se mudanças manuais voltarem para ele. Se eu altero uma unit em /etc, adiciono um pacote ou mudo o layout de subvolumes e não atualizo o repositório, criei drift. Nix modela esse problema no sistema. No Arch, eu dependo de revisão e disciplina. A máquina continua permitindo improviso, inclusive quando seria melhor que ela não permitisse.
Reproduzível só depois de reproduzir
Ter scripts no GitHub não prova que eles reinstalam uma máquina. O teste mais barato é rodar o bootstrap duas vezes:
./install.sh
./install.sh
A segunda execução deve encontrar pacotes presentes, arquivos iguais, units habilitadas e configurações válidas. Não deve criar backups infinitos nem duplicar linhas. Depois eu verifico as propriedades que realmente importam:
pacman -Q linux-zen linux-lts snapper snap-pac limine
snapper -c root list
systemctl is-active snapper-timeline.timer
systemctl is-active snapper-cleanup.timer
systemctl is-active arch-snapper-limine-sync.path
Também preciso bootar o LTS, criar um snapshot manual, confirmar a entrada no Limine e testar recuperação antes de precisar dela. Partes do instalador podem ser exercitadas em VM e CI, mas bootloader, Btrfs, GPU, rede e módulos de kernel pedem uma máquina virtual configurada de forma realista ou hardware secundário. Um mock que diz que systemctl retornou zero não prova que meu notebook voltará do snapshot.
O teste mais honesto é uma instalação limpa. Clonar os repositórios, executar o bootstrap, instalar o Hyprism e anotar tudo que ainda precisei fazer à mão. Cada passo esquecido é uma diferença entre a máquina descrita e a máquina existente.
O que continua fora da reprodução
Mesmo depois disso, firmware, BIOS/UEFI, particionamento inicial, chaves de criptografia, características do hardware e drivers específicos continuam fora do Git. Secrets, dados pessoais e estado interno de aplicações exigem outro processo. O AUR pode mudar ou desaparecer, upstreams podem remover artefatos, serviços externos podem deixar de existir e pacotes instalados hoje podem não estar disponíveis daqui a um ano.
O próprio host não tem versões exatas do grafo de dependências fixadas. Containers sem digest também mudam. /home separado sobrevive ao rollback da raiz, o que é desejável, mas significa que a aplicação antiga pode encontrar um formato de dados novo. Snapshots locais não ajudam quando o SSD morre. E qualquer ajuste manual que eu esquecer de codificar existe apenas enquanto aquele filesystem existir.
NixOS é estruturalmente melhor em várias dessas dimensões. Seu modelo declarativo e o store endereçado por conteúdo oferecem garantias que uma lista de pacotes e meia dúzia de scripts não oferecem. O objetivo aqui nunca foi provar que Bash é melhor. Foi perceber que talvez eu não precise resolver o problema inteiro para resolver o meu problema.
Três custos diferentes, três camadas
No fim, organizei a recuperação em três níveis. Git reduz o custo de reconstruir: se o SSD morrer, reinstalo a base, clono os repositórios, executo o bootstrap e restauro os dados. O linux-lts reduz o custo de continuar funcionando quando o kernel principal regride. Snapper reduz o custo de desfazer mudanças no userspace, desde que eu tenha testado o caminho de boot e entendido os limites de /boot e dos subvolumes excluídos.
As camadas se complementam porque respondem a falhas diferentes. Um snapshot não substitui backup. Um segundo kernel não restaura /etc. Git não volta o sistema ao estado de cinco minutos atrás. Juntas, porém, elas evitam que qualquer atualização ruim comece imediatamente pela pergunta “onde está o pendrive?”.
Perto o suficiente
Continuo gostando das ideias do Nix: configuração declarativa, ambientes isolados, rollback, dependências explícitas e a capacidade de reconstruir sistemas a partir de uma descrição. O experimento com NixOS foi útil mesmo sem terminar numa migração, porque me obrigou a separar a tecnologia que eu admirava das propriedades que eu precisava.
No meu Arch, a descrição virou Git, manifests e scripts idempotentes. Rollback virou Btrfs e Snapper. Continuidade virou um kernel LTS testado. Ambientes por projeto viraram Distrobox, Podman ou um Containerfile, conforme o nível de integração e isolamento necessário. A parte que Nix impõe pelo modelo eu substituí, em parte, por convenções que preciso manter conscientemente.
Matematicamente, não é a mesma coisa. Operacionalmente, para minha workstation, está perto o bastante.
Se tudo falhar, tenho dois kernels, snapshots e Git. Ainda preciso de backup. Milagre declarativo também não recupera SSD morto.