<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Thiago Vasconcelos</title>
    <description>The latest articles on DEV Community by Thiago Vasconcelos (@thiagovasc).</description>
    <link>https://dev.to/thiagovasc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F824879%2F95579f91-1163-4c09-9018-1dbe372d039d.jpeg</url>
      <title>DEV Community: Thiago Vasconcelos</title>
      <link>https://dev.to/thiagovasc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thiagovasc"/>
    <language>en</language>
    <item>
      <title>Salvando um notebook velho + Homelab</title>
      <dc:creator>Thiago Vasconcelos</dc:creator>
      <pubDate>Thu, 17 Sep 2026 14:52:37 +0000</pubDate>
      <link>https://dev.to/thiagovasc/salvando-um-notebook-velho-homelab-5bid</link>
      <guid>https://dev.to/thiagovasc/salvando-um-notebook-velho-homelab-5bid</guid>
      <description>&lt;h1&gt;
  
  
  De notebook velho a home server: começando com um simples Pi-hole
&lt;/h1&gt;

&lt;p&gt;Eu só queria reaproveitar um notebook velho.&lt;/p&gt;

&lt;p&gt;Era um Samsung NP270E4E, com um Core i3 de terceira geração e 12 GB de RAM. Hardware que já não fazia muito sentido como computador principal, mas que ainda tinha recursos suficientes para ficar ligado em casa executando serviços leves.&lt;/p&gt;

&lt;p&gt;O plano inicial parecia simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;instalar Linux;&lt;/li&gt;
&lt;li&gt;usar o notebook como home server;&lt;/li&gt;
&lt;li&gt;colocar alguns serviços em Docker;&lt;/li&gt;
&lt;li&gt;instalar Pi-hole;&lt;/li&gt;
&lt;li&gt;futuramente ter agentes autonomos e/ou equipe agêntica&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O que começou como reaproveitamento de hardware acabou se transformando em um laboratório para experimentar conceitos que normalmente associamos a sistemas de produção: rollout gradual, dry-run, fail-closed, audit trail, reconciliação de estado, rollback e observabilidade com minimização de dados.&lt;/p&gt;

&lt;p&gt;E boa parte disso começou porque a placa de rede do notebook não era confiável.&lt;/p&gt;




&lt;h2&gt;
  
  
  O primeiro problema não foi Docker. Foi a rede.
&lt;/h2&gt;

&lt;p&gt;Antes de pensar em Pi-hole, precisei tornar o servidor minimamente confiável.&lt;/p&gt;

&lt;p&gt;A Ethernet onboard apresentava quedas recorrentes de link. Não era um problema de aplicação, container ou DNS. Era um problema físico — exatamente o tipo de coisa que um homelab feito com hardware antigo eventualmente obriga você a enfrentar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Um adendo a correção via substituição do hardware com um usb ethernet adapter, tive que usar comandos que não conhecia ou pouco usei
&lt;/h2&gt;

&lt;p&gt;Até então eu só tinha usado Linux para escapar dos bloats do windows ou como uma especie de plataforma de desenvolvimento mais livre, mas nunca me preocupando como administrar um servidor sem interface gráfica, começou a me obrigar a entender melhor as ferramentas que o próprio sistema oferece para explicar o que está acontecendo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugando o servidor
&lt;/h3&gt;

&lt;p&gt;Comandos como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status &amp;lt;service&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ajudavam a verificar se os componentes responsáveis pela rede estavam ativos, enquanto o &lt;code&gt;journalctl&lt;/code&gt; permitia olhar para trás e descobrir o que havia acontecido antes da queda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou filtrar eventos relacionados ao boot, kernel e serviços específicos.&lt;/p&gt;

&lt;p&gt;Também comecei a usar ferramentas como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip addr
ip route
networkctl status
resolvectl status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para diferenciar problemas de endereço IP, rota, DNS e estado físico da interface.&lt;/p&gt;

&lt;p&gt;Foi através dessa investigação que apareceram eventos recorrentes de perda e recuperação de link na Ethernet onboard.&lt;/p&gt;

&lt;p&gt;Ou seja: a aplicação não estava caindo, o Docker não era o problema e o DNS sequer era o principal suspeito.&lt;/p&gt;

&lt;p&gt;O próprio link de rede estava desaparecendo.&lt;/p&gt;

&lt;p&gt;Esse momento foi importante porque mudou a forma como eu fazia troubleshooting.&lt;/p&gt;

&lt;p&gt;Em vez de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;não funciona
    ↓
reiniciar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;o processo começou a ficar mais parecido com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sintoma
   ↓
estado atual
   ↓
logs históricos
   ↓
hipótese
   ↓
teste
   ↓
evidência
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o &lt;code&gt;journalctl&lt;/code&gt; foi particularmente útil porque mostrou uma característica importante de administrar Linux: muitas vezes o problema já deixou pistas suficientes no sistema — você só precisa saber onde procurar.&lt;/p&gt;

&lt;p&gt;A partir dali o homelab também virou uma desculpa prática para aprender ferramentas Unix que eu dificilmente estudaria apenas lendo documentação.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemctl&lt;/code&gt;, &lt;code&gt;journalctl&lt;/code&gt;, &lt;code&gt;ip&lt;/code&gt;, &lt;code&gt;networkctl&lt;/code&gt;, &lt;code&gt;resolvectl&lt;/code&gt;, &lt;code&gt;ss&lt;/code&gt;, &lt;code&gt;dig&lt;/code&gt; e outras ferramentas deixaram de ser comandos isolados e passaram a fazer parte de um processo real de diagnóstico.&lt;/p&gt;

&lt;p&gt;No fim, a decisão de substituir a Ethernet onboard por um adaptador USB não veio apenas de uma impressão de que “a rede estava ruim”.&lt;/p&gt;

&lt;p&gt;Veio de evidência coletada no próprio sistema.&lt;/p&gt;

&lt;p&gt;E essa talvez tenha sido uma das primeiras mudanças de mentalidade do projeto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Antes de corrigir um problema, tente fazer o sistema explicar o que está acontecendo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A solução prática foi introduzir um adaptador Ethernet USB, só assim tinha sessões ssh estaveis e possibilidade de seguir com demais projetos sem intermitencia de hardware&lt;/p&gt;

&lt;p&gt;Mas eu não queria trocar uma interface por outra e torcer para funcionar.&lt;/p&gt;

&lt;p&gt;A partir daí começou a surgir um padrão que, sem perceber, eu acabaria reutilizando no restante do projeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inspect
  ↓
plan
  ↓
test
  ↓
validate
  ↓
apply
  ↓
validate again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antes de alterar a configuração de rede, foram criadas verificações somente leitura para entender interfaces, rotas e estado do servidor.&lt;/p&gt;

&lt;p&gt;Quando chegou a hora de definir um endereço estável para a nova interface, a mudança passou primeiro por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netplan generate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netplan try
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Somente depois de validar conectividade e acesso durante a janela de teste a configuração foi persistida.&lt;/p&gt;

&lt;p&gt;O Wi-Fi permaneceu disponível como fallback.&lt;/p&gt;

&lt;p&gt;Parece exagero para um notebook em casa.&lt;/p&gt;

&lt;p&gt;Mas havia uma razão simples: eu administrava aquele servidor remotamente. Uma configuração errada de rede poderia significar levantar da cadeira, conectar teclado e monitor e descobrir o que eu tinha quebrado.&lt;/p&gt;

&lt;p&gt;Foi provavelmente a primeira lição relevante do projeto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nunca faça uma mudança capaz de remover seu próprio acesso sem saber previamente como voltar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Naquele momento eu ainda não chamava isso de control plane.&lt;/p&gt;

&lt;p&gt;Mas a ideia já estava ali.&lt;/p&gt;




&lt;h2&gt;
  
  
  Então veio o Pi-hole
&lt;/h2&gt;

&lt;p&gt;Com a rede suficientemente estável, o próximo passo foi transformar o notebook em servidor DNS da rede usando Pi-hole.&lt;/p&gt;

&lt;p&gt;A aplicação foi preparada em Docker Compose, com estado persistente fora do repositório e configuração sensível mantida localmente.&lt;/p&gt;

&lt;p&gt;O objetivo da primeira execução também foi deliberadamente limitado.&lt;/p&gt;

&lt;p&gt;Nada de alterar o DNS do roteador.&lt;/p&gt;

&lt;p&gt;Nada de migrar todos os dispositivos da casa.&lt;/p&gt;

&lt;p&gt;Nada de transformar imediatamente o servidor no ponto central da rede.&lt;/p&gt;

&lt;p&gt;Primeiro, o objetivo era apenas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;subir o container;&lt;/li&gt;
&lt;li&gt;verificar saúde;&lt;/li&gt;
&lt;li&gt;verificar portas;&lt;/li&gt;
&lt;li&gt;testar DNS diretamente contra o Pi-hole;&lt;/li&gt;
&lt;li&gt;verificar a interface web;&lt;/li&gt;
&lt;li&gt;confirmar que o restante do servidor continuava intacto.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;O container subiu.&lt;/p&gt;

&lt;p&gt;O healthcheck ficou saudável.&lt;/p&gt;

&lt;p&gt;As portas pareciam corretas.&lt;/p&gt;

&lt;p&gt;E o DNS não funcionou.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;healthy&lt;/code&gt; não significa que o serviço funciona
&lt;/h2&gt;

&lt;p&gt;Esse foi um dos episódios mais úteis do projeto.&lt;/p&gt;

&lt;p&gt;O Docker dizia que o container estava saudável, mas consultas DNS reais não recebiam resposta.&lt;/p&gt;

&lt;p&gt;Investigando os logs, ficou claro que o Pi-hole estava ignorando as consultas porque elas chegavam através do caminho de rede do Docker e eram classificadas como não locais.&lt;/p&gt;

&lt;p&gt;A solução não foi simplesmente usar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;network_mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;nem publicar DNS indiscriminadamente em todas as interfaces.&lt;/p&gt;

&lt;p&gt;Mantivemos a exposição restrita ao endereço destinado ao serviço e alteramos explicitamente o modo de escuta do Pi-hole.&lt;/p&gt;

&lt;p&gt;Depois de recriar o container, as consultas passaram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig @&amp;lt;PIHOLE_IP&amp;gt; pi-hole.net
dig @&amp;lt;PIHOLE_IP&amp;gt; google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A interface web também respondeu como esperado.&lt;/p&gt;

&lt;p&gt;Foi uma pequena falha, mas consolidou outra regra:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Container saudável é evidência de que o container está saudável. Não é evidência suficiente de que o sistema funciona.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Healthcheck não substitui teste funcional.&lt;/p&gt;




&lt;h2&gt;
  
  
  O rollout começou com um cliente
&lt;/h2&gt;

&lt;p&gt;Com o Pi-hole funcionando no servidor, seria tentador alterar o DNS do roteador e migrar toda a casa.&lt;/p&gt;

&lt;p&gt;Não fizemos isso.&lt;/p&gt;

&lt;p&gt;O próximo teste foi feito a partir de um único cliente.&lt;/p&gt;

&lt;p&gt;A estratégia era simples: apontar explicitamente algumas consultas para o Pi-hole sem alterar ainda a configuração persistente do sistema operacional.&lt;/p&gt;

&lt;p&gt;Consultas normais funcionaram.&lt;/p&gt;

&lt;p&gt;Um domínio conhecido da blocklist retornou a resposta de bloqueio esperada.&lt;/p&gt;

&lt;p&gt;Até aí, ótimo.&lt;/p&gt;

&lt;p&gt;Mas outros testes não passaram.&lt;/p&gt;

&lt;p&gt;A conectividade TCP para DNS apresentou problema.&lt;/p&gt;

&lt;p&gt;A interface web não estava acessível pelo mesmo caminho do cliente.&lt;/p&gt;

&lt;p&gt;A tentativa de alterar persistentemente o DNS do Windows também encontrou restrições de permissão.&lt;/p&gt;

&lt;p&gt;Tecnicamente, parte do sistema funcionava.&lt;/p&gt;

&lt;p&gt;A classificação do teste, entretanto, não foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUCCESS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PARTIAL / BLOCKED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o rollout global permaneceu bloqueado.&lt;/p&gt;

&lt;p&gt;Essa talvez tenha sido uma das decisões mais importantes do projeto.&lt;/p&gt;

&lt;p&gt;Porque existe uma diferença enorme entre:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Eu consegui resolver um domínio usando o Pi-hole."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;e:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Tenho confiança operacional suficiente para transformar esse serviço no DNS da minha rede."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O primeiro é um teste.&lt;/p&gt;

&lt;p&gt;O segundo é uma decisão operacional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Client-by-client em vez de big bang
&lt;/h2&gt;

&lt;p&gt;Os problemas foram sendo investigados, novos critérios de validação apareceram e mais clientes foram adicionados gradualmente.&lt;/p&gt;

&lt;p&gt;Nesse meio tempo também ficou claro que o acesso administrativo ao roteador não era tão simples quanto gostaríamos.&lt;/p&gt;

&lt;p&gt;A arquitetura poderia ter sido forçada.&lt;/p&gt;

&lt;p&gt;Em vez disso, o Pi-hole passou a operar inicialmente em modo &lt;strong&gt;client-by-client&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cada dispositivo era incorporado de forma controlada.&lt;/p&gt;

&lt;p&gt;Isso não era o desenho mais elegante possível.&lt;/p&gt;

&lt;p&gt;Mas era o desenho cujo rollback eu conhecia.&lt;/p&gt;

&lt;p&gt;Essa escolha resume bastante a filosofia que começou a aparecer no homelab:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O caminho operacionalmente previsível pode ser melhor que o caminho arquiteturalmente mais bonito.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Em algum momento, o Pi-hole deixou de ser apenas um Pi-hole
&lt;/h2&gt;

&lt;p&gt;Até aí o projeto ainda poderia ser descrito como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clients
   │
   ▼
Pi-hole
   │
   ▼
upstream DNS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas o &lt;code&gt;homelab-ai&lt;/code&gt; tem um objetivo maior.&lt;/p&gt;

&lt;p&gt;A ideia é que alguns serviços do homelab eventualmente possam ser operados através de uma camada de automação — e, no futuro, possivelmente através de ferramentas, agentes ou MCP.&lt;/p&gt;

&lt;p&gt;Isso muda completamente a pergunta.&lt;/p&gt;

&lt;p&gt;Deixa de ser apenas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Como bloquear um domínio?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E passa a ser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Como permitir que software altere infraestrutura doméstica sem transformar qualquer bug, prompt ruim ou erro de integração em uma mudança perigosa?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A API administrativa do Pi-hole tornou essa questão concreta.&lt;/p&gt;

&lt;p&gt;Tecnicamente, seria possível construir uma abstração simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /block
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;recebendo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e chamar diretamente a API.&lt;/p&gt;

&lt;p&gt;Isso funciona.&lt;/p&gt;

&lt;p&gt;Também seria uma excelente forma de construir uma automação perigosa.&lt;/p&gt;




&lt;h2&gt;
  
  
  O bloqueio virou um pequeno control plane
&lt;/h2&gt;

&lt;p&gt;Antes de expor qualquer operação de escrita para componentes maiores do projeto, foi criada uma política específica para mutações no Pi-hole.&lt;/p&gt;

&lt;p&gt;O primeiro caso implementado foi intencionalmente minúsculo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;adicionar uma única regra exact-domain deny.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sem regex.&lt;/p&gt;

&lt;p&gt;Sem wildcard.&lt;/p&gt;

&lt;p&gt;Sem edição em massa.&lt;/p&gt;

&lt;p&gt;Sem alteração de grupos.&lt;/p&gt;

&lt;p&gt;Sem manipulação arbitrária de clientes.&lt;/p&gt;

&lt;p&gt;Sem endpoint remoto.&lt;/p&gt;

&lt;p&gt;Uma única operação.&lt;/p&gt;

&lt;p&gt;Mas com várias salvaguardas.&lt;/p&gt;

&lt;p&gt;O fluxo ficou aproximadamente assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request
   │
   ▼
input validation
   │
   ▼
read current state
   │
   ▼
dry-run
   │
   ▼
short-lived plan
   │
   ├─ target state
   ├─ expected pre-state
   ├─ expiry
   └─ digest
   │
   ▼
explicit apply
   │
   ▼
single mutation
   │
   ▼
post-state verification
   │
   ▼
audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E existe um caminho separado para rollback.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dry-run deixou de ser uma flag decorativa
&lt;/h2&gt;

&lt;p&gt;Uma das decisões principais foi tornar o dry-run obrigatório.&lt;/p&gt;

&lt;p&gt;O sistema não recebe uma solicitação e imediatamente modifica o Pi-hole.&lt;/p&gt;

&lt;p&gt;Primeiro ele produz um plano.&lt;/p&gt;

&lt;p&gt;Esse plano possui vida curta — dez minutos — e é associado a um digest.&lt;/p&gt;

&lt;p&gt;Quando chega a hora do &lt;code&gt;apply&lt;/code&gt;, o sistema verifica novamente o estado atual antes de realizar a alteração.&lt;/p&gt;

&lt;p&gt;Isso reduz alguns riscos importantes.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T0: plano criado
T1: outro processo altera o estado
T2: apply executado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem reconciliação, o plano poderia operar sobre premissas que não existem mais.&lt;/p&gt;

&lt;p&gt;Por isso o apply não assume que o mundo permaneceu congelado.&lt;/p&gt;

&lt;p&gt;Ele observa novamente.&lt;/p&gt;




&lt;h2&gt;
  
  
  Uma mutação significa uma mutação
&lt;/h2&gt;

&lt;p&gt;Outro detalhe proposital:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;não existe retry automático para operações de escrita.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Retries são excelentes para muitas operações read-only.&lt;/p&gt;

&lt;p&gt;Para mutações, podem ser perigosos.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request sent
      │
      ▼
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O timeout não responde uma pergunta essencial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a operação falhou
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a operação aconteceu, mas a resposta se perdeu?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o sistema simplesmente tentar novamente, pode executar a mesma alteração duas vezes ou esconder um estado ambíguo.&lt;/p&gt;

&lt;p&gt;No control plane do Pi-hole, uma operação de escrita tenta no máximo uma mutação.&lt;/p&gt;

&lt;p&gt;Se o resultado não puder ser determinado com segurança, o estado é classificado como incerto e exige reconciliação.&lt;/p&gt;

&lt;p&gt;Isso é bem menos conveniente que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;try_again&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E exatamente por isso é mais interessante.&lt;/p&gt;




&lt;h2&gt;
  
  
  O rollback também precisa provar que é dono da mudança
&lt;/h2&gt;

&lt;p&gt;Remover uma entrada parece simples.&lt;/p&gt;

&lt;p&gt;Mas existe uma pergunta importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Como sabemos que essa entrada ainda é a mesma entrada criada por nós?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O MVP mantém um marcador de ownership associado à alteração e registra eventos de auditoria.&lt;/p&gt;

&lt;p&gt;O rollback só é autorizado quando o estado atual e o histórico esperado correspondem.&lt;/p&gt;

&lt;p&gt;Isso evita que a ferramenta simplesmente delete algo porque encontrou um domínio igual.&lt;/p&gt;

&lt;p&gt;Em outras palavras:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"essa entrada existe"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;não é suficiente.&lt;/p&gt;

&lt;p&gt;Precisamos conseguir afirmar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"essa entrada existe,
foi criada por esta operação,
há evidência de auditoria correspondente,
e seu estado continua compatível com o rollback"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Só então a operação prossegue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Locks, estado privado e fail-closed
&lt;/h2&gt;

&lt;p&gt;O fluxo também possui lock exclusivo durante as janelas críticas de planejamento e aplicação.&lt;/p&gt;

&lt;p&gt;Dois processos não devem poder preparar ou aplicar mudanças concorrentes assumindo o mesmo estado inicial.&lt;/p&gt;

&lt;p&gt;O estado operacional sensível — credenciais, planos, auditoria e arquivos de controle — fica fora do Git.&lt;/p&gt;

&lt;p&gt;Permissões incorretas, arquivos inesperados, symlinks ou estado local considerado inseguro fazem a operação falhar.&lt;/p&gt;

&lt;p&gt;A lógica é deliberadamente &lt;strong&gt;fail-closed&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;não consigo provar que é seguro
            ↓
          parar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em vez de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provavelmente está tudo certo
            ↓
          continuar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Então testamos de verdade
&lt;/h2&gt;

&lt;p&gt;Implementar salvaguardas sem exercitá-las seria pouco convincente.&lt;/p&gt;

&lt;p&gt;O MVP recebeu uma suíte sintética cobrindo cenários como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entrada inválida;&lt;/li&gt;
&lt;li&gt;estado local inseguro;&lt;/li&gt;
&lt;li&gt;plano expirado;&lt;/li&gt;
&lt;li&gt;plano adulterado;&lt;/li&gt;
&lt;li&gt;conflitos;&lt;/li&gt;
&lt;li&gt;concorrência;&lt;/li&gt;
&lt;li&gt;confirmação interativa;&lt;/li&gt;
&lt;li&gt;resultado de mutação ambíguo;&lt;/li&gt;
&lt;li&gt;logout da sessão;&lt;/li&gt;
&lt;li&gt;rollback autorizado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A suíte passou com 16 testes.&lt;/p&gt;

&lt;p&gt;Depois veio o runtime dry-run.&lt;/p&gt;

&lt;p&gt;E, finalmente, uma janela controlada de validação.&lt;/p&gt;

&lt;p&gt;Nenhum domínio real foi usado.&lt;/p&gt;

&lt;p&gt;O alvo era um domínio reservado &lt;code&gt;.invalid&lt;/code&gt;, criado especificamente para o teste.&lt;/p&gt;

&lt;p&gt;O fluxo executado foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plan
  ↓
apply
  ↓
verify post-state
  ↓
create rollback plan
  ↓
rollback
  ↓
verify final state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O bloqueio foi aplicado.&lt;/p&gt;

&lt;p&gt;O novo estado foi confirmado.&lt;/p&gt;

&lt;p&gt;O rollback foi planejado.&lt;/p&gt;

&lt;p&gt;O rollback foi executado.&lt;/p&gt;

&lt;p&gt;E a verificação final confirmou que a entrada fictícia não permaneceu configurada.&lt;/p&gt;

&lt;p&gt;Esse foi o ponto em que, para mim, o projeto deixou definitivamente de ser apenas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Tenho Pi-hole rodando em casa."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Observabilidade também pode vazar informação
&lt;/h2&gt;

&lt;p&gt;O passo seguinte trouxe outro problema interessante.&lt;/p&gt;

&lt;p&gt;DNS é uma fonte extremamente sensível de dados.&lt;/p&gt;

&lt;p&gt;Um sistema de observabilidade mal projetado poderia transformar algo criado para melhorar a operação do homelab em um registro detalhado da atividade da casa.&lt;/p&gt;

&lt;p&gt;Por isso definimos uma separação explícita entre três superfícies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pi-hole native data
        │
        ├─ queries
        ├─ domains
        └─ clients

control-plane observability
        │
        ├─ health
        ├─ operation status
        └─ bounded categories

private mutation audit
        │
        └─ recovery information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os dados não devem ser misturados.&lt;/p&gt;

&lt;p&gt;A primeira versão de observabilidade não pode registrar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;domínios;&lt;/li&gt;
&lt;li&gt;consultas DNS;&lt;/li&gt;
&lt;li&gt;clientes;&lt;/li&gt;
&lt;li&gt;endereços;&lt;/li&gt;
&lt;li&gt;credenciais;&lt;/li&gt;
&lt;li&gt;IDs de planos;&lt;/li&gt;
&lt;li&gt;IDs de auditoria;&lt;/li&gt;
&lt;li&gt;respostas HTTP cruas;&lt;/li&gt;
&lt;li&gt;stack traces;&lt;/li&gt;
&lt;li&gt;erros arbitrários.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Até métricas DNS agregadas foram adiadas.&lt;/p&gt;

&lt;p&gt;Em uma infraestrutura enorme, um contador global pode parecer anônimo.&lt;/p&gt;

&lt;p&gt;Em uma casa com poucos dispositivos, até um gráfico agregado de atividade pode revelar padrões sobre os moradores.&lt;/p&gt;

&lt;p&gt;Às vezes privacy engineering não significa criptografar mais dados.&lt;/p&gt;

&lt;p&gt;Significa simplesmente decidir:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Não precisamos coletar isso.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  O hardware continua sendo ruim — e isso é ótimo
&lt;/h2&gt;

&lt;p&gt;O servidor continua sendo um notebook antigo.&lt;/p&gt;

&lt;p&gt;O processador continua sendo um Core i3 de terceira geração.&lt;/p&gt;

&lt;p&gt;A Ethernet onboard não magicamente se tornou confiável.&lt;/p&gt;

&lt;p&gt;O objetivo nunca foi competir com um servidor moderno.&lt;/p&gt;

&lt;p&gt;Mas essa limitação acabou sendo parte importante do valor do projeto.&lt;/p&gt;

&lt;p&gt;Quando hardware, rede e acesso administrativo não são perfeitos, você precisa pensar mais sobre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recovery;&lt;/li&gt;
&lt;li&gt;fallback;&lt;/li&gt;
&lt;li&gt;rollout;&lt;/li&gt;
&lt;li&gt;observabilidade;&lt;/li&gt;
&lt;li&gt;dependências;&lt;/li&gt;
&lt;li&gt;blast radius;&lt;/li&gt;
&lt;li&gt;estado persistente;&lt;/li&gt;
&lt;li&gt;operações irreversíveis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uma cloud abstrata muitas vezes esconde essas coisas.&lt;/p&gt;

&lt;p&gt;Um notebook velho na sua casa não esconde.&lt;/p&gt;

&lt;p&gt;Se você derrubar a rede, você percebe.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que eu realmente construí?
&lt;/h2&gt;

&lt;p&gt;Se eu resumisse o estado atual visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;old laptop
    │
    ▼
Linux home server
    │
    ▼
network hardening
    │
    ▼
Docker
    │
    ▼
Pi-hole
    │
    ▼
client-by-client DNS
    │
    ▼
authenticated API
    │
    ▼
controlled mutation layer
    │
    ├─ dry-run
    ├─ expiring plans
    ├─ locking
    ├─ audit
    ├─ reconciliation
    └─ rollback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não criei uma alternativa ao Pi-hole.&lt;/p&gt;

&lt;p&gt;Também não inventei nenhuma dessas técnicas.&lt;/p&gt;

&lt;p&gt;O que fiz foi usar um serviço doméstico real como ambiente para praticar princípios de engenharia que normalmente aparecem em sistemas muito maiores.&lt;/p&gt;

&lt;p&gt;E talvez essa seja a parte mais útil de um homelab.&lt;/p&gt;

&lt;p&gt;Você não precisa simular completamente uma empresa.&lt;/p&gt;

&lt;p&gt;Só precisa criar problemas reais o bastante para ser obrigado a tomar decisões de engenharia.&lt;/p&gt;




&lt;h2&gt;
  
  
  E onde entra IA nisso?
&lt;/h2&gt;

&lt;p&gt;Essa é a próxima parte.&lt;/p&gt;

&lt;p&gt;O projeto se chama &lt;code&gt;homelab-ai&lt;/code&gt;, mas deliberadamente ainda não coloquei um agente controlando essas mutações.&lt;/p&gt;

&lt;p&gt;Isso não é atraso.&lt;/p&gt;

&lt;p&gt;É parte do design.&lt;/p&gt;

&lt;p&gt;Antes de fornecer a uma LLM algo equivalente a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;block_domain(domain)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;eu quero que o mecanismo abaixo dela já saiba responder perguntas como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Essa operação é permitida?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qual é o estado atual?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O plano ainda é válido?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O estado mudou desde o dry-run?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quem criou essa alteração?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existe rollback conhecido?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O resultado da mutação é realmente conhecido?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A LLM não deveria ser o componente responsável por garantir essas propriedades.&lt;/p&gt;

&lt;p&gt;Essas garantias pertencem à infraestrutura.&lt;/p&gt;

&lt;p&gt;Se no futuro um agente ou servidor MCP chamar esse control plane, a ideia é que ele receba &lt;strong&gt;menos poder do que parece&lt;/strong&gt;, não mais.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que um notebook velho acabou me ensinando
&lt;/h2&gt;

&lt;p&gt;Quando comecei esse projeto, eu provavelmente descreveria o objetivo assim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reaproveitar um notebook velho como home server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hoje eu descreveria de outra forma:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Construir um ambiente pequeno o suficiente para eu controlar, mas real o suficiente para me obrigar a pensar sobre engenharia operacional.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O Pi-hole foi apenas o primeiro componente a tornar isso muito claro.&lt;/p&gt;

&lt;p&gt;A jornada foi mais ou menos esta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"vamos instalar Pi-hole"

        ↓

"por que a rede cai?"

        ↓

"preciso de fallback"

        ↓

"vamos testar antes de aplicar"

        ↓

"o container está healthy, mas o DNS não funciona"

        ↓

"vamos validar com um cliente"

        ↓

"funcionou parcialmente, então ainda não é rollout"

        ↓

"e se software começar a alterar o Pi-hole?"

        ↓

"precisamos de dry-run"

        ↓

"e de reconciliation"

        ↓

"e de audit"

        ↓

"e de rollback"

        ↓

"e talvez não devêssemos coletar metade desses dados"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é, até agora, a parte mais interessante do &lt;code&gt;homelab-ai&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;E o próximo problema provavelmente será ainda melhor:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;como conectar agentes e MCP a esse ambiente sem destruir exatamente as garantias que acabamos de construir?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>hardware</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
