Automatizei Meus Dotfiles com GNU Stow e Ansible — E Agora Formato PC Sem Medo
Na última vez que formatei o notebook, jurei que ia ser a última vez que eu perdia um dia inteiro reconfigurando tudo. Vim do Ubuntu pro Fedora — motives à parte — e quando abri o terminal pensei: “qual era minha configuração do Neovim mesmo?”
Não sabia. O Git estava sem chave SSH, o alias gs pra git status não existia, meu Tmux tava pelado, e o Zsh nem tinha sido instalado. Perdi 6 horas só pra deixar o ambiente minimamente usável. Seis horas que eu poderia ter escrito código, estudado, ou simplesmente assistido YouTube sem culpa.
Foi nesse dia que eu decidi: nunca mais. Comecei a pesquisar como as pessoas que levam a sério gerenciam seus dotfiles, e descobri um combo que mudou minha relação com formatar PC: GNU Stow + Ansible.
Neste artigo, vou te mostrar como automatizar seus dotfiles com GNU Stow e Ansible — do zero até um repositório Git que replica seu ambiente inteiro em qualquer máquina Linux em menos de 15 minutos. Sem mágica, sem ferramentas obscuras. Apenas organização e um pouquinho de script.

O Que São Dotfiles (E Por Que Você Deveria Se Importar)
Dotfiles são aqueles arquivos de configuração escondidos na sua home que começam com ponto: .bashrc, .vimrc, .gitconfig, .tmux.conf. Eles guardam todas as suas personalizações — aliases, temas, keybindings, variáveis de ambiente.
O problema? Eles estão espalhados por toda a árvore de diretórios do sistema. Seu .bashrc tá em ~/, seu init.vim tá em ~/.config/nvim/, seu .ssh/config tá em ~/.ssh/. Não é só copiar a home e pronto.
E tem mais: se você mexe em duas máquinas (notebook + servidor, ou trabalho + pessoal), manter tudo sincronizado manualmente é um parto que ninguém merece. Já falei aqui sobre como shell scripts me economizaram 30 horas por semana — gerenciar dotfiles é a extensão natural disso.
O Problema dos Symlinks Manuais
A primeira tentativa de todo mundo é criar symlinks manualmente:
# A abordagem ingênua
ln -s ~/dotfiles/.bashrc ~/.bashrc
ln -s ~/dotfiles/.vimrc ~/.vimrc
ln -s ~/dotfiles/.config/nvim/init.vim ~/.config/nvim/init.vim
ln -s ~/dotfiles/.tmux.conf ~/.tmux.conf
# ... e por aí vai
Funciona? Sim. Escala? Nem um pouco. Problemas reais que eu enfrentei:
- Esqueci de criar o diretório pai antes do symlink → erro silencioso, e passei dias sem perceber que o Tmux não estava lendo meu config
- Symlinks quebrados ao mudar o caminho do repositório →
.gitconfigapontando pro vazio - Conflitos ao rodar o mesmo script em máquina que já tinha configuração diferente → sobrescrevi sem querer
- Nenhuma idempotência — rodar duas vezes criava links redundantes ou falhava
Eu precisava de algo que gerenciasse os symlinks de forma limpa, previsível e reversível. Entrei num buraco de pesquisa e saí com o GNU Stow.
GNU Stow: O Gerenciador de Symlinks Que Você Não Conhecia
O GNU Stow é um gerenciador de symlinks que existe desde 1993. Sim, 1993. Ele resolve exatamente o problema de “tenho arquivos organizados numa pasta e quero que apareçam espalhados em outra”.
A ideia é genial na simplicidade:
- Você organiza seus dotfiles em pacotes dentro de um diretório (
~/dotfiles) - Cada pacote espelha a estrutura de diretórios relativo à home
- Você roda
stow <pacote>e ele cria os symlinks automaticamente
Estrutura de Diretórios
~/dotfiles/
├── bash/
│ └── .bashrc
│ └── .bash_aliases
├── git/
│ └── .gitconfig
│ └── .gitignore_global
├── nvim/
│ └── .config/
│ └── nvim/
│ └── init.lua
│ └── lua/
│ └── plugins.lua
├── tmux/
│ └── .tmux.conf
└── zsh/
└── .zshrc
└── .p10k.zsh
Quando você roda stow bash a partir de ~/dotfiles, o Stow cria:
~/.bashrc → ~/dotfiles/bash/.bashrc
~/.bash_aliases → ~/dotfiles/bash/.bash_aliases
Quando precisa remover? stow -D bash. Pronto, symlinks removidos, arquivos originais intactos.
Instalação e Configuração Inicial
# Ubuntu/Debian
sudo apt install stow
# Fedora
sudo dnf install stow
# macOS (via Homebrew)
brew install stow
# Arch (porque alguém vai perguntar)
sudo pacman -S stow
O fluxo completo pra adicionar um dotfile existente:
# 1. Criar a estrutura do pacote
mkdir -p ~/dotfiles/git
# 2. Mover o arquivo original
mv ~/.gitconfig ~/dotfiles/git/.gitconfig
# 3. Criar o symlink
cd ~/dotfiles
stow git
# 4. Verificar
ls -la ~/.gitconfig
# lrwxrwxrwx 1 user user 22 Apr 17 .gitconfig -> dotfiles/git/.gitconfig
Um comando. Um symlink limpo. Reversível. Idempotente. É o tipo de coisa que faz você se perguntar por que não fazia isso antes.
Quando o Stow Não Chega Sozinho: Entra o Ansible
O Stow resolve a parte de symlinks dos arquivos de configuração. Mas e o resto?
- Quem instala os pacotes que seus dotfiles assumem que existem? (Neovim, Tmux, Zsh, ripgrep, fd, bat…)
- Quem configura os repositórios extras? (PPA do Neovim, rpmfusion, Homebrew…)
- Quem instala plugins do Vim/Tmux automaticamente?
- Quem garante que o SSH key existe e está no agent?
É aqui que o Ansible entra como o orquestrador. Enquanto o Stow cuida dos dotfiles, o Ansible cuida de tudo ao redor.

Montando o Playbook Ansible
A estrutura completa do meu repositório de dotfiles ficou assim:
~/dotfiles/
├── ansible/
│ ├── playbook.yml
│ ├── inventory.ini
│ └── tasks/
│ ├── packages.yml
│ ├── stow.yml
│ ├── ssh.yml
│ └── vim-plug.yml
├── bash/
│ └── .bashrc
├── git/
│ └── .gitconfig
│ └── .gitignore_global
├── nvim/
│ └── .config/nvim/
├── tmux/
│ └── .tmux.conf
├── zsh/
│ └── .zshrc
├── install.sh # ponto de entrada
└── README.md
O Playbook Principal
---
# ansible/playbook.yml
- name: Configurar ambiente de desenvolvimento
hosts: localhost
connection: local
vars:
stow_packages:
- bash
- git
- nvim
- tmux
- zsh
fedora_packages:
- neovim
- tmux
- zsh
- git
- ripgrep
- fd-find
- bat
- curl
- stow
- util-linux-user # para chsh
ubuntu_packages:
- neovim
- tmux
- zsh
- git
- ripgrep
- fd-find
- bat
- curl
- stow
tasks:
- name: Detectar distribuição
ansible.builtin.setup:
filter: ansible_distribution
- name: Instalar pacotes (Fedora)
become: true
ansible.builtin.dnf:
name: "{{ fedora_packages }}"
state: present
when: ansible_distribution == "Fedora"
- name: Instalar pacotes (Ubuntu/Debian)
become: true
ansible.builtin.apt:
name: "{{ ubuntu_packages }}"
state: present
update_cache: true
when: ansible_distribution in ["Ubuntu", "Debian"]
- name: Stow dotfiles
ansible.builtin.command:
cmd: "stow -t {{ ansible_env.HOME }} {{ item }}"
chdir: "{{ playbook_dir }}/.."
loop: "{{ stow_packages }}"
changed_when: false
- name: Instalar Vim-Plug (Neovim)
ansible.builtin.shell: |
curl -fLo "{{ ansible_env.HOME }}/.local/share/nvim/site/autoload/plug.vim" --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
args:
creates: "{{ ansible_env.HOME }}/.local/share/nvim/site/autoload/plug.vim"
- name: Instalar plugins do Neovim
ansible.builtin.command:
cmd: nvim --headless +PlugInstall +qall
changed_when: false
ignore_errors: true
- name: Instalar TPM (Tmux Plugin Manager)
ansible.builtin.git:
repo: "https://github.com/tmux-plugins/tpm"
dest: "{{ ansible_env.HOME }}/.tmux/plugins/tpm"
depth: 1
- name: Definir Zsh como shell padrão
become: true
ansible.builtin.user:
name: "{{ ansible_user_id }}"
shell: /bin/zsh
when: ansible_user_id != "root"
- name: Gerar chave SSH se não existir
ansible.builtin.command:
cmd: 'ssh-keygen -t ed25519 -C "{{ ssh_email }}" -f "{{ ansible_env.HOME }}/.ssh/id_ed25519" -N ""'
args:
creates: "{{ ansible_env.HOME }}/.ssh/id_ed25519"
O Script de Entrada
#!/bin/bash
# install.sh — Clone e execute
set -euo pipefail
DOTFILES_DIR="$HOME/dotfiles"
if [ ! -d "$DOTFILES_DIR" ]; then
echo "❌ Clone o repositório primeiro:"
echo " git clone git@github.com:SEU_USER/dotfiles.git $DOTFILES_DIR"
exit 1
fi
# Instalar Ansible se não existir
if ! command -v ansible &>/dev/null; then
echo "📦 Instalando Ansible..."
if command -v dnf &>/dev/null; then
sudo dnf install -y ansible
elif command -v apt &>/dev/null; then
sudo apt update && sudo apt install -y ansible
else
pip3 install --user ansible
fi
fi
# Rodar o playbook
echo "🚀 Configurando ambiente..."
cd "$DOTFILES_DIR"
ansible-playbook ansible/playbook.yml \
--extra-vars "ssh_email=${1:-$(git config user.email)}" \
-v
echo ""
echo "✅ Ambiente configurado! Reinicie o terminal ou execute:"
echo " source ~/.zshrc"
Gerenciando Configurações Específicas Por Máquina
Um problema real: seu notebook e seu servidor não precisam das mesmas coisas. Você quer Oh My Zsh com Powerlevel10k no notebook, mas no servidor quer só o básico funcional.
A solução que funcionou pra mim é usar includes condicionais no .gitconfig e variáveis no Ansible:
# ~/.gitconfig (base)
[user]
name = Seu Nome
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
[includeIf "gitdir:~/pessoal/"]
path = ~/.gitconfig-pessoal
[core]
editor = nvim
autocrlf = input
[alias]
s = status
l = log --oneline --graph --decorate -15
co = checkout
br = branch
E no Ansible, você pode usar group_vars pra definir o que vai em cada tipo de máquina:
~/dotfiles/
├── ansible/
│ ├── group_vars/
│ │ ├── desktop.yml # GUI, fontes, tema, barra de status
│ │ └── server.yml # mínimo indispensável
│ ├── host_vars/
│ │ └── vps-production.yml
│ └── inventory.ini
# ansible/inventory.ini
[desktop]
localhost
[server]
vps-production ansible_host=seu.server.com
[desktop:vars]
stow_packages=bash,git,nvim,tmux,zsh,alacritty
[server:vars]
stow_packages=bash,git,nvim,tmux
Assim, ao rodar ansible-playbook playbook.yml --limit server, só os dotfiles essenciais são implantados. Sem bloat.
O Repositório Git: Versão, Backup e Portabilidade
O repositório de dotfiles no GitHub é sua seguro contra amnésia digital. Cada mudança é um commit. Cada máquina é um clone. E se algo quebrar, git diff te diz exatamente o que mudou.
Mas tem um detalhe crucial: o que NÃO versionar.
# .gitignore do repositório de dotfiles
# Segredos — NUNCA comitar
.env
.netrc
.ssh/id_*
.ssh/known_hosts
.config/gh/hosts.yml
# Machine-specific
.local/
.cache/
# Regeneráveis
.tmux/plugins/
.config/nvim/plugged/
.config/nvim/autoload/
O Ansible cuida de regenerar o que for necessário (plugins, autoloads). O Git guarda a intenção; o Ansible executa.
Se quiser ir além, o mesmo princípio de versionamento com Git que uso pro Obsidian se aplica aqui: branch por máquina, merge quando quiser sincronizar, revert quando algo quebrar.
Box Perrengue: O Dia Que Eu Commitei Minha SSH Key
🚨 Log de Erros #47 — O perrengue que me ensinou .gitignore
Primeiro commit do repositório de dotfiles. Empolgado, mandei
git add .egit pushsem revisar. Commitei tudo. Inclusive a pasta.ssh/inteira com minha chave privada.Não percebi até receber um alerta do GitHub: “Pushed SSH private key detected”. Eles literalmente me notificaram que eu tinha vazado minha própria chave. Cortesia da GitHub, que faz esse scan automático.
Lições aprendidas:
1.
git add .é um atalho pro desastre. Sempre usegit add -ppra revisar.
2. SSH keys no.gitignoreantes de qualquer commit.
3. Se vazar, revogue a chave imediatamente. Eu demorei 2 horas pra perceber e já tinha sido clonada por um scanner antes de eu deletar. Felizmente, a GitHub bloqueou o push com a chave antes que ficasse pública de verdade.Dica: Adicione gitignore templates ao seu repo. Eu uso um
.gitignoreglobal no repo com tudo que é sensível. E, se quiser ir paranoico (recomendo), use as mesmas práticas de segurança que aplico em servidores.
O Workflow Completo: Do Zero ao Ambiente Pronto
Resumindo, aqui está o fluxo completo que uso hoje:
Na máquina nova:
# 1. Clonar o repo
git clone git@github.com:SEU_USER/dotfiles.git ~/dotfiles
# 2. Rodar o installer
cd ~/dotfiles && chmod +x install.sh
./install.sh seu@email.com
# 3. Aguardar ~3 minutos (pacotes + stow + plugins)
# 4. Reiniciar o terminal
source ~/.zshrc
Quando quero alterar uma config:
# 1. Editar o arquivo normalmente
nvim ~/.tmux.conf # (que é um symlink pro repo)
# 2. Commitar a mudança
cd ~/dotfiles
git add tmux/.tmux.conf
git commit -m "tmux: habilitar mouse scroll"
git push
# 3. Nas outras máquinas, só pull
cd ~/dotfiles && git pull
Quando formato o PC:
# Install Fedora/Ubuntu
# Instalar Git
# Clonar dotfiles
# Rodar install.sh
# Ir tomar café enquanto Ansible faz tudo
# Voltar com ambiente idêntico
Simples assim. De 6 horas manuais pra 15 minutos automatizados. E cada segundo foi ganho porque parei de ser teimoso e comecei a ser metódico.
Stow vs. Alternativas: Chega de Comparação superficial
Existem outras ferramentas pra gerenciar dotfiles. Vou ser direto sobre cada uma:
- Chezmoidi: Unifica tudo em um lugar. Bom, mas é uma ferramenta a mais pra aprender e maintain. O Stow já vem no package manager de qualquer distro.
- YADM (Yet Another Dotfiles Manager): Usa Git diretamente com worktree na home. Inteligente, mas conflitos de Git na home são um pesadelo pra debugar.
- Bare Git Repository: A mesma ideia do YADM, mas manual. Funciona até o dia que
git statusna home te mostra 500 untracked files. - GNU Stow: Faz UMA coisa e faz bem. Symlinks limpos, reversíveis, sem mágica. Se você já sabe usar symlinks, já sabe usar Stow.
O Ansible não é concorrente do Stow — é complementar. O Stow gerencia os arquivos; o Ansible gerencia o ambiente. Juntos, cobrem tudo. E, como mostrei em automações com n8n, automatizar é sobre combinar as ferramentas certas, não sobre encontrar A Ferramenta Única.
Dicas Que Eu Queria Ter Sabido No Começo
- Comece pequeno. Não tente stowar tudo de uma vez. Comece com
.bashrce.gitconfig. Adicione o resto aos poucos. - Teste numa máquina virtual antes. Clone o repo, rode o playbook. Se algo quebrar, é VM — ninguém se machuca.
- Use tags no Ansible pra poder rodar partes específicas:
ansible-playbook playbook.yml --tags "stow" - Versionamento semântico funciona pra dotfiles também. Quando mudo algo grande no Neovim, faço um tag:
git tag nvim-v2. Se der ruim,git checkout nvim-v2. - Não versione segredos. Nunca. Sério. Use variáveis de ambiente ou um password manager. Se quiser saber como montar um sistema de senhas que funciona, contei tudo neste post sobre Bitwarden e 2FA.
- Documente no README. Seu eu do futuro vai agradecer. Anote o que cada pacote faz e quais dependências ele assume.
E Agora, Qual Automação Você Quer Ver?
Gerenciar dotfiles é só a ponta do iceberg. Uma vez que você tem um ambiente versionado e automatizado, as possibilidades explodem: containers com Docker que herdam suas configs, scripts de provisioning pra novas VMs, CI/CD pra validar seus dotfiles antes de deploy…
Me conta nos comentários: qual parte do seu ambiente você mais sofre pra reconfigurar quando formata? Qual automação você quer ver aqui no Lab da Garra? Tô coletando ideias pra próximos posts — e prometo que vou automatizar todas.
E se você já usa Stow, Ansible, ou qualquer outra abordagem pra seus dotfiles, compartilha aí. A melhor configuração é aquela que funciona pra você — mas a segunda melhor é aquela que alguém já debugou antes de você. 😉
