<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Linux | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/linux/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>Usuários Linux: sempre escolham BTRFS, não EXT4</title><link>https://akitaonrails.github.io/2026/10/12/usuarios-linux-sempre-escolham-btrfs-nao-ext4/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/12/usuarios-linux-sempre-escolham-btrfs-nao-ext4/</guid><pubDate>Mon, 12 Oct 2026 23:00:00 -0300</pubDate><description>&lt;p&gt;Este texto é continuação direta do &lt;a href="https://akitaonrails.github.io/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/"&gt;desastre no meu NAS&lt;/a&gt;. Resumo pra quem chegou agora: meu Synology, com oito discos em SHR (paridade simples), perdeu dois discos numa tacada durante um upgrade. Sobrevivi por detalhe de implementação do SHR, comprei um QNAP às pressas, passei uma semana copiando e verificando 91 TB, e não perdi nenhum arquivo por acidente. O QNAP novo roda ZFS em RAID-6; o Synology, refeito, agora roda SHR-2.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este texto é continuação direta do <a href="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/">desastre no meu NAS</a>. Resumo pra quem chegou agora: meu Synology, com oito discos em SHR (paridade simples), perdeu dois discos numa tacada durante um upgrade. Sobrevivi por detalhe de implementação do SHR, comprei um QNAP às pressas, passei uma semana copiando e verificando 91 TB, e não perdi nenhum arquivo por acidente. O QNAP novo roda ZFS em RAID-6; o Synology, refeito, agora roda SHR-2.</p>
<p>Embaixo dessa história toda tem uma decisão que quase ninguém faz conscientemente: <strong>o filesystem</strong>. O Synology usava btrfs o tempo todo. O QNAP usa ZFS. E a sua máquina Linux, muito provavelmente, veio de fábrica com ext4 sem ninguém te perguntar nada. É sobre isso.</p>
<h2>De onde vieram os dois<span class="hx:absolute hx:-mt-20" id="de-onde-vieram-os-dois"></span>
    <a href="#de-onde-vieram-os-dois" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O ext4 é o final de uma dinastia de mais de três décadas. O ext (extended filesystem) nasceu com o próprio Linux, o ext2 virou padrão nos anos 90, o ext3 adicionou journal em 2001 pra não precisar de fsck épico depois de cada queda de energia, e o ext4 chegou em 2008 com extents, alocação atrasada e volumes de até 1 exbibyte. O objetivo de design sempre foi o mesmo: ser o filesystem padrão, rápido, simples e previsível. Ele é excelente nisso. E parou aí.</p>
<p>O btrfs nasceu em 2007, na Oracle, com o Chris Mason, com um objetivo completamente diferente: ser a resposta do Linux ao ZFS. Em vez de escrever dado por cima de dado, ele é copy-on-write: toda escrita vai pra um bloco novo e os ponteiros são atualizados em cascata na árvore. Essa decisão única é a mãe de tudo que interessa nele.</p>
<p>O que cada um entrega, na lista:</p>
<p><strong>ext4:</strong></p>
<ul>
<li>journaling (metadados consistentes depois de queda)</li>
<li>extents e alocação atrasada (boa performance)</li>
<li>maduro, estável, em todo lugar, qualquer ferramenta funciona</li>
</ul>
<p><strong>btrfs:</strong></p>
<ul>
<li>copy-on-write em tudo</li>
<li>checksum de dados e metadados em cada bloco</li>
<li>snapshots instantâneos e baratos</li>
<li>subvolumes independentes dentro do mesmo volume</li>
<li>compressão transparente (zstd, lzo, zlib)</li>
<li>RAID nativo e scrub (varredura de verificação)</li>
<li>send/receive de snapshots pra backup incremental</li>
<li>reflinks (cópias que não duplicam blocos)</li>
</ul>
<p>A lista já responde a pergunta do título. Vamos por partes.</p>
<h2>Até o NTFS e o APFS passaram o ext4<span class="hx:absolute hx:-mt-20" id="até-o-ntfs-e-o-apfs-passaram-o-ext4"></span>
    <a href="#at%c3%a9-o-ntfs-e-o-apfs-passaram-o-ext4" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>E se a comparação com o btrfs parece injusta, olha pro lado. O NTFS nasceu em 1993, com o Windows NT, já com journaling de metadados e ACLs, e ainda nos anos 2000 ganhou o log de todas as mudanças do volume e as cópias de sombra (o &ldquo;Versões Anteriores&rdquo; do Windows). O APFS, que a Apple lançou em 2017 feito pra flash, é copy-on-write como o btrfs: snapshots nativos (é o que o Time Machine usa pra restauração local), clones instantâneos de arquivo que compartilham blocos, volumes compartilhando o espaço do mesmo container e criptografia por arquivo.</p>
<p>Leia de novo a lista do ext4 e compare: sem snapshot, sem checksum de dados, sem CoW, sem compressão. O filesystem do Windows e o do Mac, que ninguém considera exemplos de sofisticação, protegem os seus dados melhor que o padrão do Linux, cuja última grande feature saiu em 2008. O btrfs é o que coloca o Linux na mesma geração dos outros dois, e não uma década atrás.</p>
<h2>Por que o btrfs é muito superior<span class="hx:absolute hx:-mt-20" id="por-que-o-btrfs-é-muito-superior"></span>
    <a href="#por-que-o-btrfs-%c3%a9-muito-superior" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>O que é copy-on-write, pra quem nunca parou pra pensar<span class="hx:absolute hx:-mt-20" id="o-que-é-copy-on-write-pra-quem-nunca-parou-pra-pensar"></span>
    <a href="#o-que-%c3%a9-copy-on-write-pra-quem-nunca-parou-pra-pensar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nos filesystems clássicos, a escrita é destrutiva por definição: atualizar um bloco significa gravar o conteúdo novo por cima do velho, no mesmo lugar do disco. Se a máquina morre no meio da escrita, o arquivo fica metade velho, metade novo. O journal do ext3/ext4 cobre isso para os <strong>metadados</strong> (a estrutura de diretórios não corrompe), mas o seu dado em si fica por conta da sorte.</p>
<p>Copy-on-write inverte a ordem: <strong>nada nunca é sobrescrito</strong>. Escrever num arquivo aloca um bloco novo, grava o conteúdo novo ali, e só quando está tudo seguro no disco o filesystem troca os ponteiros pra versão nova, de forma atômica. A qualquer instante, você tem ou a versão velha inteira ou a versão nova inteira. Nunca uma mistura das duas.</p>
<p>O que isso compra na prática:</p>
<ul>
<li><strong>Snapshots quase de graça.</strong> Os blocos velhos já persistem por natureza do CoW; um snapshot é só o filesystem segurando o ponteiro de raiz daquele instante, pura contabilidade.</li>
<li><strong>Queda de energia não corrompe.</strong> Uma escrita interrompida deixa a versão anterior intacta, porque ela nunca foi tocada. Pra quem já perdeu imagem de VM ou banco de dados pra queda no meio da escrita, isso vale o filesystem inteiro.</li>
<li><strong>Verificação possível.</strong> Como cada bloco é escrito uma vez e nunca mexido, faz sentido guardar checksum dele pra sempre. É a base da proteção contra corrupção que detalho mais abaixo.</li>
</ul>
<p>O custo existe e eu pago ele sabendo: arquivos de escrita aleatória intensa (disco de VM, banco de dados) fragmentam e duplicam bloco, porque cada reescrita vira um bloco novo. Pra esses casos se desliga o CoW por diretório com <code>chattr +C</code>, que precisa ser marcado <strong>antes</strong> do arquivo ser criado. No meu layout, a imagem de VM feita com o <code>+C</code> certo ficou saudável; a feita sem virou o caso de fragmentação de livro-texto.</p>
<h3>Snapshots são a feature que muda a sua vida<span class="hx:absolute hx:-mt-20" id="snapshots-são-a-feature-que-muda-a-sua-vida"></span>
    <a href="#snapshots-s%c3%a3o-a-feature-que-muda-a-sua-vida" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Snapshot no btrfs é uma foto instantânea de um subvolume, e custa quase nada, porque o CoW já mantém os blocos velhos enquanto alguém referencia eles. Criar um snapshot é operação de segundo, não de horas. Na prática: antes de cada upgrade, o sistema tira uma foto. Se o upgrade quebrar, você não &ldquo;desinstala pacote&rdquo;: você boota de volta na foto anterior e pronto. A seção de recuperação de boot, mais abaixo, mostra isso acontecendo de verdade.</p>
<h3>Subvolumes são o jeito certo de organizar o disco<span class="hx:absolute hx:-mt-20" id="subvolumes-são-o-jeito-certo-de-organizar-o-disco"></span>
    <a href="#subvolumes-s%c3%a3o-o-jeito-certo-de-organizar-o-disco" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Subvolume é um namespace independente dentro do mesmo filesystem, com política própria de snapshot. E tem uma propriedade matadora: <strong>snapshot não é recursivo</strong>. Um subvolume filho dentro de outro fica automaticamente de fora do snapshot do pai.</p>
<p>Eu uso isso pra tudo. No meu desktop, os diretórios de cache que dá pra baixar de novo (mise, rustup, npm, opencode, caches de IA) estão virando subvolumes aninhados dentro do <code>@home</code>: ficam no mesmo lugar pra qualquer programa, mas de fora de qualquer snapshot. Só essa separação tira 165 GiB de escopo de snapshot, e os caches grandes (Hugging Face, yay, uv, pip) já moram fora do volume do sistema, com symlinks apontando pra eles: menos 148 GiB de snapshot, parte já migrada e parte em andamento. O snapshot fica pequeno, o restore fica rápido, e o que pode ser rebaixado da internet nunca entra na foto.</p>
<p>O custo real, medido no QNAP novo (ZFS, mas a matemática de CoW é a mesma): 27 snapshots de hora em hora de uma pasta com quase 80 TB, e quase todos consomem megabytes, porque entre uma hora e outra quase nada mudou. Snapshot barato é o que permite essa rotina existir sem culpa.</p>
<p><img src="qnap-snapshot-manager.png" alt="Snapshot Manager do QNAP com 27 snapshots horários da pasta CHAD, a maioria consumindo entre 3 e 300 MB"  loading="lazy" /></p>
<h3>Deduplicação por reflink<span class="hx:absolute hx:-mt-20" id="deduplicação-por-reflink"></span>
    <a href="#deduplica%c3%a7%c3%a3o-por-reflink" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Antes, a ressalva que importa: o btrfs não tem deduplicação online nativa igual ao ZFS (que mantém uma tabela de dedup em RAM e cobra caro por isso). O que ele tem são duas coisas práticas: <strong>reflink</strong> e dedup offline. Reflink é cópia que compartilha blocos: <code>cp --reflink=auto</code> cria um arquivo novo que aponta pros mesmos blocos do original, instantâneo e de graça, e cada um passa a divergir só onde for escrito. Na era de dezenas de checkouts, worktrees e builds parecidos, isso economiza uma montanha de blocos. Pra dedup retroativo existem ferramentas de userspace como <a href="https://github.com/markfasheh/duperemove"target="_blank" rel="noopener">duperemove</a> e <a href="https://github.com/Zygo/bees"target="_blank" rel="noopener">bees</a>.</p>
<h3>Proteção contra corrupção de verdade<span class="hx:absolute hx:-mt-20" id="proteção-contra-corrupção-de-verdade"></span>
    <a href="#prote%c3%a7%c3%a3o-contra-corrup%c3%a7%c3%a3o-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Todo bloco do btrfs, dado ou metadado, carrega checksum. Quando o filesystem lê e o checksum não bate, ele sabe que o bloco apodreceu, e se houver uma segunda cópia (RAID ou metadata DUP) ele se autocorrige. O ext4 não checa dado nenhum: bit rot passa silencioso até você abrir o arquivo anos depois.</p>
<p>Duas peças da minha configuração mostram o que isso compra na prática, e o que o ext4 simplesmente não oferece. A primeira é o metadado em modo DUP. Num ext4, cada estrutura do volume existe uma vez só: um setor ruim bem posicionado corrompe a tabela de diretórios e o disco inteiro deixa de montar. No btrfs, mesmo com um único disco, todo bloco de metadado existe duplicado. O setor ruim continua existindo, mas o checksum acusa o bloco podre na leitura e a segunda cópia responde no lugar, sem você perceber. É a diferença entre perder o volume e anotar um aviso no log.</p>
<p>A segunda é o scrub mensal. Um ext4 nunca relê nada pra conferir saúde: se um bloco apodrece, você descobre no dia em que alguém tenta abrir aquele arquivo específico, talvez anos depois, quando já não tem conserto. O scrub é uma varredura agendada que relê o volume inteiro validando checksums, e encontra a podridão enquanto ela ainda é pequena e recuperável. Quando o contador do <code>btrfs device stats</code> sai de zero, é a sirene pra trocar o disco antes do desastre, não depois.</p>
<h3>Compressão transparente, medida<span class="hx:absolute hx:-mt-20" id="compressão-transparente-medida"></span>
    <a href="#compress%c3%a3o-transparente-medida" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Todo mount do meu desktop usa <code>compress=zstd:1</code>. O nível 1 custa uns pontos percentuais de um núcleo de CPU, e o btrfs detecta o que não comprime (blob de modelo, vídeo) e grava cru, sem custo. O resultado, medido essa semana nos meus dois discos novos:</p>
<p><img src="btrfs-compressao-economia.png" alt="Tabela de economia com compressão: 2,2 TB de dados ocupando 1,6 TB em disco, com 35% de economia no sistema, 39% nas camadas de Docker e zero nos pesos de modelos de IA"  loading="lazy" /></p>
<p>São <strong>630 GB de espaço grátis</strong> em 2,2 TB: 15% a mais de disco, sem pagar nada. Na migração inicial o ganho foi ainda maior, 45% no volume de sistema, porque havia mais texto e menos binário. Repare nos detalhes da tabela: programas e bibliotecas comprimem bem, camadas de Docker comprimem melhor ainda (39%), log de texto comprime 4,5x, e peso de modelo de IA não comprime nada, o que é esperado e não custa nada. Código-fonte, que é o que mais produzimos na era da IA, comprime lindamente.</p>
<h2>Synology usa btrfs, QNAP usa ZFS<span class="hx:absolute hx:-mt-20" id="synology-usa-btrfs-qnap-usa-zfs"></span>
    <a href="#synology-usa-btrfs-qnap-usa-zfs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Sendo justo com o ZFS: pra NAS grande ele é superior, e foi o que eu escolhi pro QNAP. Checksum e autocorreção nativos, RAIDZ maduro, send/receive excelente. O preço é complexidade e recurso: o ZFS quer RAM pro cache ARC (meu QNAP tem 7 GB pra um pool de 174 TB, e sinto a falta), a deduplicação online dele precisaria de uns 190 GB de RAM só pra tabela no meu volume, e o modelo de provisionamento dele me deu um susto de graça quando o snapshot foi recusado com 20 TB livres (a história do thick vs thin está no artigo do NAS).</p>
<p>O btrfs é o equilíbrio certo pra máquina de consumo. Leve, nativo no kernel, faz quase tudo que o ZFS faz sem exigir um administrador de storage. Desktop, notebook, homelab, NAS pequeno ou médio: btrfs. O Synology provou na prática, duas vezes: o volume btrfs sobreviveu legível com dois discos fora, e os snapshots dele salvaram 11 álbuns que eu deletei por engano no meio da limpeza, byte a byte.</p>
<blockquote>
  <p>NAS grande e sério, com RAM de sobra: ZFS. Todo o resto: btrfs. E pra máquina Linux de uso pessoal, não é nem discussão.</p>

</blockquote>
<h2>Recuperação de boot por snapshot<span class="hx:absolute hx:-mt-20" id="recuperação-de-boot-por-snapshot"></span>
    <a href="#recupera%c3%a7%c3%a3o-de-boot-por-snapshot" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A feature que já me salvou de verdade, contada nas duas máquinas.</p>
<p>No desktop, o <a href="https://omarchy.org"target="_blank" rel="noopener">Omarchy</a> configura o <a href="https://snapper.io/"target="_blank" rel="noopener">snapper</a> pra tirar snapshot antes de cada update e o <code>limine-snapper-sync</code> publica cada snapshot como entrada de boot no <a href="https://github.com/limine-bootloader/limine"target="_blank" rel="noopener">Limine</a>. Kernel novo não sobe? No menu de boot você escolhe o snapshot de ontem e a máquina volta como se nada tivesse acontecido. Na minha máquina a rotina agendada roda via <a href="https://github.com/teejee2008/timeshift"target="_blank" rel="noopener">Timeshift</a> em cron (a cada boot e de hora em hora), com o snapper cobrindo os pontos de update: o mecanismo de retorno é o mesmo. (A gotcha: cada entrada de snapshot carrega a imagem do kernel junto, então ESP pequena enche rápido. A minha de 1 GB já recusou um update de kernel por falta de espaço; o sistema seguiu bootável no kernel velho, que é o comportamento certo.)</p>
<p>No servidor, que roda <a href="https://get.opensuse.org/microos/"target="_blank" rel="noopener">openSUSE MicroOS</a>, é ainda mais forte: o sistema inteiro é btrfs transacional, e cada update é um snapshot novo. Semana passada, o kernel 7.2.9 derrubou o controlador NVMe em silêncio, duas vezes, no meio do backup noturno. O conserto foi um <code>transactional-update rollback</code> e um reboot de volta pro 7.2.6, que rodou a carga inteira (754 GiB de leitura de backup com download concorrente) sem um soluço. Regressão de kernel virou reboot agendado, não madrugada de recovery. Uma ressalva documentada: o <code>/etc</code> mora dentro do snapshot, então o rollback desfaz mudanças de configuração junto. Depois de qualquer rollback, confira de novo as suas units.</p>
<h2>Manutenção não é opcional (e o arquivo deletado que não some)<span class="hx:absolute hx:-mt-20" id="manutenção-não-é-opcional-e-o-arquivo-deletado-que-não-some"></span>
    <a href="#manuten%c3%a7%c3%a3o-n%c3%a3o-%c3%a9-opcional-e-o-arquivo-deletado-que-n%c3%a3o-some" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Filesystem CoW moderno entrega muito, mas cobra duas rotinas que o ext4 nunca pediu.</p>
<p>A primeira é o scrub, que já apareceu aqui: reler tudo e conferir checksums, mensalmente, em todas as máquinas. Ele é o detector de bit rot em ação, e sem ele o checksum só te avisa da podridão quando você tentar ler o arquivo. O segundo ponto é mais sutil: <strong>snapshot prende bloco de arquivo deletado</strong>. Quando você apaga um arquivo dentro de um subvolume com snapshot, o espaço não volta, porque o bloco continua referenciado pela foto. Foi exatamente o que aconteceu no meu NAS: a limpeza de 2,14 TB que rodei no Synology só liberou espaço de verdade depois que os snapshots velhos foram removidos; até lá, os blocos ficaram presos.</p>
<p>O tratamento é rotina simples. A retenção tem que ser automática, com o snapper ou o Timeshift apagando os snapshots antigos conforme a política (no QNAP, a política de retenção do Snapshot Manager faz o mesmo papel). Espaço que não volta depois de deletar snapshot velho geralmente é chunk alocado demais: um <code>btrfs balance start -dusage=50</code> devolve os pedaços pouco usados pro pool livre (aqui em casa eu não agendo balance cego, eu monitoro o espaço não-alocado e ajo quando aperta). E no lado do restic, <code>forget</code> sozinho só remove a referência do snapshot: o espaço do repositório só é recuperado com o <code>prune</code> junto, <code>forget --prune</code>.</p>
<h2>A limpeza da casa na era do AI coding<span class="hx:absolute hx:-mt-20" id="a-limpeza-da-casa-na-era-do-ai-coding"></span>
    <a href="#a-limpeza-da-casa-na-era-do-ai-coding" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora a parte que quase ninguém conecta com filesystem: a era do agente produz lixo em velocidade industrial. Eu sei porque medi.</p>
<ul>
<li><strong>Rust é o maior poluidor de disco do mundo dev.</strong> Cada build deixa pra trás binários de teste de 100 a 180 MB, e eles se acumulam a cada edição. No meu desktop, os diretórios <code>target/</code> somavam <strong>920 GiB</strong>. Só o ai-memory respondia por 704 GiB. A ferramenta de higiene que eu usava é o <a href="https://github.com/holmgr/cargo-sweep"target="_blank" rel="noopener">cargo-sweep</a>, semanal. Um leitor avisou que o projeto se declara sem mantenedor, e o próprio README manda pro <a href="https://github.com/dnlmlr/cargo-clean-all"target="_blank" rel="noopener">cargo-clean-all</a>, que faz diferente: em vez de podar artefato velho dentro de um <code>target/</code>, ele acha todos os projetos Rust abaixo de um diretório e apaga o <code>target/</code> inteiro dos que você filtrar, por exemplo <code>cargo clean-all --keep-days 7 ~</code> pra limpar só o que não compila há uma semana.</li>
<li><strong>Docker não fica atrás.</strong> Na minha máquina achei 1,26 TB de build cache mais 938 GB de imagens. Uma faxina única recuperou uns 600 GB. No servidor, a limpeza semanal recolhe de 0,6 a 4,5 GB todo domingo.</li>
<li><strong>Caches de IA e de pacotes incham quietos.</strong> O cache do Hugging Face tinha 86 GB até eu apagar três modelos mortos e ele cair pra 21. Pacman tinha 22 GB de pacotes superados. yay, uv e pip, mais 125 GB.</li>
<li><strong>E os agentes criam diretórios temporários sem pedir licença.</strong> Projeto complexo com várias branches significa worktrees, e cada worktree de release tem seu próprio <code>CARGO_TARGET_DIR</code> cheio de binário de teste. No ai-memory isso já estourou o disco, e virou regra obrigatória no AGENTS.md do projeto: depois de cada release, <code>cargo clean</code> em cada worktree criado e remoção dos worktrees terminados, sem exceção.</li>
</ul>
<p>A solução é automatizar o varrimento. O meu exemplo mínimo, um timer de usuário que roda dia 1 e 15 de madrugada, com Nice e I/O idle:</p>
<ul>
<li>apaga diretórios <code>target/</code> de projetos parados há mais de 14 dias</li>
<li>limpa o cache do yay</li>
<li>poda só o build cache do Docker com mais de 30 dias, nunca imagem ou volume</li>
</ul>
<p>E a parte que virou padrão aqui em casa: <strong>instruir os agentes a limpar o que criam</strong>. Três regras reais dos meus projetos, pra você copiar:</p>
<blockquote>
  <p><strong>No AGENTS.md do desktop:</strong> &ldquo;Nunca rode limpezas amplas como <code>docker system prune</code> sem autorização explícita do usuário depois de um inventário read-only. Limpeza usa lista exata de IDs; parado, antigo ou sem referência não significa descartável.&rdquo;</p>

</blockquote>
<blockquote>
  <p><strong>No CLAUDE.md do homelab:</strong> &ldquo;Antes de terminar qualquer tarefa, remova as coisas temporárias que a tarefa criou, e só elas. Nunca delete o que você não criou ou não consegue atribuir à tarefa. Na dúvida, liste pro dono.&rdquo;</p>

</blockquote>
<blockquote>
  <p><strong>No AGENTS.md do ai-memory:</strong> &ldquo;<code>target/</code> cresce sem limite: cada edição num crate compartilhado deixa pra trás uma cópia de cada binário de teste de 100-180 MB (visto a 157 GiB). <code>cargo sweep --time 7</code> semanal, e <code>cargo clean</code> obrigatório depois de cada release.&rdquo;</p>

</blockquote>
<h2>E o backup de verdade: restic<span class="hx:absolute hx:-mt-20" id="e-o-backup-de-verdade-restic"></span>
    <a href="#e-o-backup-de-verdade-restic" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Snapshot mora no mesmo disco, então snapshot não é backup: se o disco morre, o snapshot morre junto. O último item do kit é o <a href="https://restic.net/"target="_blank" rel="noopener">restic</a>, que pra mim é o melhor sistema de backup do Linux hoje.</p>
<p>O restic é um binário único, open source, escrito em Go, que faz backup deduplicado, criptografado e versionado pra praticamente qualquer destino: outro diretório, outro NAS via SFTP ou REST, S3, Glacier. O truque central é o content-defined chunking: ele fatia cada arquivo em blocos de tamanho variável definidos pelo conteúdo, tira o hash de cada bloco e só grava no repositório os blocos que ainda não existem lá. Por isso o delta é barato: o backup de hoje carrega os blocos que mudaram desde ontem, não os arquivos que mudaram. Editar um banco de 100 GB no meio custa alguns megabytes de backup, não 100 GB de novo. Cada execução vira um snapshot do repositório, um ponto de restauração que você navega com <code>restic mount</code> como se fosse filesystem, e o <code>restic check</code> audita a integridade do repositório inteiro quando você pedir.</p>
<p>A configuração importa mais que a ferramenta, então vai a nossa, com os erros inclusos. No desktop, o repositório mora no NAS e a retenção é <code>keep-daily 7, keep-weekly 4, keep-monthly 6, keep-yearly 1</code>. No servidor, o backup roda diário de madrugada pro repositório no QNAP, cobrindo <code>/etc</code>, <code>/usr/local/bin</code>, <code>/home</code> e os volumes dos containers, com a senha fora da máquina e <code>pg_dump</code> antes dos bancos, porque snapshot de filesystem não garante consistência de banco escrevendo. A primeira carga completa foi 729 GiB em menos de uma hora, e o restic atravessou ileso, na base do retry, a janela em que a montagem NFS ficou read-only por engano durante a migração.</p>
<p>A parte mais importante da configuração é o que <strong>não</strong> entra no backup. As nossas exclusões, todas com motivo:</p>
<ul>
<li>caches de modelo de IA (Immich, Hugging Face, EasyOCR): se perder, baixa de novo;</li>
<li>métricas do Prometheus: histórico que se regenera sozinho;</li>
<li>o banco do Fulcrum: reindexa a partir do bitcoind;</li>
<li>os pesos dos classificadores de decisão: rebaixáveis por hash de revisão (e o glob dessa exclusão vai entre aspas dentro do array do bash, porque sem aspa ele vira fonte de backup em vez de exclusão);</li>
<li><code>node_modules</code>, <code>target/</code>, registry do cargo, toolchains do rustup: build artifact puro.</li>
</ul>
<p>E um aprendizado de retenção pra fechar. Quando encurtamos a política do servidor pra &ldquo;ontem, 2 diários, 2 semanais e 1 mensal&rdquo;, a tradução correta pros flags é <code>--keep-daily 3 --keep-weekly 3 --keep-monthly 2</code>, não 2/2/1: os níveis do restic se sobrepõem, e o snapshot de ontem também é o mais novo desta semana e deste mês, então cada número precisa ser um a mais que os pontos antigos que você quer guardar. O detalhe pegou a gente no contrapé: o script estava com 2/2/1 literal, e com ele o nível mensal nunca seguraria nada mais velho que a noite passada. Na prática, a intenção era guardar 5 pontos de restauração, e o 2/2/1 entrega só 3: ontem, mais um diário, mais um semanal. Erramos, medimos, corrigimos.</p>
<p>Uma nota de humildade vinda do desastre: os repositórios antigos ficaram no Synology por decisão minha (quase 2 TB de histórico abandonado, porque o conteúdo já estava re-coberto no QNAP novo). Repositório de backup também envelhece, e revisitar o escopo dele faz parte da higiene.</p>
<h2>Conclusão: na era da IA, disco não é detalhe<span class="hx:absolute hx:-mt-20" id="conclusão-na-era-da-ia-disco-não-é-detalhe"></span>
    <a href="#conclus%c3%a3o-na-era-da-ia-disco-n%c3%a3o-%c3%a9-detalhe" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agentes codificando o dia inteiro mudam a matemática do armazenamento nas duas pontas. Na ponta do volume: builds, caches, worktrees e modelos enchem o disco muito mais rápido do que qualquer hábito humano enchia, e sem rotina de limpeza você descobre quando estoura. Na ponta da segurança: quanto mais código gerado, mais você precisa de ponto de retorno barato, porque nem toda mudança de agente é boa.</p>
<p>O btrfs cobre as duas. A compressão transparente devolve 15% de disco de graça, e código-fonte é exatamente o tipo de dado que mais comprime. Snapshots e subvolumes te dão volta atrás instantânea no sistema e isolamento do que não precisa de proteção. Checksums e scrub vigiam a saúde dos blocos que o ext4 simplesmente ignora. O ext4 teve seus 15 anos de reinado merecido. Em 2026, escolher ele num Linux novo é abrir mão de proteção que você vai precisar, cedo ou tarde. Escolha btrfs e configure a limpeza hoje.</p>
]]></content:encoded><category>linux</category><category>armazenamento-e-backup</category><category>homelab</category><category>omarchy</category></item><item><title>Removendo DRM de ebooks de Kindle em 2026</title><link>https://akitaonrails.github.io/2026/07/30/removendo-drm-de-ebooks-de-kindle-em-2026/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/07/30/removendo-drm-de-ebooks-de-kindle-em-2026/</guid><pubDate>Thu, 30 Jul 2026 18:00:00 -0300</pubDate><description>&lt;p&gt;Sou cliente da Amazon há tempo demais. Compro livros no Kindle há muitos anos, tenho vários aparelhos e continuo achando péssima a maneira como a empresa trata conteúdo digital.&lt;/p&gt;
&lt;p&gt;Você paga pelo livro, mas só consegue ler onde a Amazon deixa. Não existe um aplicativo nativo decente pra Linux. Mudar pra outro leitor vira um exercício de arqueologia. Comprei um Xteink X4 novo e, claro, minha biblioteca não podia simplesmente ir junto. O arquivo estava &amp;ldquo;na minha conta&amp;rdquo;, mas não estava sob meu controle.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Sou cliente da Amazon há tempo demais. Compro livros no Kindle há muitos anos, tenho vários aparelhos e continuo achando péssima a maneira como a empresa trata conteúdo digital.</p>
<p>Você paga pelo livro, mas só consegue ler onde a Amazon deixa. Não existe um aplicativo nativo decente pra Linux. Mudar pra outro leitor vira um exercício de arqueologia. Comprei um Xteink X4 novo e, claro, minha biblioteca não podia simplesmente ir junto. O arquivo estava &ldquo;na minha conta&rdquo;, mas não estava sob meu controle.</p>
<p>Durante anos, eu contornei isso pelo caminho oficial. Entrava no site da Amazon, usava <strong>Download &amp; Transfer via USB</strong>, baixava o <code>.azw</code> dos livros que comprei e importava tudo no <a href="https://calibre-ebook.com/about"target="_blank" rel="noopener">calibre</a>. Com o plugin DeDRM configurado pro serial do meu Kindle, convertia pra EPUB e pronto.</p>
<p>Em <strong>26 de fevereiro de 2025</strong>, a Amazon <a href="https://www.vice.com/en/article/amazon-is-killing-your-ability-to-download-kindle-books-next-week/"target="_blank" rel="noopener">desativou essa opção</a>. Sem anúncio decente, sem substituto que entregasse o arquivo e sem qualquer preocupação com quem queria manter um backup local. Desde então, meus livros mais novos ficaram presos no ecossistema deles. Voltei a tentar no ano passado, atualizei plugin, copiei arquivos do meu Kindle Paperwhite, coloquei o serial correto e nada.</p>
<p>Pra ser justo, em <strong>20 de janeiro de 2026</strong> a Amazon passou a permitir que compradores verificados baixem EPUB ou PDF quando o publisher <a href="https://kdp.amazon.com/pt_BR/help/topic/GDDXGH9VR22ACM8U"target="_blank" rel="noopener">confirma o livro como DRM-free</a>. Ótimo. Só não resolve os livros protegidos, e a decisão continua nas mãos da editora. Títulos antigos sem DRM também precisam ser confirmados individualmente pelo publisher. Minha biblioteca não ganhou botão de download por causa disso.</p>
<p>Resolvi tentar de novo agora. A versão nova do DeDRM conseguiu lidar com o DRM atual, mas o processo mudou bastante. Dá trabalho, precisa de Windows numa das etapas e envolve uma ferramenta comunitária em pre-release. Funcionou. Recuperei <strong>106 livros comprados</strong>, testei a conversão pra EPUB e agora posso ler no aparelho que eu quiser.</p>
<p>Este é o estado do jogo em <strong>30 de julho de 2026</strong> (atualizado em 19 de agosto). A Amazon pode mudar a criptografia ou o aplicativo amanhã. Não conte com tutorial antigo dizendo pra instalar Kindle for PC 1.17, desabilitar update e colocar o serial do aparelho no Calibre. Esse mundo já acabou.</p>
<p>E o óbvio precisa ser dito: estou falando dos livros que <strong>eu comprei</strong>. Não estou falando de baixar Kindle Unlimited, empréstimo de biblioteca ou livro dos outros. Leis sobre circumvention de DRM variam por país. Pesquise a legislação de onde mora e assuma responsabilidade pelo que faz.</p>
<h2>Antes de tudo: não jogue Kindle velho fora<span class="hx:absolute hx:-mt-20" id="antes-de-tudo-não-jogue-kindle-velho-fora"></span>
    <a href="#antes-de-tudo-n%c3%a3o-jogue-kindle-velho-fora" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Amazon também não facilita reaproveitar Kindle antigo. O hardware continua bom, a bateria costuma ser substituível, a tela de e-ink dura uma eternidade, mas o software vai ficando cada vez mais fechado e limitado.</p>
<p>Pra quem quer ressuscitar esses aparelhos, recomendo o canal <a href="https://www.youtube.com/@DammitJeff"target="_blank" rel="noopener">Dammit Jeff</a>. Ele acompanha jailbreaks, KOReader, lojas alternativas e formas de manter Kindles úteis depois que a Amazon perde o interesse. Este vídeo sobre o <a href="https://www.youtube.com/watch?v=l4ZliC82RtA"target="_blank" rel="noopener">AdBreak e os jailbreaks recentes</a> é um bom começo. Leia também o <a href="https://kindlemodding.org/"target="_blank" rel="noopener">guia atualizado do Kindle Modding</a> antes de fazer qualquer coisa, porque firmware e método compatível mudam o tempo todo.</p>
<p>Jailbreak do aparelho e remoção de DRM do ebook são problemas diferentes. Você não precisa desbloquear o Kindle pra seguir o resto deste artigo. Estou mencionando porque ambos partem da mesma ideia: hardware e mídia que já pagamos deveriam continuar úteis sem pedir bênção eterna ao fabricante.</p>
<h2>Crash course: AZW, EPUB e KFX<span class="hx:absolute hx:-mt-20" id="crash-course-azw-epub-e-kfx"></span>
    <a href="#crash-course-azw-epub-e-kfx" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>DRM e formato de arquivo não são a mesma coisa. O formato diz como texto, imagens, fontes, índice e metadados são empacotados. DRM adiciona uma trava criptográfica por cima e decide qual conta, aplicativo ou aparelho consegue abrir aquilo.</p>
<p>Os três nomes que importam aqui são:</p>
<table>
  <thead>
      <tr>
          <th>Formato</th>
          <th>O que é</th>
          <th>Na prática</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>AZW / AZW3</strong></td>
          <td>Família mais antiga de formatos proprietários do Kindle. AZW3 também é conhecido como Kindle Format 8.</td>
          <td>Era o que normalmente vinha pelo antigo download via USB. Ferramentas e leitores alternativos entendem bem depois de remover o DRM.</td>
      </tr>
      <tr>
          <td><strong>EPUB</strong></td>
          <td>Padrão aberto da W3C. É um pacote único com conteúdo baseado em HTML, CSS, SVG, fontes e metadados.</td>
          <td>É o formato mais portátil pra guardar e ler fora do ecossistema Amazon. É o destino que quero.</td>
      </tr>
      <tr>
          <td><strong>KFX</strong></td>
          <td>Formato moderno de entrega da Amazon, usado pra recursos como Enhanced Typesetting e Page Flip. Um livro comprado pode vir dividido entre vários containers, recursos auxiliares e um voucher de DRM.</td>
          <td>É o que o aplicativo Kindle atual baixa. Precisamos reunir as partes, remover o DRM e só depois converter.</td>
      </tr>
  </tbody>
</table>
<p>A extensão sozinha pode enganar. Parte de um livro KFX pode aparecer como <code>.azw</code>, <code>.azw.res</code>, <code>.voucher</code> e outras variações. O plugin <a href="https://www.mobileread.com/forums/showthread.php?t=291290"target="_blank" rel="noopener">KFX Input</a> existe justamente pra entender esse conjunto.</p>
<p>O EPUB é bem menos misterioso. A <a href="https://www.w3.org/TR/epub-33/"target="_blank" rel="noopener">especificação EPUB 3.3</a> o define como um container de arquivo único pra conteúdo Web estruturado. É basicamente um pequeno site empacotado. Qualquer leitor minimamente decente consegue implementar suporte sem depender de uma chave secreta da Amazon.</p>
<h2>Calibre e os dois plugins<span class="hx:absolute hx:-mt-20" id="calibre-e-os-dois-plugins"></span>
    <a href="#calibre-e-os-dois-plugins" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://calibre-ebook.com/"target="_blank" rel="noopener">calibre</a> é o canivete suíço de ebooks. É software livre, roda em Linux, macOS e Windows, organiza biblioteca, baixa metadados, edita capas, transfere livros e converte dezenas de formatos. Existe desde 2006 e nasceu justamente porque o primeiro Sony Reader não funcionava direito no Linux.</p>
<p>No Omarchy ou qualquer Arch, basta instalar o pacote:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S calibre</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em outras distribuições, veja o <a href="https://calibre-ebook.com/download"target="_blank" rel="noopener">download oficial</a>. Não invente de instalar um pacote aleatório de site de downloads.</p>
<p>Uma pegadinha específica de ambientes com <code>mise</code>: se o Calibre morrer com <code>ModuleNotFoundError: msgpack</code> ou <code>BrokenPipeError</code>, provavelmente um shim de Python entrou antes de <code>/usr/bin</code>. O pacote do Arch precisa do Python do sistema. Inicie assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">env <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;/usr/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span> /usr/bin/python3 /usr/bin/calibre</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso não tem relação com DRM nem com os plugins. É só o helper do Calibre chamando o Python errado. No meu desktop deixei um launcher permanente com esse <code>PATH</code>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-library-epub.webp" alt="Minha biblioteca no Calibre, com um livro já convertido e disponível em AZW3 e EPUB."  loading="lazy" /></p>
<p>O Calibre sozinho não remove DRM. Precisamos de dois plugins:</p>
<ol>
<li><strong>DeDRM</strong> reconhece e remove as travas de vários ecossistemas durante a importação. Ele só roda quando o livro entra na biblioteca. Clicar em Convert depois não remove nada.</li>
<li><strong>KFX Input</strong> entende os containers modernos da Amazon, reúne as partes de um livro e permite converter o resultado pra EPUB.</li>
</ol>
<p>No momento deste artigo, estou usando <strong>DeDRM 10.0.28</strong>, publicado em 14 de julho no <a href="https://github.com/Satsuoni/DeDRM_tools/releases/tag/v10.0.28"target="_blank" rel="noopener">fork mantido por Satsuoni</a>. É um pre-release. Baixe o asset chamado <code>DeDRM_tools.zip</code>, nunca um executável solto de algum mirror obscuro.</p>
<p><strong>Atualização (agosto de 2026):</strong> o aplicativo do Kindle na Microsoft Store não para quieto, e cada versão nova dele exige uma versão nova da ferramenta. Desde a publicação deste artigo, o app saiu da 1.0.18632: a <a href="https://github.com/Satsuoni/DeDRM_tools/releases/tag/v10.0.29"target="_blank" rel="noopener">10.0.29</a> adicionou suporte ao app 1.22326 e a <a href="https://github.com/Satsuoni/DeDRM_tools/releases/tag/v10.0.30"target="_blank" rel="noopener">10.0.30</a> suporta o app 1.0.22920. A regra prática é uma só: o nome do executável carrega a versão do app que ele entende (<code>MSIXKFXArchiverMobi1_22920.exe</code> pra Store app 1.0.22920, e assim por diante). Antes de começar, confira sempre a <a href="https://github.com/Satsuoni/DeDRM_tools/releases"target="_blank" rel="noopener">página de releases do fork do Satsuoni</a> e baixe o pre-release <strong>mais novo</strong>. Todo o resto do processo descrito aqui continua igual.</p>
<p>Eu descompactei tudo numa pasta que depois fica compartilhada com a VM Windows:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/Windows/Kindle-DeDRM/v10.0.28
</span></span><span class="line"><span class="cl">unzip ~/Downloads/DeDRM_tools.zip -d ~/Windows/Kindle-DeDRM/v10.0.28</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O SHA-256 do <code>DeDRM_tools.zip</code> que usei foi:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">520cce704edf9ae26e43196efe2871daf9b25d6cb489aa56051626801c362947</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Confira com <code>sha256sum</code>. Isso não transforma um binário comunitário em software magicamente seguro, mas pelo menos garante que está usando o mesmo arquivo que eu testei.</p>
<p>Dentro da pasta existe <code>DeDRM_plugin.zip</code>. <strong>Não descompacte esse segundo ZIP.</strong> No Calibre, abra <strong>Preferences → Plugins → Load plugin from file</strong>, escolha <code>DeDRM_plugin.zip</code>, aceite o aviso e reinicie o programa.</p>
<p>Depois volte em <strong>Preferences → Plugins → Get new plugins</strong>, procure por <strong>KFX Input</strong> e instale. Meu teste usou a versão <strong>2.33.0</strong>. Reinicie de novo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-plugins-dedrm-kfx.webp" alt="Calibre 9.11 com DeDRM 10.0.28 e KFX Input 2.33.0 instalados."  loading="lazy" /></p>
<h2>Por que copiar do Kindle pelo USB não resolveu<span class="hx:absolute hx:-mt-20" id="por-que-copiar-do-kindle-pelo-usb-não-resolveu"></span>
    <a href="#por-que-copiar-do-kindle-pelo-usb-n%c3%a3o-resolveu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O caminho mais simples ainda seria ligar meu Kindle Paperwhite novo no cabo, copiar o livro e configurar o serial do aparelho no DeDRM. Foi o primeiro teste que fiz.</p>
<p>O arquivo entrou como <code>KFX-ZIP</code>, o plugin rodou, mas continuou criptografado. Abri o Calibre em debug e o log mostrou que esse livro usava a estratégia <code>ACCOUNT_SECRET</code>. O serial estava certo. O problema é que a chave necessária não vinha mais apenas do aparelho.</p>
<p>É por isso que muita gente segue tutorial antigo, tenta cinco serial numbers diferentes e conclui que o DeDRM está quebrado. Pra livros novos com esse DRM, precisamos do segredo que o aplicativo Kindle atual guarda no Windows. E esse segredo é protegido pelas APIs do TPM.</p>
<h2>Um Windows descartável dentro do Omarchy<span class="hx:absolute hx:-mt-20" id="um-windows-descartável-dentro-do-omarchy"></span>
    <a href="#um-windows-descart%c3%a1vel-dentro-do-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu não vou manter dual boot só pra baixar ebook. Também não vou fingir que Wine roda tudo. Felizmente, o Omarchy <a href="https://learn.omacom.io/books/2/pages/100"target="_blank" rel="noopener">já vem preparado pra instalar e abrir uma VM Windows</a>: ela aparece no próprio menu do sistema, abre por RDP e compartilha <code>~/Windows</code> automaticamente com o guest. Não precisei montar essa integração do zero.</p>
<p>Por baixo, ele usa o <a href="https://github.com/dockur/windows"target="_blank" rel="noopener">dockur/windows</a>. O dockur empacota QEMU/KVM num container e automatiza a instalação do Windows. Continua sendo uma máquina virtual de verdade, com disco e kernel próprios. O container só organiza a distribuição, configuração e ciclo de vida.</p>
<p>Pra instalar pelo terminal, rode:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">omarchy-windows-vm install</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Escolha RAM, CPUs e tamanho do disco. O instalador cria <code>~/.config/windows/docker-compose.yml</code>, guarda o disco virtual em <code>~/.windows</code> e compartilha <code>~/Windows</code> com o guest. Dentro do Windows, essa pasta aparece como <strong>Shared</strong>, normalmente em <code>Z:</code>, e também fica acessível por <code>\\host.lan\Data</code>.</p>
<p>O detalhe que fez diferença no meu caso foi habilitar <strong>TPM 2.0</strong>. Edite o Compose gerado e acrescente <code>TPM: &quot;Y&quot;</code> no bloco <code>environment</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">windows</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">dockurr/windows</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">VERSION</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;11&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TPM</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Y&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois recrie o container. O disco do Windows continua no volume persistente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">omarchy-windows-vm stop
</span></span><span class="line"><span class="cl">omarchy-windows-vm launch -k</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>-k</code> deixa a VM ligada quando você fechar o RDP. Também dá pra acompanhar pelo navegador em <code>http://127.0.0.1:8006</code>.</p>
<!-- Screenshot pendente: Windows 11 do dockur aberto pelo Omarchy. -->
<h2>Baixando os livros pelo aplicativo certo<span class="hx:absolute hx:-mt-20" id="baixando-os-livros-pelo-aplicativo-certo"></span>
    <a href="#baixando-os-livros-pelo-aplicativo-certo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Dentro do Windows, abra a Microsoft Store e instale <strong><a href="https://apps.microsoft.com/detail/9p8jq0jjstll"target="_blank" rel="noopener">Amazon Kindle: Reading App</a></strong>, produto <code>9P8JQ0JJSTLL</code>.</p>
<p>Preste atenção aqui. O instalador antigo de Kindle for PC encontrado em blogs não é o mesmo aplicativo. A ferramenta que usei procura o pacote <code>AMZNKindle.AmazonKindleReadingApp</code> da Microsoft Store. Com o programa legado, ela simplesmente responde <code>No AmazonKindleReadingApp installation found</code>.</p>
<p>Entre na sua conta e mande baixar localmente cada livro que quer preservar. Abrir a capa ou ver o título na biblioteca não basta. O conteúdo precisa estar disponível offline dentro do app.</p>
<p>Infelizmente, a parte mais chata continua manual. A Amazon sabe colocar botão de compra com um clique, mas não achou espaço pra um &ldquo;baixe tudo que eu já paguei&rdquo;.</p>
<p>Eu fiz isso em 106 livros. Sim, foi um saco.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/kindle-store-library.webp" alt="Aplicativo Kindle da Microsoft Store com os ebooks baixados na biblioteca local."  loading="lazy" /></p>
<h2>Gerando os KFX-ZIP e a chave K4I<span class="hx:absolute hx:-mt-20" id="gerando-os-kfx-zip-e-a-chave-k4i"></span>
    <a href="#gerando-os-kfx-zip-e-a-chave-k4i" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <code>DeDRM_tools.zip</code> que descompactamos no Linux já está visível no Windows por causa da pasta compartilhada. Abra PowerShell e entre nela:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">cd </span><span class="p">\\</span><span class="n">host</span><span class="p">.</span><span class="n">lan</span><span class="p">\</span><span class="n">Data</span><span class="p">\</span><span class="nb">Kindle-DeDRM</span><span class="p">\</span><span class="n">v10</span><span class="p">.</span><span class="py">0</span><span class="p">.</span><span class="py">28</span>
</span></span><span class="line"><span class="cl"><span class="p">.\</span><span class="n">MSIXKFXArchiverMobi1_18632</span><span class="p">.</span><span class="n">exe</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O banner ainda pode mencionar uma build antiga <code>1.0.15230</code>. O executável versionado acima procura a Store app <code>1.0.18632</code> e foi o que funcionou comigo. O número no nome do arquivo precisa bater com a versão do <strong>seu</strong> app: se a Store já te entregou uma versão mais nova, baixe a release correspondente na página do projeto (veja a atualização na seção dos plugins).</p>
<p>Esse executável localiza o cache da versão atual da Microsoft Store, reúne os componentes de cada livro e gera duas coisas:</p>
<ul>
<li><code>archived_kfx/</code>, com um <code>.kfx-zip</code> pra cada ebook baixado;</li>
<li><code>oldbooks.k4i</code>, o keyfile que o DeDRM precisa pra abrir aqueles arquivos.</li>
</ul>
<p>Na minha máquina, o processo encontrou e arquivou <strong>106 livros</strong>. O SHA-256 do executável que rodei foi:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0c78b45ccea2c36a5fbb01b9f66bdd9e5d5960a68a340ba2ee96e138f5cddf4a</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele é um binário de 32 bits. Se reclamar que falta <code>MSVCP140.dll</code>, instale o <a href="https://aka.ms/vc14/vc_redist.x86.exe"target="_blank" rel="noopener">Microsoft Visual C++ Redistributable x86 oficial</a>. Não procure DLL avulsa no Google. Esse é um ótimo jeito de transformar backup de livro em backup de malware.</p>
<p>O <code>oldbooks.k4i</code> é sensível. Ele deriva do segredo da sua conta naquela instalação do Kindle. Não publique, não mande em issue do GitHub, não tire screenshot do conteúdo e não coloque em repositório aberto.</p>
<!-- Screenshot pendente: PowerShell após o archiver gerar archived_kfx e oldbooks.k4i. -->
<h2>Configurando a chave no DeDRM<span class="hx:absolute hx:-mt-20" id="configurando-a-chave-no-dedrm"></span>
    <a href="#configurando-a-chave-no-dedrm" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>De volta ao Linux, abra o Calibre e siga:</p>
<p><strong>Preferences → Plugins → DeDRM → Customize plugin → Kindle for Mac/PC ebooks → Import Existing Keyfiles</strong></p>
<p>Selecione:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/Windows/Kindle-DeDRM/v10.0.28/oldbooks.k4i</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Use <strong>Import Existing Keyfiles</strong>. Não use o botão genérico <strong>Set Keyfile</strong> da tela principal. Ele grava outro tipo de candidato e não cadastra a chave K4I no lugar certo.</p>
<p>Feche a lista de chaves, clique em <strong>OK</strong> na configuração principal e reinicie o Calibre. Se fechar com Cancel, ele joga a alteração fora sem cerimônia.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-dedrm-key.webp" alt="DeDRM com o keyfile oldbooks importado na lista de chaves do Kindle for Mac/PC."  loading="lazy" /></p>
<h2>Importando e convertendo pra EPUB<span class="hx:absolute hx:-mt-20" id="importando-e-convertendo-pra-epub"></span>
    <a href="#importando-e-convertendo-pra-epub" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de despejar cem arquivos na biblioteca, teste um.</p>
<p>Escolha um <code>.kfx-zip</code> dentro de:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">~/Windows/Kindle-DeDRM/v10.0.28/archived_kfx/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Use <strong>Add books</strong> no Calibre. A ordem interna é esta:</p>
<ol>
<li>KFX Input reconhece o pacote;</li>
<li>DeDRM tenta as chaves K4I durante a importação;</li>
<li>KFX Input monta o livro descriptografado como KFX;</li>
<li>Calibre passa a conseguir abrir e converter o resultado.</li>
</ol>
<p>Se o formato continuar aparecendo como <code>KFX-ZIP</code>, deu errado. Não adianta apertar Convert dez vezes. Remova aquela entrada da biblioteca, corrija plugin ou chave e importe de novo. DeDRM só atua na <strong>entrada</strong>.</p>
<p>Quando o livro aparecer como <code>KFX</code>, abra no viewer e confira algumas páginas. Depois clique em <strong>Convert books</strong>, escolha <strong>EPUB</strong> como saída e teste o arquivo resultante no leitor onde pretende usar. Só depois disso faça a importação em lote.</p>
<p>No meu caso, o KFX abriu, converteu e o EPUB funcionou fora do Kindle. Com isso eu sabia que o caminho inteiro estava funcionando: Microsoft Store, TPM, archiver, K4I, DeDRM 10.0.28 e KFX Input 2.33.0.</p>
<!-- Screenshot pendente: livro importado como KFX no Calibre. -->
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/07/30/kindle-dedrm/calibre-convert-epub.webp" alt="Diálogo de conversão do Calibre com EPUB selecionado como formato de saída."  loading="lazy" /></p>
<!-- Screenshot pendente: EPUB final aberto no Xteink X4. -->
<h2>Faça backup do que realmente importa<span class="hx:absolute hx:-mt-20" id="faça-backup-do-que-realmente-importa"></span>
    <a href="#fa%c3%a7a-backup-do-que-realmente-importa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois da conversão, eu preservo três coisas:</p>
<ul>
<li>os EPUBs finais, que leio em qualquer aparelho;</li>
<li>os KFX-ZIP originais, até conferir que todos os EPUBs estão íntegros;</li>
<li>o <code>oldbooks.k4i</code>, criptografado e separado da biblioteca.</li>
</ul>
<p>Meu keyfile em texto puro fica com permissão <code>0600</code>. Também mantenho uma cópia criptografada com SOPS e age, enquanto a chave privada fica em outro backup. Não coloque <code>oldbooks.k4i</code> cru no Git só porque o repositório é privado. Repositório privado vaza também.</p>
<p>Os EPUBs entram na mesma estratégia que uso pros outros arquivos importantes: cópia local, NAS e backup off-site. Já expliquei essa paranoia em <a href="/2023/10/19/akitando-146-protegendo-e-recuperando-dados-perdidos-git-backup-btrfs/">Protegendo e Recuperando Dados Perdidos</a> e mostrei a mesma filosofia aplicada a filmes no meu <a href="/2024/04/03/meu-netflix-pessoal-com-docker-compose/">Netflix Pessoal</a>.</p>
<p>Não apague os arquivos do Kindle ou da VM no minuto em que a conversão termina. Abra os EPUBs, veja capa, índice, imagens, notas e algumas páginas. Backup que nunca foi testado é só torcida organizada.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Amazon já apagou livros remotamente, editoras atualizam capas e conteúdo, aplicativos antigos param de funcionar e formatos mudam. O próprio fim do download via USB mostrou que uma função disponível por dezoito anos pode desaparecer com uma linha num aviso.</p>
<p>O exemplo clássico continua inacreditável: em 2009 a Amazon apagou <code>1984</code> e <code>Animal Farm</code>, de George Orwell, dos Kindles de clientes. Depois vieram revisões de Roald Dahl, R.L. Stine e Agatha Christie empurradas pra cópias digitais que já tinham sido compradas. O <a href="https://www.vice.com/en/article/amazon-is-killing-your-ability-to-download-kindle-books-next-week/"target="_blank" rel="noopener">artigo da Vice</a> resume esses casos. O Dammit Jeff também bate muito nessa tecla: até a capa original que você escolheu pode virar pôster feio da adaptação do streaming porque publisher e loja decidiram atualizar sua &ldquo;compra&rdquo;.</p>
<p>Não importa se a alteração futura é censura, correção legítima, nova capa horrorosa de adaptação da Netflix ou apenas um bug. Eu comprei uma edição. Quero preservar a edição que comprei.</p>
<p>DRM não impede pirataria. Livro popular aparece em torrent no dia do lançamento. DRM só atrapalha o cliente que pagou, dificulta acessibilidade, prende hardware ainda bom e transforma uma compra em aluguel por prazo indefinido.</p>
<p>Calibre, DeDRM, KFX Input e uma VM descartável devolveram minha biblioteca. Agora posso colocar os EPUBs no Xteink, num Kobo, num tablet, no celular ou num leitor que ainda nem existe. Posso trocar de sistema operacional e posso desligar a internet. Os arquivos continuam comigo.</p>
<p>Se não está na sua máquina, não é seu.</p>
]]></content:encoded><category>armazenamento-e-backup</category><category>linux</category><category>open-source</category></item><item><title>Burlando o bloqueio à API do GitHub no Brasil</title><link>https://akitaonrails.github.io/2026/06/11/burlando-bloqueio-api-github-brasil/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/06/11/burlando-bloqueio-api-github-brasil/</guid><pubDate>Thu, 11 Jun 2026 20:00:00 -0300</pubDate><description>&lt;p&gt;Hoje apareceu um problema daqueles que parecem piada, mas não são: &lt;code&gt;api.github.com&lt;/code&gt; começou a dar timeout no Brasil. O site &lt;code&gt;github.com&lt;/code&gt; abre. Clonar via SSH pode continuar funcionando. Mas as ferramentas que dependem da API do GitHub, como &lt;code&gt;gh&lt;/code&gt;, automações de CI, scripts pessoais, bots, CLIs, dashboards e tudo que consulta issues e pull requests, começam a travar.&lt;/p&gt;
&lt;p&gt;O &lt;a href="https://x.com/ayubio/status/2064878070394183955"target="_blank" rel="noopener"&gt;Ayub comentou no X&lt;/a&gt; que há todos os indícios típicos de bloqueio nacional determinado pela Anatel contra &lt;code&gt;api.github.com&lt;/code&gt;. No mesmo contexto, o tweet citado por ele mostrava &lt;code&gt;api.github.com&lt;/code&gt; resolvendo para &lt;code&gt;4.228.31.149&lt;/code&gt;, mas sem rota funcionando. O autor dizia ter testado por VIVO, Claro e Oracle, e que só funcionava via VPN de Miami. Ou seja: não parece &amp;ldquo;meu Wi-Fi caiu&amp;rdquo;. Parece bloqueio no caminho.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Hoje apareceu um problema daqueles que parecem piada, mas não são: <code>api.github.com</code> começou a dar timeout no Brasil. O site <code>github.com</code> abre. Clonar via SSH pode continuar funcionando. Mas as ferramentas que dependem da API do GitHub, como <code>gh</code>, automações de CI, scripts pessoais, bots, CLIs, dashboards e tudo que consulta issues e pull requests, começam a travar.</p>
<p>O <a href="https://x.com/ayubio/status/2064878070394183955"target="_blank" rel="noopener">Ayub comentou no X</a> que há todos os indícios típicos de bloqueio nacional determinado pela Anatel contra <code>api.github.com</code>. No mesmo contexto, o tweet citado por ele mostrava <code>api.github.com</code> resolvendo para <code>4.228.31.149</code>, mas sem rota funcionando. O autor dizia ter testado por VIVO, Claro e Oracle, e que só funcionava via VPN de Miami. Ou seja: não parece &ldquo;meu Wi-Fi caiu&rdquo;. Parece bloqueio no caminho.</p>
<p>Segundo o Ayub, esse tipo de coisa costuma acontecer em ondas de bloqueios, muitas vezes em reuniões de quarta-feira com grandes operadoras, dentro desse mecanismo brasileiro de bloquear endereços sob pretexto de combater pirataria. O problema é o efeito colateral: em vez de derrubar só o alvo, derruba pedaço legítimo de infraestrutura. Hoje foi a API do GitHub. Amanhã pode ser qualquer outra coisa que seu trabalho depende.</p>
<p>E sim, eu confirmei daqui.</p>
<h2>Como saber se você foi afetado<span class="hx:absolute hx:-mt-20" id="como-saber-se-você-foi-afetado"></span>
    <a href="#como-saber-se-voc%c3%aa-foi-afetado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Primeiro, teste se o site principal abre:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> https://github.com/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aqui funcionou. Depois teste a API:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> https://api.github.com/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aqui deu:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl: (28) Connection timed out after 8002 milliseconds</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora veja pra qual IP seu sistema está resolvendo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">getent ahostsv4 api.github.com</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No meu caso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">4.228.31.149    STREAM api.github.com
</span></span><span class="line"><span class="cl">4.228.31.149    DGRAM
</span></span><span class="line"><span class="cl">4.228.31.149    RAW</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E o <code>gh</code> também depende dessa API. Então comandos como estes podem travar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh api rate_limit
</span></span><span class="line"><span class="cl">gh pr list
</span></span><span class="line"><span class="cl">gh issue list</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se você é desenvolvedor e de repente <code>gh</code> parou, seus scripts de release pararam, seu bot que lista PRs parou, ou aquele CLI que consulta issues começou a ficar pendurado, pode ser isso. Não é bug seu. É a sua rota até <code>api.github.com</code> sendo jogada no buraco.</p>
<h2>Primeiro tente DNS 9.9.9.9<span class="hx:absolute hx:-mt-20" id="primeiro-tente-dns-9999"></span>
    <a href="#primeiro-tente-dns-9999" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Algumas pessoas comentaram que trocar o DNS para <a href="https://www.quad9.net/"target="_blank" rel="noopener">Quad9</a>, <code>9.9.9.9</code>, poderia contornar. Faz sentido testar, porque alguns bloqueios são feitos via DNS: o provedor responde um IP errado, NXDOMAIN, ou manda para um buraco. Se o problema for só o DNS do seu provedor, trocar de DNS resolve.</p>
<p>Teste sem mudar nada no sistema:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig @9.9.9.9 +short api.github.com A</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No meu caso, não resolveu. O Quad9 também retornou:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">4.228.31.149</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E forçar esse IP no <code>curl</code> continuou dando timeout:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --resolve api.github.com:443:4.228.31.149 <span class="se">\
</span></span></span><span class="line"><span class="cl">  https://api.github.com/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Resultado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">curl: (28) Connection timed out after 8002 milliseconds</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, no meu link, 9.9.9.9 não resolveu. Mas teste no seu. Se o seu provedor está apenas envenenando DNS, pode funcionar.</p>
<p>No Arch com NetworkManager, seria algo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S bind-tools curl
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">nmcli con show --active
</span></span><span class="line"><span class="cl">sudo nmcli con mod <span class="s2">&#34;NOME DA SUA CONEXÃO&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  ipv4.ignore-auto-dns yes <span class="se">\
</span></span></span><span class="line"><span class="cl">  ipv4.dns <span class="s2">&#34;9.9.9.9 149.112.112.112&#34;</span>
</span></span><span class="line"><span class="cl">sudo nmcli con down <span class="s2">&#34;NOME DA SUA CONEXÃO&#34;</span>
</span></span><span class="line"><span class="cl">sudo nmcli con up <span class="s2">&#34;NOME DA SUA CONEXÃO&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No Ubuntu, a ideia é a mesma. Instale as ferramentas e ajuste pelo NetworkManager:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install dnsutils curl
</span></span><span class="line"><span class="cl">nmcli con show --active</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou use a tela de rede do GNOME/KDE e coloque DNS manual <code>9.9.9.9</code> e <code>149.112.112.112</code> na conexão. Depois desconecte e conecte de novo.</p>
<p>Teste de novo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short api.github.com A
</span></span><span class="line"><span class="cl">curl -4 -I --connect-timeout <span class="m">8</span> --max-time <span class="m">12</span> https://api.github.com/</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se funcionar, ótimo. Você terminou aqui.</p>
<p>Se continuar dando timeout, o bloqueio não está só no DNS. Aí precisa rotear a conexão por outro caminho.</p>
<h2>Por que eu não gosto da gambiarra de IP fixo<span class="hx:absolute hx:-mt-20" id="por-que-eu-não-gosto-da-gambiarra-de-ip-fixo"></span>
    <a href="#por-que-eu-n%c3%a3o-gosto-da-gambiarra-de-ip-fixo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Outra sugestão que vi foi colocar IPs manualmente no DNS local, tipo Pi-hole, Unbound, <code>/etc/hosts</code>, dnsmasq, essas coisas. Exemplo: &ldquo;resolve <code>api.github.com</code> pra tal IP que funciona&rdquo;.</p>
<p>Isso pode funcionar por algumas horas. Eu não gosto.</p>
<p>GitHub é infraestrutura grande. IP muda. Rota muda. Balanceamento muda. Região muda. O IP que funciona pra uma pessoa em Miami pode ser ruim pra você em São Paulo. O IP que funciona hoje pode cair amanhã. E se o bloqueio for por rota blackhole no provedor, trocar DNS para outro IP pode só trocar um buraco por outro.</p>
<p>Pi-hole é ótimo pra rede doméstica. Unbound é ótimo. <code>/etc/hosts</code> salva em emergência. Mas fixar IP de serviço grande como GitHub é pedir pra esquecer uma gambiarra invisível e perder duas horas daqui a um mês tentando entender por que só a sua máquina não funciona.</p>
<p>Use DNS alternativo como teste. Use IP fixo só como curativo temporário e documente onde mexeu.</p>
<h2>Fallback: proxy SOCKS só para ferramentas do GitHub<span class="hx:absolute hx:-mt-20" id="fallback-proxy-socks-só-para-ferramentas-do-github"></span>
    <a href="#fallback-proxy-socks-s%c3%b3-para-ferramentas-do-github" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Minha solução foi fazer o mínimo invasivo: um wrapper que sobe um SOCKS local via Tor e executa só o comando que eu quero por ele. Não roteia o sistema inteiro. Não mexe no navegador. Não altera pacote, Steam, banco, nada. Só <code>gh</code>, ou qualquer ferramenta que eu chamar explicitamente.</p>
<p>No meu caso, o wrapper está em <code>~/.config/zsh/bin/github-proxy</code> e o alias em <code>~/.config/zsh/aliases</code> faz:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">alias</span> <span class="nv">gh</span><span class="o">=</span><span class="s1">&#39;$HOME/.config/zsh/bin/github-proxy gh&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O wrapper é este:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">proxy_host</span><span class="o">=</span><span class="s2">&#34;127.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">proxy_port</span><span class="o">=</span><span class="s2">&#34;9050&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">tor_dir</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_RUNTIME_DIR</span><span class="k">:-</span><span class="p">/tmp</span><span class="si">}</span><span class="s2">/github-proxy-tor&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">tor_rc</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">/torrc&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">tor_log</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">/tor.log&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="nv">$#</span> -eq <span class="m">0</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;usage: github-proxy &lt;command&gt; [args...]&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! ss -ltn <span class="s2">&#34;sport = :</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span> <span class="p">|</span> grep -q <span class="s2">&#34;</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  mkdir -p <span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  : &gt;<span class="s2">&#34;</span><span class="nv">$tor_rc</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  tor -f <span class="s2">&#34;</span><span class="nv">$tor_rc</span><span class="s2">&#34;</span> --SocksPort <span class="s2">&#34;</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span> --DataDirectory <span class="s2">&#34;</span><span class="nv">$tor_dir</span><span class="s2">/data&#34;</span> &gt;<span class="s2">&#34;</span><span class="nv">$tor_log</span><span class="s2">&#34;</span> 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> _ in <span class="o">{</span>1..60<span class="o">}</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> grep -q <span class="s1">&#39;Bootstrapped 100%&#39;</span> <span class="s2">&#34;</span><span class="nv">$tor_log</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nb">break</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> ! <span class="nb">kill</span> -0 <span class="s2">&#34;</span><span class="nv">$!</span><span class="s2">&#34;</span> 2&gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;github-proxy: tor exited before bootstrap; see </span><span class="nv">$tor_log</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">      <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> ! grep -q <span class="s1">&#39;Bootstrapped 100%&#39;</span> <span class="s2">&#34;</span><span class="nv">$tor_log</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;github-proxy: timed out waiting for tor bootstrap; see </span><span class="nv">$tor_log</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPS_PROXY</span><span class="o">=</span><span class="s2">&#34;socks5h://</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTP_PROXY</span><span class="o">=</span><span class="s2">&#34;socks5h://</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">ALL_PROXY</span><span class="o">=</span><span class="s2">&#34;socks5h://</span><span class="nv">$proxy_host</span><span class="s2">:</span><span class="nv">$proxy_port</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">exec</span> <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Repare no <code>socks5h</code>. Esse <code>h</code> importa. Ele faz a resolução DNS pelo proxy, não pela sua rede local. Se você usa <code>socks5://</code> normal, dependendo da biblioteca, o DNS ainda pode acontecer localmente antes de passar pelo proxy. Com <code>socks5h://</code>, a ideia é: &ldquo;leva o hostname até o outro lado e resolve lá&rdquo;.</p>
<h2>Instalando no Arch<span class="hx:absolute hx:-mt-20" id="instalando-no-arch"></span>
    <a href="#instalando-no-arch" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No Arch:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S tor github-cli iproute2
</span></span><span class="line"><span class="cl">mkdir -p ~/.local/bin</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crie o script:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nano ~/.local/bin/github-proxy</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cole o wrapper acima e dê permissão:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod +x ~/.local/bin/github-proxy</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Teste direto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">~/.local/bin/github-proxy gh api rate_limit --jq .resources.core.limit</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Conta autenticada normalmente retorna:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">5000</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Foi o que aconteceu aqui. Direto dava timeout. Pelo wrapper, <code>gh api rate_limit --jq .resources.core.limit</code> retornou <code>5000</code>.</p>
<p>Agora adicione um alias no seu shell.</p>
<p>ZSH:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;alias gh=&#39;</span><span class="nv">$HOME</span><span class="s2">/.local/bin/github-proxy gh&#39;&#34;</span> &gt;&gt; ~/.zshrc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.zshrc</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Bash:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;alias gh=&#39;</span><span class="nv">$HOME</span><span class="s2">/.local/bin/github-proxy gh&#39;&#34;</span> &gt;&gt; ~/.bashrc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.bashrc</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Teste:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gh api rate_limit --jq .resources.core.limit
</span></span><span class="line"><span class="cl">gh pr list</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Ubuntu<span class="hx:absolute hx:-mt-20" id="ubuntu"></span>
    <a href="#ubuntu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No Ubuntu, o pacote do Tor é fácil:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install tor iproute2 curl</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Para o GitHub CLI, se o pacote <code>gh</code> estiver disponível na sua versão:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install gh</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se não estiver, siga a instalação oficial do GitHub CLI para Ubuntu/Debian. O resto é igual: crie <code>~/.local/bin/github-proxy</code>, dê <code>chmod +x</code>, e coloque o alias no <code>.bashrc</code> ou <code>.zshrc</code>.</p>
<h2>Alternativas melhores que Tor<span class="hx:absolute hx:-mt-20" id="alternativas-melhores-que-tor"></span>
    <a href="#alternativas-melhores-que-tor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tor funcionou e é conveniente, mas não é a única opção. Às vezes é lento. Às vezes algum serviço encrenca com saída Tor. Para <code>gh api</code> e listar PR, tudo bem. Para uso pesado, talvez você queira algo mais previsível.</p>
<p>Opções razoáveis:</p>
<ul>
<li>VPN normal: Proton, Mullvad, IVPN, WireGuard próprio. Roteia tudo ou usa split tunneling se você souber configurar.</li>
<li>Tailscale com exit node fora do Brasil: limpo, se você tiver uma máquina confiável fora ou uma VPS.</li>
<li>SSH SOCKS para uma VPS: <code>ssh -N -D 127.0.0.1:9050 usuario@seu-vps</code>. Depois use o mesmo <code>HTTPS_PROXY=socks5h://127.0.0.1:9050</code>.</li>
<li>Proxy corporativo: se sua empresa tem saída fora do Brasil, configure só as ferramentas que precisam.</li>
</ul>
<p>O cuidado aqui é não sair mexendo em tudo ao mesmo tempo. Primeiro prove que <code>api.github.com</code> direto falha. Depois prove que via outro caminho funciona. Só então automatize.</p>
<h2>Ajustando ferramentas próprias: ghpending<span class="hx:absolute hx:-mt-20" id="ajustando-ferramentas-próprias-ghpending"></span>
    <a href="#ajustando-ferramentas-pr%c3%b3prias-ghpending" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse bloqueio me pegou também num utilitário meu: <a href="https://github.com/akitaonrails/ghpending"target="_blank" rel="noopener">ghpending</a>. Eu já falei dele no post <a href="/2026/05/23/criei-cli-pra-checar-pendencias-github-ghpending/">&ldquo;Criei um CLI pra Checar Pendências no Meu GitHub: ghpending&rdquo;</a>. Ele lista issues e pull requests abertos nos meus repositórios, pra eu saber onde tem gente esperando resposta.</p>
<p>O problema é óbvio: ele usa a API do GitHub. Se <code>api.github.com</code> cai no buraco, ele cai junto.</p>
<p>Então adicionei fallback de proxy. O <code>ghpending</code> agora lê <code>GITHUB_TOKEN</code> como antes, mas também tenta usar proxy SOCKS nesta ordem:</p>
<ol>
<li><code>GHPENDING_GITHUB_PROXY</code>, se você quiser forçar um proxy específico.</li>
<li><code>HTTPS_PROXY</code>, <code>https_proxy</code>, <code>ALL_PROXY</code> ou <code>all_proxy</code>, se forem <code>socks5://</code> ou <code>socks5h://</code>.</li>
<li>Um SOCKS local em <code>127.0.0.1:9050</code>, se estiver escutando.</li>
<li>Se nada disso existir, tenta direto.</li>
</ol>
<p>No README ficou assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">GITHUB_TOKEN</span><span class="o">=</span><span class="k">$(</span>gh auth token<span class="k">)</span> ghpending</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E, para proxy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">GHPENDING_GITHUB_PROXY</span><span class="o">=</span>socks5h://127.0.0.1:9050 ghpending</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou simplesmente rode pelo wrapper:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">~/.local/bin/github-proxy ghpending</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Meu wrapper sobe Tor em <code>127.0.0.1:9050</code>; o <code>ghpending</code> detecta esse proxy e usa para chamadas à API. Se você não está bloqueado, ele continua funcionando direto. Se você está bloqueado, ele tem um caminho de fuga.</p>
<p>Isso é o tipo de fallback que agora todo dev brasileiro deveria considerar para ferramenta que depende de API estrangeira. Não porque é bonito. Porque a internet brasileira está ficando imprevisível.</p>
<h2>O problema político<span class="hx:absolute hx:-mt-20" id="o-problema-político"></span>
    <a href="#o-problema-pol%c3%adtico" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No fim da thread, o <a href="https://x.com/ayubio/status/2064880160893968643"target="_blank" rel="noopener">Ayub lembrou</a> que, por causa desses bloqueios colaterais sigilosos, já haveria cerca de 250 milhões de endereços bloqueados na internet brasileira. Ele também linkou uma apresentação sobre o panorama dos bloqueios no Brasil. Em outro tweet do mesmo fio, ele diz que autoridades defendem sigilo como única alternativa para combater crimes.</p>
<p>Eu não compro essa desculpa.</p>
<p>Bloqueio sigiloso de infraestrutura crítica, sem transparência, sem contraditório público, sem lista auditável, sem explicação de escopo, é censura operacional. Na minha leitura, é ilegal justamente por fugir do rito público e auditável que deveria existir quando o Estado manda quebrar acesso à internet. Pode ter começado com pirataria de TV box. Pode ter discurso bonito. Mas o mecanismo está aí: alguém aperta botão, operadoras aplicam bloqueio, usuários legítimos descobrem só quando ferramenta de trabalho para.</p>
<p>Hoje, o efeito colateral bateu em desenvolvedor honesto tentando trabalhar com GitHub. Amanhã bate em pacote de Linux, registry de container, CDN, API de pagamento, serviço de autenticação. Aí todo mundo corre pra VPN, Tor, proxy, DNS alternativo, <code>/etc/hosts</code>, Pi-hole. Parabéns: estamos recriando, aos poucos, o manual de sobrevivência de uma internet censurada.</p>
<p>Não precisa exagerar na metáfora. Ainda não somos a China. Mas o cheiro é o mesmo: bloqueio centralizado, opaco, com dano colateral, e usuário comum tendo que aprender bypass pra trabalhar.</p>
<p>Se você foi afetado, documente. Rode os comandos. Salve outputs. Abra issue no seu provedor. Reclame com dados. Compartilhe workaround, mas não normalize. Bypass é curativo. O problema real é um Estado que se dá o poder de quebrar pedaços da internet em silêncio.</p>
<p>E se você só queria rodar <code>gh pr list</code> pra revisar um pull request: bem-vindo à infraestrutura brasileira de 2026.</p>
]]></content:encoded><category>ghpending</category><category>redes</category><category>seguranca</category><category>linux</category></item><item><title>Backup de Gmail pra Maildir em Linux</title><link>https://akitaonrails.github.io/2026/05/28/backup-gmail-maildir-linux/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/05/28/backup-gmail-maildir-linux/</guid><pubDate>Thu, 28 May 2026 16:00:00 -0300</pubDate><description>&lt;p&gt;A maioria das pessoas confia no Google e deixa tudo lá. Quem acompanha o blog sabe que eu não confio em ninguém: se tá na nuvem, não é meu. Por isso tenho um NAS em casa pra fazer backup de tudo que importa. Não confio em Google Drive, OneDrive, Dropbox, e nem em Gmail.&lt;/p&gt;
&lt;p&gt;E-mail sempre foi a parte mais chata desse exercício. Meu workflow antigo era abrir o Thunderbird, deixar ele puxar tudo de Gmail via IMAP, e fazer backup do diretório &lt;code&gt;~/.thunderbird&lt;/code&gt;. Funciona, mas é manual, chato (lembrar de abrir o Thunderbird de vez em quando), e preso ao próprio Thunderbird: se quero ler aqueles e-mails em outro cliente, eles estão num formato interno proprietário.&lt;/p&gt;</description><content:encoded><![CDATA[<p>A maioria das pessoas confia no Google e deixa tudo lá. Quem acompanha o blog sabe que eu não confio em ninguém: se tá na nuvem, não é meu. Por isso tenho um NAS em casa pra fazer backup de tudo que importa. Não confio em Google Drive, OneDrive, Dropbox, e nem em Gmail.</p>
<p>E-mail sempre foi a parte mais chata desse exercício. Meu workflow antigo era abrir o Thunderbird, deixar ele puxar tudo de Gmail via IMAP, e fazer backup do diretório <code>~/.thunderbird</code>. Funciona, mas é manual, chato (lembrar de abrir o Thunderbird de vez em quando), e preso ao próprio Thunderbird: se quero ler aqueles e-mails em outro cliente, eles estão num formato interno proprietário.</p>
<p>Depois de um tempo caiu a ficha: Linux tem suporte nativo a e-mail desde sempre. Os protocolos modernos (IMAP, SMTP) são padrão aberto. E o formato de armazenamento local, <strong>Maildir</strong>, existe e funciona muito bem desde os anos 90. Não precisa de Thunderbird pra nada.</p>
<h2>O que é Maildir<span class="hx:absolute hx:-mt-20" id="o-que-é-maildir"></span>
    <a href="#o-que-%c3%a9-maildir" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Maildir foi criado pelo Daniel J. Bernstein (djb) como parte do qmail em 1995. A ideia era resolver os problemas de file locking do formato mbox, onde a caixa de e-mail inteira é um arquivo único e dois processos escrevendo ao mesmo tempo podiam corromper tudo. No Maildir, <strong>cada mensagem vira um arquivo separado</strong> numa estrutura de três subdiretórios:</p>
<ul>
<li><code>tmp/</code> — onde o e-mail é escrito enquanto chega</li>
<li><code>new/</code> — mensagens novas, ainda não lidas</li>
<li><code>cur/</code> — mensagens já vistas pelo cliente, com flags no nome do arquivo (<code>:2,S</code> = seen, <code>:2,F</code> = flagged, etc.)</li>
</ul>
<p>A entrega é atômica: o MTA escreve a mensagem em <code>tmp/</code>, depois faz um <code>rename()</code> pra <code>new/</code>. <code>rename()</code> em filesystem POSIX é atômico, então não tem janela de corrupção. Sem locking, sem corrupção, e cada e-mail é só um arquivo de texto puro que você pode ler com <code>cat</code>, indexar com <code>grep</code>, versionar com git se quiser.</p>
<p>Trinta anos depois, ainda é o formato local padrão da maioria dos clientes de e-mail Linux. E é o formato que vou usar pra fazer o mirror do Gmail.</p>
<h2>A ferramenta: mbsync (isync)<span class="hx:absolute hx:-mt-20" id="a-ferramenta-mbsync-isync"></span>
    <a href="#a-ferramenta-mbsync-isync" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que faz a ponte entre o IMAP do Gmail e o Maildir local é o <a href="https://isync.sourceforge.io/"target="_blank" rel="noopener"><code>mbsync</code></a>, o binário do pacote <code>isync</code>. Ele lê uma config simples (<code>~/.config/isyncrc</code>), conecta no IMAP do Gmail, e copia as mensagens pro disco. A diferença pro <code>offlineimap</code> (alternativa mais conhecida) é que <code>mbsync</code> é escrito em C, é bem mais rápido em mailboxes grandes, e tem menos casos esquisitos com Gmail.</p>
<p>A config é direta. Resumo do que tem no meu <code>~/.config/isyncrc</code> (sem senha, claro):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">IMAPAccount minha-conta</span>
</span></span><span class="line"><span class="cl"><span class="na">Host imap.gmail.com</span>
</span></span><span class="line"><span class="cl"><span class="na">User you@example.com</span>
</span></span><span class="line"><span class="cl"><span class="na">PassCmd &#34;/home/akitaonrails/.config/mbsync/bin/mbsync-password you@example.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">TLSType IMAPS</span>
</span></span><span class="line"><span class="cl"><span class="na">CertificateFile /etc/ssl/certs/ca-certificates.crt</span>
</span></span><span class="line"><span class="cl"><span class="na">AuthMechs LOGIN</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">IMAPStore minha-remote</span>
</span></span><span class="line"><span class="cl"><span class="na">Account minha-conta</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">MaildirStore minha-local</span>
</span></span><span class="line"><span class="cl"><span class="na">SubFolders Verbatim</span>
</span></span><span class="line"><span class="cl"><span class="na">Path ~/.local/share/mail/mbsync/you.example.com/</span>
</span></span><span class="line"><span class="cl"><span class="na">Inbox ~/.local/share/mail/mbsync/you.example.com/INBOX</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">Channel minha-backup</span>
</span></span><span class="line"><span class="cl"><span class="na">Far :minha-remote:</span>
</span></span><span class="line"><span class="cl"><span class="na">Near :minha-local:</span>
</span></span><span class="line"><span class="cl"><span class="na">Patterns * !&#34;[Gmail]/Trash&#34; !&#34;[Gmail]/Spam&#34; !&#34;[Gmail]/Drafts&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Create Near</span>
</span></span><span class="line"><span class="cl"><span class="na">Sync Pull New Flags</span>
</span></span><span class="line"><span class="cl"><span class="na">Expunge None</span>
</span></span><span class="line"><span class="cl"><span class="na">Remove None</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Três pontos importantes nessa config:</p>
<ol>
<li><strong><code>Sync Pull</code></strong>: só puxa do servidor pro local. Nada que eu faça no Maildir vai propagar pro Gmail. Isso é proteção contra acidentes (se eu apagar um arquivo localmente, não some do Gmail).</li>
<li><strong><code>Expunge None</code> + <code>Remove None</code></strong>: se uma mensagem some do Gmail (alguém apagou ou o Google decidiu mover pra lugar nenhum), ela <strong>continua</strong> no meu Maildir. O backup acumula histórico, não espelha o estado atual.</li>
<li><strong><code>PassCmd</code> em vez de senha em texto</strong>: a senha vai num script que faz <code>secret-tool lookup</code> no keyring do desktop. Nada de senha em arquivo de config no disco.</li>
</ol>
<h3>O bloco da senha<span class="hx:absolute hx:-mt-20" id="o-bloco-da-senha"></span>
    <a href="#o-bloco-da-senha" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <code>mbsync-password</code> é um wrapper minúsculo que isola o segredo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl"><span class="nv">account</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="p">:?usage: mbsync-password ACCOUNT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">password</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>secret-tool lookup service mbsync account <span class="s2">&#34;</span><span class="nv">$account</span><span class="s2">&#34;</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;missing keyring secret for </span><span class="nv">$account</span><span class="s2">&#34;</span> &gt;<span class="p">&amp;</span>2<span class="p">;</span> <span class="nb">exit</span> 1<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;%s&#39;</span> <span class="s2">&#34;</span><span class="nv">$password</span><span class="s2">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Senha é armazenada uma vez com <code>secret-tool store --label=&quot;mbsync gmail&quot; service mbsync account you@example.com</code>. O Gmail moderno exige uma <strong>app password</strong> específica (16 caracteres, gerada em <a href="https://myaccount.google.com/apppasswords"target="_blank" rel="noopener">myaccount.google.com/apppasswords</a>) porque não aceita mais autenticação básica direta — você não cola sua senha do Google ali, cola uma chave gerada exclusivamente pro app, que dá pra revogar a qualquer momento sem mexer no resto da conta.</p>
<h3>Automação com systemd user timer<span class="hx:absolute hx:-mt-20" id="automação-com-systemd-user-timer"></span>
    <a href="#automa%c3%a7%c3%a3o-com-systemd-user-timer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra não ter que rodar o mbsync na mão, montei dois user units no systemd. Primeiro o serviço, que é só uma chamada do script wrapper:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># ~/.config/systemd/user/mbsync-backup.service</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Mirror Gmail accounts locally with mbsync</span>
</span></span><span class="line"><span class="cl"><span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">%h/.config/mbsync/bin/mbsync-run</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E o timer que dispara o serviço periodicamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># ~/.config/systemd/user/mbsync-backup.timer</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Run mbsync periodically</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Timer]</span>
</span></span><span class="line"><span class="cl"><span class="na">OnBootSec</span><span class="o">=</span><span class="s">10m</span>
</span></span><span class="line"><span class="cl"><span class="na">OnUnitActiveSec</span><span class="o">=</span><span class="s">30m</span>
</span></span><span class="line"><span class="cl"><span class="na">Persistent</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">Unit</span><span class="o">=</span><span class="s">mbsync-backup.service</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>OnBootSec=10m</code> espera 10 minutos depois do boot pra primeira execução (evita brigar com o network-online no início da sessão). <code>OnUnitActiveSec=30m</code> re-dispara 30 minutos depois de cada execução terminar. <code>Persistent=true</code> faz com que, se a máquina estava desligada na hora que seria pra disparar, ele compense rodando logo no próximo boot. O wrapper já pega <code>flock</code> pra não rodar dois mbsync em paralelo, então se uma sincronização demorar mais que 30min, a próxima só espera. Tudo loga em <code>~/.local/state/mbsync/mbsync.log</code>.</p>
<p>Pra gerenciar o timer, os comandos básicos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Re-ler os arquivos depois de criar/editar</span>
</span></span><span class="line"><span class="cl">systemctl --user daemon-reload
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Ativar agora + habilitar pra subir no próximo boot</span>
</span></span><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> --now mbsync-backup.timer
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Conferir se o timer tá ativo e quando dispara da próxima vez</span>
</span></span><span class="line"><span class="cl">systemctl --user status mbsync-backup.timer
</span></span><span class="line"><span class="cl">systemctl --user list-timers mbsync-backup.timer
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Disparar uma sincronização imediata sem esperar o intervalo</span>
</span></span><span class="line"><span class="cl">systemctl --user start mbsync-backup.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Acompanhar logs em tempo real</span>
</span></span><span class="line"><span class="cl">journalctl --user -u mbsync-backup.service -f
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Conferir status da última execução do serviço</span>
</span></span><span class="line"><span class="cl">systemctl --user status mbsync-backup.service
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Pra desligar tudo</span>
</span></span><span class="line"><span class="cl">systemctl --user disable --now mbsync-backup.timer</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra que o timer continue rodando quando você não está logado (útil em desktop que fica ligado 24/7), habilite linger uma única vez: <code>sudo loginctl enable-linger $USER</code>. Sem isso, os user units só rodam enquanto você tem sessão ativa.</p>
<p>Ativa, confere o status uma vez pra ter certeza que tá verde, e esquece que existe.</p>
<h2>Backup do Maildir com restic<span class="hx:absolute hx:-mt-20" id="backup-do-maildir-com-restic"></span>
    <a href="#backup-do-maildir-com-restic" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora que o Gmail está espelhado em <code>~/.local/share/mail/</code>, ele entra no mesmo pipeline de backup do resto do meu home. Eu uso o <a href="https://restic.net/"target="_blank" rel="noopener">restic</a> pra mandar snapshots criptografados pro NAS. O restic é incremental por content-addressable storage, então adicionar 100MB de e-mail no snapshot diário custa basicamente o delta — não os 100MB inteiros toda vez. Pra cópia off-site, meu NAS Synology já tem o pacote Glacier Backup nativo, que cuida de mandar snapshots criptografados pro AWS Glacier sem eu precisar configurar nada além do bucket e da chave. O Maildir entra junto no mesmo plano, então o backup off-site sai de graça em termos de configuração extra.</p>
<p>O Maildir é amigável a backup incremental por construção: cada e-mail é um arquivo imutável (nem o nome muda depois de baixado, exceto pela flag de seen/flagged). O restic deduplica perfeitamente. Snapshot de mailbox de 5GB tem custo marginal próximo de zero depois do primeiro.</p>
<h2>A vantagem do formato aberto: muitos clientes<span class="hx:absolute hx:-mt-20" id="a-vantagem-do-formato-aberto-muitos-clientes"></span>
    <a href="#a-vantagem-do-formato-aberto-muitos-clientes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui mora a parte que me convenceu de vez. Maildir é um padrão aberto, então <strong>qualquer cliente que entenda Maildir lê o mesmo backup</strong>. Eu posso:</p>
<ul>
<li>Abrir no <a href="http://www.mutt.org/"target="_blank" rel="noopener">mutt</a> ou <a href="https://neomutt.org/"target="_blank" rel="noopener">neomutt</a> quando quero algo rápido no terminal.</li>
<li>Abrir no <a href="https://aerc-mail.org/"target="_blank" rel="noopener">aerc</a> quando quero TUI mais moderna.</li>
<li>Apontar o Thunderbird ou <a href="https://www.claws-mail.org/"target="_blank" rel="noopener">Claws Mail</a> pra <code>~/.local/share/mail/...</code> quando quero GUI.</li>
<li>Migrar pra outro cliente no futuro sem ter que exportar/importar nada.</li>
</ul>
<p>Os e-mails são meus. Vivem no meu disco. Em formato texto, legível por qualquer ferramenta.</p>
<h2>Bônus: scripts e bots leem direto<span class="hx:absolute hx:-mt-20" id="bônus-scripts-e-bots-leem-direto"></span>
    <a href="#b%c3%b4nus-scripts-e-bots-leem-direto" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A consequência menos óbvia disso é que, se eu quiser construir um filtro, um bot, um classificador, ou qualquer coisa que mexe nos e-mails, eu <strong>não preciso passar por OAuth do Gmail, criar projeto no Google Cloud, gerenciar refresh tokens, lidar com quota</strong> — nada. Eu leio direto do Maildir.</p>
<p>O Python tem suporte nativo a Maildir na biblioteca <code>mailbox</code>. Um exemplo bobo pra listar assuntos da inbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">mailbox</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">email.header</span> <span class="kn">import</span> <span class="n">decode_header</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">inbox</span> <span class="o">=</span> <span class="n">mailbox</span><span class="o">.</span><span class="n">Maildir</span><span class="p">(</span><span class="s2">&#34;/home/akitaonrails/.local/share/mail/mbsync/you.example.com/INBOX&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">key</span><span class="p">,</span> <span class="n">msg</span> <span class="ow">in</span> <span class="n">inbox</span><span class="o">.</span><span class="n">iteritems</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">subject</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;Subject&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sender</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;From&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># decode_header lida com Subject em UTF-8 / latin-1 / etc</span>
</span></span><span class="line"><span class="cl">    <span class="n">decoded</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">part</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">enc</span> <span class="ow">or</span> <span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">part</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">)</span> <span class="k">else</span> <span class="n">part</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">part</span><span class="p">,</span> <span class="n">enc</span> <span class="ow">in</span> <span class="n">decode_header</span><span class="p">(</span><span class="n">subject</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">sender</span><span class="p">[:</span><span class="mi">40</span><span class="p">]</span><span class="si">:</span><span class="s2">&lt;40</span><span class="si">}</span><span class="s2">  </span><span class="si">{</span><span class="n">decoded</span><span class="p">[:</span><span class="mi">60</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Roda em milissegundos, não consome quota de API nenhuma, não tem rate limit, não precisa renovar credencial. É só leitura de arquivos. Se você quer fazer um pipeline mais sério (parser de anexos, classificação por LLM, regras automáticas), o mesmo princípio se aplica.</p>
<h2>Resumo<span class="hx:absolute hx:-mt-20" id="resumo"></span>
    <a href="#resumo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A combinação que finalmente me deixou tranquilo:</p>
<ul>
<li><strong>mbsync</strong> (isync) puxa do IMAP do Gmail pra Maildir local, automaticamente via systemd timer.</li>
<li><strong>Maildir</strong> preserva o formato aberto, cada e-mail é um arquivo, pull-only protege contra exclusão acidental.</li>
<li><strong>restic</strong> snapshota o Maildir pro NAS com deduplicação ridícula, e o Glacier Backup do Synology leva isso pra off-site cifrado.</li>
<li><strong>Cliente de e-mail livre</strong> — mutt, neomutt, aerc, Thunderbird, Claws, qualquer um lê.</li>
<li><strong>Automação leve</strong> — bot ou script lê direto do disco, sem OAuth, sem API.</li>
</ul>
<p>O Gmail vira só uma interface web confortável pra ler e responder. A propriedade real dos e-mails está no meu disco, replicada no NAS, replicada em backup off-site cifrado. Se o Google fechar minha conta amanhã (já aconteceu com gente), perco a interface, mas mantenho a história inteira. Pra mim é o jeito definitivo de fazer isso. Recomendo.</p>
]]></content:encoded><category>armazenamento-e-backup</category><category>linux</category><category>automacao</category></item><item><title>NW-Omarchy: Trazendo Omarchy pro X11 com XLibre</title><link>https://akitaonrails.github.io/2026/05/01/omarchy-no-xlibre/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/05/01/omarchy-no-xlibre/</guid><pubDate>Fri, 01 May 2026 18:00:00 -0300</pubDate><description>&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/launcher-drun.png" alt="Launcher do NW-Omarchy: super&amp;#43;space abre rofi com todos os .desktop do sistema, ícones renderizados pelo tema Papirus" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Esse post inaugura um projeto experimental que tenho mexido nas últimas semanas: &lt;a href="https://github.com/akitaonrails/NW-Omarchy"target="_blank" rel="noopener"&gt;NW-Omarchy&lt;/a&gt;. A ideia é simples de explicar e razoavelmente teimosa de defender: pegar o look-and-feel opinativo e bonito do &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;Omarchy&lt;/a&gt; (que é Hyprland, ou seja, Wayland) e levar tudo isso pro mundo X11, como uma sessão paralela que você escolhe no SDDM. Sua sessão Hyprland continua intocada. Quando quiser experimentar X11, escolhe &lt;code&gt;nw-bspwm&lt;/code&gt; no login e cai num ambiente que parece o mesmo Omarchy só que com outras peças por baixo.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/launcher-drun.png" alt="Launcher do NW-Omarchy: super&#43;space abre rofi com todos os .desktop do sistema, ícones renderizados pelo tema Papirus"  loading="lazy" /></p>
<p>Esse post inaugura um projeto experimental que tenho mexido nas últimas semanas: <a href="https://github.com/akitaonrails/NW-Omarchy"target="_blank" rel="noopener">NW-Omarchy</a>. A ideia é simples de explicar e razoavelmente teimosa de defender: pegar o look-and-feel opinativo e bonito do <a href="https://omarchy.org/"target="_blank" rel="noopener">Omarchy</a> (que é Hyprland, ou seja, Wayland) e levar tudo isso pro mundo X11, como uma sessão paralela que você escolhe no SDDM. Sua sessão Hyprland continua intocada. Quando quiser experimentar X11, escolhe <code>nw-bspwm</code> no login e cai num ambiente que parece o mesmo Omarchy só que com outras peças por baixo.</p>
<p>A pergunta óbvia é &ldquo;por quê&rdquo;. A Red Hat já comunicou em 2025 que o <code>xorg-server</code> upstream sai do RHEL 10, e o discurso oficial do mundo Linux corporativo virou &ldquo;use Wayland e pronto&rdquo;. Como sempre, em open source a gente não precisa concordar. Existe XLibre, um fork do xorg-server retomando manutenção ativa, e existe muita gente com hardware velho, driver legado ou software legado pra qual Wayland ainda não é caminho viável. NW-Omarchy é pra esse cenário: dar uma sobrevida elegante pra esse equipamento e, de quebra, deixar ele bonito.</p>
<h2>O que é XLibre e por que ele importa<span class="hx:absolute hx:-mt-20" id="o-que-é-xlibre-e-por-que-ele-importa"></span>
    <a href="#o-que-%c3%a9-xlibre-e-por-que-ele-importa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>XLibre é um fork do xorg-server iniciado em 2025, depois que ficou claro que o upstream não tinha mais energia pra evoluir. O X.Org em si só recebia patches críticos de segurança, e o principal financiador histórico do desenvolvimento (Red Hat) <a href="https://www.redhat.com/en/blog/rhel-10-plans-wayland-and-xorg-server"target="_blank" rel="noopener">comunicou que vai parar</a> com o RHEL 10. XLibre pegou a base e voltou a fazer cleanup, security fixes, modernização e bumps de driver-ABI. A primeira release pública foi a <a href="https://www.phoronix.com/news/XLibre-25.0-Released"target="_blank" rel="noopener">XLibre 25.0</a>, e o projeto vive em <a href="https://github.com/X11Libre/xserver"target="_blank" rel="noopener">github.com/X11Libre/xserver</a>.</p>
<p>A pegada técnica do XLibre é ser drop-in replacement pro xorg-server. Mesmo protocolo X11, mesma API client (libxcb/libx11), mesmo grafo de dependência no pacman. Seu xterm, seu Firefox, seu Steam, seu emulador favorito de fliperama dos anos 90, nada disso sabe que o servidor X mudou. Distros que já adotam XLibre como default ou tier-one:</p>
<ul>
<li><strong>Artix Linux</strong> (<a href="https://linuxiac.com/artix-linux-2026-04-released-with-xlibre-as-default-x-serve/"target="_blank" rel="noopener">2026.04+</a>) usa como X server padrão.</li>
<li><strong>Fedora</strong> tem <a href="https://fedoraproject.org/wiki/Changes"target="_blank" rel="noopener">Change proposal aberta</a> pra migrar.</li>
<li><strong>Arch Linux</strong> tem repo binário oficial em <code>x11libre.net/repo/arch_based/</code>. É exatamente esse que NW-Omarchy usa.</li>
</ul>
<p>Comparando o que tá vivo:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>xorg-server</th>
          <th>XLibre</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Última release não-security</td>
          <td>21.1 (2022)</td>
          <td>25.0 (2025)</td>
      </tr>
      <tr>
          <td>Trabalho ativo no codebase</td>
          <td>nenhum</td>
          <td>sim</td>
      </tr>
      <tr>
          <td>Driver ABI</td>
          <td>congelado</td>
          <td>atualizado (<code>X-ABI-VIDEODRV_VERSION=28.0</code> no 25.0)</td>
      </tr>
      <tr>
          <td>Futuro declarado</td>
          <td>&ldquo;use Wayland&rdquo;</td>
          <td>continuar evolução do X server</td>
      </tr>
      <tr>
          <td>Convive com <code>xorg-xwayland</code></td>
          <td>sim</td>
          <td>sim</td>
      </tr>
  </tbody>
</table>
<p>Se o seu argumento pra ficar no X11 é qualquer um dos clássicos (driver proprietário antigo de Nvidia, GPU Intel vintage, laptop Optimus com muxing esquisito, dependência de <code>xdotool</code>/<code>wmctrl</code>/<code>xprop</code>/<code>xinput</code> pra acessibilidade ou ferramentas de remap, <code>ssh -X</code> pra rodar app remoto, clipboard sem prompt de permissão, screen reader que precisa ler input de qualquer janela), o XLibre é o caminho que mantém esse stack em manutenção ativa.</p>
<h2>Por que esse projeto existe<span class="hx:absolute hx:-mt-20" id="por-que-esse-projeto-existe"></span>
    <a href="#por-que-esse-projeto-existe" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu rodo Omarchy no meu Thinkpad T14 Gen 6 (<a href="/2026/04/18/omarchy-no-thinkpad-t14-gen-6/">escrevi sobre isso aqui</a>) e gosto bastante. O DHH fez um trabalho de curadoria muito bem pensado: tema único e consistente em tudo (terminal, launcher, top bar, lockscreen), atalhos coerentes, distinções visuais que não cansam o olho. É opinativo do jeito certo, te tira da fadiga de configurar tudo do zero.</p>
<p>O problema é que Omarchy é Hyprland, Hyprland é Wayland-only, e Wayland tem cenários onde ainda não é a ferramenta certa. Máquinas mais velhas com driver Nvidia legado, ambientes que dependem de software com integração X11 profunda, situações onde você quer <code>ssh -X</code> e não quer aprender <code>waypipe</code>, ou simplesmente máquinas que você quer manter funcionando sem trocar metade do stack. Pra esses casos, eu queria a estética e o workflow do Omarchy, mas em cima do X11. Não existia. Então fiz.</p>
<p>O nome NW-Omarchy é abreviação de &ldquo;Not Wayland Omarchy&rdquo;. É deliberadamente um sidecar: roda em paralelo, não substitui. Você instala em cima de uma instalação Omarchy existente, ele cria uma sessão SDDM separada chamada <code>nw-bspwm</code>, e nada do seu Hyprland é tocado. Quer voltar pro Hyprland? Escolhe Hyprland no SDDM no próximo login. Quer desinstalar? Tem script de uninstall que reverte exatamente o que foi instalado, baseado num manifest que rastreia cada ação.</p>
<h2>O que muda no stack (Wayland → X11)<span class="hx:absolute hx:-mt-20" id="o-que-muda-no-stack-wayland--x11"></span>
    <a href="#o-que-muda-no-stack-wayland--x11" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Hyprland é compositor + WM no mesmo binário. No mundo X11 essas duas funções vêm em peças separadas, e cada uma tem um substituto bem-conhecido. O esforço de tradução foi mapear cada componente do Omarchy pro equivalente X11 e garantir que o atalho de teclado, o tema e o comportamento ficassem o mais próximos possível.</p>
<table>
  <thead>
      <tr>
          <th>Função</th>
          <th>Omarchy/Wayland</th>
          <th>NW-Omarchy/X11</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Window manager</td>
          <td>Hyprland</td>
          <td><a href="https://github.com/baskerville/bspwm"target="_blank" rel="noopener">bspwm</a></td>
      </tr>
      <tr>
          <td>Hotkey daemon</td>
          <td>Hyprland built-in</td>
          <td><a href="https://github.com/baskerville/sxhkd"target="_blank" rel="noopener">sxhkd</a></td>
      </tr>
      <tr>
          <td>Compositor</td>
          <td>Hyprland built-in</td>
          <td><a href="https://github.com/yshui/picom"target="_blank" rel="noopener">picom</a> (upstream v13)</td>
      </tr>
      <tr>
          <td>Top bar</td>
          <td>Waybar</td>
          <td><a href="https://github.com/polybar/polybar"target="_blank" rel="noopener">polybar</a></td>
      </tr>
      <tr>
          <td>App launcher</td>
          <td>Walker</td>
          <td><a href="https://github.com/davatorium/rofi"target="_blank" rel="noopener">rofi</a></td>
      </tr>
      <tr>
          <td>Notifications</td>
          <td>Mako</td>
          <td><a href="https://github.com/dunst-project/dunst"target="_blank" rel="noopener">dunst</a></td>
      </tr>
      <tr>
          <td>Idle/lock</td>
          <td>hypridle / hyprlock</td>
          <td>xidlehook + xss-lock + i3lock-color</td>
      </tr>
      <tr>
          <td>Nightlight</td>
          <td>hyprsunset</td>
          <td>redshift</td>
      </tr>
      <tr>
          <td>Clipboard history</td>
          <td>walker <code>-m clipboard</code></td>
          <td><a href="https://github.com/cdown/clipmenu"target="_blank" rel="noopener">clipmenu</a> (rofi-backed)</td>
      </tr>
      <tr>
          <td>Screenshots</td>
          <td>hyprshot</td>
          <td>maim + slop + xclip</td>
      </tr>
      <tr>
          <td>Color picker</td>
          <td>hyprpicker</td>
          <td>xcolor</td>
      </tr>
  </tbody>
</table>
<p><strong>bspwm</strong> é tiling window manager minimalista. O WM em si só responde a comandos via socket, e o <code>bspwmrc</code> é literalmente um shell script. Atalho de teclado fica num daemon separado, <strong>sxhkd</strong>, que lê um arquivo de texto. Essa separação é típica do mundo X11: cada peça faz uma coisa só.</p>
<p><strong>picom</strong> é o compositor que dá blur, sombra, cantos arredondados, fade-in/out e regras de opacidade por janela. NW-Omarchy usa o upstream v13 (release de fevereiro de 2026), não o fork antigo <code>picom-ftlabs-git</code>, <a href="https://github.com/yshui/picom/issues"target="_blank" rel="noopener">abandonado desde 2024</a>. Os efeitos visuais que o Omarchy tem no Hyprland (cantos arredondados, fade ao abrir/fechar, blur no rofi e no dunst) vêm todos do picom no NW-Omarchy.</p>
<p><strong>polybar</strong> substitui o waybar. Mesma topologia visual: logo do Omarchy à esquerda, data no centro, indicadores de áudio/wifi/bluetooth/bateria à direita. Os ícones nerd-font são os mesmos. Click esquerdo abre os mesmos TUIs (wiremix, impala, bluetui), click direito as variantes GUI. Scroll no áudio ajusta volume, click do meio mute. Paridade prática.</p>
<p><strong>rofi</strong> substitui o walker. <code>super + space</code> abre o launcher completo com todos os <code>.desktop</code> do sistema e ícones. <code>super + shift + space</code> abre uma cheat-sheet das aplicações fixadas, mostrando o atalho ao lado do nome. Útil pra lembrar &ldquo;qual era o chord pro Signal mesmo&rdquo;.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/launcher-cheatsheet.png" alt="Cheat-sheet das aplicações fixadas: super&#43;shift&#43;space abre o rofi com cada app e o chord ao lado, parseado direto do sxhkdrc"  loading="lazy" /></p>
<p>Pra usuário que não vai abrir o <code>~/.config</code> pra customizar nada, a diferença entre as duas sessões é cosmética em níveis homeopáticos. Os mesmos atalhos do Omarchy (mais de 70 bindings) estão mapeados, os mesmos menus aparecem, o mesmo logo no canto da bar, os mesmos ícones nerd-font.</p>
<h2>O sistema de temas é o mesmo<span class="hx:absolute hx:-mt-20" id="o-sistema-de-temas-é-o-mesmo"></span>
    <a href="#o-sistema-de-temas-%c3%a9-o-mesmo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa parte foi a mais legal de implementar e a que mais surpreende quando você experimenta. O Omarchy tem um sistema de temas centralizado: cada tema vive em <code>~/.local/share/omarchy/themes/&lt;nome&gt;/</code>, traz um <code>colors.toml</code> com paleta universal (<code>accent</code>, <code>background</code>, <code>foreground</code>, <code>color0..15</code>), e <code>omarchy-theme-set &lt;nome&gt;</code> re-renderiza configs de cada app pra refletir essa paleta. O tema ativo fica em <code>~/.config/omarchy/current/theme/</code>, e cada app importa as cores de lá.</p>
<p>NW-Omarchy se encaixa nesse mesmo pipeline. Eu adicionei templates <code>.tpl</code> pra bspwm, polybar, rofi e dunst que seguem a mesma convenção do Omarchy, e o <code>omarchy-theme-set-templates</code> regenera tudo a cada troca de tema. O resultado é que os 19 temas que o Omarchy distribui funcionam tal e qual no NW-Omarchy, sem trabalho de portar. Catppuccin, gruvbox, kanagawa, nord, tokyo-night, todos funcionam. O wallpaper troca junto via um daemon inotify (<code>nw-omarchy-bg-watch</code>).</p>
<p>Olha alguns exemplos rodando:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-ristretto.png" alt="Tema Ristretto: paleta quente em curvas marrons no wallpaper, polybar e bspwm border seguindo a mesma paleta"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-lumon.png" alt="Tema Lumon: referência ao Severance, paleta corporativa fria, “United in Severance”"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-everforest.png" alt="Tema Everforest: tons de verde-musgo, wallpaper de copa de árvores enevoada"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/theme-retro-82.png" alt="Tema Retro-82: synthwave, grade rosa em fundo escuro, neon nos detalhes"  loading="lazy" /></p>
<p>Quando o Omarchy ganha um tema novo via <code>omarchy-theme-update</code>, NW-Omarchy pega ele de graça. Sem trabalho extra, sem rebuild, sem precisar republicar nada do meu lado.</p>
<h2>Menu do sistema e TUIs<span class="hx:absolute hx:-mt-20" id="menu-do-sistema-e-tuis"></span>
    <a href="#menu-do-sistema-e-tuis" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O menu de sistema do Omarchy (aquele <code>super + alt + space</code> que abre o menu hierárquico com Hardware, Setup, Update, etc.) tem porte 1-pra-1 no NW-Omarchy. Mesma árvore, mesma navegação. Onde o Omarchy chama <code>walker --dmenu</code>, eu chamo <code>rofi -dmenu</code>. O resto do código é idêntico, e a maioria dos helpers que o menu invoca (<code>omarchy-pkg-add</code>, <code>omarchy-theme-set</code>, <code>omarchy-update</code>, etc.) não tem nada de Wayland-específico e funciona direto no X11.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/system-menu.png" alt="nw-omarchy-menu aberto: a árvore do menu de sistema do Omarchy, navegação por seta, mesma estética"  loading="lazy" /></p>
<p>Os TUIs também: wiremix pra áudio, impala pra wifi, bluetui pra bluetooth, btop pra recursos do sistema. Todos abrem como janela flutuante centralizada, igual o Omarchy faz no Hyprland. O bspwm tem regra (<code>bspc rule -a</code>) que reconhece a classe <code>org.nw-omarchy.&lt;cmd&gt;</code> e aplica <code>state=floating center=on rectangle=900x600</code> automaticamente.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/05/01/nw-omarchy/wifi-tui.png" alt="Impala TUI rodando como janela flutuante centralizada via super&#43;ctrl&#43;w, paleta seguindo o tema ativo"  loading="lazy" /></p>
<h2>O que tem e o que não tem<span class="hx:absolute hx:-mt-20" id="o-que-tem-e-o-que-não-tem"></span>
    <a href="#o-que-tem-e-o-que-n%c3%a3o-tem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Honestidade primeiro: tem coisa do Hyprland que é Wayland-only e não volta. Fractional scaling per-monitor não rola, X11 só faz scaling inteiro por output, então setup com mix de DPI fica torto. HDR não existe no X11. Renderização tear-free por design exige Wayland; no X11 precisa de picom + vsync e GPU cooperativa, e mesmo assim fica perto da Wayland sem chegar igual. Workspace swipe com preview ao vivo durante o gesto também não rola, porque o bspwm não tem estado de switch em progresso, então o gesto dispara comando discreto só na conclusão (você vai pra próxima workspace, mas não vê o preview deslizando). E os daemons especificamente Wayland (voxtype, walker preview pane, hyprsunset, hypridle) viram <code>redshift</code> e <code>xss-lock</code>, sem preview pane no rofi.</p>
<p>Se qualquer um desses pontos é decisivo pra você, fica no Omarchy/Hyprland. SDDM escolhe entre as duas sessões a cada login, então dá pra coexistir sem ressentimento.</p>
<p>Em compensação, o que o X11 tem de bom continua valendo. <code>xdotool</code>, <code>wmctrl</code>, <code>xprop</code> e <code>xinput</code> continuam funcionando pra automação, key remap, screen reader, AutoKey, kanata, xkeysnail. <code>xclip</code>, <code>xsel</code> e clipmenu fazem clipboard plumbing direto, sem dialog de permissão. <code>ssh -X</code> segue funcionando: você roda Firefox numa máquina remota e a janela aparece local. Driver Nvidia legacy, Intel vintage e Optimus quirky são first-class no X11. O ecossistema de tiling WM é maduro (bspwm, i3, awesome, openbox, xmonad, dwm) e configs migram entre eles com pouca dor. E o modelo de input previsível, onde qualquer cliente lê qualquer evento, é ruim pra segurança mas ótimo pra acessibilidade e ferramenta de produtividade.</p>
<p>Pra hardware mais velho, isso pesa como diferença entre &ldquo;máquina viva&rdquo; e &ldquo;máquina parada num canto&rdquo;.</p>
<h2>Instalação<span class="hx:absolute hx:-mt-20" id="instalação"></span>
    <a href="#instala%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pré-requisito: Arch Linux com Omarchy já instalado. NW-Omarchy roda em cima dele, não substitui.</p>
<p>One-liner de instalação:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://raw.githubusercontent.com/akitaonrails/NW-Omarchy/master/boot.sh <span class="p">|</span> bash</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>boot.sh</code> faz preflight (checa Arch, Omarchy, git), clona o repo pra <code>~/.local/share/nw-omarchy</code>, e roda a pipeline de install completa: pacotes, sessão <code>nw-bspwm</code>, configs em <code>~/.config</code>, e troca pro XLibre como último passo. Pede confirmação uma vez antes de tocar em qualquer coisa. Se você quer pular a confirmação:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://raw.githubusercontent.com/akitaonrails/NW-Omarchy/master/boot.sh <span class="p">|</span> bash -s -- --yes</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando termina, reboota. A troca do X server (xorg-server → XLibre) só vale na próxima sessão. No SDDM, você verá um picker de sessão. A gente também substituiu o tema do SDDM por uma versão com seletor, porque o tema vanilla do Omarchy tem o nome da sessão hardcoded no QML, sem UI pra trocar. Escolhe <code>nw-bspwm</code> e tá dentro.</p>
<p>Se você prefere clonar e rodar à mão pra inspecionar antes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/akitaonrails/NW-Omarchy.git ~/.local/share/nw-omarchy
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ~/.local/share/nw-omarchy
</span></span><span class="line"><span class="cl">./install.sh           <span class="c1"># dry-run, mostra o que faria</span>
</span></span><span class="line"><span class="cl">./install.sh --apply   <span class="c1"># executa de verdade</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Atualização<span class="hx:absolute hx:-mt-20" id="atualização"></span>
    <a href="#atualiza%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando sai release nova, o caminho é:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nw-omarchy-upgrade --check    <span class="c1"># checa current vs latest, sai</span>
</span></span><span class="line"><span class="cl">nw-omarchy-upgrade            <span class="c1"># yay -Syu, fetch latest tag, migrations, install</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O script roda <code>yay -Syu</code> (ou <code>pacman -Syu</code> se você não tem yay), compara a tag local vs a remota, e se tiver release nova faz <code>git fetch &amp;&amp; git checkout v&lt;latest&gt;</code>, roda as migrations entre a versão atual e a nova, e re-aplica <code>install.sh --apply</code>. Tudo idempotente. Re-rodar numa máquina já atualizada é no-op.</p>
<h2>Desinstalação<span class="hx:absolute hx:-mt-20" id="desinstalação"></span>
    <a href="#desinstala%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se experimentou e não gostou, desinstala assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">~/.local/share/nw-omarchy/uninstall.sh --apply</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O uninstaller lê <code>~/.local/state/nw-omarchy/manifest.tsv</code> e replay reverso: remove só os pacotes que ele instalou (<code>pkg-skip</code> rows ficam intocadas; pacote que já existia continua no sistema), restaura configs originais a partir de <code>~/.local/state/nw-omarchy/backups/</code>, deleta a session entry <code>nw-bspwm</code>, limpa o state dir. Sua sessão Hyprland continua funcionando como antes. Nada do <code>~/.config/hypr/</code> ou do <code>~/.local/share/omarchy/</code> é tocado em momento algum durante a instalação ou desinstalação.</p>
<p>Pra reverter especificamente o XLibre de volta pro xorg-server (sem desinstalar o resto), o pacman resolve sozinho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S xorg-server xorg-server-common xf86-input-libinput</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pacote <code>provides</code>/<code>conflicts</code> cuida do resto da troca atomicamente.</p>
<h2>Status: experimental, feedback bem-vindo<span class="hx:absolute hx:-mt-20" id="status-experimental-feedback-bem-vindo"></span>
    <a href="#status-experimental-feedback-bem-vindo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A versão atual tá em 1.0 mas o projeto continua novo, em fase de polimento. Os itens grandes já estão de pé: paridade de bindings, paridade de menu, paridade de temas, paridade dos TUIs, doctor pra checar saúde da instalação, manifest pra uninstall limpo. Falta lapidar, descobrir edge cases, exercitar em hardware variado, ouvir feedback de quem rodar em máquina diferente da minha (Thinkpad T14 Gen 6).</p>
<p>Se você experimentar e bater em algo (coisa que não funciona, configuração que faltou, comportamento que difere do Omarchy de um jeito que quebra fluxo), abre issue lá:</p>
<p><a href="https://github.com/akitaonrails/NW-Omarchy/issues"target="_blank" rel="noopener">github.com/akitaonrails/NW-Omarchy/issues</a></p>
<p>Reproduções, stack traces, output do <code>nw-omarchy-doctor</code>, qualquer coisa ajuda. PRs também são bem-vindos.</p>
<p>A defesa do X11 em 2026 é pragmática. A transição pro Wayland leva tempo, muito hardware e muito software ainda vivem confortavelmente do lado de cá, e a comunidade open source comporta mais de um caminho ao mesmo tempo. XLibre é a aposta de manter o X server vivo por mais alguns anos. NW-Omarchy é minha aposta de que dá pra fazer esse stack mais velho parecer tão bonito quanto o stack mais novo, sem comprometer nada do que faz X11 ainda valer a pena.</p>
<p>Se você tá em máquina nova, com GPU moderna, monitor 4K HiDPI, querendo HDR, fica no Omarchy/Hyprland. Se você tá numa máquina mais antiga, ou simplesmente prefere o ecossistema X11 por qualquer um dos motivos acima, dá uma chance pro NW-Omarchy.</p>
]]></content:encoded><category>omarchy</category><category>linux</category></item><item><title>Meus Retrogames de Corrida Favoritos Rodando no Meu Distrobox</title><link>https://akitaonrails.github.io/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/</guid><pubDate>Sun, 19 Apr 2026 20:00:00 -0300</pubDate><description>&lt;p&gt;Eu passei o domingo inteiro nisso, e foi um dos domingos mais produtivos que eu tive em tempos. A missão foi específica: fechar os jogos de corrida simcade mais difíceis de emular, que eu quero rodar há anos, e só agora consegui chegar num estado confiável.&lt;/p&gt;
&lt;p&gt;No topo dessa lista estão dois monstros. Primeiro, &lt;strong&gt;&lt;a href="#driveclub-o-imposs%c3%advel-finalmente-poss%c3%advel"&gt;Driveclub&lt;/a&gt;&lt;/strong&gt; no shadPS4. É exclusivo de PS4, nunca foi portado, a Evolution Studios foi fechada, não tem remaster. Pra jogar fora do PS4 original, só via emulação, e emulação de PS4 ainda é a parte mais imatura da ecossistema hoje. Esse é &lt;strong&gt;de longe o mais difícil da lista&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu passei o domingo inteiro nisso, e foi um dos domingos mais produtivos que eu tive em tempos. A missão foi específica: fechar os jogos de corrida simcade mais difíceis de emular, que eu quero rodar há anos, e só agora consegui chegar num estado confiável.</p>
<p>No topo dessa lista estão dois monstros. Primeiro, <strong><a href="#driveclub-o-imposs%c3%advel-finalmente-poss%c3%advel">Driveclub</a></strong> no shadPS4. É exclusivo de PS4, nunca foi portado, a Evolution Studios foi fechada, não tem remaster. Pra jogar fora do PS4 original, só via emulação, e emulação de PS4 ainda é a parte mais imatura da ecossistema hoje. Esse é <strong>de longe o mais difícil da lista</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/19/distrobox-gaming/shadps4-launcher.png" alt="shadPS4 Qt Launcher mostrando DRIVECLUB CUSA00003 pronto pra rodar"  loading="lazy" /></p>
<p>Segundo, <strong><a href="#forza-motorsport-4-o-goat">Forza Motorsport 4 com Project Forza Plus</a></strong> no Xenia Canary. FM4 é o GOAT da era Xbox 360, Project Forza Plus é a mod comunitária que consolida patches e conteúdo, e fazer os dois rodarem juntos no Xenia com gráfico decente, sem crash de áudio e sem shadow bug, levou horas sérias de trial and error.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/19/distrobox-gaming/xenia-manager.png" alt="Xenia Manager com a coleção Forza do Xbox 360 instalada"  loading="lazy" /></p>
<p>Em torno desses dois eu organizei o resto do que eu já sabia que funcionava em algum nível, só faltava consolidar num setup repetível: <strong><a href="#gran-turismo-4">Gran Turismo 4 com Retexture 3.0 e HD HUD</a></strong>, <strong><a href="#gran-turismo-3-a-spec">Gran Turismo 3 com HD textures e widescreen</a></strong>, <strong><a href="#gran-turismo-2-spec-ii-mod">Gran Turismo 2 na mod Spec II</a></strong>, os Gran Turismos de PS3 (<a href="#gran-turismo-5">GT5</a> e <a href="#gran-turismo-6">GT6</a>), os Forza Horizon <a href="#forza-horizon-1-com-xe-mod">1</a> e <a href="#forza-horizon-2">2</a>, os <a href="#project-gotham-racing-3-e-4">PGRs</a>, <a href="#ridge-racer-v">Ridge Racer</a>, <a href="#enthusia-professional-racing">Enthusia</a>, <a href="#colin-mcrae-rally-1-e-2">Colin McRae</a>. Esses eu tinha ideia de como fazer, só precisava consolidar num setup que não volta a quebrar na próxima vez que eu reinstalar o sistema.</p>
<p>Antes de entrar no detalhe, preciso esclarecer duas coisas.</p>
<p>Primeiro, sobre a infra: não vou repetir aqui o como o setup foi montado. Já escrevi isso em detalhe no artigo <a href="/2026/04/11/distrobox-de-emulacao-com-claude-code/">Distrobox de Emulação com Claude Code</a>. Arch Linux num distrobox com <code>--nvidia</code>, 17 roles Ansible, todos os emuladores no AUR, ES-DE como frontend, configs per-game automatizadas, scripts Python pra PS3 update check e Xbox 360 title updates, Xenia via Wine. Lê lá. O repo tá em <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">akitaonrails/distrobox-gaming</a> pra quem quiser reproduzir. Aqui eu vou falar dos jogos.</p>
<p>Segundo, sobre o gosto: eu gosto de simcade de corrida. Gran Turismo é meu vício declarado desde o PS1, e escrevi sobre o resto no artigo do <a href="/2026/04/01/meu-cockpit-de-sim-racing-formula-fx1/">meu cockpit Formula FX1</a>. Volante direct drive, pedal com load cell, triple monitor quando tô afim de sofrer. Esse é o tipo de corrida que eu gosto.</p>
<h2>O aviso pra quem já tá de dedo no teclado<span class="hx:absolute hx:-mt-20" id="o-aviso-pra-quem-já-tá-de-dedo-no-teclado"></span>
    <a href="#o-aviso-pra-quem-j%c3%a1-t%c3%a1-de-dedo-no-teclado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Sim, eu conheço iRacing e os outros sims e simcades atuais. Eu tenho 12 TB de ISO e ROM no NAS, provavelmente tenho algum que você ia lembrar de citar. Não é por falta de opção que eu tô focando em emulação hoje.</p>
<p>Aliás, dos jogos novos que eu realmente tô esperando: <strong><a href="https://forza.net/"target="_blank" rel="noopener">Forza Horizon 6</a> em Tóquio</strong>, <strong><a href="https://www.assettocorsa.it/"target="_blank" rel="noopener">Assetto Corsa EVO</a></strong> e <strong><a href="https://www.assettocorsa.it/"target="_blank" rel="noopener">Assetto Corsa Rally</a></strong>. Tô jogando as demos dos dois Assetto Corsa e gostando muito das campanhas single-player. Quando eles saírem em versão final, vão virar meus jogos principais de sim na rotina. Esse artigo aqui cobre um lado diferente da coleção, que é tirar do esquecimento os clássicos que ainda não têm remaster.</p>
<p>Hoje, aqui, eu estou interessado nesses jogos específicos que listei acima. Ponto. Você faz o que você quer, eu faço o que eu quero. Se você quer iRacing, abra um Steam e vá em paz. Esse artigo é sobre mexer com emulador, preservar jogo velho, e tirar essas pérolas do esquecimento num setup Linux. Quem tá procurando review de sim atual não vai encontrar aqui.</p>
<p>Agora vamos ao que interessa.</p>
<p><strong>Observação sobre os vídeos:</strong> a maioria tá sem áudio porque eu esqueci de ligar a captura de som no OBS antes de gravar e fiquei com preguiça de re-gravar. O áudio funciona perfeito em todos os emuladores aqui, é falha minha no OBS, não do setup.</p>
<h2>Settings globais por emulador<span class="hx:absolute hx:-mt-20" id="settings-globais-por-emulador"></span>
    <a href="#settings-globais-por-emulador" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes dos jogos, vale consolidar as configs globais que valem pra tudo em cada emulador. Isso tá automatizado no repo, mas se você tá configurando na mão, aqui tá o resumo.</p>
<h3>DuckStation (PS1)<span class="hx:absolute hx:-mt-20" id="duckstation-ps1"></span>
    <a href="#duckstation-ps1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Internal Resolution Scale:</strong> 8x (8K render, downscalado pro monitor)</li>
<li><strong>Texture Filtering:</strong> JINC2 (preserva pixel art mas suaviza)</li>
<li><strong>PGXP:</strong> ligado (corrige wobble de vértice típico do PS1)</li>
<li><strong>Widescreen Hack:</strong> ligado como fallback, mas preferência por cheat widescreen per-game</li>
<li><strong>Aspect Ratio:</strong> 16:9 com widescreen cheat, 4:3 sem</li>
</ul>
<p>PS1 hoje em dia sobe liso no DuckStation. É o emulador mais maduro da lista. A atenção vai pras escolhas per-game (alguns jogos precisam de cheat específico de widescreen).</p>
<h3>PCSX2 (PS2)<span class="hx:absolute hx:-mt-20" id="pcsx2-ps2"></span>
    <a href="#pcsx2-ps2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Upscale:</strong> 4x SSAA (1440p nativo → 4K interno)</li>
<li><strong>Anisotropic Filtering:</strong> 16x</li>
<li><strong>Post Processing:</strong> FXAA ligado, PCRTC antiblur ligado</li>
<li><strong>Deinterlacing:</strong> Automatic (alguns jogos precisam override)</li>
<li><strong>Controller:</strong> bindings Xbox-style (eu uso controle 8BitDo)</li>
</ul>
<p>PCSX2 2.7.x é muito superior ao 2.6.3 que ainda tá no repo oficial do Arch. O AUR <code>pcsx2-latest-bin</code> tracka o 2.7.x e é onde mora o texture replacement, FXAA moderno e PCRTC antiblur. Se você tá em distro diferente e tá no 2.6.3, procure o AppImage oficial.</p>
<h3>RPCS3 (PS3)<span class="hx:absolute hx:-mt-20" id="rpcs3-ps3"></span>
    <a href="#rpcs3-ps3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Resolution Scale:</strong> 300% (geralmente) ou nativo (jogos com issue de upscale)</li>
<li><strong>Shader Precision:</strong> Ultra</li>
<li><strong>Force High Precision Z:</strong> ligado</li>
<li><strong>SPU XFloat:</strong> Accurate</li>
<li><strong>Multithreaded RSX:</strong> ligado</li>
<li><strong>Write Color Buffers (WCB) / Read Color Buffers (RCB):</strong> desligados por padrão (GT-series safety)</li>
</ul>
<p>RPCS3 é onde mais tem armadilha. Cada jogo tem preset recomendado na <a href="https://rpcs3.net/compatibility"target="_blank" rel="noopener">DB de compatibilidade oficial</a>. E atenção: configs per-game ficam em <code>~/.config/rpcs3/custom_configs/config_&lt;SERIAL&gt;.yml</code>. Prefixo <code>config_</code> obrigatório. Perdi tempo até descobrir isso, o RPCS3 aceita o YAML silenciosamente e depois ignora se o nome tá errado.</p>
<h3>Xenia Canary (Xbox 360)<span class="hx:absolute hx:-mt-20" id="xenia-canary-xbox-360"></span>
    <a href="#xenia-canary-xbox-360" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>Build:</strong> Canary (o <code>master</code> tá parado há meses, Canary é o que anda)</li>
<li><strong>Renderer:</strong> Vulkan</li>
<li><strong>Render Target Path:</strong> <code>rtv</code> por default, <code>fsi</code> pra jogos específicos (PGR4)</li>
<li><strong>User Profile:</strong> criado uma vez, persiste</li>
<li><strong>Wine Prefix:</strong> dedicado, gerenciado pelo Xenia Manager</li>
</ul>
<p>Xenia no Linux roda via Wine, gerenciado pelo <a href="https://github.com/xenia-manager/xenia-manager"target="_blank" rel="noopener">Xenia Manager</a>. Não tem build nativo decente. Funciona bem, mas cada jogo pede ajuste específico, e Title Updates tem que ser puxado manualmente do archive.org (escrevi um script pra isso, detalhes no artigo anterior).</p>
<h3>shadPS4 (PS4)<span class="hx:absolute hx:-mt-20" id="shadps4-ps4"></span>
    <a href="#shadps4-ps4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse merece a seção dedicada no final. Resumindo: funciona pra alguns jogos simples, pra Driveclub ainda não é confiável. Continua sendo um tiro no escuro.</p>
<h2>PS1 no DuckStation<span class="hx:absolute hx:-mt-20" id="ps1-no-duckstation"></span>
    <a href="#ps1-no-duckstation" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo (o primeiro, de 1997)<span class="hx:absolute hx:-mt-20" id="gran-turismo-o-primeiro-de-1997"></span>
    <a href="#gran-turismo-o-primeiro-de-1997" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%201.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Esse aqui é puro sentimento. GT1 foi um dos primeiros jogos que eu comprei pro PS1, e joguei até rachar o CD. Na época, era outro patamar. Simulação real numa máquina de 128K de RAM de vídeo, física decente, 140 carros quando todo mundo tinha 15, trilha sonora memorável.</p>
<p>Mas sejamos honestos: não dá pra voltar hoje. A física do GT1 tem drift exagerado, o carro desliza mais do que deveria em qualquer curva acima de 80 km/h. É parte do charme de 1997 mas irrita em 2026. O <strong>modelo de controle foi muito refinado a partir do GT2</strong>, e tudo que veio depois só melhorou. Entre GT1 e GT2, GT2 vence em conteúdo (650+ carros vs 140) e principalmente em dirigibilidade.</p>
<p>Eu deixo o GT1 no ES-DE pra abrir em momentos de nostalgia, jogar uns 10 minutos em Trial Mountain e fechar. Pra gameplay sério da era PS1, vai de GT2.</p>
<h3>Gran Turismo 2 (Spec II Mod)<span class="hx:absolute hx:-mt-20" id="gran-turismo-2-spec-ii-mod"></span>
    <a href="#gran-turismo-2-spec-ii-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>O Gran Turismo original do PS1 é um marco histórico. Eu joguei muito na época. Hoje, pra jogar de novo? Não. GT2 existe, e GT2 é superior em tudo. Mais carros (650+), mais pistas, mais modos, e principalmente, <strong>um modelo de controle muito melhor</strong>. O GT1 tem aquele drift exagerado onde o carro desliza mais do que deveria, o GT2 ajustou isso pra algo mais próximo do que a série seria a partir do PS2. Entre jogar GT1 ou GT2, sempre GT2.</p>
<p>E o GT2 que eu rodo hoje é o <strong>GT2 Spec II Mod</strong> do <a href="https://x.com/projectaspec?lang=en"target="_blank" rel="noopener">Project A-Spec</a>, um projeto comunitário que consolida as duas variantes regionais (Arcade + Simulation) num único jogo, traz de volta eventos que foram cortados no release final, corrige bugs de física, adiciona suporte a widescreen nativo e dá quality-of-life updates em menus. É o jeito definitivo de jogar GT2 em 2026. O projeto não mantém mais site próprio, as atualizações vêm pelo Twitter do autor.</p>
<p>Pra rodar:</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disco</td>
          <td>GT2 Spec II Mod (patch aplicado sobre ISO Simulation USA)</td>
      </tr>
      <tr>
          <td>Serial</td>
          <td>SCUS-94455</td>
      </tr>
      <tr>
          <td>Widescreen cheat</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>8 MB RAM cheat</td>
          <td>Ligado (corrige áudio em pistas cheias)</td>
      </tr>
      <tr>
          <td>Texture Filter</td>
          <td>JINC2</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>8x</td>
      </tr>
  </tbody>
</table>
<p>O cheat de 8 MB RAM é importante pra pistas cheias (tipo Seattle em corridas populated) onde o áudio começa a estourar. Ele simula a expansão de memória da versão japonesa que nunca veio pro Ocidente.</p>
<h3>Colin McRae Rally 1 e 2<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-1-e-2"></span>
    <a href="#colin-mcrae-rally-1-e-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Jogos icônicos do PS1. CMR1 tem aquela sensação áspera de rally primeiro-gen que tem seu charme. CMR2 é tecnicamente superior, física melhor, mais rallys, melhor grafia. Mas eu vou ser honesto: <strong>pra jogar CMR2 hoje, vale procurar a versão PC original</strong>. Resolução maior, 60fps nativo (a versão PS1 trava em 30), controle direto de teclado/volante. Existem repacks do Codemasters na internet, tem umas 40 segundos num search decente. Não vou linkar, você sabe onde procurar.</p>
<p>No PS1 via DuckStation eles rodam liso, nostalgia garantida, mas não é onde eu passo o tempo.</p>
<h2>PS2 no PCSX2<span class="hx:absolute hx:-mt-20" id="ps2-no-pcsx2"></span>
    <a href="#ps2-no-pcsx2" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo 3 A-Spec<span class="hx:absolute hx:-mt-20" id="gran-turismo-3-a-spec"></span>
    <a href="#gran-turismo-3-a-spec" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%203.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT3 foi um salto geracional. Saiu do mundo quadradinho do PS1 pra um mundo de shaders, environment mapping decente, modelos de carro com polígono sério, física que finalmente começou a parecer com física de verdade. A trilha sonora com Feeder de &ldquo;Just a Day&rdquo; abrindo o jogo ainda me dá arrepio.</p>
<p>O problema é que GT3 tem MENOS conteúdo que GT2. Polyphony foi mais cautelosa, cortou modos pra focar em qualidade. Licença A/B/S é menor, carros são menos, pistas são fewer. Mas o que tem é polido.</p>
<p>Em 2026 no PCSX2 2.7.x, GT3 fica muito bom. HD textures de comunidade, widescreen pnach, anti-aliasing via SSAA, 16x AF. Parece jogo relançado.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disco</td>
          <td>GT3 A-spec (SCUS-97102) ou Bundle (PBPX-95503)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>Pack de retextura</td>
          <td>Opcional (desativado por default, ativar se quiser ver tudo HD)</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
      <tr>
          <td>Deinterlacing</td>
          <td>Automatic</td>
      </tr>
  </tbody>
</table>
<p>Caveat sobre o pack de retextura: quando ligado, tem cenas de mostruário de carro no garage onde o NFS stream causa um flicker momentâneo. Não incomoda em corrida. Se te irrita no menu, desliga.</p>
<p><strong>Lembrete:</strong> pro widescreen pnach fazer efeito você ainda precisa entrar nas opções <strong>dentro do jogo</strong> e mudar o aspect ratio pra 16:9. O pnach libera a opção no menu, não aplica automaticamente.</p>
<h3>Gran Turismo 4 (Spec II Mod)<span class="hx:absolute hx:-mt-20" id="gran-turismo-4-spec-ii-mod"></span>
    <a href="#gran-turismo-4-spec-ii-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%204.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT4 é o meu GOAT da série. 700+ carros, 50+ circuitos, física ainda respeitável pra padrão 2026, e aquela trilha sonora que mistura rock alternativo, lounge japonês, e o infame &ldquo;Sail Away&rdquo; do Moby no menu principal. Foi o jogo que mais mexi em 2004, e é o que eu mais mexo hoje.</p>
<p>Igual o GT2, o GT4 que eu rodo hoje <strong>não é o disco original</strong>. É o <a href="https://www.theadmiester.co.uk/specii/"target="_blank" rel="noopener"><strong>Gran Turismo 4: Spec II</strong></a> mantido pelo TheAdmiester — uma mod comunitária massiva que é objetivamente o melhor jeito de jogar GT4 em 2026. Ela combina o disco final USA, a Beta prototype de 2003 e o release japonês original num único jogo, restaurando carros cortados (incluindo vários que nunca saíram fora do Japão), eventos cancelados, dezenas de faixas de trilha sonora faltantes, suporte a widescreen 16:9, mais de 30 cheats integrados como opções de menu (pro tuning, velocidade máxima, economia de combustível), opção de trocar entre idiomas Inglês/Japonês, e correções de bugs originais. O projeto é ativo, ainda recebe patches e tem instalador próprio que aplica sobre uma ISO USA limpa.</p>
<p>Na prática, Spec II é o que o GT4 deveria ter sido se a Polyphony tivesse tido mais seis meses pra polir. Quando você joga hoje, é GT4 com todo o conteúdo que ficou de fora + quality-of-life moderno. Pra qualquer um que gostou do original, é obrigatório.</p>
<p>Por cima disso vai o <strong>Retexture 3.0</strong> da comunidade (pack de textura HD) e o <strong>HD HUD</strong> do <a href="https://github.com/Silentwarior112/GT4-HD-HUD-Pack"target="_blank" rel="noopener">Silentwarior112</a>. Com essas três peças juntas (Spec II Mod + Retexture + HD HUD), GT4 roda a 1440p SSAA no PCSX2 2.7.x com 16x AF, FXAA, e uma cara que honestamente é indistinguível de um jogo novo em low-poly style. É o meu atual favorito pra sentar e jogar longo.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disco</td>
          <td>GT4 USA (SCUS-97328) ou Spec II (SCUS-97436, CRC <code>4CE521F2</code>)</td>
      </tr>
      <tr>
          <td>HD HUD pack</td>
          <td>Instalado via symlink (Silentwarior112)</td>
      </tr>
      <tr>
          <td>Retexture 3.0</td>
          <td>Instalado</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>Ligado (renomeado pro CRC do Spec II se for Spec II)</td>
      </tr>
      <tr>
          <td>Silent&rsquo;s trigger/camera patches</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>Deinterlace (Spec II)</td>
          <td>Mode 8 Adaptive TFF</td>
      </tr>
      <tr>
          <td>ShadeBoost (Spec II)</td>
          <td>Saturation +10, Brightness +3, Contrast +2</td>
      </tr>
      <tr>
          <td>FXAA + PCRTC antiblur</td>
          <td>Ligado global</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<p>Quem tá usando o Spec II Mod precisa atentar pro CRC: os HD packs do GT4 vanilla foram feitos pro <code>77E61C8A</code> (USA), então no Spec II com CRC <code>4CE521F2</code> eu linko os assets via symlink na pasta CRC-suffixed que o PCSX2 procura. O pnach de widescreen também foi renomeado pra bater com o CRC do Spec II. Sem esse cuidado, o jogo roda mas os mods não carregam.</p>
<p><strong>Lembretes in-game:</strong> igual ao GT3, o widescreen pnach só libera a opção, você ainda precisa entrar em Options dentro do jogo e trocar pra 16:9. E importante: <strong>mude o modo de vídeo pra progressive 480p</strong> (também em Options). O default é interlaced 480i, que fica péssimo em tela moderna. O Spec II Mod tem suporte nativo a progressive, é só ligar.</p>
<p>Se eu pudesse recomendar um único GT da série pra alguém começar hoje, seria esse. Se você gosta de corrida e nunca jogou GT4 full, tá perdendo.</p>
<h3>Enthusia Professional Racing<span class="hx:absolute hx:-mt-20" id="enthusia-professional-racing"></span>
    <a href="#enthusia-professional-racing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/enthusia.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Enthusia é o jogo esquecido da Konami de 2005. Saiu na sombra do GT4 e morreu no balcão. É uma pena, porque a <strong>física do Enthusia era mais realista que a do GT4</strong>. Peso do carro, transferência de massa, comportamento em limite, tudo mais próximo do comportamento real. A comunidade sim-racing dos anos 2000 sabia disso e o jogo tem culto hoje.</p>
<p>O problema do Enthusia era o sistema de Enthusia Points: você ganhava pontos por andar limpo, perdia por bater, e a progressão do jogo dependia desses pontos. Muita gente achou punitivo. Eu achei perfeito. Forçava você a dirigir com cabeça.</p>
<p>Em PCSX2 com retexture e widescreen, o jogo fica muito bom. Recomendação forte pra quem quer algo diferente dos Gran Turismo.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disco</td>
          <td>Enthusia Professional Racing (SLUS-20967)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>Retexture</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<h3>Ridge Racer V<span class="hx:absolute hx:-mt-20" id="ridge-racer-v"></span>
    <a href="#ridge-racer-v" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/ridge%20racer%20v.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Ridge Racer é sua própria coisa. Não é simcade, é arcade puro. Drift tudo, colinha dinâmica, trilha sonora eletrônica, cores saturadas. Ridge Racer V foi o jogo de lançamento do PS2 no Japão e por isso tem aquele cheiro de &ldquo;mostra o que o console pode fazer&rdquo;, com cenários colossais e sensação de velocidade que GT nunca tentou.</p>
<p>No PCSX2 ele roda bem, com uma caveira: textura de carro às vezes flicka em hardware renderer (issues <a href="https://github.com/PCSX2/pcsx2/issues/3639"target="_blank" rel="noopener">#3639</a> e <a href="https://github.com/PCSX2/pcsx2/issues/13729"target="_blank" rel="noopener">#13729</a> do PCSX2). Software renderer resolve mas mata performance. Eu fico no hardware e aceito o flicker ocasional.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Disco</td>
          <td>Ridge Racer V (SLUS-20002)</td>
      </tr>
      <tr>
          <td>Widescreen pnach</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>No-interlace pnach</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>Upscale</td>
          <td>4x SSAA</td>
      </tr>
  </tbody>
</table>
<h3>Colin McRae Rally 3, 4, 5 (PS2)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-3-4-5-ps2"></span>
    <a href="#colin-mcrae-rally-3-4-5-ps2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Versões de PS2 dos Colin McRae rodam bem no PCSX2 com configs default. CMR3 e CMR04 são bons. CMR05 (o último antes do rebrand pra Dirt) é o melhor dos três tecnicamente. Mas de novo: <strong>se você tá em PC, procura a versão nativa</strong>. Os ports de PS2 eram relativamente inferiores aos PC da época, rodavam em resolução menor, e o PC moderno com repack vai dar 144Hz fácil.</p>
<p>Fica o registro no PCSX2 pra completude da coleção.</p>
<h2>PS3 no RPCS3<span class="hx:absolute hx:-mt-20" id="ps3-no-rpcs3"></span>
    <a href="#ps3-no-rpcs3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gran Turismo 5<span class="hx:absolute hx:-mt-20" id="gran-turismo-5"></span>
    <a href="#gran-turismo-5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%205.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT5 é controverso. Polyphony prometeu mundos e entregou um jogo meio fragmentado: modelos Premium lindos ao lado de Standard que eram basicamente carros do GT4 em HD, sistema de níveis que alguns amaram e outros detestaram, online que foi pra lama quando o serviço PSN desligou, modo Top Gear que era estranho. A crítica na época foi mista, e eu lembro de comunidades inteiras brigando sobre se o jogo era decepção ou genial.</p>
<p>Pra mim, é genial. GT5 tem o Course Maker (edita tua própria pista baseada em seções reais), o Nurburgring 24h real com chuva dinâmica, o Moon Rover (sim, modo de dirigir na Lua), Red Bull X-Challenge com Vettel, e a experiência de endurance single-player é ridiculamente grande. Quem entrou no jogo a fundo sabe que tem conteúdo pra ano inteiro.</p>
<p>No RPCS3 em 2026, GT5 roda <strong>muito bem</strong>. Estável, sem os bugs graves de anos passados. É o GT mais estável que eu tenho emulado hoje.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Serial</td>
          <td>BCUS98114 (US) ou BCES00569 (EU)</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>300%</td>
      </tr>
      <tr>
          <td>Shader Precision</td>
          <td>Ultra</td>
      </tr>
      <tr>
          <td>Force High Precision Z</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>SPU XFloat</td>
          <td>Accurate</td>
      </tr>
      <tr>
          <td>Multithreaded RSX</td>
          <td>Ligado</td>
      </tr>
      <tr>
          <td>WCB / RCB</td>
          <td>Desligados</td>
      </tr>
  </tbody>
</table>
<p>A combinação Shader Precision Ultra + Force High Precision Z mata o dithering típico do RSX que fazia GT5 parecer granulado em RPCS3 antigamente. Sem esses dois, o asfalto parece ter ruído constante. Com eles, o jogo fica limpo.</p>
<h3>Gran Turismo 6<span class="hx:absolute hx:-mt-20" id="gran-turismo-6"></span>
    <a href="#gran-turismo-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/gran%20turismo%206.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>GT6 é um dos meus preferidos da série. Saiu em 2013 pra um PS3 no final de vida, enquanto a concorrência já olhava pra PS4. Vendeu mal. Muita gente não jogou. E é uma pena, porque GT6 pegou a base do GT5, cortou a gordura (Standards foram reduzidos), adicionou mais Premiums, sessões em circuito no Moon melhores, meteorologia dinâmica em mais pistas, refinamento de física. É basicamente o GT5 polido.</p>
<p>Em conteúdo, GT6 é comparável ao GT4. Muitos jogadores da velha guarda botam o GT4 acima por causa do charme da era PS2. Eu fico entre os dois e vou mais pro GT6 em dia normal, GT4 em dia nostálgico.</p>
<p>Em RPCS3, GT6 tem uma pegadinha grave: <strong>patches 1.06 em diante regridem visualmente</strong>. Superfícies pretas em carros no menu garagem, flicker em cockpit view, tela inteira piscando em certas pistas. A versão que funciona bem em 2026 é <strong>travada em v1.05</strong>. O script <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/scripts/extract_ps3_dlc.py"target="_blank" rel="noopener"><code>extract_ps3_dlc.py</code></a> do repo tem um ceiling por título específico pra pinar GT6 em 1.05 mesmo quando o PSN cuspa 1.22 como &ldquo;mais recente&rdquo;.</p>
<p>Além disso, Force CPU Blit é mandatório. Sem ele, tem flicker de tela inteira em menu. O trade-off é que o retrovisor fica permanentemente preto. Eu prefiro perder o retrovisor a ter o flicker. Quem quer retrovisor pode ligar Write Color Buffers, mas aí a resolução cai pra 720p nativo.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Serial</td>
          <td>BCES01893 + variantes regionais</td>
      </tr>
      <tr>
          <td>Versão travada</td>
          <td>v1.05 (patches 1.06+ causam black surfaces)</td>
      </tr>
      <tr>
          <td>Resolution Scale</td>
          <td>300% (menus) ou 200% (gameplay pesada)</td>
      </tr>
      <tr>
          <td>Force CPU Blit</td>
          <td>Ligado (mandatório, mata flicker)</td>
      </tr>
      <tr>
          <td>WCB</td>
          <td>Desligado (trade-off: retrovisor preto)</td>
      </tr>
      <tr>
          <td>Shader Precision</td>
          <td>Ultra</td>
      </tr>
  </tbody>
</table>
<p>Em 2026, com essas configs, GT6 tá no meu rodízio. Não é perfeito, mas é jogável o suficiente pra eu passar uma tarde fazendo endurance.</p>
<h3>Ridge Racer 7<span class="hx:absolute hx:-mt-20" id="ridge-racer-7"></span>
    <a href="#ridge-racer-7" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ridge Racer 7 foi lançamento do PS3 em 2006 e é o último mainline da franquia. Arcade igual Ridge Racer V, drift-first, trilha sonora eletrônica, câmera cinemática. Pra PS3 da época era uma demo tecnológica bonita.</p>
<p>No RPCS3 ele roda, mas com caveats: requer Write Color Buffers ligado pra não ter problemas de iluminação, e o preset recomendado na compat DB é diferente do meu preset GT. Eu uso config per-game dedicada pra ele. Não é meu favorito, mas tá lá pra completude.</p>
<p>Sem vídeo pra esse, gravo depois.</p>
<h2>Xbox 360 no Xenia Canary<span class="hx:absolute hx:-mt-20" id="xbox-360-no-xenia-canary"></span>
    <a href="#xbox-360-no-xenia-canary" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui tá a parte que mais me deu dor de cabeça nesse domingo. Xbox 360 no Linux é território hostil. Xenia roda via Wine, cada jogo tem tweaks específicos, title updates tem que vir do archive.org, e Forza Motorsport 4 especificamente tinha problemas sérios até pouco tempo atrás.</p>
<h3>Forza Motorsport 4 (o GOAT)<span class="hx:absolute hx:-mt-20" id="forza-motorsport-4-o-goat"></span>
    <a href="#forza-motorsport-4-o-goat" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%204.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM4 é, pra muita gente, o melhor Forza Motorsport já lançado. Polyphony fez Gran Turismo, Turn 10 fez Forza, e FM4 é o pico da era 360. Modelos de carro belíssimos pra época, pistas bem construídas, física sólida sem ser simulador puro, Autovista mode com narração do Jeremy Clarkson, trilha sonora épica, e aquela sensação de Xbox 360 sendo usado no limite.</p>
<p>FM4 no Xenia <strong>era um pesadelo até recentemente</strong>. Shadow com artefato severo, texture pop-in constante no carro, textura de pista chiada e pixelada, céu com brilho completamente errado (ficava branco-estourado em vez do azul profundo original), e o pior: crash de áudio frequente, onde o XMA (codec proprietário do Xbox 360) travava e o som virava ruído agudo até congelar o jogo. Eu passei horas nesse domingo mexendo em config, testando builds, procurando issue no xenia-canary, testando diferentes title updates.</p>
<p>O que destravou:</p>
<ul>
<li><strong>Build Xenia Canary mais recente</strong> (o <code>master</code> tá parado, Canary acompanha desenvolvimento ativo)</li>
<li><strong>Render Target Path:</strong> <code>rtv</code> (o default funciona bem pra FM4)</li>
<li><strong>Title Update 1.0.17.0</strong> instalado via Xenia Manager (versão importa, mais velha dá regressão em sombras)</li>
<li><strong>GPU Vulkan com ICD NVIDIA forçado</strong> (no meu setup híbrido com AMD iGPU, tinha que forçar a 5090)</li>
</ul>
<p>Depois disso, FM4 rodou. Não é perfeito. Ainda tem algum crash esporádico de áudio (<a href="https://github.com/xenia-canary/xenia-canary/issues/161"target="_blank" rel="noopener">xenia-canary issue #161</a> aberta). Mas é jogável, e eu passei umas duas horas do domingo no Top Gear Test Track só apreciando o Koenigsegg CCX. Valeu cada hora de debug.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary recente</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td><code>rtv</code> (default)</td>
      </tr>
      <tr>
          <td>Title Update</td>
          <td>1.0.17.0</td>
      </tr>
      <tr>
          <td>Wine Prefix</td>
          <td>dedicado via Xenia Manager</td>
      </tr>
      <tr>
          <td>GPU</td>
          <td>NVIDIA via <code>VK_ICD_FILENAMES</code> hardforce</td>
      </tr>
  </tbody>
</table>
<h3>Forza Motorsport 3<span class="hx:absolute hx:-mt-20" id="forza-motorsport-3"></span>
    <a href="#forza-motorsport-3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%203.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM3 é uma das entradas boas da série, entre o charme áspero do FM2 e o pico técnico do FM4. Tentar emular foi surpreendentemente chato, porque eu tava usando a ISO errada.</p>
<p>Existem dois releases do FM3: o <strong>retail</strong> (disco original vendido em loja) e o <strong>Ultimate Edition</strong> (com todas as DLCs empacotadas num segundo disco). Eu tava tentando rodar o Ultimate, que combina dois discos numa ISO híbrida que o Xenia não parseia direito. Troquei pro retail padrão + DLCs instaladas separadamente via Title Update, e bingo, o jogo rodou na primeira.</p>
<p>Moral: se teu FM3 não tá rodando no Xenia, verifica se é retail. Ultimate dá problema.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Versão</td>
          <td>Retail (NÃO Ultimate Edition)</td>
      </tr>
      <tr>
          <td>Build</td>
          <td>Xenia Canary recente</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td><code>rtv</code></td>
      </tr>
      <tr>
          <td>DLCs</td>
          <td>Instaladas via Xenia Manager separadamente</td>
      </tr>
  </tbody>
</table>
<h3>Forza Motorsport 2<span class="hx:absolute hx:-mt-20" id="forza-motorsport-2"></span>
    <a href="#forza-motorsport-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20motorsport%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FM2 é da era 2007, primeira-gen do 360. Sistemas menos refinados, modelagem mais básica, mas aquele charme de Forza original que alguns preferem à sofisticação do FM4. No Xenia rodou praticamente out-of-the-box. Nenhum tweak especial.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td>default</td>
      </tr>
  </tbody>
</table>
<h3>Forza Horizon 2<span class="hx:absolute hx:-mt-20" id="forza-horizon-2"></span>
    <a href="#forza-horizon-2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%202.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FH2 foi o Forza Horizon mais polido da geração 360 (existe também a versão Xbox One, que é melhor tecnicamente, mas o 360 tem seu charme por causa do mapa menor e da progressão mais focada). Mundo aberto no sul da Europa, trilha sonora icônica, festival Horizon no seu pico.</p>
<p>No Xenia rodou sem pedir nada extra. Como o FM2.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>Render Target Path</td>
          <td>default</td>
      </tr>
  </tbody>
</table>
<h3>Forza Horizon 1 (com XE Mod)<span class="hx:absolute hx:-mt-20" id="forza-horizon-1-com-xe-mod"></span>
    <a href="#forza-horizon-1-com-xe-mod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%201%20xe%20mod.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>FH1 original roda OK no Xenia, mas tem uma comunidade que mantém o <strong>Forza Horizon 1 XE Mod</strong>, que melhora IA do tráfego, rebalanceia progressão, adiciona carros que ficaram de fora, corrige bugs conhecidos, e traz uns quality-of-life tweaks na UI. Eu testei o XE Mod nesse domingo e vale a pena. O jogo parece mais vivo e progressão mais justa.</p>
<p>Detalhes da instalação do XE Mod estão no site do projeto. Basicamente é patch sobre a ISO original + alguns title updates customizados.</p>
<p>Pra comparação, aqui é o FH1 <strong>sem</strong> o mod, rodando puro:</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/forza%20horizon%201.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>A diferença mais visível é no tráfego e na variedade de situações que aparecem, mas o XE Mod tem muita coisa que você só percebe depois de algumas horas.</p>
<table>
  <thead>
      <tr>
          <th>Config</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Build</td>
          <td>Xenia Canary</td>
      </tr>
      <tr>
          <td>XE Mod</td>
          <td>Aplicado sobre ISO original</td>
      </tr>
      <tr>
          <td>Title Updates</td>
          <td>Version pinada pelo XE Mod</td>
      </tr>
  </tbody>
</table>
<h3>Project Gotham Racing 3 e 4<span class="hx:absolute hx:-mt-20" id="project-gotham-racing-3-e-4"></span>
    <a href="#project-gotham-racing-3-e-4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>PGR3 foi lançamento do 360 em 2005 e continua lindo hoje. Pistas urbanas (Nova York, Tokyo, Nurburgring, Las Vegas), kudos system, câmera cinemática. PGR4 adicionou clima dinâmico (chuva e neve) e motos (algumas pessoas adoram, eu prefiro fingir que não existe).</p>
<p>PGR3 no Xenia Canary roda razoavelmente bem com configs default. PGR4 precisa de tweak específico: <strong><code>render_target_path_vulkan = &quot;fsi&quot;</code></strong> no config. Sem isso, algumas pistas quebram com artefato visual severo (barras verdes atravessando a tela em corridas de neve).</p>
<p>E PGR4 no NVIDIA tem um bug conhecido de áudio: decoding XMA gera garbage intermitente (issue aberta no xenia-canary, sem resolução). Não é game-breaking, mas incomoda.</p>
<table>
  <thead>
      <tr>
          <th>Jogo</th>
          <th>Config relevante</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PGR3</td>
          <td>Default</td>
      </tr>
      <tr>
          <td>PGR4</td>
          <td><code>render_target_path_vulkan = &quot;fsi&quot;</code>, aceitar bug de áudio XMA</td>
      </tr>
  </tbody>
</table>
<h3>Ridge Racer 6<span class="hx:absolute hx:-mt-20" id="ridge-racer-6"></span>
    <a href="#ridge-racer-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ridge Racer 6 foi o Ridge Racer do 360, pula o 7 que ficou em PS3. Arcade puro, igual Ridge Racer V do PS2. No Xenia Canary roda com config default, sem tweak especial. É divertido pra sessão curta. Sem vídeo dele no artigo, mas tá no catálogo rodando.</p>
<h2>PS4 no shadPS4: o estado do emulador<span class="hx:absolute hx:-mt-20" id="ps4-no-shadps4-o-estado-do-emulador"></span>
    <a href="#ps4-no-shadps4-o-estado-do-emulador" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://shadps4.net/"target="_blank" rel="noopener">shadPS4</a> <strong>ainda não tem release estável</strong>. O projeto tá em desenvolvimento ativo, main branch muda várias vezes por semana, e &ldquo;o que funciona hoje&rdquo; pode regredir num rebase amanhã. Dito isso, <strong>muitos jogos já bootam e rodam bem o suficiente</strong>. Lista da comunidade tá no <a href="https://github.com/shadps4-compatibility/shadps4-game-compatibility/issues"target="_blank" rel="noopener">compatibility tracker</a> e cresce toda semana.</p>
<p>A diferença entre shadPS4 e os outros emuladores desse artigo é o tipo de esforço. PCSX2, RPCS3, Xenia são projetos maduros onde cada jogo tem uma página de wiki razoavelmente atualizada. shadPS4 é o oposto: você lê um guia do Reddit, tenta reproduzir, descobre que o guia usa um fork específico (não o main oficial), ou um commit de dois meses atrás, ou um firmware com <code>sys_modules</code> linkados de jeito particular, ou patches XML aplicados numa ordem específica que o autor não documentou. Reproduzir setup de shadPS4 a partir de vídeo YouTube é tentar resolver Rubik&rsquo;s Cube com venda.</p>
<p>Mas isso não significa que você não deve tentar. Significa que você precisa tratar shadPS4 como brincadeira, não como workflow. E se você tiver paciência, dá pra colher resultados impressionantes.</p>
<h2>Driveclub: o impossível, finalmente possível<span class="hx:absolute hx:-mt-20" id="driveclub-o-impossível-finalmente-possível"></span>
    <a href="#driveclub-o-imposs%c3%advel-finalmente-poss%c3%advel" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  >
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/videos/driveclub.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Driveclub é meu Moby Dick da emulação. É o único jogo de PS4 que eu quero muito rodar. Saiu em 2014 pela Evolution Studios, teve lançamento catastrófico (servers online colapsaram no dia 1), foi patchado durante dois anos até virar um dos melhores jogos de corrida da geração, e foi descontinuado pela Sony quando a Evolution foi fechada em 2016. Não tem port pra PC. Não tem remaster. Não tem sequel. Só existe no PS4.</p>
<p>E hoje, depois de muitas horas de debug, <strong>está rodando aqui</strong>. Menu principal carrega, intro roda, corridas começam, controle responde, áudio funciona, pistas de dia visíveis, pistas de noite com faróis iluminando pista. O vídeo acima é gameplay real, a 30 FPS estáveis, v1.28 com conteúdo DLC acessível.</p>
<p>Chegar até aqui exigiu desarmar várias armadilhas. Vale documentar, porque a maioria dos guias online não menciona nenhuma.</p>
<h3>Armadilha 1: o FMOD loop error que não era FMOD<span class="hx:absolute hx:-mt-20" id="armadilha-1-o-fmod-loop-error-que-não-era-fmod"></span>
    <a href="#armadilha-1-o-fmod-loop-error-que-n%c3%a3o-era-fmod" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando você tenta bootar Driveclub no shadPS4 direto da PKG retail, o log estoura um loop infinito de:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/app0/audio/fmodstudio/masterbank.bank failed, file does not exist</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A primeira reação é investigar FMOD. Tem gente na internet que passou horas mexendo em sceFont, libtbb, sceNgs2, convencidos que era problema de áudio ou lib HLE. <strong>Nada disso é a causa</strong>. O arquivo <code>masterbank.bank</code> literalmente não existe no disco, porque ele tá empacotado dentro dos arquivos <code>gameNNN.dat</code> num formato de archive customizado da Evolution Studios que o VFS do shadPS4 não sabe ler. A mesma história vale pra fontes, modelos, shaders. O conteúdo todo do jogo mora dentro dos <code>.dat</code>.</p>
<p>A única ferramenta pública que parse esse formato é o <strong><a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">DriveClubFS do Nenkai</a></strong>. É uma app .NET que lê o <code>game.ndx</code> (o índice) e decomprime os <code>.dat</code> em arquivos soltos. Sem esse unpack, o shadPS4 não consegue abrir nada.</p>
<p>Detalhe de build: o projeto DriveClubFS tá em <code>net9.0</code>, e o Arch tem <code>net8</code> e <code>net10</code>. Precisa retargetar o csproj pra <code>net10.0</code> antes de buildar. O comando exato tá documentado no <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> do repo.</p>
<h3>Armadilha 2: v1.28 é patch, não instalador base<span class="hx:absolute hx:-mt-20" id="armadilha-2-v128-é-patch-não-instalador-base"></span>
    <a href="#armadilha-2-v128-%c3%a9-patch-n%c3%a3o-instalador-base" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como em qualquer PS4, update é patch que vai em cima de uma install base. A PKG do v1.28 traz o eboot atualizado, <code>.dat</code> com conteúdo novo e patch content (<code>sce_sys/about/right.sprx</code>), mas <strong>não traz <code>param.sfo</code>, <code>disc_info.dat</code> nem <code>keystone</code></strong>. Esses metadata ficam na base de v1.00 — num PS4 real já estariam gravados do install original.</p>
<p>E tem mais: o <code>game.ndx</code> do v1.28 referencia tanto os <code>.dat</code> da base quanto os novos. Rodar o DriveClubFS contra só o v1.28 faz o índice apontar pra arquivos que não tão ali.</p>
<p>O sintoma mais cruel vem depois: o jogo boota, a Qt Launcher mostra o tile, você clica, a tela carrega. Aí o jogo decide que nenhum conteúdo &ldquo;foi lançado ainda&rdquo; e pede pra fazer download. Download obviamente não vai acontecer porque PSN pra PS4 não tá acessível. Você fica travado achando que é bug de rede, quando na verdade é metadata faltando.</p>
<p>O fluxo correto é tratar v1.28 como overlay sobre a base. Primeiro, extrai a PKG de v1.00 pra um dir dedicado (<code>CUSA00003.v100-working-backup/</code> funciona bem, e dobra como snapshot de rollback se algum experimento quebrar a instalação viva). Depois extrai o v1.28 pra staging e linka os <code>.dat</code> de índice baixo da base dentro do staging pra o DriveClubFS enxergar o set completo. Roda o unpack. Sai com 8018 arquivos soltos, ~47 GB.</p>
<p>Move pra <code>CUSA00003/</code> final, copia o eboot e conteúdo novo do v1.28 por cima, e puxa <code>param.sfo</code>, <code>disc_info.dat</code> e <code>keystone</code> da base de v1.00. O <code>param.sfo</code> da base já vem com <code>APP_VER = 01.28</code> gravado (porque ela recebeu o patch original), então não tem conflito de versão. O eboot v1.28 lê esses arquivos e libera o conteúdo.</p>
<h3>Armadilha 3: o patch XML de 60fps gera slow-motion<span class="hx:absolute hx:-mt-20" id="armadilha-3-o-patch-xml-de-60fps-gera-slow-motion"></span>
    <a href="#armadilha-3-o-patch-xml-de-60fps-gera-slow-motion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Tem um <code>Driveclub.xml</code> patch comunitário que faz o jogo renderizar a 60fps. Intuitivamente, parece ganho puro. Na prática, o patch reescreve o render rate mas não mexe no logic tickrate interno do jogo, que é fixo em 30fps. Num PS4 Pro real, o hardware reconcilia a diferença. No shadPS4, não.</p>
<p>Resultado: o jogo renderiza liso a 60fps, mas toda a física, AI, áudio, timing de corrida roda em câmera lenta. Você aperta acelerador e o carro leva dois segundos perceptíveis pra sair. Parece bug aleatório até você entender o mismatch.</p>
<p>Desabilitar o patch XML resolve (basta renomear o arquivo pra algo como <code>.disabled</code>). Sem o patch, o jogo fica a 30 FPS nativos, mesma velocidade do PS4 base stock. Que é o que a gente quer. Se alguém conseguir no futuro fazer um patch que mexa nos dois rates (render + logic), a gente religa.</p>
<h3>Armadilha 4: pipeline cache precisa estar ligado<span class="hx:absolute hx:-mt-20" id="armadilha-4-pipeline-cache-precisa-estar-ligado"></span>
    <a href="#armadilha-4-pipeline-cache-precisa-estar-ligado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Default do shadPS4 vem com <code>pipeline_cache_enabled = false</code>. Num jogo grande como Driveclub, que tem centenas de shaders e pipelines Vulkan, isso significa que cada cold launch recompila tudo. Nos meus testes o primeiro launch contou ~864 shaders e ~590 pipelines sendo compilados em tempo real. O menu animava a 2-3 fps. Um minuto depois, com tudo compilado, saltava pra fluidez normal. Próximo launch, mesmo ciclo de compilação do zero.</p>
<p>Basta ligar <code>&quot;pipeline_cache_enabled&quot;: true</code> no <code>config.json</code> global. Primeira sessão paga o custo único de compilação, sessões seguintes leem do cache e sobem direto.</p>
<h3>Armadilha 5: TOML silenciosamente ignorado<span class="hx:absolute hx:-mt-20" id="armadilha-5-toml-silenciosamente-ignorado"></span>
    <a href="#armadilha-5-toml-silenciosamente-ignorado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O shadPS4 migrou de TOML pra JSON em config per-game em late 2025. Se você tem um <code>CUSA00003.toml</code> antigo na pasta <code>custom_configs/</code>, ele é silenciosamente ignorado, sem warning no log. Você pensa que sua config tá ativa, na prática tá rodando tudo default.</p>
<p>Deleta qualquer TOML e usa <code>CUSA00003.json</code>. As configs per-game que funcionam pra Driveclub são específicas e detalhadas, documentei todas no <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> do repo.</p>
<h3>Armadilha 6: controle não detectado sem hidapi hints<span class="hx:absolute hx:-mt-20" id="armadilha-6-controle-não-detectado-sem-hidapi-hints"></span>
    <a href="#armadilha-6-controle-n%c3%a3o-detectado-sem-hidapi-hints" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Qt Launcher do shadPS4 chama um <code>Shadps4-sdl.AppImage</code> interno. Sem env vars específicas do SDL hidapi, o controle (no meu caso 8BitDo Ultimate 2) não é detectado, mesmo funcionando em outros emuladores. O fix é wrapar o AppImage num shell que exporta <code>SDL_JOYSTICK_HIDAPI=1</code> e variantes específicas por plataforma (PS4, PS5, Xbox), antes de executar o original.</p>
<p>Plugar o controle <strong>antes</strong> de abrir o Qt Launcher também ajuda. Hot-plug funciona durante o jogo, não antes.</p>
<h3>Armadilha 7: pistas de noite ficam pretas no upstream<span class="hx:absolute hx:-mt-20" id="armadilha-7-pistas-de-noite-ficam-pretas-no-upstream"></span>
    <a href="#armadilha-7-pistas-de-noite-ficam-pretas-no-upstream" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse aqui é o único que precisou de patch de código, não só config. No upstream do shadPS4, pistas de Driveclub à noite ficavam pretas. HUD aparecia em cima, mas pista, carro, tudo escondido num preto absoluto. Faróis do próprio carro não iluminavam nada.</p>
<p>Investigação apontou pro log repetindo 1325 vezes a mensagem <code>ResolveDepthOverlap: Unimplemented depth overlap copy</code>. A causa: o Driveclub usa renderer forward+, que escreve a profundidade da cena num buffer MSAA 4x D32Sfloat, depois lê esse mesmo buffer como <code>R32G32B32A32Sfloat</code> 1x pra efeitos tipo SSAO, volumetrics e iluminação dinâmica (faróis). O shadPS4 upstream tem caminho pra MSAA → MSAA e pra cor → cor como MSAA depth, mas <strong>não tinha caminho pra &ldquo;MSAA depth de 4x vira color de 1x&rdquo;</strong> nesse formato específico. Cai no <code>else</code> final, libera o buffer, e o shader lê lixo não inicializado — ou seja, preto.</p>
<p>Implementei no meu fork local do shadPS4 um <code>ReinterpretMsDepthAsColor</code> simétrico aos <code>ReinterpretColorAsMsDepth</code> que já existiam. É um fragment shader minúsculo (<code>ms_depth_to_color.frag</code>) que faz <code>texelFetch</code> da profundidade sample 0 e escreve como <code>vec4(depth, 0, 0, 1)</code> no attachment de cor. Plus um helper no <code>BlitHelper</code> e um novo <code>else if</code> no <code>TextureCache::ResolveDepthOverlap</code>. Com isso, pistas de noite iluminam corretamente, faróis dos oponentes aparecem, AO e volumetrics no dia também ficam mais precisos.</p>
<p>O fix tá num branch <code>gamma-debug</code> do meu fork, pronto pra virar PR upstream. Enquanto não mergeia, quem quer rodar Driveclub à noite no shadPS4 precisa buildar desse branch. Quem fica só em corridas de dia pode usar o upstream nightly direto.</p>
<h3>O que finalmente funcionou<span class="hx:absolute hx:-mt-20" id="o-que-finalmente-funcionou"></span>
    <a href="#o-que-finalmente-funcionou" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Componente</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>shadPS4</td>
          <td>Pre-release (nightly) do upstream pra dia; fork <code>gamma-debug</code> pra noite (até MSAA depth fix mergear)</td>
      </tr>
      <tr>
          <td>Driveclub</td>
          <td><strong>v1.28 base</strong> (cumulative-patch, ~47 GB, 8018 arquivos)</td>
      </tr>
      <tr>
          <td>Extração</td>
          <td><a href="https://github.com/shadps4-emu/ShadPKG"target="_blank" rel="noopener">ShadPKG</a> → <a href="https://github.com/Nenkai/DriveClubFS"target="_blank" rel="noopener">DriveClubFS</a> (retargetado pra net10.0)</td>
      </tr>
      <tr>
          <td>Metadata</td>
          <td>Restaurar <code>param.sfo</code>, <code>disc_info.dat</code>, <code>keystone</code> da PKG v1.00</td>
      </tr>
      <tr>
          <td>Config global</td>
          <td><code>pipeline_cache_enabled: true</code>, <code>readbacks_mode: 0</code></td>
      </tr>
      <tr>
          <td>Config per-game</td>
          <td>JSON em <code>custom_configs/CUSA00003.json</code></td>
      </tr>
      <tr>
          <td><code>vblank_frequency</code></td>
          <td>60</td>
      </tr>
      <tr>
          <td><code>gpu_id</code></td>
          <td>0 (NVIDIA dGPU hardforced)</td>
      </tr>
      <tr>
          <td><code>Driveclub.xml</code> patch</td>
          <td><strong>Desabilitado</strong> (60fps gera slow-motion, causa raiz: logic tickrate fixo)</td>
      </tr>
      <tr>
          <td>Controle</td>
          <td>Wrapper AppImage com <code>SDL_JOYSTICK_HIDAPI=1</code> + variantes</td>
      </tr>
      <tr>
          <td>Qt Launcher</td>
          <td><code>checkForUpdates=false</code> pra evitar dialog rate-limit do GitHub</td>
      </tr>
  </tbody>
</table>
<p>Tudo isso tá encapsulado no <a href="https://github.com/akitaonrails/distrobox-gaming/blob/master/docs/driveclub-shadps4.md"target="_blank" rel="noopener"><code>docs/driveclub-shadps4.md</code></a> do repo, com o passo a passo exato de extração, config e wrapper. A investigação técnica completa (5 fases, engineering log detalhado) tá no <a href="https://github.com/akitaonrails/shadPS4/blob/gamma-debug/docs/driveclub-v128-investigation.md"target="_blank" rel="noopener"><code>driveclub-v128-investigation.md</code></a> do fork.</p>
<h3>Quão jogável tá hoje<span class="hx:absolute hx:-mt-20" id="quão-jogável-tá-hoje"></span>
    <a href="#qu%c3%a3o-jog%c3%a1vel-t%c3%a1-hoje" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Bom. O vídeo acima é gameplay real, a 30 FPS estáveis, com física respondendo, áudio sincronizado, HUD correto, v1.28 com DLC acessível. Corridas de dia e de noite funcionam (com o fork do MSAA depth pra noite). O que sobra de limitação:</p>
<ul>
<li><strong>Brilho de dia um pouco abaixo do que o PS4 entregava.</strong> Não é bug do emulador. Passei horas testando tonemap, auto-exposure, gamma curves, ACES, vários shaders de post-processing, e no fim descobri que o jogo <strong>escreve SDR já correto</strong> no framebuffer. O que estava faltando era o pipeline de calibração de display que o firmware 4.0+ do PS4 aplicava por fora, e que o Linux não tem. O slider de brilho in-game (que no shadPS4 mapeia pra <code>sceVideoOutAdjustColor</code>) funciona, mas só vai até o ponto que o jogo pensou que seria complementado pelo firmware. A cena de 5 segundos escuros no início da corrida também é fade-in cinematográfico do jogo, não falha de emulador. Aceitei.</li>
<li><strong>30 FPS nativos.</strong> Sem patch de 60fps viável (pelo motivo do logic tickrate fixo). Stock PS4 speed.</li>
<li><strong>MSAA depth fix ainda no fork.</strong> Tem PR candidate pronto, aguardando upstream mergear.</li>
</ul>
<p><strong>&ldquo;Achievement Unlocked&rdquo;.</strong> O jogo roda, com som, controle, jogabilidade, dia e noite, e eu completo corrida inteira sem crash. Isso é um marco pessoal. Eu passei anos tentando fazer esse jogo rodar no Linux e chegava num ponto onde simplesmente desistia. Agora é parte do meu ES-DE, atalho no desktop, tá lá esperando eu sentar pra jogar.</p>
<p>Se você quer replicar, o repo tá público. Boa sorte. Vai precisar de paciência.</p>
<h2>&ldquo;Achievement Unlocked&rdquo;<span class="hx:absolute hx:-mt-20" id="achievement-unlocked"></span>
    <a href="#achievement-unlocked" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu venho tentando fazer esses jogos funcionarem, em algum nível, desde que cada respectivo emulador saiu de alpha. GT2 eu tô rodando desde ePSXe. GT4 eu mexo desde PCSX2 0.9.6. FM4 eu tentei fazer rodar em Xenia duas vezes em anos anteriores e desisti nas duas. E Driveclub eu olhei pra cara do shadPS4 três vezes antes desse domingo e engoli em seco.</p>
<p>Hoje fechou. Os dois monstros que abriram esse artigo (Driveclub no shadPS4 e FM4 com Project Forza Plus no Xenia) estão rodando, junto com todo o resto da coleção de Gran Turismo, Forza, Ridge Racer, Colin McRae e Enthusia. Tudo num setup único, reproduzível, automatizado. Agora eu faço parte do <strong>grupo bem pequeno de pessoas que conseguiu brute force emular Driveclub no Linux</strong>, e isso sozinho já tornou o domingo inesquecível.</p>
<p>A frustração de anos não era falta de informação. Era o OPOSTO. Informação demais, mal indexada, espalhada em fóruns apagados, vídeos YouTube que ficaram datados, threads Reddit com soluções pra versão do emulador de dois anos atrás que não valem hoje, wiki de projeto desatualizada em metade das páginas, e mods com requisitos MUITO específicos de CRC, versão, patch, path, setting. Cada vez que eu sentava pra fazer um desses jogos rodar direito, eu ficava três horas lendo material conflitante antes de escrever a primeira linha de config. Pior ainda quando o material bom estava enterrado entre diagnósticos errados, tipo o FMOD loop do Driveclub que todo mundo investigava como se fosse problema de áudio quando na real era formato de archive proprietário da Evolution Studios.</p>
<p>O que mudou nesse último ano é que Claude Code consegue ser meu agregador. Eu peço pro Claude ir na issue tracker do Xenia, na wiki do PCSX2, no subreddit do RPCS3, nos PRs do shadPS4, comparar todas as informações com a versão atual do emulador instalada, cruzar com os logs que eu tô vendo, e me devolver a combinação que funciona hoje. Ele lê source code quando precisa, entende por que <code>Force CPU Blit</code> muda comportamento no GT6, acha qual title update do FM4 foi o que consertou o shadow bug, descobre que o DriveClubFS precisa de retarget pra .NET 10 antes de compilar. O que antes levava três fins de semana pesquisando agora leva uma tarde de execução assistida.</p>
<p>Esse domingo é o resultado consolidado de todo esse trabalho. <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">distrobox-gaming</a> é a versão desse conhecimento virada código, reprodutível, automatizada. Rodando numa máquina fresca, um <code>ansible-playbook site.yml</code> me põe de volta nesse estado em menos de duas horas. Menos de 10 comandos separam &ldquo;Arch Linux vanilla&rdquo; de &ldquo;Driveclub rodando&rdquo;.</p>
<p>Se alguém quiser contribuir, testar em outro hardware, reportar bug ou mandar config que ficou faltando, o repo tá aberto. Quanto mais gente testar em setups diferentes, mais resiliente o projeto fica. Se você melhorar a situação do Driveclub (mergear o MSAA depth fix upstream, patch de 60fps que também corrija o logic tickrate, dump de fontes que não precise hardware jailbroken), manda PR que eu abraço.</p>
<p>Por enquanto, eu vou fazer uma corrida na Islândia no Driveclub. Boa noite de domingo.</p>
]]></content:encoded><category>games</category><category>emulacao</category><category>linux</category></item><item><title>Omarchy no Thinkpad T14 Gen 6: Mini-Review e Setup Completo</title><link>https://akitaonrails.github.io/2026/04/18/omarchy-no-thinkpad-t14-gen-6/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/18/omarchy-no-thinkpad-t14-gen-6/</guid><pubDate>Sat, 18 Apr 2026 08:30:00 -0300</pubDate><description>&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/closed-lid.jpg" alt="Thinkpad T14 Gen 6 fechado ao lado do carregador USB-C" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Comprei um Lenovo Thinkpad T14 Gen 6 e instalei Omarchy em cima. Não é minha máquina principal, nem pretende ser. É companion: um notebook pra abrir em cima da mesa do escritório de impressão 3D, dar um SSH no desktop, chamar Claude Code, acessar arquivos no NAS, debugar rede pelo ethernet sem ficar procurando adaptador USB e cabo longo. Esse artigo cobre a escolha do hardware, o setup de Omarchy em cima, as customizações específicas pra notebook e pra esse Thinkpad em particular, e as decisões de arquitetura que podem não ser óbvias.&lt;/p&gt;</description><content:encoded><![CDATA[<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/closed-lid.jpg" alt="Thinkpad T14 Gen 6 fechado ao lado do carregador USB-C"  loading="lazy" /></p>
<p>Comprei um Lenovo Thinkpad T14 Gen 6 e instalei Omarchy em cima. Não é minha máquina principal, nem pretende ser. É companion: um notebook pra abrir em cima da mesa do escritório de impressão 3D, dar um SSH no desktop, chamar Claude Code, acessar arquivos no NAS, debugar rede pelo ethernet sem ficar procurando adaptador USB e cabo longo. Esse artigo cobre a escolha do hardware, o setup de Omarchy em cima, as customizações específicas pra notebook e pra esse Thinkpad em particular, e as decisões de arquitetura que podem não ser óbvias.</p>
<h2>Mini-review do Thinkpad T14 Gen 6<span class="hx:absolute hx:-mt-20" id="mini-review-do-thinkpad-t14-gen-6"></span>
    <a href="#mini-review-do-thinkpad-t14-gen-6" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/open-lid-turned-off.jpg" alt="Thinkpad T14 Gen 6 aberto, tela desligada, mostrando o teclado e o selo do Intel Core Ultra 5"  loading="lazy" /></p>
<p>Vamos tirar do caminho: esse não é o notebook dos sonhos. Se eu fosse escolher por estética, pegaria um Asus Zenbook S 14 com tela OLED. Se fosse por portabilidade, o Thinkpad T14s com casca de alumínio, que é mais leve e muito mais bonito. O T14 regular tem tela IPS 1920x1200 de 14&quot;, 400 nits, 60Hz, sem HDR. Dá pro gasto, mas está longe da tela do Zenbook. A casca é de plástico com um acabamento emborrachado, o que deixa ele resistente a arranhão, mas não é premium. Revisões da <a href="https://www.notebookcheck.net/AMD-Ryzen-AI-meets-classic-ThinkPad-Lenovo-ThinkPad-T14-Gen-6-AMD-laptop-review.1222690.0.html"target="_blank" rel="noopener">NotebookCheck</a> e da <a href="https://www.xda-developers.com/lenovo-thinkpad-t14-gen-6-review/"target="_blank" rel="noopener">XDA Developers</a> batem na mesma tecla: bom custo, boa conectividade, desempenho adequado pra escritório, mas a tela é datada.</p>
<p>O que compensa:</p>
<ul>
<li>Seleção de portas. HDMI full-size. Ethernet gigabit. USB-A, USB-C Thunderbolt 4, carrega pelo USB-C (não é carregador proprietário). Pra um companion de debug, isso é exatamente o que eu queria. O Zenbook S 14, por ser mais fino, corta portas.</li>
<li>Sensor de digital funcional no Linux (Goodix MOC, funciona com libfprint em kernel 6.11+). Isso eu uso, detalho mais à frente.</li>
<li>Teclado Thinkpad. Curso de tecla de 1.5mm, trackpoint, layout clássico. Não é o melhor teclado do mundo em 2026, mas é confiável e durável.</li>
<li>Carcaça rugged. Vai cair, vai arranhar, vai viajar na mala. Se eu colocasse um Zenbook OLED ou um Macbook Pro em cima da mesa do escritório de impressão 3D, ao lado da impressora, com poeira de PLA voando, eu ficaria nervoso. O Thinkpad pode levar porrada.</li>
</ul>
<p>Se eu quisesse máquina de jogo, pegaria um Asus Zephyrus G14, meu favorito de notebook gamer. Se eu quisesse máquina de trabalho criativo, pegaria o <a href="https://www.asus.com/laptops/for-home/zenbook/zenbook-duo-ux8406/"target="_blank" rel="noopener">Asus Zenbook Duo (UX8406)</a> com duas telas OLED verticais, ótimo pra edição de vídeo e modelagem 3D. Tenho grana pra qualquer Macbook Pro ou Mac Studio, e escolhi não ir por esse caminho. Explico melhor na próxima seção.</p>
<h2>Meu caso de uso<span class="hx:absolute hx:-mt-20" id="meu-caso-de-uso"></span>
    <a href="#meu-caso-de-uso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/open-lid-fastfetch.jpg" alt="Thinkpad aberto numa bancada do escritório de impressão 3D, com fastfetch mostrando Omarchy no terminal"  loading="lazy" /></p>
<p>Meu PC principal é um desktop com Ryzen 9 7950X3D, 96 GB de RAM, RTX 5090 de 32 GB. É onde eu trabalho, experimento com modelos locais, rodo containers, edito o blog. Pra jogo, tenho um mini-PC separado com RTX 4090. Esses dois atendem 100% do que eu preciso fazer em casa.</p>
<p>O notebook existe pra cobrir o 1% restante: sentar no sofá, levar no escritório de impressão 3D, levar na cozinha, levar em viagem curta. Não é pra substituir o desktop. É pra ter acesso remoto ao desktop quando eu estiver longe dele.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/ssh-to-remote-desktop-on-left.png" alt="Hyprland com dois terminais lado a lado: à esquerda SSH pro desktop principal (96 GB, RTX 5090), à direita o próprio Thinkpad. Mesma UI, duas máquinas."  loading="lazy" /></p>
<p>Na prática fica assim: abro o notebook, dou split no Hyprland, janela da esquerda dá SSH no desktop principal, janela da direita é o próprio notebook. Mesmo Omarchy nos dois, mesmos atalhos, mesmo bash. O notebook vira extensão do desktop, não um ambiente paralelo que eu tenho que reconfigurar na cabeça toda vez que alterno.</p>
<h3>SSH fora de casa: Tailscale<span class="hx:absolute hx:-mt-20" id="ssh-fora-de-casa-tailscale"></span>
    <a href="#ssh-fora-de-casa-tailscale" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Dentro da rede local, SSH é trivial, o notebook fala com o desktop via IP interno. Fora de casa é outra história. Meu IP doméstico é dinâmico, abrir porta 22 pra internet é péssima ideia, e mesmo com DDNS e port forwarding você está colocando SSH na cara da internet pra qualquer scanner achar.</p>
<p>A solução que uso é <a href="https://tailscale.com/"target="_blank" rel="noopener">Tailscale</a>. Pra quem não conhece: Tailscale é uma mesh VPN baseada no WireGuard, que cria uma rede privada entre seus dispositivos (o &ldquo;tailnet&rdquo;). Cada máquina roda o agente, autentica uma vez, e passa a ter um IP fixo na rede privada (tipo 100.x.y.z). O tráfego entre seus próprios dispositivos vai direto peer-to-peer, cifrado pelo WireGuard. Não passa por servidor central da Tailscale, eles só coordenam NAT traversal. Resultado: do meu notebook num café em qualquer lugar do mundo, eu dou <code>ssh hal9000</code> e caio no desktop de casa como se estivesse na mesma rede.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/tailscale-machines.png" alt="Painel admin da Tailscale mostrando as duas máquinas do meu tailnet, hal9000 (desktop) e hal9666 (thinkpad), ambas com SSH habilitado"  loading="lazy" /></p>
<p>Existem opções mais sofisticadas: <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/"target="_blank" rel="noopener">Cloudflare Tunnel</a> com Zero Trust pra expor serviços publicamente com autenticação SSO, headscale self-hosted, WireGuard cru com config manual, Nebula, OpenVPN. Cada uma tem seu caso de uso. Se você precisa expor serviços pra terceiros, controlar acesso granular por identidade, rodar a infraestrutura inteira em casa sem depender de terceiros, essas opções ganham. No meu caso, é só notebook falando com desktop, por períodos curtos (não é trabalho de uma semana inteira, é debug rápido de fim de semana), então Tailscale grátis resolve. O tier free aceita até 100 dispositivos e 3 usuários, muito mais do que eu preciso.</p>
<p>Setup é o mais simples possível:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># No Arch/Omarchy</span>
</span></span><span class="line"><span class="cl">sudo pacman -S tailscale
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now tailscaled.service
</span></span><span class="line"><span class="cl">sudo tailscale up --ssh</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A flag <code>--ssh</code> ativa o <a href="https://tailscale.com/kb/1193/tailscale-ssh/"target="_blank" rel="noopener">Tailscale SSH</a>, que faz autenticação via identidade da tailnet em vez de chave SSH local. Uma vez fez login via browser no tailscale, cada máquina registrada pode entrar nas outras com base em política de ACL definida no painel admin. Zero key management.</p>
<p>Repito no desktop, faço login com a mesma conta, e pronto. As duas máquinas aparecem no painel (hal9000 e hal9666 na screenshot acima, com SSH habilitado nas duas). Do notebook: <code>ssh hal9000</code>. Do desktop pro notebook: <code>ssh hal9666</code>. Sem port forward, sem IP público, sem expor porta 22 pra internet. Se o notebook for roubado, eu removo ele do tailnet num clique.</p>
<p>Um detalhe prático: como o tailnet dá nome estável, eu adicionei entradas no <code>~/.ssh/config</code> pra usar esses nomes curtos:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Host hal9000
  HostName hal9000
  User akitaonrails</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Assim <code>ssh hal9000</code> funciona de qualquer lugar que tenha Tailscale conectado. É a solução mais próxima de &ldquo;it just works&rdquo; que já vi pra SSH remoto.</p>
<h3>Por que não um Mac<span class="hx:absolute hx:-mt-20" id="por-que-não-um-mac"></span>
    <a href="#por-que-n%c3%a3o-um-mac" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu não tenho uso pro macOS. Como dev, eu vivo melhor em Linux nativo. Toda ferramenta que eu preciso tem versão Linux de primeira, e no macOS eu teria versão de segunda via Homebrew. Não faço iOS, então não preciso do XCode. Pra mobile uso Flutter ou Hotwire Native, que rodam em qualquer OS. iTerm2 e Ghostty no Mac são bons, mas Alacritty, Kitty e o próprio Ghostty no Linux me atendem igual. Todo software bom chega primeiro no Linux, daí é portado pros outros. Arch com AUR cobre tudo num único <code>yay</code>.</p>
<p>Pra trabalho criativo, faz anos que não faço profissionalmente. DaVinci Resolve Studio no Linux é superior ao Final Cut Pro. Krita ou Affinity Photo substituem Photoshop na maioria dos casos. Clip Studio Paint no Android é superior ao Procreate. Eu simplesmente não tenho um workflow que depende da Apple, e a App Store me irrita.</p>
<p>Pra jogo, Mac é terrível. Apple Silicon tem GPU boa pra algumas coisas, mas a biblioteca de jogo pro macOS nativo é pífia comparada com Windows ou Linux. Game Porting Toolkit existe, CrossOver existe, mas pra quem joga sério, não serve. Eu não vou jogar no Thinkpad, pra isso tenho o desktop principal e o mini-PC com RTX 4090, mas se um dia bater vontade de rodar um Hollow Knight ou um indie num trem, é só instalar Steam e deixar o Proton fazer o trabalho. Linux virou plataforma de jogo de verdade nos últimos anos, com Proton/DXVK rodando quase todo o catálogo da Steam (checa <a href="https://www.protondb.com/"target="_blank" rel="noopener">ProtonDB</a>). Inclusive documentei recentemente como <a href="/2026/04/11/distrobox-de-emulacao-com-claude-code/">rodei minha biblioteca de emulação em distrobox</a> sem poluir o sistema host. Com Mac, essas opções não existem.</p>
<p>Outro argumento que sempre aparece: &ldquo;mas um Mac Mini M4 ou Mac Studio com 128GB de memória unificada roda modelos grandes localmente, é o substituto do ChatGPT&rdquo;. Já testei essa tese e escrevi um <a href="/2026/04/05/testando-llms-open-source-e-comerciais-quem-consegue-bater-o-claude-opus/">benchmark detalhado</a>. A conclusão: hardware local caro pra rodar LLM é hobby de fim de semana, não é ferramenta de produção. Os modelos open source que cabem não entregam a qualidade que o Claude Opus entrega. Eu tenho um <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">home server com AMD Strix Halo e 96 GB de RAM unificada</a>, rodei modelos por dezenas de horas, e no meu fluxo real de programação eles servem pra tarefas simples. Pras tarefas complexas, Claude Opus. Antes de gastar os $4000 num Mac Studio com a justificativa de rodar modelos locais, faça o teste de verdade. Você provavelmente vai voltar a pagar Opus depois.</p>
<h2>Por que Omarchy<span class="hx:absolute hx:-mt-20" id="por-que-omarchy"></span>
    <a href="#por-que-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu uso Omarchy no desktop há meses e documentei o caminho numa <a href="/tags/omarchy/">série de artigos</a>. O <a href="/2025/08/29/new-omarchy-2-0-install/">Omarchy 2.0</a> tem instalador próprio com LUKS, Btrfs, Limine, snapper e SDDM já configurados. Escrevi sobre <a href="/2025/09/12/omarchy-2-0-instale-com-a-iso-do-omarchy/">usar a ISO oficial</a>, sobre as <a href="/2025/09/07/omarchy-2-0-zsh-configs/">customizações de ZSH</a> com atuin, starship, secrets bem organizados, sobre <a href="/2025/09/07/omarchy-2-0-mise-pra-organizar-ambientes-de-desenvolvimento/">Mise pra múltiplas linguagens</a>, sobre <a href="/2025/09/07/omarchy-2-0-lazyvim-lazyextras/">LazyVim e LazyExtras</a>, sobre <a href="/2025/09/09/omarchy-2-0-entendendo-ssh-e-yubikeys/">SSH e Yubikeys</a>, sobre <a href="/2025/09/09/omarchy-2-0-tuis/">TUIs modernas</a>. Também tem o <a href="/2026/01/21/omarchy-3-setup-de-dual-gpus-com-amd-e-nvidia/">Omarchy 3 com dual GPU AMD + NVIDIA</a> e o <a href="/2026/01/09/omarchy-3-um-dos-melhores-agentes-pra-programacao-crush/">Crush</a> como agente de coding.</p>
<p>Pra quem não conhece: Omarchy é Arch Linux puro com uma camada de cosmética em cima do Hyprland/Wayland. Pré-configurado com defaults sãos. Eu podia montar tudo isso do zero, já fiz isso várias vezes na vida, mas por que refazer trabalho que alguém já fez bem feito? Instalo Omarchy, faço os tweaks que são meus em cima, e tenho Arch customizado numa fração do tempo.</p>
<p>Um ponto que eu levanto toda vez que recomendo Omarchy: a documentação é excelente. Tem um <a href="https://manuals.omamix.org/2/the-omarchy-manual"target="_blank" rel="noopener">manual oficial</a> que cobre desde instalação até customização de tema, keybindings, Hyprland, Waybar, tudo. Se você vem de Ubuntu ou Fedora e tem receio de Arch, esse manual resolve o grosso das dúvidas. Pra quem nunca tocou em Hyprland, abre ele e vai tangenciando. Não é um README jogado no GitHub, é manual de verdade, com capítulos e índice.</p>
<p>A história desse artigo começa num fio anterior. Eu migrei meu home server recentemente, trocando um servidor Ubuntu antigo por um <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">Minisforum MS-S1 com openSUSE MicroOS configurado com Claude Code</a>. Esse foi o primeiro experimento sério de deixar o Claude Code guiar uma migração de infra inteira, com containers, NFS, serviços, networking. Deu certo. E deixou no ar a ideia: por que não usar a mesma abordagem pra configurar um notebook novo?</p>
<p>Foi o que fiz. Peguei o Thinkpad, baixei a ISO do Omarchy mais recente, gravei num pendrive, instalei por cima do Windows. Do zero ao desktop funcional, menos de uma hora. Dali pra frente, tudo é tweaking, que eu fui documentando conforme fazia.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/open-lid-claude-terminal.jpg" alt="Claude Code rodando no terminal do Thinkpad, pronto pra começar a configurar o notebook"  loading="lazy" /></p>
<p>Vou detalhar as duas camadas de customização: o que precisa pra um notebook qualquer, e o que é específico desse Thinkpad.</p>
<h2>Configs específicos de notebook<span class="hx:absolute hx:-mt-20" id="configs-específicos-de-notebook"></span>
    <a href="#configs-espec%c3%adficos-de-notebook" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um notebook tem problemas que um desktop não tem: bateria, suspensão, tampa, brilho, trackpad. Essas são as partes que Omarchy default não cobre do jeito que eu quero.</p>
<h3>Power management com TLP<span class="hx:absolute hx:-mt-20" id="power-management-com-tlp"></span>
    <a href="#power-management-com-tlp" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O default do Omarchy vem com <code>power-profiles-daemon</code>. Troquei por TLP, que dá controle granular de CPU scaling, thresholds de bateria e perfil dinâmico baseado em AC vs bateria.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S tlp tlp-rdw
</span></span><span class="line"><span class="cl">sudo systemctl mask power-profiles-daemon.service
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now tlp.service</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>mask</code> é necessário porque o upower traz o <code>power-profiles-daemon</code> de volta se você só der <code>disable</code>.</p>
<p>Thresholds de carga: 60% pra começar, 85% pra parar. O notebook fica a maior parte do tempo plugado na mesa do escritório, e manter a bateria em 100% 24/7 detona a capacidade ao longo do tempo. Com 60/85, a bateria passa a maior parte do tempo na faixa saudável de lítio e ainda sobra capacidade útil decente.</p>
<p>Perfis: <code>balanced</code> com <code>balance_power</code> na bateria, <code>performance</code> no AC. O <code>low-power</code> do TLP era agressivo demais no Core Ultra 5 235U, a resposta de janela e terminal ficava perceptível. Balanced dá a melhor relação consumo/responsividade pra uso normal.</p>
<p>Tem uma pegadinha: <code>tlp auto</code> depois de <code>tlp ac</code> nem sempre reaplica o perfil de bateria. Criei um pequeno script ligado ao <code>Super+Ctrl+P</code> que lê o estado atual e chama <code>tlp bat</code> ou <code>tlp ac</code> explicitamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nb">set</span> -euo pipefail
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">profile</span><span class="o">=</span><span class="k">$(</span>cat /sys/firmware/acpi/platform_profile 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> unknown<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">on_ac</span><span class="o">=</span><span class="k">$(</span>cat /sys/class/power_supply/AC*/online 2&gt;/dev/null <span class="p">|</span> head -1 <span class="o">||</span> <span class="nb">echo</span> 0<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$profile</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;performance&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$on_ac</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;1&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    sudo /usr/bin/tlp auto &gt;/dev/null
</span></span><span class="line"><span class="cl">    <span class="nv">label</span><span class="o">=</span><span class="s2">&#34;Plugged in — back to AC auto&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    sudo /usr/bin/tlp bat &gt;/dev/null
</span></span><span class="line"><span class="cl">    <span class="nv">label</span><span class="o">=</span><span class="s2">&#34;On battery — normal profile&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">  notify-send -t <span class="m">2000</span> <span class="s2">&#34;Performance mode: off&#34;</span> <span class="s2">&#34;</span><span class="nv">$label</span><span class="s2">&#34;</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  sudo /usr/bin/tlp ac &gt;/dev/null
</span></span><span class="line"><span class="cl">  notify-send -t <span class="m">2000</span> <span class="s2">&#34;Performance mode: on&#34;</span> <span class="s2">&#34;Forcing AC profile&#34;</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No waybar, um módulo custom <code>custom/perf</code> mostra o estado atual (󰓅 PERF, 󰌪 ECO, ou vazio pra balanced) e aceita clique pra alternar. O script de output é bem curto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nv">p</span><span class="o">=</span><span class="k">$(</span>cat /sys/firmware/acpi/platform_profile 2&gt;/dev/null<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$p</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="cl">  performance<span class="o">)</span> <span class="nb">printf</span> <span class="s1">&#39;󰓅 PERF&#39;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  low-power<span class="o">)</span>   <span class="nb">printf</span> <span class="s1">&#39;󰌪 ECO&#39;</span>  <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  balanced<span class="p">|</span><span class="s2">&#34;&#34;</span><span class="o">)</span> <span class="nb">printf</span> <span class="s1">&#39;&#39;</span>       <span class="p">;;</span>
</span></span><span class="line"><span class="cl">  *<span class="o">)</span>           <span class="nb">printf</span> <span class="s1">&#39;%s&#39;</span> <span class="s2">&#34;</span><span class="nv">$p</span><span class="s2">&#34;</span> <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Suspend, hibernate, e tampa<span class="hx:absolute hx:-mt-20" id="suspend-hibernate-e-tampa"></span>
    <a href="#suspend-hibernate-e-tampa" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Isso aqui é o que mudou em relação aos notebooks que eu usava em 2015. O T14 Gen 6 fecha a tampa, suspende, e acorda quando você abre de novo. Sem bug, sem delay estranho, sem precisar dar login de novo no meio da sessão gráfica. O hyprlock pega depois do suspend, aceita digital ou senha, e volta pro desktop em segundos. Isso é o comportamento que a gente tem na Apple há anos e que no Linux era uma aventura. Em 2026, em hardware moderno com kernel 6.11+, simplesmente funciona.</p>
<p>Mem sleep mode é s2idle (sem deep sleep). Hibernação habilitada via swapfile Btrfs de ~30 GB e <code>resume=/dev/mapper/root resume_offset=...</code> no kernel cmdline do Limine. Raramente uso, mas está lá.</p>
<p>O hypridle tem timeouts agressivos pra notebook:</p>
<ul>
<li>2.5 min → screensaver</li>
<li>5 min → lock</li>
<li>5.5 min → DPMS off + keyboard backlight off</li>
</ul>
<p>Depois de trancado, passa mais 5 min e a tela apaga de vez. No unlock, restaura brilho de tela e de teclado pro nível anterior. Essas timings são muito mais curtas que as do desktop (20-40 min lá). A diferença de bateria ao longo do dia é mensurável.</p>
<h3>Brilho e backlight de teclado<span class="hx:absolute hx:-mt-20" id="brilho-e-backlight-de-teclado"></span>
    <a href="#brilho-e-backlight-de-teclado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><code>brightnessctl</code> controla os dois. Fn+Space alterna o backlight do teclado em três níveis. Hypridle salva o nível atual antes de apagar e restaura no retorno. Comando que uso no script:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">brightnessctl -sd <span class="s1">&#39;*::kbd_backlight&#39;</span> <span class="nb">set</span> <span class="m">0</span>    <span class="c1"># salva e desliga</span>
</span></span><span class="line"><span class="cl">brightnessctl -rd <span class="s1">&#39;*::kbd_backlight&#39;</span>          <span class="c1"># restaura</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Touchpad<span class="hx:absolute hx:-mt-20" id="touchpad"></span>
    <a href="#touchpad" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No <code>hypr/input.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>touchpad {
    natural_scroll = true
    clickfinger_behavior = true
    disable_while_typing = true
    scroll_factor = 0.4
}
gesture = 3, horizontal, workspace</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>clickfinger_behavior</code> troca o clique de 2 dedos como botão direito (mais confortável que acertar a zona inferior direita). <code>disable_while_typing</code> é palm rejection básico. Três dedos horizontais alternam workspaces, o gesto mais útil no Hyprland.</p>
<h2>Configs específicos do Thinkpad<span class="hx:absolute hx:-mt-20" id="configs-específicos-do-thinkpad"></span>
    <a href="#configs-espec%c3%adficos-do-thinkpad" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui entra o que é particular desse modelo. Algumas coisas o Linux moderno já cobre sozinho, outras exigem configuração específica.</p>
<h3>Sensor de digital<span class="hx:absolute hx:-mt-20" id="sensor-de-digital"></span>
    <a href="#sensor-de-digital" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Goodix MOC <code>27c6:6594</code>, funciona com libfprint 1.94.9+ em kernel 6.11+. Pacote é <code>fprintd</code>.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S fprintd
</span></span><span class="line"><span class="cl">fprintd-enroll                        <span class="c1"># dedo indicador direito por default</span>
</span></span><span class="line"><span class="cl">fprintd-enroll -f left-index-finger   <span class="c1"># outro dedo</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pro <code>sudo</code> aceitar digital, adiciono <code>auth sufficient pam_fprintd.so</code> acima da linha do <code>pam_unix.so</code> em <code>/etc/pam.d/sudo</code>. Com <code>sufficient</code>, se a digital passar, autentica direto. Se falhar ou se eu digitar ESC, cai pro prompt de senha. Isso vale a pena de verdade: dezenas de vezes por dia, <code>sudo pacman -Syu</code> ou <code>sudo systemctl restart algo</code>, e é só encostar o dedo.</p>
<p>No hyprlock, uso a configuração nativa do próprio hyprlock, não o PAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>auth {
    fingerprint {
        enabled = true
        ready_message = Scan fingerprint or type password
        present_message = Scanning...
        retry_delay = 250
    }
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A razão é que o caminho PAM dá prompt duplo. Configurado nativo, o hyprlock aceita digital ou senha, o que vier primeiro desbloqueia.</p>
<p>No SDDM (login inicial), deixei senha apenas. O motivo: a senha de login destrava o keyring do GNOME, e a digital não consegue fornecer plaintext pra isso. Depois que o keyring está destrancado, o hyprlock pode usar digital sem problema.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/sudo-asks-for-fingerprinting.png" alt="Prompt do sudo pedindo leitura de digital"  loading="lazy" /></p>
<p>Na prática, o sudo fica assim. Encosta o dedo, autentica, segue.</p>
<h3>Teclado brasileiro do Thinkpad<span class="hx:absolute hx:-mt-20" id="teclado-brasileiro-do-thinkpad"></span>
    <a href="#teclado-brasileiro-do-thinkpad" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O teclado brasileiro do Thinkpad tem uma peculiaridade chata. A tecla <code>/?</code> fica na posição que seria o Ctrl direito (keycode 97), não na posição AB11 tradicional do ABNT2 (keycode 89). Se você usar o layout padrão <code>br(abnt2)</code>, essa tecla fica inacessível. Literalmente não imprime nada.</p>
<p>A solução é o variant <code>br(thinkpad)</code> que existe em <code>/usr/share/X11/xkb/symbols/br</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>xkb_symbols &#34;thinkpad&#34; {
    include &#34;br(abnt2)&#34;
    name[Group1]=&#34;Portuguese (Brazil, IBM/Lenovo ThinkPad)&#34;;
    key &lt;RCTL&gt; { [ slash, question, degree, questiondown ] };
};</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No <code>hypr/input.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>kb_layout = br
kb_variant = thinkpad
kb_model = thinkpad60</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E system-wide pra TTY/X11:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo localectl set-keymap br-abnt2
</span></span><span class="line"><span class="cl">sudo localectl set-x11-keymap br thinkpad thinkpad60</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Escrevi um pequeno Python que lê scancodes brutos de <code>/dev/input/event*</code> pra diagnosticar essas esquisitices. Útil quando uma tecla decide não funcionar e você precisa descobrir se é hardware, kernel, ou xkb.</p>
<h3>Input method: fcitx5<span class="hx:absolute hx:-mt-20" id="input-method-fcitx5"></span>
    <a href="#input-method-fcitx5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Junto com o layout eu instalo fcitx5. Input method, pra quem não conhece, é a camada que transforma sequência de teclas em caracteres. É onde entram deadkeys (til pra nasalizar, acento agudo, circunflexo), composição de caracteres que não estão no teclado (ç, Ç, letras acentuadas em maiúscula), suporte a emoji. Em app Qt ou GTK, o input method também cuida dos menus de contexto pra cedilha e acentos.</p>
<p>Pacotes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S --needed fcitx5 fcitx5-configtool fcitx5-gtk fcitx5-qt</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E as variáveis de ambiente pros toolkits encontrarem o fcitx5. Criei <code>~/.config/environment.d/fcitx.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>INPUT_METHOD=fcitx
QT_IM_MODULE=fcitx
XMODIFIERS=@im=fcitx
SDL_IM_MODULE=fcitx</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>environment.d</code> do systemd é carregado antes das sessões gráficas, então Brave, Alacritty, VS Code e qualquer GTK/Qt pegam automaticamente. Habilito autostart no Hyprland:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>exec-once = fcitx5 -d</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Digitando <code>~a</code> vira <code>ã</code>, <code>'e</code> vira <code>é</code>, <code>ç</code> funciona como deveria em toda aplicação. Num teclado Thinkpad brasileiro, isso é a diferença entre conseguir escrever em português com naturalidade ou ficar catando letra.</p>
<h3>Áudio SOF<span class="hx:absolute hx:-mt-20" id="áudio-sof"></span>
    <a href="#%c3%a1udio-sof" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Codec Realtek ALC3306/ALC287 via Sound Open Firmware. Sem o <code>sof-firmware</code>, o módulo de kernel carrega mas o DSP nunca boota e o PipeWire cai silenciosamente pra <code>auto_null</code>. Resultado: você acha que o alto-falante está no mudo, mas na verdade o PipeWire não tem device nenhum.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S --needed sof-firmware alsa-ucm-conf pipewire pipewire-pulse wireplumber</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se precisar forçar SOF em vez do HDA legacy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;options snd-intel-dspcfg dsp_driver=3&#34;</span> <span class="p">|</span> sudo tee /etc/modprobe.d/alsa.conf</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Reload sem reboot:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo modprobe -r snd_sof_pci_intel_mtl
</span></span><span class="line"><span class="cl">sudo modprobe snd_sof_pci_intel_mtl
</span></span><span class="line"><span class="cl">systemctl --user restart wireplumber pipewire pipewire-pulse</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Firmware updates pelo fwupd<span class="hx:absolute hx:-mt-20" id="firmware-updates-pelo-fwupd"></span>
    <a href="#firmware-updates-pelo-fwupd" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/bios-update.jpg" alt="Firmware update da Lenovo (ME Corp) rodando no boot"  loading="lazy" /></p>
<p><code>fwupdmgr update</code> funciona, mas com Limine tem uma pegadinha: o fwupd tenta escrever em <code>/boot/EFI/systemd/</code> ou <code>/boot/EFI/arch/</code>, que não existem. O workaround:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir -p /boot/EFI/arch
</span></span><span class="line"><span class="cl">sudo fwupdmgr update -y --no-reboot-check
</span></span><span class="line"><span class="cl">fwupdmgr get-history  <span class="c1"># deve mostrar &#34;Success&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>HiDPI / fractional scaling<span class="hx:absolute hx:-mt-20" id="hidpi--fractional-scaling"></span>
    <a href="#hidpi--fractional-scaling" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Painel 14&quot; em 1920x1200 numa resolução do Hyprland livre. O auto 1.5x do Omarchy ficava chunky demais. Fixei em 1.333x:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>env = GDK_SCALE,1
monitor=,preferred,auto,1.3333,vrr,2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Efetivo 1440x900. GTK com <code>GDK_SCALE=1</code> renderiza 1:1 com o Hyprland (não magnifica duplo). VRR mode 2 só em fullscreen, porque painéis LCD tendem a cintilar em conteúdo estático com VRR ativa.</p>
<p>Flags do Brave e Chromium pra renderizar bem nessa escala:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>--ozone-platform=wayland
--enable-features=WaylandFractionalScaleV1,UseOzonePlatform,VaapiVideoDecoder,VaapiVideoEncoder
--enable-gpu-rasterization</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O VAAPI faz diferença real na bateria em YouTube.</p>
<h2>Tunando Omarchy pra ser meu<span class="hx:absolute hx:-mt-20" id="tunando-omarchy-pra-ser-meu"></span>
    <a href="#tunando-omarchy-pra-ser-meu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Omarchy vem com defaults bons. O que eu ajusto é no topo deles, sem tocar em <code>~/.local/share/omarchy/</code> (que é clobbered pelo <code>omarchy-update</code>). Toda customização fica em <code>~/.config/</code>.</p>
<h3>Infra: Btrfs, snapshots, Snapper<span class="hx:absolute hx:-mt-20" id="infra-btrfs-snapshots-snapper"></span>
    <a href="#infra-btrfs-snapshots-snapper" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Omarchy já vem com Btrfs e subvolumes separados:</p>
<table>
  <thead>
      <tr>
          <th>Subvolume</th>
          <th>Mount</th>
          <th>Entra nos snapshots?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>@</code></td>
          <td><code>/</code></td>
          <td>sim</td>
      </tr>
      <tr>
          <td><code>@home</code></td>
          <td><code>/home</code></td>
          <td>não</td>
      </tr>
      <tr>
          <td><code>@log</code></td>
          <td><code>/var/log</code></td>
          <td>não</td>
      </tr>
      <tr>
          <td><code>@pkg</code></td>
          <td><code>/var/cache/pacman/pkg</code></td>
          <td>não</td>
      </tr>
  </tbody>
</table>
<p><code>@home</code> separado significa que <code>~/.cache</code>, <code>~/.config/BraveSoftware</code>, etc. não incham snapshots do root. Snapshot é de sistema, não de perfil.</p>
<p>Swap: zram de 4 GB com prioridade 100 (hit primeiro), mais swapfile de 30 GB com prioridade 0 (habilita hibernação, dimensionado pra RAM).</p>
<p>O stack de snapshot: Snapper tira snapshot antes e depois de cada <code>pacman -Syu</code> via snap-pac. <code>limine-snapper-sync</code> escreve esses snapshots no menu do Limine, então dá pra bootar num snapshot anterior pra reverter. Se algo quebra depois de update, você segura uma tecla no boot, escolhe o snapshot pré-update, boota read-only pra verificar, e se estiver bom, faz <code>snapper rollback</code>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/btrfs-snapshots-config.png" alt="Subvolumes Btrfs separados pra que snapshots do root não inchem com cache de usuário"  loading="lazy" /></p>
<p>Omarchy deixa o <code>snapper-cleanup.timer</code> e o <code>snapper-boot.timer</code> desabilitados por default. Habilitei os dois e configurei retenção pra caber num SSD de 1 TB sem explodir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>TIMELINE_LIMIT_HOURLY=10
TIMELINE_LIMIT_DAILY=0
TIMELINE_LIMIT_WEEKLY=1
TIMELINE_LIMIT_MONTHLY=1
NUMBER_LIMIT=50</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Um detalhe que custa debug pra quem esquece: Docker e Ollama escrevem gigabytes em <code>/var/lib/docker</code> e <code>/var/lib/ollama</code>. Se isso cai dentro de <code>@</code>, os snapshots catastrofizam. Cada imagem Docker ou modelo Ollama baixado triplica em tamanho. Criei subvolumes aninhados pros dois, com <code>chattr +C</code> pra desligar CoW:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo btrfs subvolume create /var/lib/docker
</span></span><span class="line"><span class="cl">sudo chattr +C /var/lib/docker
</span></span><span class="line"><span class="cl">sudo btrfs subvolume create /var/lib/ollama</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso tem que ser feito ANTES de o Docker ou Ollama escrever dados. Se já tem dados lá, precisa migrar.</p>
<h3>NFS pro NAS Synology<span class="hx:absolute hx:-mt-20" id="nfs-pro-nas-synology"></span>
    <a href="#nfs-pro-nas-synology" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Meu Synology expõe três volumes. No desktop, monto normal. No notebook, tem que ser mais defensivo. O notebook anda, esquece a rede, conecta em WiFi público. Fstab do notebook:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>nfs4  _netdev,noauto,nofail,x-systemd.automount,x-systemd.idle-timeout=10min,x-systemd.mount-timeout=15s,noatime,nodiratime,nconnect=4,actimeo=10,soft,timeo=30,retrans=2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Diferença crítica em relação ao desktop: <code>soft</code> com timeouts curtos (não fica pendurado pra sempre), <code>x-systemd.idle-timeout=10min</code> (auto-unmount quando ocioso), sem <code>network-online.target</code> no require (não atrasa boot). Resultado prático: <code>cd /mnt/gigachad</code> em casa monta lazy, fora de casa falha rápido sem trancar shell.</p>
<p>Outro detalhe importante: meu user no notebook tem UID 1026, que bate com a permissão do share no Synology. O Linux default cria user em 1000, o Synology impõe identidade via UID no fio. Se os UIDs não batem, você não consegue ler os arquivos, ou ainda pior, escreve como nobody. Rodei de TTY (com usuário deslogado) os <code>usermod</code>/<code>groupmod</code> pra remapear o user pra 1026/1026 e fiz <code>chown -R</code> em <code>/home</code>.</p>
<h3>Hardening de WiFi público<span class="hx:absolute hx:-mt-20" id="hardening-de-wifi-público"></span>
    <a href="#hardening-de-wifi-p%c3%bablico" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Notebook vai sair de casa. Num aeroporto ou café, eu não quero anunciar hostname, não quero MAC rastreável, não quero serviço escutando porta aberta.</p>
<p><code>/etc/NetworkManager/conf.d/00-macrandomize.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[device]
wifi.scan-rand-mac-address=yes

[connection]
wifi.cloned-mac-address=stable
ethernet.cloned-mac-address=stable
connection.stable-id=${CONNECTION}/${BOOT}

ipv6.ip6-privacy=2
ipv6.addr-gen-mode=stable-privacy</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>MAC aleatório por scan (anti-fingerprinting passivo). MAC clonado estável por SSID (o portal cativo não te re-pergunta login toda vez) mas diferente entre redes. IPv6 com endereços temporários e interface ID derivado de segredo estável, não do MAC (sem vazar EUI-64).</p>
<p><code>/etc/systemd/resolved.conf.d/hardening.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Resolve]
LLMNR=no
MulticastDNS=no</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mata broadcast de hostname em LLMNR e mDNS. Também desabilito avahi:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl disable --now avahi-daemon.service avahi-daemon.socket</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>UFW firewall:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo ufw default deny incoming
</span></span><span class="line"><span class="cl">sudo ufw default allow outgoing
</span></span><span class="line"><span class="cl">sudo ufw --force enable</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>SSH server já é off por default no Omarchy. Nada escutando. Mesmo que o firewall vaze, não tem superfície.</p>
<p>Tudo isso entra num único ritual de setup inicial.</p>
<h3>Persistência de SSH agent (keyring + keychain)<span class="hx:absolute hx:-mt-20" id="persistência-de-ssh-agent-keyring--keychain"></span>
    <a href="#persist%c3%aancia-de-ssh-agent-keyring--keychain" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você usa SSH sério, quer digitar a passphrase uma vez por boot e ter <code>ssh</code>, <code>git push</code>, <code>scp</code> mudos pelo resto do dia. No Arch atual isso tem uma pegadinha: o <code>gnome-keyring</code> 50 deixou de oferecer componente SSH, e o substituto (<code>gcr-ssh-agent</code>) é um agente plain em memória, sem persistência de passphrase. A caixa &ldquo;lembrar esta chave&rdquo; que você viu em guias antigos simplesmente não existe mais.</p>
<p>A combinação que funciona tem três peças:</p>
<ol>
<li><code>gcr-ssh-agent.socket</code> gerenciado pelo systemd serve o <code>SSH_AUTH_SOCK</code> em <code>$XDG_RUNTIME_DIR/gcr/ssh</code></li>
<li><code>pam_gnome_keyring</code> no login SDDM destrava o keyring com a senha de login (usado por apps GUI tipo Brave, não pelo SSH)</li>
<li><code>keychain</code> (wrapper) mantém um <code>ssh-agent</code> vivo entre logouts, sobrescreve o <code>SSH_AUTH_SOCK</code> pra apontar pra esse agent persistente, e cacheia o PID em <code>~/.keychain/&lt;host&gt;-sh</code></li>
</ol>
<p>Primeiro, instalo o keyring:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S --needed gnome-keyring seahorse keychain</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois, edito <code>/etc/pam.d/sddm</code> pra o login password destravar o keyring automaticamente. Adiciono no topo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-auth      optional    pam_gnome_keyring.so</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E no final:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-session   optional    pam_gnome_keyring.so    auto_start</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O hífen na frente (<code>-auth</code>, <code>-session</code>) marca como opcional, se o módulo não carregar, não quebra login.</p>
<p>Pino o <code>SSH_AUTH_SOCK</code> pra sessões gráficas e TTY via <code>~/.config/environment.d/ssh-agent.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>SSH_AUTH_SOCK=%t/gcr/ssh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>%t</code> resolve pra <code>$XDG_RUNTIME_DIR</code>, tipo <code>/run/user/1026</code>. Habilito o socket do gcr e o linger do usuário (pra user daemons sobreviverem ao logout):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> --now gcr-ssh-agent.socket
</span></span><span class="line"><span class="cl">sudo loginctl enable-linger <span class="nv">$USER</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por fim, no <code>~/.config/bash/init.sh</code> o keychain inicia ou reusa o agent:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="k">if</span> <span class="nb">command</span> -v keychain <span class="p">&amp;</span>&gt;/dev/null <span class="o">&amp;&amp;</span> <span class="o">[[</span> -r ~/.ssh/id_ed25519 <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">eval</span> <span class="s2">&#34;</span><span class="k">$(</span>keychain --eval --quiet ~/.ssh/id_ed25519<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>--eval</code> emite atribuições shell (<code>SSH_AUTH_SOCK</code>, <code>SSH_AGENT_PID</code>) apontando pro agent do keychain, que passa por cima do gcr setado pelo environment.d. <code>--quiet</code> cala o banner depois que a chave está carregada.</p>
<p>Fluxo na prática: boot → primeiro terminal → keychain pede passphrase uma vez → chave fica carregada. Logout → login de novo → novos shells re-atacham no mesmo agent (via <code>~/.keychain/&lt;host&gt;-sh</code>) → zero prompt. <code>ssh-add -l</code> confirma que a chave está lá, <code>echo $SSH_AUTH_SOCK</code> confirma que está no caminho do keychain.</p>
<p>Quando você vai digitar a passphrase de novo: reboot, <code>ssh-add -D</code>, ou <code>keychain --clear</code>. Em uso normal, uma vez por dia.</p>
<h3>Bash em vez de ZSH<span class="hx:absolute hx:-mt-20" id="bash-em-vez-de-zsh"></span>
    <a href="#bash-em-vez-de-zsh" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No desktop uso ZSH. No notebook fui de Bash pra alinhar com o default do Omarchy, sem dupla de camadas modulares pra manter. <code>~/.bashrc</code> é um symlink pra <code>~/.config/bash/bashrc</code>, que faz source dos defaults do Omarchy primeiro e depois empilha as minhas customizações:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">source</span> ~/.local/share/omarchy/default/bash/rc
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/envs.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/aliases.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/mounts.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/init.sh
</span></span><span class="line"><span class="cl"><span class="nb">source</span> ~/.config/bash/secrets    <span class="c1"># gitignored, chmod 600</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>envs.sh</code> tem o que é meu: OpenRouter base URL, Ollama apontando pro GPU box da rede local (192.168.0.14), AWS region, analytics do Hugo, configs de zoxide e SSH agent. <code>aliases.sh</code> tem os atalhos de TLP, um alias pra <code>shell-gpt</code> via Docker, e functions pra hardenar o PATH quando rodo <code>makepkg</code> ou <code>yay</code> (prevenir injeção de binário via user config malicioso).</p>
<p><code>init.sh</code> faz o trabalho de integração. Atuin com bind manual do Ctrl-R (pra deixar a seta pra cima com o history-search padrão do bash, que eu uso mais). Keychain carregando <code>~/.ssh/id_ed25519</code> uma vez por boot e reusando nos shells seguintes (sem ter que re-autenticar SSH toda hora). Blesh se estiver instalado (autosuggestion estilo ZSH pra Bash). E uma função que manda o PROMPT_COMMAND ajustar o título da janela com o pwd atual e o comando em execução:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">__title_idle<span class="o">()</span> <span class="o">{</span> <span class="nb">printf</span> <span class="s1">&#39;\033]2;%s\007&#39;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PWD</span><span class="p">/#</span><span class="nv">$HOME</span><span class="p">/~</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">__title_busy<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">local</span> <span class="nv">cmd</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_COMMAND</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;__title_&#34;</span>* <span class="o">||</span> <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span> <span class="o">==</span> *<span class="s2">&#34;PROMPT_COMMAND&#34;</span>* <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span>
</span></span><span class="line"><span class="cl">  <span class="nb">printf</span> <span class="s1">&#39;\033]2;%s — %s\007&#39;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PWD</span><span class="p">/#</span><span class="nv">$HOME</span><span class="p">/~</span><span class="si">}</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$cmd</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROMPT_COMMAND</span><span class="p">-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nv">PROMPT_COMMAND</span><span class="o">=</span><span class="s2">&#34;__title_idle; </span><span class="si">${</span><span class="nv">PROMPT_COMMAND</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nv">PROMPT_COMMAND</span><span class="o">=</span><span class="s2">&#34;__title_idle&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> <span class="s1">&#39;__title_busy&#39;</span> DEBUG</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No idle, título mostra só o pwd. Com um comando rodando, <code>trap DEBUG</code> pega o <code>BASH_COMMAND</code> em execução e atualiza o título. Integra com o <code>hyprland/window</code> do waybar pra mostrar tudo lá.</p>
<p>Pro toolbelt Rust moderno, a lista:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo pacman -S --needed <span class="se">\
</span></span></span><span class="line"><span class="cl">  eza bat fd ripgrep sd git-delta dust procs bottom duf tokei hyperfine <span class="se">\
</span></span></span><span class="line"><span class="cl">  zoxide atuin tldr starship</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>eza</code> substitui <code>ls</code>. <code>bat</code> substitui <code>cat</code> com syntax highlight. <code>fd</code> substitui <code>find</code>. <code>ripgrep</code> substitui <code>grep</code>. <code>sd</code> substitui <code>sed</code>. <code>delta</code> pluga no git pra diff side-by-side colorido. <code>dust</code> pro <code>du</code> visual. <code>procs</code> pro <code>ps</code>. <code>btm</code> (bottom) pro <code>top</code>. <code>duf</code> pro <code>df</code>. <code>tokei</code> conta linhas de código. <code>hyperfine</code> pra benchmark de comando. <code>zoxide</code> é <code>cd</code> com memória (muito útil). <code>atuin</code> é history do shell com sync criptografado (aponto pro meu home server via <code>sync_address = &quot;http://192.168.0.90:8888&quot;</code>). <code>starship</code> é o prompt. <code>tldr</code> é man page em 10 linhas.</p>
<p>O <code>atuin key</code> tem que ser backupeado offline. Se você perde, perde o histórico criptografado. Salvei no meu Bitwarden self-hosted (Vaultwarden), que eu documentei no <a href="/2025/09/10/omarchy-2-0-bitwarden-self-hosted-vaultwarden/">artigo sobre Bitwarden self-hosted</a>.</p>
<p>O git passa diff pelo delta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[core]        pager = delta
[interactive] diffFilter = delta --color-only
[delta]       navigate side-by-side line-numbers hyperlinks, light=false
[merge]       conflictstyle = zdiff3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Hyprland e Waybar<span class="hx:absolute hx:-mt-20" id="hyprland-e-waybar"></span>
    <a href="#hyprland-e-waybar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Customizações visuais e de UX que fiz em cima do default.</p>
<p>No <code>hypr/looknfeel.conf</code>, gaps menores (2/5 vs 5/10 do default), animação de slide entre workspaces, VFR ligado (reduz consumo quando a tela está estática), <code>allow_session_lock_restore</code> (se o hyprlock crasha, volta pra tela de lock em vez de jogar pro desktop).</p>
<p>No <code>hypr/bindings.conf</code>:</p>
<table>
  <thead>
      <tr>
          <th>Binding</th>
          <th>Ação</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Super+B</code></td>
          <td>Brave</td>
      </tr>
      <tr>
          <td><code>Super+L</code></td>
          <td>Lock screen (default era layout toggle)</td>
      </tr>
      <tr>
          <td><code>Super+Ctrl+L</code></td>
          <td>Layout toggle (movi pra cá)</td>
      </tr>
      <tr>
          <td><code>Super+Ctrl+P</code></td>
          <td>Toggle TLP perf mode</td>
      </tr>
  </tbody>
</table>
<p>No Waybar, três módulos custom que recomendo:</p>
<p><strong>Título de janela</strong>: o módulo <code>hyprland/window</code> mostra o que tá focado. No bash, o PROMPT_COMMAND atualiza o título pra algo como &ldquo;~/Projects/blog — hugo server&rdquo;. Então no waybar aparece o diretório e o comando em execução, sem eu precisar olhar pro terminal.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---window-title-on-waybar.png" alt="Waybar mostrando workspaces à esquerda e o título da janela focada com o pwd e comando em execução"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---another-window-title-on-waybar.png" alt="Outro exemplo do título da janela no waybar, dessa vez mostrando “Claude Code”"  loading="lazy" /></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;hyprland/window&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{title}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;max-length&#34;</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;rewrite&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;(.{47}).+&#34;</span><span class="p">:</span> <span class="s2">&#34;$1…&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>Perf mode</strong>: o <code>custom/perf</code> executa um script que lê o estado do TLP e mostra um ícone:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;custom/perf&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;exec&#34;</span><span class="p">:</span> <span class="s2">&#34;~/.config/scripts/perf-waybar.sh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;interval&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;on-click&#34;</span><span class="p">:</span> <span class="s2">&#34;~/.config/scripts/perf-toggle.sh&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Vazio em balanced, 󰓅 PERF em performance, 󰌪 ECO em low-power. Clique alterna. Útil pra ver rapidamente em que modo a máquina está.</p>
<p><strong>Claudebar</strong>: integrei o <a href="https://github.com/alfredopiquet/claudebar"target="_blank" rel="noopener">claudebar</a> num módulo custom que mostra a % de uso da sessão do Claude Code e o gasto extra inline no waybar. Sem ter que abrir outra janela.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---power-profile-and-claudebar-next-to-traybar.png" alt="Claudebar mostrando 9%, 1h16m restantes e $1120.74 de uso extra, seguido do indicador PERF e o tray"  loading="lazy" /></p>
<p>O clock:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsonc" data-lang="jsonc"><span class="line"><span class="cl"><span class="s2">&#34;clock&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{:L%A %d %B - %H:%M}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format-alt&#34;</span><span class="p">:</span> <span class="s2">&#34;{:L%A W%V %Y - %H:%M}&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Exibe &ldquo;quinta 17 abril - 14:32&rdquo; por padrão, clique alterna pra &ldquo;quinta W16 2026 - 14:32&rdquo; com número da semana do ano, útil pra planejar artigos e tarefas.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/18/thinkpad/hypr---better-date-time-on-waybar-center.png" alt="Clock do waybar no centro com data e hora em português"  loading="lazy" /></p>
<h3>Atuin self-hosted<span class="hx:absolute hx:-mt-20" id="atuin-self-hosted"></span>
    <a href="#atuin-self-hosted" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Atuin roda no meu home server em 192.168.0.90:8888. Criei uma conta separada pro notebook (<code>akitaonrails-thinkpad</code>), não misturo histórico com desktop de propósito. <code>atuin sync</code> roda a cada 5 minutos e todo histórico é criptografado antes de ir pro server. O server não vê os comandos, só vê bytes criptografados.</p>
<h2>Conclusão: escolhendo notebook pra Linux<span class="hx:absolute hx:-mt-20" id="conclusão-escolhendo-notebook-pra-linux"></span>
    <a href="#conclus%c3%a3o-escolhendo-notebook-pra-linux" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se tem uma coisa que eu aprendi nessa jornada: escolher notebook pra rodar Linux confortavelmente não é trivial. Precisa checar a <a href="https://wiki.archlinux.org/title/Laptop"target="_blank" rel="noopener">ArchWiki</a> pelo modelo específico antes de comprar. A do <a href="https://wiki.archlinux.org/title/Lenovo_ThinkPad_T14s_%28AMD%29_Gen_6"target="_blank" rel="noopener">T14s Gen 6 AMD</a>, por exemplo, cataloga cada peculiaridade de hardware e como contornar. Sem essa referência, você descobre os problemas instalando.</p>
<p>Regra que sigo: nunca comprar modelo recém-lançado. Deixar passar uns 6 a 12 meses depois do lançamento. Isso dá tempo pra comunidade iron out os bugs, pros drivers entrarem no mainline, pra ArchWiki ter uma página decente, pra libfprint incluir o sensor de digital, pro kernel cobrir o WiFi e a câmera IR. Comprar de primeira é assumir o papel de tester beta.</p>
<p>Marcas com trilha Linux decente: Lenovo (especialmente Thinkpad, eles oferecem Ubuntu e Fedora pré-instalados), Dell (a linha XPS e Latitude), Asus (Zenbook e ROG têm suporte razoável), Framework (fábrica direto pra Linux). Mac qualquer geração recente é cortado do Linux mainline ou via Asahi com caveats.</p>
<p>O Thinkpad T14 Gen 6 não é o notebook mais bonito que eu podia ter comprado. Mas é rugged, tem as portas certas, o sensor de digital funciona, e a carcaça plástica aceita cair, arranhar, viajar na mala sem eu ficar nervoso. Pra servir de companion de debug remoto, é o que eu precisava. Se esse é o seu perfil de uso também, recomendo. Se você quer OLED e metal, vai de Zenbook ou de T14s. Cada premium tem seu compromisso, nenhum notebook Linux em 2026 é perfeito.</p>
<p>Tudo que descrevi aqui ficou versionado num repo privado meu. Se eu precisar reinstalar amanhã, rodo meia dúzia de passos em ordem e volto ao mesmo lugar. Esse é o ponto todo de manter configuração em Git: notebook é commodity, config é minha.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>hardware</category><category>reviews</category></item><item><title>Omarchy 3: Setup de Dual GPUs com AMD e NVIDIA</title><link>https://akitaonrails.github.io/2026/01/21/omarchy-3-setup-de-dual-gpus-com-amd-e-nvidia/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/01/21/omarchy-3-setup-de-dual-gpus-com-amd-e-nvidia/</guid><pubDate>Wed, 21 Jan 2026 15:29:35 -0300</pubDate><description>&lt;p&gt;Mais um post só pra eu não me esquecer destes detalhes na configuração do &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;Omarchy&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hyprland não funciona bem com NVIDIA&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dá pra usar. Mas tem vários probleminhas, em particular com &lt;a href="https://github.com/basecamp/omarchy/issues/1147"target="_blank" rel="noopener"&gt;Hyprlock&lt;/a&gt;, em particular com DPMS onde eu deixo meu computador em idle, daí ele se bloqueia, e depois de um timeout desliga os monitores pra economizar energia, não ter burn in, etc.&lt;/p&gt;
&lt;p&gt;Quando eu tento acordar do sleep, o Hyprlock crasheou e eu preciso ir pra outro TTY pra reiniciar, até fiz um script &lt;code&gt;~/.local/bin/hypr_unlock.sh&lt;/code&gt; assim:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Mais um post só pra eu não me esquecer destes detalhes na configuração do <a href="https://omarchy.org/"target="_blank" rel="noopener">Omarchy</a>:</p>
<blockquote>
  <p>Hyprland não funciona bem com NVIDIA</p>

</blockquote>
<p>Dá pra usar. Mas tem vários probleminhas, em particular com <a href="https://github.com/basecamp/omarchy/issues/1147"target="_blank" rel="noopener">Hyprlock</a>, em particular com DPMS onde eu deixo meu computador em idle, daí ele se bloqueia, e depois de um timeout desliga os monitores pra economizar energia, não ter burn in, etc.</p>
<p>Quando eu tento acordar do sleep, o Hyprlock crasheou e eu preciso ir pra outro TTY pra reiniciar, até fiz um script <code>~/.local/bin/hypr_unlock.sh</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hyprctl --instance <span class="m">0</span> <span class="s1">&#39;keyword misc:allow_session_lock_restore 1&#39;</span>
</span></span><span class="line"><span class="cl">hyprctl --instance <span class="m">0</span> <span class="s1">&#39;dispatch exec hyprlock&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas aí desbloqueia e eu vejo que meu Brave crasheou também. A nvidia realmente não gosta que desliguem o monitor enquanto ele tá ativo. Disseram que isso acontecia no Hyprlock 0.9.1 mas eu já estou no 0.9.2 e continua crasheando. Tentei habilitar serviços como nvidia-suspend/resume/hibernate mas nada também.</p>
<p>No fim do dia resolvi que não vale a pena brigar com isso: eu tenho uma <strong>AMD 7850X3D</strong> e essa CPU tem uma iGPU (GPU integrada), uma AMD Raphael, que pra coisas como renderizar um gerenciador de janelas, é mais do que potente o suficiente.</p>
<p>Primeiro, detectar as GPUs:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ ls /dev/dri/by-path
</span></span><span class="line"><span class="cl">Permissions Size User Date Modified Name
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:01:00.0-card -&gt; ../card1
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:01:00.0-render -&gt; ../renderD128
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:1a:00.0-card -&gt; ../card2
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - root <span class="m">21</span> Jan 15:22   pci-0000:1a:00.0-render -&gt; ../renderD129</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra saber qual é AMD e qual é NVIDIA só executar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ lspci <span class="p">|</span> grep -i vga
</span></span><span class="line"><span class="cl">01:00.0 VGA compatible controller: NVIDIA Corporation GB202 <span class="o">[</span>GeForce RTX 5090<span class="o">]</span> <span class="o">(</span>rev a1<span class="o">)</span>
</span></span><span class="line"><span class="cl">1a:00.0 VGA compatible controller: Advanced Micro Devices, Inc. <span class="o">[</span>AMD/ATI<span class="o">]</span> Raphael <span class="o">(</span>rev c9<span class="o">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto, pelo ID de PCI sabemos que card1 é NVIDIA e card2 é AMD. Agora precisa configurar meu <code>~/.config/hypr/hyprland.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">....
</span></span><span class="line"><span class="cl"><span class="c1"># NVIDIA environment variables</span>
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> NVD_BACKEND,direct
</span></span><span class="line"><span class="cl"><span class="c1">#env = LIBVA_DRIVER_NAME,nvidia</span>
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> LIBVA_DRIVER_NAME,radeonsi
</span></span><span class="line"><span class="cl"><span class="c1">#env = __GLX_VENDOR_LIBRARY_NAME,nvidia</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Comment these out when using AMD as primary</span>
</span></span><span class="line"><span class="cl"><span class="c1">#env = __NV_PRIME_RENDER_OFFLOAD,1</span>
</span></span><span class="line"><span class="cl"><span class="c1">#env = __VK_LAYER_NV_optimus,NVIDIA_only</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># card1=nvidia, card2=amdgpu (verify with: cat /sys/class/drm/card*/device/uevent)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># AMD first for iGPU primary:</span>
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> AQ_DRM_DEVICES,/dev/dri/card2:/dev/dri/card1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">env</span> <span class="o">=</span> ELECTRON_OZONE_PLATFORM_HINT,auto</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu deixei as configurações específicas de NVIDIA comentadas, nenhuma delas pode estar ativa. Mas a linha mais importante é a do <code>AQ_DRM_DEVICES</code> que lista a ordem pro Hyprland pegar a GPU. Card2/AMD tem que estar primeiro. E pronto, só rebootar e recolocar os cabos HDMI na saída da iGPU.</p>
<p>Com isso eu posso escolher agora dar offload de somente alguns apps da NVIDIA usando Prime. Pra isso primeiro eu criei os script <code>~/.local/bin/nvidia-load.sh</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="nv">__NV_PRIME_RENDER_OFFLOAD</span><span class="o">=</span><span class="m">1</span> <span class="nv">__VK_LAYER_NV_optimus</span><span class="o">=</span>NVIDIA_only <span class="nv">__GLX_VENDOR_LIBRARY_NAME</span><span class="o">=</span>nvidia <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Finalmente, configurei alguns bindings em <code>~/.config/hypr/bindings.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl"><span class="nv">bindd</span> <span class="o">=</span> SUPER SHIFT, B, Blender, exec, ~/.local/bin/nvidia-load.sh blender
</span></span><span class="line"><span class="cl"><span class="nv">bindd</span> <span class="o">=</span> SUPER SHIFT, L, Bambu Studio, exec, ~/.local/bin/nvidia-load.sh bambu-studio
</span></span><span class="line"><span class="cl"><span class="nv">bindd</span> <span class="o">=</span> SUPER SHIFT, P, Blender, exec, ~/.local/bin/nvidia-load.sh plasticity
</span></span><span class="line"><span class="cl">...</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Desta forma, somente os apps Blender, Plasticity e Bambu Studio, que vão primariamente usar a NVIDIA pra renderizar 3D em real time. Todos os outros apps vão abrir na iGPU AMD.</p>
<p>Ferramentas de IA como ComfyUI, Ollama ou LM Studio automaticamente reconhecem a NVIDIA e escolhem lá. E com isso eu também tenho mais VRAM livre pra carregar LLMs.</p>
<p>Se for usar XLibre/X.org, dá pra ficar de boa em NVIDIA. Mas se for usar Wayland, nvidia ainda não é totalmente estável, em particular com Hyprland. Dá menos dor de cabeça colocar uma segunda GPU AMD só pra isso.</p>
]]></content:encoded><category>omarchy</category><category>hardware</category><category>linux</category></item><item><title>AI Agents: Garantindo a Proteção do seu Sistema</title><link>https://akitaonrails.github.io/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/</guid><pubDate>Sat, 10 Jan 2026 11:50:36 -0300</pubDate><description>&lt;p&gt;Hoje em dia temos dezenas de Agentes de IA pra programação, seja Cursor, Claude Code, Windsurf, Copilot, OpenCode, Crush e mais. Mas tem um problema que já virou até meme: o risco deles danificarem seu sistema ou enviar seus dados privados pra alguém na Internet (comportamento de malware).&lt;/p&gt;
&lt;p&gt;O problema é que pra serem eficientes, essas ferramentas precisam ter acesso ao seu sistema de arquivos pra vasculhar os arquivos do seu projeto, pra analizar o código. E mais: conseguir rodar ferramentas como compiladores, linters, e coisas mundanas como grep, sed, awk, ls, e outros comandos do seu sistema.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Hoje em dia temos dezenas de Agentes de IA pra programação, seja Cursor, Claude Code, Windsurf, Copilot, OpenCode, Crush e mais. Mas tem um problema que já virou até meme: o risco deles danificarem seu sistema ou enviar seus dados privados pra alguém na Internet (comportamento de malware).</p>
<p>O problema é que pra serem eficientes, essas ferramentas precisam ter acesso ao seu sistema de arquivos pra vasculhar os arquivos do seu projeto, pra analizar o código. E mais: conseguir rodar ferramentas como compiladores, linters, e coisas mundanas como grep, sed, awk, ls, e outros comandos do seu sistema.</p>
<p>Eventualmente, uma LLM pode mandar executar um <code>rm -Rf /</code> aleatório e apagar coisas no seu sistema, por exemplo. O código em si, se por acidente apagar o que não devia, teoricamente você deveria estar seguro porque seu código já deveria estar, no mínimo, num repositório de Git (GitHub, GitLab, etc), então seria só reverter a mudança. Mas se o agente rodar um <code>rm</code> fora do diretório do seu projeto, pode criar todo tipo de problema.</p>
<p>Não é comum isso acontecer e todas as ferramentas costumam ter checagens pra isso, incluindo perguntar se você quer realmente executar o comando. Ao mesmo tempo, eles também tem a opção de permitir rodar qualquer comando durante a sessão, sem perguntar - porque uma hora começa a ficar tedioso dar &ldquo;ok&rdquo; pra cada comando individual. Todo mundo acaba ficando com preguiça e diz <em>&ldquo;foda-se, roda o que quiser&rdquo;</em>.</p>
<p>E vocês devem estar pensando <em>&ldquo;ah, minha ferramenta X é bem comportada, ele sempre pergunta antes de rodar qualquer coisa, então eu controlo&rdquo;</em>. Não esqueçam: <strong>ATAQUES DE SUPPLY-CHAIN</strong> existem. Toda hora alguma biblioteca open source é invadida com malware e os programas que usam essas libs acabam sendo infectados sem saber. Isso já aconteceu várias vezes, em várias libs de NPM por exemplo. Não tem como garantir que mesmo um software open source seja confiável. Sempre parta do princípio que tudo é inseguro até prova em contrário e trate esses programas dessa forma: com restrição máxima.</p>
<p>Pra garantir que esses agentes realmente não vão fazer nada fora do diretório do projeto, o melhor é criar um <strong>JAIL</strong>. E aqui vai o comando pra isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bwrap --ro-bind /usr /usr <span class="se">\
</span></span></span><span class="line"><span class="cl">      --ro-bind /bin /bin <span class="se">\
</span></span></span><span class="line"><span class="cl">      --ro-bind /lib /lib <span class="se">\
</span></span></span><span class="line"><span class="cl">      --ro-bind /lib64 /lib64 <span class="se">\
</span></span></span><span class="line"><span class="cl">      --dev /dev <span class="se">\
</span></span></span><span class="line"><span class="cl">      --proc /proc <span class="se">\
</span></span></span><span class="line"><span class="cl">      --bind <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span> <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      --chdir <span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      --unshare-all <span class="se">\
</span></span></span><span class="line"><span class="cl">      --share-net <span class="se">\
</span></span></span><span class="line"><span class="cl">      bash</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><code>bwrap</code> é o <strong>Bubblewrap</strong> um dos componentes que permitem a existência de coisas como <strong>Flatpak</strong>. Ele cria um sandbox. O Bash que vai rodar ao executar o comando acima está essencialmente em &ldquo;jail&rdquo;, ou seja, &ldquo;preso&rdquo;. Nele damos acesso somente de leitura a alguns diretórios do sistema caso precise de bibliotecas e ferramentas. Pra isso serve o <code>--ro-bind</code>.</p>
<p>Mas somente o diretório corrente <code>pwd</code> vai ter permissão de escrita. Qualquer comando que rodar dentro desse jail não tem permissão de escrever em nenhum outro lugar o seu sistema. Veja o exemplo na foto abaixo onde eu tento apagar um arquivo na minha <code>$HOME</code>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260110120225_screenshot-2026-01-10_12-02-14.png" alt="sandbox"  loading="lazy" /></p>
<p>Ele não consegue nem listar os arquivos na minha home. É tudo invisível dentro do sandbox. Essa é uma das vantagens de apps rodando com Flatpak: eles são muito mais isolados do que apps instalados direto.</p>
<p>Dentro desse bash em sandbox podemos agora executar <code>opencode</code>, <code>crush</code>, <code>cursor</code> ou qualquer outro agente e ter certeza que eles não vão fazer nenhuma besteira no seu sistema.</p>
<p>Pra facilitar, recomendo criar um script como <code>~/.local/bin/ai-jail</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ai-jail — bubblewrap sandbox for AI coding agents</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Mounts the project dir read-write, auto-discovers home dotfiles with a</span>
</span></span><span class="line"><span class="cl"><span class="c1"># deny-list for sensitive dirs, and isolates namespaces.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Usage: ai-jail [--map PATH]... COMMAND [ARGS...]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#        ai-jail claude</span>
</span></span><span class="line"><span class="cl"><span class="c1">#        ai-jail bash</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">PROJECT_DIR</span><span class="o">=</span><span class="k">$(</span><span class="nb">pwd</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">TEMP_HOSTS</span><span class="o">=</span><span class="k">$(</span>mktemp /tmp/bwrap-hosts.XXXXXX<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">trap</span> <span class="s1">&#39;rm -f &#34;$TEMP_HOSTS&#34;&#39;</span> EXIT
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Mise discovery ─────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">REAL_MISE_BIN</span><span class="o">=</span><span class="k">$(</span><span class="nb">type</span> -p mise 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Localhost fix (Go resolver needs /etc/hosts) ───────────────</span>
</span></span><span class="line"><span class="cl"><span class="nb">printf</span> <span class="s1">&#39;127.0.0.1 localhost ai-sandbox\n::1       localhost ai-sandbox\n&#39;</span> &gt; <span class="s2">&#34;</span><span class="nv">$TEMP_HOSTS</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Parse --map / --rw-map arguments ─────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">EXTRA_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> --map <span class="o">||</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> --rw-map <span class="o">]]</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nv">FLAG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MAP_PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> -e <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$FLAG</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;--rw-map&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">            <span class="nv">EXTRA_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="nv">EXTRA_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$MAP_PATH</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;Warning: Path </span><span class="nv">$MAP_PATH</span><span class="s2"> not found, skipping.&#34;</span> &gt;<span class="p">&amp;</span><span class="m">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="nb">shift</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Mise init command ──────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$REAL_MISE_BIN</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MISE_INIT</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$REAL_MISE_BIN</span><span class="s2"> trust &amp;&amp; eval \&#34;\$(</span><span class="nv">$REAL_MISE_BIN</span><span class="s2"> activate bash)\&#34; &amp;&amp; eval \&#34;\$(</span><span class="nv">$REAL_MISE_BIN</span><span class="s2"> env)\&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MISE_INIT</span><span class="o">=</span><span class="s2">&#34;true&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Dotfile deny-list (never mounted — sensitive data) ─────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTDIR_DENY</span><span class="o">=(</span>.gnupg .aws .mozilla .basilisk-dev .sparrow<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Subdirs of ~/.config to hide (tmpfs over rw config mount)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_DENY</span><span class="o">=(</span>BraveSoftware Bitwarden<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Subdirs of ~/.cache to hide (sensitive browser/app caches)</span>
</span></span><span class="line"><span class="cl"><span class="nv">CACHE_DENY</span><span class="o">=(</span>BraveSoftware basilisk-dev chromium spotify nvidia mesa_shader_cache<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Dotdirs requiring read-write access</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTDIR_RW</span><span class="o">=(</span>.claude .crush .codex .aider .config .cargo .cache .docker<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Helper functions ───────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl">is_denied<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">local</span> <span class="nv">name</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> d in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOTDIR_DENY</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$d</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span> 0<span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">is_rw<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nb">local</span> <span class="nv">name</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> d in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOTDIR_RW</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$d</span><span class="s2">&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">return</span> 0<span class="p">;</span> <span class="k">done</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Auto-discover dot-directories in $HOME ─────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Only directories — regular dotfiles (e.g. .claude.json) are NOT mounted.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># The tmpfs $HOME is writable so tools can create dotfiles as needed.</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTFILE_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> entry in <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">&#34;</span>/.*<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="nv">name</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;.&#34;</span> <span class="o">||</span> <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;..&#34;</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    is_denied <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="k">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> is_rw <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$entry</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/</span><span class="nv">$name</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Explicit dotfile mounts (regular files) ────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gitconfig&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gitconfig&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.gitconfig&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude.json&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DOTFILE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude.json&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.claude.json&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Hide sensitive subdirs inside ~/.config (after rw mount) ───</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_HIDE_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> denied in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CONFIG_DENY</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/</span><span class="nv">$denied</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">CONFIG_HIDE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--tmpfs&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/</span><span class="nv">$denied</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Hide sensitive subdirs inside ~/.cache (after rw mount) ──</span>
</span></span><span class="line"><span class="cl"><span class="nv">CACHE_HIDE_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> denied in <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CACHE_DENY</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache/</span><span class="nv">$denied</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">CACHE_HIDE_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--tmpfs&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.cache/</span><span class="nv">$denied</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Override ~/.local subdirs as rw (parent .local is ro) ──────</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOCAL_OVERRIDES</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/state&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">LOCAL_OVERRIDES</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/state&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/state&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> rw_share in zoxide crush opencode atuin mise yarn flutter kotlin NuGet pipx ruby-advisory-db uv<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share/</span><span class="nv">$rw_share</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">LOCAL_OVERRIDES</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share/</span><span class="nv">$rw_share</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/share/</span><span class="nv">$rw_share</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── GPU device mounts (NVIDIA + DRM) ─────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">GPU_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> dev in /dev/nvidia* /dev/dri<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -e <span class="s2">&#34;</span><span class="nv">$dev</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">GPU_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--dev-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$dev</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$dev</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Docker socket ────────────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOCKER_MOUNT</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -S /var/run/docker.sock <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DOCKER_MOUNT</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;/var/run/docker.sock&#34;</span> <span class="s2">&#34;/var/run/docker.sock&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Shared memory (Chromium needs large /dev/shm) ───────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">SHM_MOUNT</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -d /dev/shm <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">SHM_MOUNT</span><span class="o">+=(</span><span class="s2">&#34;--dev-bind&#34;</span> <span class="s2">&#34;/dev/shm&#34;</span> <span class="s2">&#34;/dev/shm&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Display passthrough (X11 + Wayland) ─────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nv">DISPLAY_MOUNTS</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl"><span class="nv">DISPLAY_ENV</span><span class="o">=()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># X11 / XWayland socket</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -d /tmp/.X11-unix <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DISPLAY_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;/tmp/.X11-unix&#34;</span> <span class="s2">&#34;/tmp/.X11-unix&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DISPLAY</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;DISPLAY&#34;</span> <span class="s2">&#34;</span><span class="nv">$DISPLAY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">XAUTHORITY</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--ro-bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$XAUTHORITY</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$XAUTHORITY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;XAUTHORITY&#34;</span> <span class="s2">&#34;</span><span class="nv">$XAUTHORITY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wayland socket (lives in XDG_RUNTIME_DIR, typically /run/user/UID)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">XDG_RUNTIME_DIR</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_MOUNTS</span><span class="o">+=(</span><span class="s2">&#34;--bind&#34;</span> <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;XDG_RUNTIME_DIR&#34;</span> <span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -n <span class="s2">&#34;</span><span class="si">${</span><span class="nv">WAYLAND_DISPLAY</span><span class="k">:-</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nv">DISPLAY_ENV</span><span class="o">+=(</span><span class="s2">&#34;--setenv&#34;</span> <span class="s2">&#34;WAYLAND_DISPLAY&#34;</span> <span class="s2">&#34;</span><span class="nv">$WAYLAND_DISPLAY</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ── Assemble and launch ───────────────────────────────────────</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Jail Active: </span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">bwrap <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /usr /usr <span class="se">\
</span></span></span><span class="line"><span class="cl">  --symlink usr/bin /bin <span class="se">\
</span></span></span><span class="line"><span class="cl">  --symlink usr/lib /lib <span class="se">\
</span></span></span><span class="line"><span class="cl">  --symlink usr/lib /lib64 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /etc /etc <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind <span class="s2">&#34;</span><span class="nv">$TEMP_HOSTS</span><span class="s2">&#34;</span> /etc/hosts <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /opt /opt <span class="se">\
</span></span></span><span class="line"><span class="cl">  --ro-bind /sys /sys <span class="se">\
</span></span></span><span class="line"><span class="cl">  --dev /dev <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">GPU_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">SHM_MOUNT</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --proc /proc <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tmpfs /tmp <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tmpfs /run <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOCKER_MOUNT</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DISPLAY_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tmpfs <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOTFILE_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CONFIG_HIDE_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CACHE_HIDE_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">LOCAL_OVERRIDES</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">EXTRA_MOUNTS</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --bind <span class="s2">&#34;</span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --chdir <span class="s2">&#34;</span><span class="nv">$PROJECT_DIR</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --die-with-parent <span class="se">\
</span></span></span><span class="line"><span class="cl">  --unshare-pid <span class="se">\
</span></span></span><span class="line"><span class="cl">  --unshare-uts <span class="se">\
</span></span></span><span class="line"><span class="cl">  --unshare-ipc <span class="se">\
</span></span></span><span class="line"><span class="cl">  --hostname <span class="s2">&#34;ai-sandbox&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DISPLAY_ENV</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --setenv PS1 <span class="s2">&#34;(jail) \w \$ &#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --setenv _ZO_DOCTOR <span class="m">0</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  bash -c <span class="s2">&#34;</span><span class="nv">$MISE_INIT</span><span class="s2"> &amp;&amp; </span><span class="si">${</span><span class="p">*</span><span class="k">:-</span><span class="nv">bash</span><span class="si">}</span><span class="s2">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esta versão é bem customizada, deem uma boa lida porque tem deny-list pra que só diretórios de que você realmente precisa dentro do Jail estejam disponíveis. Alguns são read-write porque um Claude precisa conseguir escrever em <code>~/.claude</code> ou Codex precisa do <code>~/.codex</code>; vários diretórios nunca devem ir pra dentro do Jail como repositório de senhas de um Brave ou BitWarden. O resto de que pode precisar deve ser tudo read-only. Esse script cuida de tudo isso.</p>
<p>Só fazer <code>chmod +x ~/.local/bin/ai-jail</code>. Agora podemos executar ele direto ou já passar o comando que queremos rodar no sandbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~/.local/bin/ai-jail crush</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Nesta versão ele vai criar um diretório <code>$HOME</code> temporário, caso ele precise gravar algo temporário lá. E também mandei mapear o seu <code>~/.config</code> pros agentes terem acesso às suas configurações. Se precisar de mais coisas como variáveis de ambiente e coisas assim, modifique pra mapear essas coisas também, mas isso é o mínimo pra conseguir montar um sandbox seguro.</p>
<p>Tome muito cuidado com programas que rodam comandos aleatórios no seu sistema. Nada é 100% seguro, especialmente uma LLM que pode halucinar a qualquer momento e decidir apagar coisas, ou ferramentas suspeitas que você baixou sem ter certeza da origem e pode ser um malware disfarçado. Sempre rode coisas realmente suspeitas somente numa VM ou, no mínimo, dentro de um jail como esse.</p>
<p>Isso não serve só pra agentes de IA, lógico. Toda vez que for rodar qualquer comando que tem dúvidas ou ache suspeito, rode dentro desse jail.</p>
<p>De bônus, note que eu também mapeio o <a href="https://akitaonrails.com/2025/09/07/omarchy-2-0-mise-pra-organizar-ambientes-de-desenvolvimento/"target="_blank" rel="noopener">Mise</a> pra dentro do container. Se você usa Omarchy, deve estar usando Mise também.</p>
<p>Especificamente pra Claude Code, que eu mais uso, prefiro não deixar tudo autorizado no modo automático <code>--allow-dangerously-skip-permissions</code>, mas também não gosto de ter que ficar autorizando coisa besta como cada domínio que ele quer pesquisar na web (só pesquisar não tem problema nenhum) ou qualquer comando que só lista coisas como <code>ls</code> ou <code>grep</code> então dá pra melhorar configurando direto em <code>~/.claude/settings.json</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;permissions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allow&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git add *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git commit *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git status *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git diff *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git log *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git branch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git checkout *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git switch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git stash *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git show *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git fetch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git pull *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git merge *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git tag *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git remote -v*)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git rev-parse *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git ls-files *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(ls *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(find *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(grep *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(wc *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cat *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(head *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(tail *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(file *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(which *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(echo *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pwd)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cd *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(ps *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(env *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(printenv *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(mkdir *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cp *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(mv *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(touch *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(npm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(npx *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(yarn *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pnpm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(bun *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(node *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(ruby *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(bundle *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rails *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rake *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(go *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(cargo *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rustc *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(python *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(pip *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker compose *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker ps *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker logs *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker images *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(make *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(gh *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(curl *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(jq *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(sed *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(awk *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(sort *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(uniq *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(diff *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(* --version)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(* --help)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;WebSearch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;WebFetch&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;deny&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rm -rf *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rm -r *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(sudo *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(chmod 777 *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git reset --hard *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git clean *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git push --force *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git push -f *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker rm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker rmi *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker system prune *)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ask&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git push *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git rebase *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git branch -D *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(git branch -d *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(rm *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(kamal *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker run *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker exec *)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;Bash(docker stop *)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;defaultMode&#34;</span><span class="p">:</span> <span class="s2">&#34;acceptEdits&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;enabledPlugins&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;gopls-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;typescript-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;pyright-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;rust-analyzer-lsp@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;github@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;pr-review-toolkit@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;frontend-design@claude-plugins-official&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;alwaysThinkingEnabled&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pesquise a documentação (ou pergunte direto pro próprio Claude) sobre mais customizações que dá pra fazer nesse arquivo pra servir melhor pro seu tipo de projeto de fluxo de trabalho.</p>
<p>É basicamente assim que um container de Docker ou Podman funciona também. Eu expliquei isso no meu <a href="https://akitaonrails.com/2023/03/02/akitando-139-entendendo-como-containers-funcionam/"target="_blank" rel="noopener">video sobre Docker</a>. Se não assistiu ainda, recomendo ver.</p>
]]></content:encoded><category>ai-jail</category><category>agentes-de-codigo</category><category>seguranca</category><category>linux</category></item><item><title>Omarchy - Não entrar em Sleep enquanto Copia</title><link>https://akitaonrails.github.io/2025/11/20/omarchy-no-sleep-on-copy/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/11/20/omarchy-no-sleep-on-copy/</guid><pubDate>Thu, 20 Nov 2025 14:00:00 -0300</pubDate><description>&lt;p&gt;Esta dica não é específica de Omarchy mas de Linux em geral, mas coloquei na categoria de Omarchy só pra ficar consistente no meu blog.&lt;/p&gt;
&lt;h3&gt;Introdução&lt;span class="hx:absolute hx:-mt-20" id="introdução"&gt;&lt;/span&gt;
&lt;a href="#introdu%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Quando eu me afasto do meu PC, o Hyprland vai usar as configurações em &lt;code&gt;~/.config/hypr/hypridle.conf&lt;/code&gt; pra saber em que timeout desligar o monitor e depois, quando colocar meu PC pra dormir (sleep). Podemos configurar o tempo que quisermos lá, como 10 min pra apagar a tela, 20 min sem atividade pra dormir.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Esta dica não é específica de Omarchy mas de Linux em geral, mas coloquei na categoria de Omarchy só pra ficar consistente no meu blog.</p>
<h3>Introdução<span class="hx:absolute hx:-mt-20" id="introdução"></span>
    <a href="#introdu%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando eu me afasto do meu PC, o Hyprland vai usar as configurações em <code>~/.config/hypr/hypridle.conf</code> pra saber em que timeout desligar o monitor e depois, quando colocar meu PC pra dormir (sleep). Podemos configurar o tempo que quisermos lá, como 10 min pra apagar a tela, 20 min sem atividade pra dormir.</p>
<p>Se tiver algum app que queira fazer alguma manutenção em background que demore mais que isso pra terminar, ele pode sinalizar pro sistema não dormir. Dá pra ver quais apps fazem isso usando o comando <code>systemd-inhibit --list</code>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20251120140935_screenshot-2025-11-20_14-09-09.png" alt="inhibit list"  loading="lazy" /></p>
<h3>O Problema<span class="hx:absolute hx:-mt-20" id="o-problema"></span>
    <a href="#o-problema" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Vira e mexe eu deixo copiando grandes quantidades de arquivos, por exemplo, do meu NAS pra um micro SD Card. Pode levar mais de 1 hora fácil (minhas cópias de ROMs pra emuladores):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20251120141114_screenshot-2025-11-20_13-50-46.png" alt="copiando usb"  loading="lazy" /></p>
<p>O problema é que antes disso o <code>hypridle</code> vai entrar em ação e colocar meu PC pra dormir, pausando essa cópia. E normalmente eu queria deixar copiando, ir dormir, e estar pronto quando acordar. Só que sempre esqueço disso e quando acordo ainda não terminou. Bem inconveniente. Não sei porque o Nautilus já não sinaliza pra inibir sleep quando tem atividades assim acontecendo.</p>
<p>Pode ser que tenha alguma configuração de <code>gsettings</code> que eu não saiba? (se tiver algo assim, não deixem de mandar nos comentários).</p>
<h3>A Solução<span class="hx:absolute hx:-mt-20" id="a-solução"></span>
    <a href="#a-solu%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Enquanto não descubro algo melhor, só tem uma solução: ficar fazendo polling de <code>iostat</code> pra manualmente inibir.</p>
<p>Pra começar precisamos instalar estes dois pacotes no Arch:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S sysstat bc</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, criar um script que cheque, vamos chamar de <code>~/.local/bin/io_inhibit.sh</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># --- Configuration ---</span>
</span></span><span class="line"><span class="cl"><span class="c1"># UTIL_THRESHOLD: Percentage of time the device is busy (%util) that must be exceeded to inhibit sleep.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 10.0% is a safe low threshold to catch any continuous I/O.</span>
</span></span><span class="line"><span class="cl"><span class="nv">UTIL_THRESHOLD</span><span class="o">=</span>10.0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># CHECK_INTERVAL: How often (in seconds) to check I/O activity.</span>
</span></span><span class="line"><span class="cl"><span class="nv">CHECK_INTERVAL</span><span class="o">=</span><span class="m">5</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># File to track the PID of the systemd-inhibit process.</span>
</span></span><span class="line"><span class="cl"><span class="nv">PID_FILE</span><span class="o">=</span><span class="s2">&#34;/tmp/io_inhibit_pid&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># --- Functions ---</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Function to check if the inhibit process is currently running</span>
</span></span><span class="line"><span class="cl">is_inhibitor_running<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[</span> -f <span class="s2">&#34;</span><span class="nv">$PID_FILE</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">kill</span> -0 <span class="k">$(</span>cat <span class="s2">&#34;</span><span class="nv">$PID_FILE</span><span class="s2">&#34;</span><span class="k">)</span> 2&gt;/dev/null<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="m">0</span> <span class="c1"># True</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="m">1</span> <span class="c1"># False</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Function to get the maximum I/O utilization (%util) across all monitored devices</span>
</span></span><span class="line"><span class="cl">get_max_io_util<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># We use -x (extended stats) and monitor all devices.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># The &#39;awk&#39; script finds the column index for &#39;%util&#39; dynamically.</span>
</span></span><span class="line"><span class="cl">    iostat -x <span class="m">1</span> <span class="m">2</span> <span class="p">|</span> awk <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        BEGIN { max_util = 0.0; f = 0; util_col = 0; }
</span></span></span><span class="line"><span class="cl"><span class="s1">        
</span></span></span><span class="line"><span class="cl"><span class="s1">        # 1. Look for the header line (contains &#39;</span>r/s<span class="s1">&#39; and &#39;</span>util<span class="s1">&#39;). Set flag (f) to start processing.
</span></span></span><span class="line"><span class="cl"><span class="s1">        #    We only do this check in the second block.
</span></span></span><span class="line"><span class="cl"><span class="s1">        /avg-cpu:/ { if (f == 0) f = 1; else if (f == 1) f = 2; next }
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        # 2. In the second block (f == 2), check for the header line to map columns
</span></span></span><span class="line"><span class="cl"><span class="s1">        (f == 2 &amp;&amp; $1 == &#34;Device&#34;) {
</span></span></span><span class="line"><span class="cl"><span class="s1">            # Find the column index for the &#34;%util&#34; header
</span></span></span><span class="line"><span class="cl"><span class="s1">            for (i=1; i&lt;=NF; i++) {
</span></span></span><span class="line"><span class="cl"><span class="s1">                if ($i == &#34;%util&#34;) {
</span></span></span><span class="line"><span class="cl"><span class="s1">                    util_col = i;
</span></span></span><span class="line"><span class="cl"><span class="s1">                    break;
</span></span></span><span class="line"><span class="cl"><span class="s1">                }
</span></span></span><span class="line"><span class="cl"><span class="s1">            }
</span></span></span><span class="line"><span class="cl"><span class="s1">            # Skip the header line after mapping
</span></span></span><span class="line"><span class="cl"><span class="s1">            next
</span></span></span><span class="line"><span class="cl"><span class="s1">        }
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        # 3. Process data lines only after the column has been mapped
</span></span></span><span class="line"><span class="cl"><span class="s1">        (f == 2 &amp;&amp; util_col &gt; 0) { 
</span></span></span><span class="line"><span class="cl"><span class="s1">            # Match lines starting with a common device prefix
</span></span></span><span class="line"><span class="cl"><span class="s1">            if ($1 ~ /^(sd|nvme|loop|hd|dm|zram|mmc)/) {
</span></span></span><span class="line"><span class="cl"><span class="s1">                # Use the dynamically found column index
</span></span></span><span class="line"><span class="cl"><span class="s1">                util = $(util_col);
</span></span></span><span class="line"><span class="cl"><span class="s1">                if (util &gt; max_util) {
</span></span></span><span class="line"><span class="cl"><span class="s1">                    max_util = util;
</span></span></span><span class="line"><span class="cl"><span class="s1">                }
</span></span></span><span class="line"><span class="cl"><span class="s1">            }
</span></span></span><span class="line"><span class="cl"><span class="s1">        }
</span></span></span><span class="line"><span class="cl"><span class="s1">        END { print max_util; }
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># --- Main Loop ---</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> true<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 1. Get current I/O activity</span>
</span></span><span class="line"><span class="cl">    <span class="nv">MAX_UTIL</span><span class="o">=</span><span class="k">$(</span>get_max_io_util<span class="k">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># Use &#39;bc&#39; for floating-point comparison</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">((</span> <span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$MAX_UTIL</span><span class="s2"> &gt; </span><span class="nv">$UTIL_THRESHOLD</span><span class="s2">&#34;</span> <span class="p">|</span> bc -l<span class="k">)</span> <span class="o">))</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># I/O is active and exceeds the %util threshold</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> ! is_inhibitor_running<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">            setsid systemd-inhibit --what<span class="o">=</span>idle:sleep --why<span class="o">=</span><span class="s1">&#39;Active I/O Detected (%util)&#39;</span> --mode<span class="o">=</span>block sleep infinity <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">            <span class="nb">echo</span> <span class="nv">$!</span> &gt; <span class="s2">&#34;</span><span class="nv">$PID_FILE</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">fi</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1"># I/O is idle or below threshold</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> is_inhibitor_running<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">            <span class="nb">kill</span> <span class="k">$(</span>cat <span class="s2">&#34;</span><span class="nv">$PID_FILE</span><span class="s2">&#34;</span><span class="k">)</span> 2&gt;/dev/null
</span></span><span class="line"><span class="cl">            rm <span class="s2">&#34;</span><span class="nv">$PID_FILE</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">fi</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Wait before checking again</span>
</span></span><span class="line"><span class="cl">    sleep <span class="nv">$CHECK_INTERVAL</span> 
</span></span><span class="line"><span class="cl"><span class="k">done</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>(sim, este script foi vibe coded e precisei ajustar algumas vezes até funcionar, as versões que o Gemini me dava não funcionavam kkkk).</p>
<p>O principal é o <code>awk</code> processando a saída do comando <code>iostat -x 1 2</code>. Meu micro SD card aparece como <code>/dev/sda</code>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20251120141644_screenshot-2025-11-20_14-16-36.png" alt="iostat"  loading="lazy" /></p>
<p>Note que no segundo bloco, quando estou copiando vários arquivos, ele mostra 99.61 e fica variando entre 90 a 100% de uso, que é o desejável: significa que está usando toda a banda disponível de IO pra ele.</p>
<p>Então, queremos que, enquanto estiver numa taxa acima de zero, tipo, maior que 10% de atividade, o HyprIdle não ative o sleep no timeout normal.</p>
<p>Com esse script, agora temos o seguinte quando listamos <code>systemd-inhibit --list</code>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20251120141859_screenshot-2025-11-20_14-18-48.png" alt="sleep infinity"  loading="lazy" /></p>
<p>Agora apareceu uma nova linha dizendo:</p>
<ul>
<li>
<p><code>sleep infinity</code>: Este é o nome do processo que iniciou meu script e segura o lock.</p>
</li>
<li>
<p><code>sleep:idle</code>: Esta é a parte crucial. Diz ao systemd e logind pra bloquear tanto sleep (system suspend) quanto idle (prevenindo timers de idle como do hypridle).</p>
</li>
<li>
<p><code>Active I/O Detected (%util)</code>: Esta é a razão especificada no script.</p>
</li>
</ul>
<p>E pronto, pra garantir que temos este script sempre rodando, basta alterar o <code>~/.config/hypr/autostart.conf</code> pra ter esta linha:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>exec-once = /home/akitaonrails/.local/bin/io_inhibit.sh &amp;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em teoria, agora posso deixar essas atividades longas de IO rodando e não me preocupar do meu Arch suspender e dormir.</p>
<p>Alguém conhece alguma solução mais simples do que essa? Se tiver idéias, mandem nos comentários.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>automacao</category></item><item><title>Omarchy 2.0 - Indicado pra Iniciantes?</title><link>https://akitaonrails.github.io/2025/09/12/omarchy-2-0-indicado-pra-iniciantes/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/12/omarchy-2-0-indicado-pra-iniciantes/</guid><pubDate>Fri, 12 Sep 2025 12:00:00 -0300</pubDate><description>&lt;p&gt;O video mais importante do meu &lt;a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener"&gt;canal de YouTube&lt;/a&gt; é o &amp;ldquo;Não Terceirize suas Decisões&amp;rdquo;:&lt;/p&gt;
&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/D3L8IOncLkg"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;As perguntas que eu mais recebo, literalmente todos os dias, são no formato: &lt;em&gt;&amp;ldquo;Akita, devo fazer faculdade?&lt;/em&gt;, &lt;em&gt;&amp;ldquo;Akita, o que você acha que eu devo estudar?&amp;rdquo;&lt;/em&gt; e variações dessas.&lt;/p&gt;
&lt;p&gt;Velho, eu não te conheço, nunca vou saber nem me interessar em saber quem você é, o que almeja, quais suas aspirações, ambições, gostos, preferência. Nada. Nem eu, nem &lt;strong&gt;NINGUÉM&lt;/strong&gt;. Só você sabe o que é melhor pra você. Se não sabe, trate de procurar saber. É isso que &lt;strong&gt;VIVER&lt;/strong&gt; significa.&lt;/p&gt;</description><content:encoded><![CDATA[<p>O video mais importante do meu <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">canal de YouTube</a> é o &ldquo;Não Terceirize suas Decisões&rdquo;:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/D3L8IOncLkg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>As perguntas que eu mais recebo, literalmente todos os dias, são no formato: <em>&ldquo;Akita, devo fazer faculdade?</em>, <em>&ldquo;Akita, o que você acha que eu devo estudar?&rdquo;</em> e variações dessas.</p>
<p>Velho, eu não te conheço, nunca vou saber nem me interessar em saber quem você é, o que almeja, quais suas aspirações, ambições, gostos, preferência. Nada. Nem eu, nem <strong>NINGUÉM</strong>. Só você sabe o que é melhor pra você. Se não sabe, trate de procurar saber. É isso que <strong>VIVER</strong> significa.</p>
<p>O que você não admite é que quer que alguém que considera mais importante ou relevante aprovando sua escolha e dizendo que está certo.</p>
<p>Nunca espere isso de mim, eu nunca vou te validar.</p>
<p>Sempre vou dizer: <strong>SE VIRA</strong>. Foi assim que eu me tornei quem eu sou. Eu nunca segui ninguém, nunca perguntei pra ninguém o que deveria fazer. Simplesmente ia lá e fazia. Quebrava a cara, caía, levantava, e tentava de novo. É a vida.</p>
<p>Deixa eu mostrar o que me <strong>INCOMODA</strong> mesmo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124608_screenshot-2025-09-12_12-45-47.png" alt="pergunta idiota"  loading="lazy" /></p>
<p>Vamos ver no <a href="https://learn.omacom.io/2/the-omarchy-manual/93/security"target="_blank" rel="noopener">manual</a>?</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124657_screenshot-2025-09-12_12-46-45.png" alt="Manual"  loading="lazy" /></p>
<p>O DHH se deu ao trabalho de escrever um manual. Tem uma seção chamada &ldquo;Security&rdquo;. Se tivesse ido no Google, teria achado. Ou esta outra pérola:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912124836_screenshot-2025-09-12_12-48-24.png" alt="devops"  loading="lazy" /></p>
<p>Como quer ser &ldquo;DEVOPS&rdquo; (que tem que saber TUDO de Linux) começando assim. Já começou errado. Senta a bunda na cadeira, arregaça as mangas, e testa um por um, e aprende.</p>
<h3>Linux pra Iniciante?<span class="hx:absolute hx:-mt-20" id="linux-pra-iniciante"></span>
    <a href="#linux-pra-iniciante" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Isso dito, assista meu video. Mas como tenho falado bastante de <a href="/tags/omarchy/">Omarchy</a> já digo que sim, este é o que eu uso no meu dia a dia e é único que vou recomendar no futuro próximo (depois posso mudar de idéia). Tendo usado distros desde que Linux foi inventado no começo dos anos 90, eu sei que existem dezenas de distros e nem eu tenho paciência pra experimentar todos.</p>
<p><em>Por que Omarchy?</em></p>
<p>Primeiro porque ele fez a escolha certa da distro base: <strong>Arch Linux</strong>. Considero que é a melhor distro que existe hoje por dois fatores: ele tem pouco &ldquo;bloat&rdquo; (não instala um monte de coisas que você nunca vai usar, como um Ubuntu, Fedora, Mint, Deepin ou qualquer outros desses &ldquo;pra iniciantes&rdquo;).</p>
<p>Segundo, Arch Linux tem o AUR, que é um repositório de pacotes feitos pro usuários que tem basicamente tudo. Não precisa ficar caçando repositórios de terceiros ou ter que instalar manualmente. Com um único comando, o <code>yay</code>, instala-se tudo. Não existe isso de <code>apt-repository add</code> ou ter que fazer <code>make install</code> nem nada disso. O AUR gerencia tudo pra você.</p>
<p>Terceiro, ele instala Hyprland (que eu já usava e gostava), com configurações muito boas, escolha de pacotes muito bons. Tudo que eu mesmo instalaria manualmente. Então é uma opção mil vezes mais fácil pra ter o que eu teria muito mais trabalho pra fazer sozinho. Veja minha <a href="/tags/omarchy/">série de posts sobre Omarchy</a> pra ver essas opções.</p>
<h3>Linux pra Iniciantes?<span class="hx:absolute hx:-mt-20" id="linux-pra-iniciantes"></span>
    <a href="#linux-pra-iniciantes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><em>Iniciante em que?</em></p>
<p>Eu odeio essa pergunta. Se você é um usuário amador mesmo, não programador, talvez seja um contador, talvez seja só um adolescente gamer. A primeira pergunta que faço é: <em>pra que você quer usar Linux??</em></p>
<p>Digamos que seja porque ouviu falar que é mais seguro (de fato é). Então tanto faz, qualquer distro serve. Instale Ubuntu, instale Manjaro, instale Garuda. Não faz diferença, porque você nunca vai lidar com nada por baixo dos panos. Vai abrir o Chrome, vai abrir o Steam, e é só isso. O resto vai ser web-apps.</p>
<p>Digamos que é só pra jogar, então instale <a href="https://bazzite.gg/"target="_blank" rel="noopener">Bazzite</a> no seu mini-PC ou handheld. Ou só instale seus jogos num Windows separado só pra jogos como eu fiz. Tanto faz.</p>
<p>Ou fique no Windows ou MacOS mesmo. Se for totalmente amador, provavelmente vai usar a mesma senha fraca pra tudo, vai sair clicando em links do Whatsapp sem ver de quem é. Literalmente tanto faz qual OS vai usar. Nenhuma segurança de nenhum OS vai proteger seus maus hábitos e ignorância.</p>
<p>Se for iniciante em programação ou tecnologia, aí eu não tenho dó: aprenda Linux direito. Você quer ser um profissional, seja um profissional, aprenda do jeito certo: aprenda a ter 100% de controle sob sua própria máquina. Não tem passo a passo nem tutorial pra isso. É algo que você vai fazer continuamente pro resto da vida. Eu tenho mais de 30 anos na área e continuo aprendendo coisas novas todos os dias.</p>
<h3>Hardware<span class="hx:absolute hx:-mt-20" id="hardware"></span>
    <a href="#hardware" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <strong>MAIOR</strong> problema que qualquer pessoa vai ter, com QUALQUER distro vai ser suporte a hardware. E eis porque você não deve só seguir o que alguém te recomenda:</p>
<p><strong>Ninguém tem o mesmo PC que você.</strong></p>
<p>Qual é seu notebook? Um Lenovo? Um Dell? Um Positivo? De que ano? Qual modelo?</p>
<p>Tudo vai variar: qual é o dispositivo de Wi-fi e bluetooth? qual é a placa gráfica? Qual firmware está rodando? Com que suportes de perfis de energia? Tem necessidade de dispositivos FIDO2 pra coisas como leitor de impressão digital? Hardware keys? Tem TPU na sua máquina? Vai precisar de Secure Boot?</p>
<p>Não sei. Você que tem que saber.</p>
<p>Um exemplo prático, eu tenho um notebook <strong>Asus Zephyrus G14</strong> que uso com Windows porque serve só na minha oficina pra rodar Bambu Studio pra controlar minha Impressora 3D X1C. Ainda não parei pra instalar Omarchy nele, mas digamos que eu queira. Vai funcionar?</p>
<p>Felizmente existe <a href="https://wiki.archlinux.org/title/ASUS_ROG_Zephyrus_G16_%282023%29_GU603"target="_blank" rel="noopener">uma página no ArchWiki</a> dedicada a este modelo. Ao que parece, funciona tudo &ldquo;out-of-the-box&rdquo;. Mas se fosse a <a href="https://wiki.archlinux.org/title/ASUS_ROG_Zephyrus_G14_%282022%29_GA402"target="_blank" rel="noopener">versão do ano anterior</a> já teria que checar mais ítens extras. Tem problemas no Wi-fi, por exemplo.</p>
<p>Cada máquina diferente vai ter problemas diferentes.</p>
<h3>Live Env e Ventoy<span class="hx:absolute hx:-mt-20" id="live-env-e-ventoy"></span>
    <a href="#live-env-e-ventoy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><em>&ldquo;O que eu faço então, me ph0di?</em></p>
<p>É por isso que praticamente toda boa distro oferece a ISO de instalação que vem com um ambiente vivo (Live Environment). Já vi que tem muita gente no Windows que tem dificuldade do que fazer com uma ISO. É muito simples.</p>
<p><img src="https://etcher.balena.io/images/Etcher_steps.gif" alt="Balena Etcher"  loading="lazy" /></p>
<p>O programa mais fácil de usar é o <a href="https://etcher.balena.io/"target="_blank" rel="noopener">Balena Etcher</a> Você baixa o arquivo com extensão <code>.iso</code>, seja do Ubuntu, Mint, Elementary, etc (SEMPRE DO SITE OFICIAL!!!!). Abre o Balena e só tem 2 passos: selecione a imagem (o arquivo ISO), selecione o drive (seu pen drive, cuidado pra não escolher seu HD!), e pronto.</p>
<p>Agora é só bootar seu PC e entrar na BIOS pra selecionar o pen drive pra bootar ou usar uma tecla como F12 (varia de BIOS pra BIOS) pro UEFI selecionar seu pen drive.</p>
<p>Isso vai bootar o Live Environment direto do pen drive.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912120806_try-ubuntu-install-ubuntu-800x530.jpeg" alt="Try Ubuntu"  loading="lazy" /></p>
<p>A maioria das distros começa abrindo uma tela de &ldquo;Bem Vindo&rdquo; parecida com a de cima. Esse aplicativo de instalação se chama <a href="https://calamares.io/"target="_blank" rel="noopener"><strong>Calamares</strong></a> e todo mundo com instalação gráfica usa ele. Por isso é parecido em todos. É o mesmo passo a passo. Se instalou uma distro já instalou todas.</p>
<p>Só que você não precisa seguir o Calamares. Pode fechar ele. E agora está já no ambiente gráfico da distro que escolheu pra instalar. <strong>USE ELE.</strong> Tente configurar seu Wi-fi, tente parear seu fone ou mouse Bluetooth, tente ligar a WebCam, coloque um micro-SD card e veja se ele aparece. Tente navegar na web e sinta a performance.</p>
<p>Se nesse ponto conseguir fazer todas as coisas básicas, significa que as chances de tudo funcionar é grande. Se já neste ponto começar a ter problemas, como não conseguir conectar no seu Wi-fi, possivelmente essa distro vai te dar dor de cabeça. Comece indo no Google e veja se alguém já solucionou o problema pro seu modelo de notebook ou PC, <strong>antes</strong> de continuar com o Calamares.</p>
<p>Digamos que você decidiu tirar o dia pra testar várias distros pra ver qual roda melhor na sua máquina. Ótima decisão, mas já deve ter entendido que vai ser um enorme saco ter vários pen drives, um pra cada distro. Ou toda hora ter que voltar pro Windows e usar o Balena pra sobrescrever uma nova distro no seu único pendrive.</p>
<p>Não precisa fazer isso.</p>
<p>Basta baixar e instalar primeiro o <a href="https://www.ventoy.net/en/index.html"target="_blank" rel="noopener"><strong>Ventoy</strong></a> no seu pendrive. Veja a <a href="https://www.ventoy.net/en/doc_start.html"target="_blank" rel="noopener">documentação</a> no site deles, mas é ridiculamente simples.</p>
<p><img src="https://www.ventoy.net/static/img/screen/screen_uefi_en.png?v=4" alt="Ventoy Menu"  loading="lazy" /></p>
<p>Depois de instalar no seu pendrive, agora é só copiar <strong>TODAS AS ISOS</strong> que quiser de uma só vez no pendrive. Quando bootar por ele, vai entrar no menu do Ventoy, que vai deixar escolher qualquer uma das ISOs que copiou lá. Se não gostou da distro atual, só rebootar no Ventoy de novo, e escolher outra ISO. É assim simples.</p>
<p><img src="https://www.ventoy.net/static/img/ventoy2disk_en.png" alt="Ventoy"  loading="lazy" /></p>
<h3>Distrobox<span class="hx:absolute hx:-mt-20" id="distrobox"></span>
    <a href="#distrobox" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Digamos que você é um programador, ou um devops, ou qualquer profissional na área de tecnologia. Sua preocupação não é tanto a interface gráfica (que dá pra testar com Ventoy), mas sim se a infra do OS vai funcionar com seus requerimentos de desenvolvimento.</p>
<p>Digamos que você use Fedora. Será que se eu for pro Mint, vai ter os pacotes pro K8S do jeito que eu preciso? Será que tem suporte à versão de Java que eu uso? Será que se eu buildar um pacote <code>.deb</code> no meu Fedora, vai instalar direitinho no Ubuntu?</p>
<p>Ter que fazer dual boot ou instalar uma VM parece pesado demais, trabalhoso demais, só pra testar 2 linhas de comando, por exemplo.</p>
<p><img src="https://user-images.githubusercontent.com/598882/144294862-f6684334-ccf4-4e5e-85f8-1d66210a0fff.png" alt="Distrobox"  loading="lazy" /></p>
<p>Pra isso existe <a href="https://distrobox.it/"target="_blank" rel="noopener"><strong>Distrobox</strong></a>. Ele precisa do Docker ou Podman já instalado na sua máquina. Mas depois é só fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">distrobox create -n debian-test --init --image debian:latest --additional-packages <span class="s2">&#34;build-essential&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Uma vez criado o box é só entrar nele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">distrobox enter debian-test</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Acabou, você está dentro do Debian, super rápido. Como é um container docker, tem como mapear diretórios na sua máquina local pra dentro do container, então fica tudo integrado. E o mais importante: carrega super rápido, porque é só um container Docker.</p>
<p>O guia <a href="https://distrobox.it/#quick-start"target="_blank" rel="noopener">Quick Start</a> explica tudo isso e tem este video com um tour pra demonstrar como funciona:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/Q2PrISAOtbY"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sério. Com Distrobox não tem desculpa pra não testar outra distro em 2 minutos. Não fique perdendo tempo mandando DM pra &ldquo;influencer&rdquo;. Veja você mesmo! Se não é capaz de fazer isso, talvez esteja na profissão errada.</p>
<p>Todo &ldquo;dev&rdquo; ou profissional de tecnologia, seja de devops, cybersec, data analyst, bla bla bla, seja lá qual título da moda hoje em dia, <strong>DEVE</strong> ter controle sobre sua própria máquina. <strong>DEVE</strong> saber lidar com segurança (por isso fiz posts sobre Vaultwarden, Cloudflare, SSH, etc).</p>
<p><strong>É SUA RESPONSABILIDADE!</strong></p>
<p>Não terceirize sua responsabilidade pros outros. Não procure validação. Seja <strong>promíscuo</strong> e teste quais distros quiser, e faça o que quiser nessas distros: é sua! Não existe &ldquo;distro pra iniciante&rdquo;, &ldquo;distro pra dev&rdquo;, &ldquo;distro pra devops&rdquo;. Distro é Distro. Linux é Linux. É tudo igual, o que varia é <strong>sua habilidade</strong>. Se não tem habilidade: conquiste! Aprenda! Quebre a cabeça! Vare noites tunando configs! Leia as documentações oficiais! Não perca tempo achando que vai aprender em rede social. Não vai.</p>
<p>Eu uso Linux desde o começo dos anos 90 e não fico usando Slackware só porque foi a primeira que aprendi e sinto necessidade de validar minha primeira escolha. Eu uso todas que quiser. Agora mesmo, eu tinha minha própria instalação de Hyprland, resolvi que quero aprender e usar Omarchy. Passei a semana quebrando cabeça, escrevi meia dúzia de posts no blog. E agora eu aprendi. Eu sei Omarchy: mais um pra coleção.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>tutoriais</category></item><item><title>Omarchy 2.0 - Instale com a ISO do Omarchy</title><link>https://akitaonrails.github.io/2025/09/12/omarchy-2-0-instale-com-a-iso-do-omarchy/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/12/omarchy-2-0-instale-com-a-iso-do-omarchy/</guid><pubDate>Fri, 12 Sep 2025 10:00:00 -0300</pubDate><description>&lt;p&gt;Pra finalizar a série de &lt;a href="https://akitaonrails.github.io/tags/omarchy/"&gt;Omarchy&lt;/a&gt; por enquanto, acho que faltou comentar rapidamente sobre a instalação via a ISO do próprio Omarchy.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912093827_screenshot-2025-09-12_09-38-05.png" alt="ISO" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Só ir no &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;site oficial&lt;/a&gt; e clicar em &amp;ldquo;ISO&amp;rdquo; pra baixar. Usar um Rufus ou Balena Etcher no Windows (ou &lt;a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener"&gt;Caligula&lt;/a&gt; em Linux) pra gravar num pen drive e bootar com ele. Dali em diante é seguir o passo-a-passo e em menos de 5 minutos vai ter tudo instalado.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Pra finalizar a série de <a href="/tags/omarchy/">Omarchy</a> por enquanto, acho que faltou comentar rapidamente sobre a instalação via a ISO do próprio Omarchy.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250912093827_screenshot-2025-09-12_09-38-05.png" alt="ISO"  loading="lazy" /></p>
<p>Só ir no <a href="https://omarchy.org/"target="_blank" rel="noopener">site oficial</a> e clicar em &ldquo;ISO&rdquo; pra baixar. Usar um Rufus ou Balena Etcher no Windows (ou <a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener">Caligula</a> em Linux) pra gravar num pen drive e bootar com ele. Dali em diante é seguir o passo-a-passo e em menos de 5 minutos vai ter tudo instalado.</p>
<p><img src="https://learn.omacom.io/u/configurator-iGH96F.png" alt="Omarchy install"  loading="lazy" /></p>
<p>Como expliquei no <a href="https://akitaonrails.com/2025/08/29/new-omarchy-2-0-install/"target="_blank" rel="noopener">primeiro artigo</a> eu fiz diferente: instalei o <a href="https://archlinux.org/download/"target="_blank" rel="noopener">Arch Linux</a> puro, tipo Desktop, com Hyprland + SDDM. Depois instalei Omarchy usando a linha de comando manual:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSL https://omarchy.org/install <span class="p">|</span> bash</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dá pra fazer dos dois jeitos, no segundo tem mais controle, mas depois de ganhar mais experiência e ler os scripts de install, eu recomendo que baixe a ISO do próprio Omarchy, vai ser mais fácil mesmo.</p>
<h3>Limine vs Grub<span class="hx:absolute hx:-mt-20" id="limine-vs-grub"></span>
    <a href="#limine-vs-grub" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O motivo de ter instalado da forma mais manual foi porque eu não tinha certeza se o Omarchy tinha suporte a dar rollback de BTRFS via snapshots. Eu tinha ouvido falar que o bootload <a href="https://codeberg.org/Limine/Limine"target="_blank" rel="noopener">Limine</a> não suportava isso. Mas me enganei, o Omarchy instala Limine com suporte a <strong>Snapper</strong> (que é similar ao Timeshift que eu costumo usar). Leia no <a href="https://wiki.archlinux.org/title/Limine"target="_blank" rel="noopener">ArchWiki seção 6.2</a>, portanto deve ser uma opção superior à minha instalação de GRUB+Timeshift que falei no primeiro artigo.</p>
<p>Como eu já instalei de forma manual, vou manter como está porque na prática é a mesma coisa. Mas se fosse instalar de novo, agora instalaria com Limine+Snapper pra experimentar.</p>
<p>Significa que existe suporte a snapshot do seu filesystem inteiro com BTRFS antes de um update de pacotes e, se algo der errado, é possível rebootar e dar rollback pra um snapshot anterior. Isso te dá segurança de não perder um dia inteiro tentando consertar alguma coisa, caso algum pacote importante do sistema venha com bugs ou corrompido. É o recurso mais importante.</p>
<h3>SDDM vs UWSM<span class="hx:absolute hx:-mt-20" id="sddm-vs-uwsm"></span>
    <a href="#sddm-vs-uwsm" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Outra coisa diferente que notei foi o login manager. No Arch puro, ele instala Hyprland por padrão com <a href="https://wiki.archlinux.org/title/SDDM"target="_blank" rel="noopener">SDDM</a>, que é o mesmo que o KDE usa.</p>
<p>Omarchy usa UWSM, que faz login automático com seu usuário no boot. Então não tem tela de login, ele já abre o Hyprland automaticamente.</p>
<p><em>&ldquo;Mas isso não é inseguro??&rdquo;</em></p>
<p>Não, porque a ISO do Omarchy pré-configura seu SSD com LUKS, antes do filesystem.</p>
<p>Pra quem não sabe, <a href="https://wiki.archlinux.org/title/Dm-crypt/Encrypting_an_entire_system"target="_blank" rel="noopener">LUKS</a> é o <strong>Linux Unified Key Setup</strong>, que dá suporte a encriptar seu disco inteiro. A vantagem dele em relação a um BitLocker do Windows, é que ele é <strong>agnóstico a filesystem</strong>. Ou seja, funciona com BTRFS ou EXT4 ou qualquer outro, porque na sequência de boot, depois do bootloader (seja Limine ou Grub), ele carrega <strong>antes</strong> do filesystem. É um encriptador em nível de blocos.</p>
<p><img src="https://learn.omacom.io/u/arch-encryption-urjrDm.png" alt="install luks"  loading="lazy" /></p>
<p>Portanto, quando você bootar seu sistema, primeiro vai pedir a senha de decriptar seu disco. Daí abre o filesystem e o kernel do Linux pode continuar o boot normal com o disco aberto. Então você é obrigado a <strong>sempre</strong> digitar a senha segura do seu usuário no boot pra decriptar o disco.</p>
<p>Portanto, ter que digitar senha novamente num login manager seria redundante e desnecessário, por isso imagino que escolheram usar UWSM em vez de SDDM.</p>
<p>Especialmente em notebook, ou em casos onde mais gente mora com você, é <strong>essencial</strong> garantir que seu disco esteja encriptado, não importa o sistema operacional. Digamos que saia pra trabalhar ou vá viajar e seu notebook é roubado. Sem encriptação, acabou, não importa se tem um login manager. Ele não protege seus dados. Basta tirar o SSD do seu notebook, plugar em outro computador e pronto, dá pra copiar tudo bootando como Root.</p>
<p>Com encriptação, sem a senha pra decriptar, é impossível acessar seus dados. Impossível mesmo, porque em qualquer computador modernos dos últimos 15 anos, a CPU vem com instruções aceleradas em hardware pra encriptar usando AES 256.</p>
<p>Sendo um pouco mais nerd, a chave de decriptação <strong>não é sua senha</strong>. Durante o processo de instalação vai ser gerada uma nova senha mais forte e segura usando um processo de <strong>derivação de chaves</strong> (KDF - Key Derivation Function), hoje em dia é com Argon2id, antigamente era com PBKDF2. Explico sobre isso neste video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HCHqtpipwu4"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Portanto, quando digita sua senha, na verdade ele decripta essa chave segura, e é com essa chave segura que seu disco está encriptado. Por isso não dá pra decriptar seus dados na força-bruta.</p>
<p>Além disso AES 256 é outro algoritmo de criptografia que é <strong>quantum resistant</strong>. Mesmo se fosse possível um computador quântico (não é), nem ele conseguiria decriptar seu disco. Este é o nível de segurança.</p>
<p><em>&ldquo;Mas não vai ficar mais lento?&rdquo;</em></p>
<p>Não. É praticamente imperceptível, porque AES - como já disse - é acelerado via hardware pela sua CPU, não depende de software do seu sistema operacional. No dia a dia, dificilmente vai notar. A performance é praticamente a mesma. Não existe motivo pra não ativar encriptação, especialmente num notebook.</p>
<p>No meu caso pessoal, onde instalei num PC desktop, que eu mesmo controlo numa rede local segura - porque eu sei implementar segurança em rede, não tem problema. Os dados mais importantes pra mim ficam no meu NAS (que é encriptado), em backups (que são encriptados), eu não tenho dados sensíveis neste PC e o pouco que tenho está em arquivos encriptados pelo <a href="https://veracrypt.io/en/Downloads.html"target="_blank" rel="noopener">Veracrypt</a> num sub-volume separado.</p>
<p>Por isso não habilitei LUKS no meu PC. Daí mantive o SDDM que o Arch puro instalou. Só que ele é <strong>BEM</strong> feio por padrão, então só fica uma pequena dica: instalar ou o tema <a href="https://github.com/MarianArlt/sddm-sugar-dark"target="_blank" rel="noopener">sddm-sugar-dark</a> ou o <a href="https://github.com/simonesestito/dark-arch-sddm"target="_blank" rel="noopener">dark-arch-sddm</a>. Eu prefiro esse segundo por ser mais minimalista e fica assim:</p>
<p><img src="https://github.com/simonesestito/dark-arch-sddm/raw/master/base/screenshot.png" alt="dark arch sddm"  loading="lazy" /></p>
<p>Eu customizei e troquei o logo do Arch pelo do Omarchy e pronto.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Vou repetir: só depois que instalei manualmente que vi como o Omarchy instala e hoje eu prefiro o jeito do Omarchy. Se fosse instalar do zero, usaria a ISO do Omarchy e deixaria com Limine+Snapper e Hyprland+UWSM com suporte a rollback de snapshot, encriptação total do disco, login automático pro Hyprland.</p>
<p>Dá pra ver que o pessoal fazendo o Omarchy tem experiência e já escolheu componentes que fazem sentido pra funcionar tudo junto da forma mais conveniente e segura possível. Eu tive receio que por ser uma distro nova talvez eles não tivessem se preocupado com isso, mas fico feliz de dizer que eu estava errado.</p>
<p>Usem a ISO oficial do Omarchy.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>tutoriais</category></item><item><title>Omarchy 2.0 - Entendendo SSH e Yubikeys</title><link>https://akitaonrails.github.io/2025/09/09/omarchy-2-0-entendendo-ssh-e-yubikeys/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/09/omarchy-2-0-entendendo-ssh-e-yubikeys/</guid><pubDate>Tue, 09 Sep 2025 12:00:00 -0300</pubDate><description>&lt;p&gt;Este post não tem nada a ver diretamente com Omarchy mas resolvi incluir na &lt;a href="https://akitaonrails.github.io/tags/omarchy/"&gt;mesma série&lt;/a&gt; porque é uma dica importante pra programadores.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Todo programador precisa saber como gerenciar suas chaves SSH.&lt;/strong&gt; No mínimo porque toda conta no GitHub exige uma chave.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_12-19-38.png" alt="GitHub SSH" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Toda vez que fizer &lt;code&gt;git push&lt;/code&gt; pra um repositório seu, ele vai pedir a passphrase dessa chave:&lt;/p&gt;
&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❯ git push origin master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enter passphrase &lt;span class="k"&gt;for&lt;/span&gt; key &lt;span class="s1"&gt;&amp;#39;/home/akitaonrails/.ssh/id_ed25519&amp;#39;&lt;/span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Copy code"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;O que significa isso?&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post não tem nada a ver diretamente com Omarchy mas resolvi incluir na <a href="/tags/omarchy/">mesma série</a> porque é uma dica importante pra programadores.</p>
<p><strong>Todo programador precisa saber como gerenciar suas chaves SSH.</strong> No mínimo porque toda conta no GitHub exige uma chave.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_12-19-38.png" alt="GitHub SSH"  loading="lazy" /></p>
<p>Toda vez que fizer <code>git push</code> pra um repositório seu, ele vai pedir a passphrase dessa chave:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ git push origin master
</span></span><span class="line"><span class="cl">Enter passphrase <span class="k">for</span> key <span class="s1">&#39;/home/akitaonrails/.ssh/id_ed25519&#39;</span>:</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que significa isso?</p>
<p>SSH é um conjunto de ferramentas tanto de cliente quanto servidor pra criar uma conexão segura, encriptada, pela internet. Ela tem diversas utilidades e eu expliquei como fazer Proxy SSH neste video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/T-jHuFnxZ2k"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>O processo todo se inicia instalando SSH na sua máquina, e no Omarchy/ArchLinux é só fazer isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yay -S openssh</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se nunca criou um par de chaves (sim, é um par) é só executar este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-keygen -t ed25519 -a 100 -C &#34;your_email@example.com&#34; -f ~/.ssh/id_ed25519</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>ED25519</strong> é um algoritmo de chave-pública, ou seja, cria um par de chaves: uma privada que <strong>jamais deve sair da sua máquina</strong>, e uma pública que pode ser exposta na internet aberta (a que você registra na sua conta no GitHub, por exemplo). Eu expliquei sobre chaves-públicas neste outro video:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HCHqtpipwu4"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>ED25519 é um algoritmo mais moderno que o antigo RSA (que acho que ainda é o padrão no ssh-keygen se não fizer a escolha explícita - ela continua sendo segura, mas curva elíptica é ainda mais segura).</p>
<p>Ela é baseada em criptografia de curva elíptica (ECC). Pros paranóicos, sim, é inclusive considerada <strong>quantum-resistant</strong>, ou seja, mesmo se fosse possível fazer um computador quântico, ela não conseguiria quebrar uma chave ED25519.</p>
<p>Dois arquivos serão criados depois de rodar <code>ssh-keygen</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>.rw-------   464 akitaonrails 14 Nov  2017  󰌆 id_ed25519
.rw-r--r--   108 akitaonrails 14 Nov  2017  󰷖 id_ed25519.pub</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note as permissões desses arquivos. É importante que o diretório <code>~/.ssh</code> e os arquivos de chave privada dentro sempre estejam com permissão <code>600</code> , ou seja, read-write somente pro seu usuário, e nenhuma permissão pra grupos ou qualquer outro usuário. Ninguém deve ser capaz sequer de listar o diretório se não for você mesmo.</p>
<p>Já os arquivos <code>*.pub</code>, que são as chaves públicas, tem permissão <code>644</code> que é read-write pra você e somente-leitura pra outros grupos e usuários. Como falei, a chave pública pode ser exposta publicamente. A minha, por exemplo, é esta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ cat id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEWG5bHK02sMqilMiYu67xqdaBsk3TtCQ564bcJibDiO akitaonrails@Miner42.local</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>ED25519 tem outra vantagem, ela é ordens de grandeza mais segura que RSA e ainda oferece uma chave pública muito mais curta, o que facilita na hora de copiar e colar por aí.</p>
<p>Uma chave privada tem um envelope neste formato:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>-----BEGIN RSA PRIVATE KEY-----
MIIEpA ...
...
XfI0X....
...
... nQ5w==
-----END RSA PRIVATE KEY-----</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Repetindo: <strong>ESTE ARQUIVO JAMAIS DEVE SAIR DA SUA MÁQUINA</strong>. Nunca deixe largado num pen drive, por exemplo. E sempre garanta que a permissão é <code>chmod 600</code> como expliquei.</p>
<h3>Yubikey 5<span class="hx:absolute hx:-mt-20" id="yubikey-5"></span>
    <a href="#yubikey-5" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A forma considerada mais segura e que muitas empresas até enforçam, é o uso de uma <strong>HARDWARE-KEY</strong>. A chave privada nunca é gravada na sua máquina local e sim gerada dentro de um hardware seguro, de onde é impossível tentar ler essa chave privada. Exemplos são Yubikey ou FIDO2.</p>
<p>Sobre Yubikey, cuidado, você quer os modelos mais novos <strong>Yubikey Series 5</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yubikey.webp" alt="Yubikey 5"  loading="lazy" /></p>
<p>Se tiver o antigo - que normalmente é o de cor azul - ele é o Security Key v4.3.1 e só suporta <strong>U2F (FIDO U2F)</strong> mas não <strong>FIDO2/WebAuthn</strong> e não applets <strong>PIV/OpenPGP/OTP</strong>. Pra ter certeza, instale estes pacotes no seu Arch:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S yubikey-manager libfido2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí cheque com este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ ykman info
</span></span><span class="line"><span class="cl">WARNING: PC/SC not available. Smart card <span class="o">(</span>CCID<span class="o">)</span> protocols will not <span class="k">function</span>.
</span></span><span class="line"><span class="cl">Device type: FIDO U2F Security Key
</span></span><span class="line"><span class="cl">Firmware version: 4.3.1
</span></span><span class="line"><span class="cl">Enabled USB interfaces: FIDO
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Applications
</span></span><span class="line"><span class="cl">Yubico OTP   Not available
</span></span><span class="line"><span class="cl">FIDO U2F     Enabled
</span></span><span class="line"><span class="cl">FIDO2        Not available
</span></span><span class="line"><span class="cl">OATH         Not available
</span></span><span class="line"><span class="cl">PIV          Not available
</span></span><span class="line"><span class="cl">OpenPGP      Not available
</span></span><span class="line"><span class="cl">YubiHSM Auth Not available</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja como ele acha o Yubikey conectado via USB mas tudo está como &ldquo;Not available&rdquo;. Não é o correto, esse Yubikey só serve como Two-Factor Authentication em Websites. Não é inútil, mas pro nosso caso não funciona porque:</p>
<ul>
<li>não tem applet PIV então não serve de smartcard</li>
<li>não tem applet OpenPGP então não consegue guardar chaves privadas GPG ou SSH</li>
<li>não tem FIDO2 então não dá pra rodar <code>ssh-keygen -t ed25519-sk</code>.</li>
</ul>
<p>Um novo, no Mercado Livre hoje, vai custar uns BRL 800. Se for usar pra trabalhar por política de segurança da empresa, eles tem que providenciar ou ressarcir pra você. Mas mesmo pra uso pessoal, recomendo investir um pouco mais na segurança.</p>
<p>Tendo um Yubikey 5, o procedimento é o seguinte. Comece garantindo que tem os pacotes na sua máquina:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S yubikey-manager yubico-piv-tool opensc</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Inicialize a aplicação PIV mudando o PIN:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ykman piv reset                # wipes PIV, careful
ykman piv access change-pin    # default PIN is 123456
ykman piv access change-puk    # default PUK is 12345678</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Gere um novo par de chaves <strong>dentro</strong> do Yubikey:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ykman piv keys generate 9a pubkey.pem</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Extraia somente a chave pública pro seu diretório local <code>~/.ssh</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-keygen -D /usr/lib/opensc-pkcs11.so -e &gt; ~/.ssh/id_yubikey.pub</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O conteúdo desse arquivo <code>~/.ssh/id_yubikey.pub</code> é que você vai adicionar no arquivo <code>~/.ssh/authorized_keys</code> do seu servidor remoto que quer conectar via SSH. Pra abrir uma conexão SSH pra ele, conecte o Yubikey na porta USB e execute assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh -I /usr/lib/opensc-pkcs11.so seu-user@server</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele vai pedir seu PIN e autorizar a conexão. Se não quiser ficar digitando esse <code>-I</code> toda vez, basta editar o <code>~/.ssh/config</code> com isto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># ~/.ssh/config
Host server
  PKCS11Provider /usr/lib/opensc-pkcs11.so
  User seu-user</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><strong>PORÉM</strong></p>
<p>Este procedimento vai usar o gerador de chaves de dentro da Yubikey, que só suporta <strong>RSA (1024–2048)</strong> ou <strong>ECC (P-256, P-384)</strong>. Se quiser gerar uma chave ED25519, o fluxo é diferente, temos que executar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-keygen -t ed25519-sk -a 100 -C &#34;you@example.com&#34; -f ~/.ssh/id_ed25519_sk</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse arquivo <code>id_ed25519_sk</code> que ele gera na sua máquina local <strong>não é a chave-privada</strong> é somente um &ldquo;stub&rdquo; que aponta pra sua Yubikey. Então a chave fica armazenada no hardware e nunca passa pela sua máquina.</p>
<p>Pra adicionar sua chave-pública no servidor remoto, basta manualmente adicionar no arquivo <code>~/.ssh/authorized_keys</code> como fizemos antes ou rodar este comando pra logar com sua senha e ele vai adicionar a chave-pública pra você:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub seu-user@server</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra usar sua chave pra logar agora é só fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh -i ~/.ssh/id_ed25519_sk seu-user@server</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso só funciona porque a Yubikey 5 tem suporte a FIDO2. As antigas não tem, por isso não dá pra fazer isso.</p>
<p>Ah sim, no seu home server ou outro servidor, <strong>DEPOIS</strong> que adicionar sua chave no <code>authorized_keys</code> eu recomendo <strong>DESABILITAR</strong> a opção de login por senha (pra que nenhum bot fique tentando logar por força bruta). No servidor edite:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
...
PubkeyAuthentication yes</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou seja, desligue todas as opções de login e deixe somente por chave pública. Agora só reinicie o serviço:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl restart sshd</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>SSH-Agent<span class="hx:absolute hx:-mt-20" id="ssh-agent"></span>
    <a href="#ssh-agent" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra ficar claro: a forma considerada mais segura pra gerenciar chaves privadas é via um hardware de segurança externo, no caso a Yubikey 5.</p>
<p>Mas, não é obrigatório. Se você tem um PC exclusivo pra trabalhar, com seu Arch Linux ou Omarchy instalado, e você não fica instalando softwares suspeitos - como jogos pirateados - nem fica acessando sites suspeitos e clicando em qualquer link (risco de malware), não é completamente ruim ter suas chaves localmente na sua máquina. Eu mesmo uso assim faz anos e nunca tive problemas - porque eu sou paranóico com segurança da minha máquina.</p>
<p>Lembre-se, um dos tipos de malware mais comuns são que fazem upload dos seus arquivos ou que encripta seus arquivos e depois pedem resgate de criptomoeda pra devolver. O bom e velho <strong>RANSOMWARE</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ransomware-message.jpg" alt="Ransomware"  loading="lazy" /></p>
<p>Isso tem não só em Windows como Mac e Linux também. E navegadores web são o principal vetor, especialmente emails de phishing com links maliciosos que você sai clicando, ou links que vem em canais de Discord, Telegram ou Whatsapp.</p>
<p><strong>NUNCA CLIQUE EM LINKS</strong>. Copie o link, cole num terminal e veja que tipo de link que é. Se não for um domínio obviamente oficial como &ldquo;whatspp.com&rdquo; ou &ldquo;google.com&rdquo;, não siga em frente.</p>
<p>isso tudo dito. Digamos que você criou suas chaves localmente e cadastrou no <code>authorized_keys</code> do seu servidor e na sua conta de GitHub. Agora, toda vez que fizer <code>ssh</code> ou <code>git push</code> ele vai pedir pra digitar sua <strong>passphrase</strong> (que, se você não for bobo, fez uma passphrase <strong>BEM LONGA</strong>).</p>
<p>Isso é extremamente inconveniente de ficar digitando o tempo todo. Por isso tem um recurso no SSH pra ajudar: cachear sua autenticação durante algum tempo fixo usando ssh-agent.</p>
<p>Comece criando este serviço de usuário pro systemd:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># ~/.config/systemd/user/ssh-agent.service</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Unit</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Description</span><span class="p">=</span><span class="nx">SSH</span> <span class="nx">Agent</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Service</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Type</span><span class="p">=</span><span class="nx">simple</span>
</span></span><span class="line"><span class="cl"><span class="nx">Environment</span><span class="p">=</span><span class="nx">SSH_AUTH_SOCK</span><span class="p">=</span><span class="err">%</span><span class="nx">t</span><span class="err">/</span><span class="nx">ssh-agent</span><span class="p">.</span><span class="nx">socket</span>
</span></span><span class="line"><span class="cl"><span class="nx">ExecStart</span><span class="p">=</span><span class="err">/</span><span class="nx">usr</span><span class="err">/</span><span class="nx">bin</span><span class="err">/</span><span class="nx">ssh-agent</span> <span class="nx">-D</span> <span class="nx">-a</span> <span class="err">$</span><span class="nx">SSH_AUTH_SOCK</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Install</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">WantedBy</span><span class="p">=</span><span class="nx">default</span><span class="p">.</span><span class="nx">target</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Habilite com privilégios do seu user (sem sudo):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>systemctl --user enable --now ssh-agent.service</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Exponha isso no seu <code>.bashrc</code> ou <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ~/.zshenv (or ~/.profile)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SSH_AUTH_SOCK</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$XDG_RUNTIME_DIR</span><span class="s2">/ssh-agent.socket&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, toda vez que fizer login no seu sistema e começar a trabalhar, adicione sua chave de trabalho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-add -t 30m ~/.ssh/id_ed25519</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele vai pedir sua passphrase uma vez, mas durante <strong>30 minutos</strong> (que é essa opção <code>-t 30m</code>), não vai mais precisar re-digitar a passphrase e pode ficar acessando <code>ssh</code> ou fazendo <code>git push</code> de forma mais conveniente.</p>
<p>Se quiser remover o cache do agente, antes do timeout, pode fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ssh-add -d ~/.ssh/id_ed25519</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se sua rotina de trabalho envolve usar ssh, em vez de manualmente fazer <code>ssh-add</code> pode adicionar a seguinte configuração no seu <code>~/.ssh/config</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># ~/.ssh/config</span>
</span></span><span class="line"><span class="cl">Host *
</span></span><span class="line"><span class="cl">  AddKeysToAgent yes
</span></span><span class="line"><span class="cl">  IdentitiesOnly yes
</span></span><span class="line"><span class="cl">  IdentityFile ~/.ssh/id_ed25519</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E toda vez que abrir o terminal, ele vai pedir pra digitar sua passphrase. E isso vai durar enquanto sua sessão estiver logada.</p>
<p>Por fim, o nome do arquivo nos exemplos é sempre <code>id_ed25519</code> mas isso não tem relevância nenhuma, você pode renomear como quiser e inclusive criar múltiplas chaves pra usar em serviços separados, se quiser ser extra-seguro. Basta gerar chaves como <code>heroku_id_ed25519</code> ou <code>google_cloud_id_ed25519</code> e assim por diante. Crie quantas chaves quiser ou achar necessário. Isso vale tanto pra chaves locais quanto chaves na Yubikey 5.</p>
<p>Acho que estes são os aspectos que considero mais importante saber sobre chaves SSH, esqueci alguma outra coisa importante? Não deixe de mandar mais dicas nos comentários abaixo.</p>
]]></content:encoded><category>omarchy</category><category>seguranca</category><category>linux</category></item><item><title>Omarchy 2.0 - TUIs (Terminal User Interface Apps)</title><link>https://akitaonrails.github.io/2025/09/09/omarchy-2-0-tuis/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/09/omarchy-2-0-tuis/</guid><pubDate>Tue, 09 Sep 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;Uma das coisas mais frustrantes pra um usuário de Linux é que existem muito poucos Apps gráficos bem feitos. Veja um Gimp ou LibreOffice. São feios, antiquados, muito parecidos com a primeira vez que usei eles mais de 20 anos atrás.&lt;/p&gt;
&lt;p&gt;Os apps mais &amp;ldquo;modernos&amp;rdquo; são basicamente front-ends web enlatados dentro de um wrapper Electron. O problema? São &lt;strong&gt;PESADOS&lt;/strong&gt; consomem RAM como se não houvesse amanhã. Comece a abrir Discord, Spotify, e vários desses e veja se seu notebook velho de 8GB de RAM vai aguentar.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Uma das coisas mais frustrantes pra um usuário de Linux é que existem muito poucos Apps gráficos bem feitos. Veja um Gimp ou LibreOffice. São feios, antiquados, muito parecidos com a primeira vez que usei eles mais de 20 anos atrás.</p>
<p>Os apps mais &ldquo;modernos&rdquo; são basicamente front-ends web enlatados dentro de um wrapper Electron. O problema? São <strong>PESADOS</strong> consomem RAM como se não houvesse amanhã. Comece a abrir Discord, Spotify, e vários desses e veja se seu notebook velho de 8GB de RAM vai aguentar.</p>
<p>Mas, especialmente se você é um programador, deveria dar mais atenção pra TUIs. Num primeiro momento alguns devem virar o nariz: <em>&ldquo;apps de terminal são feios, preto e branco, quadrados, blerg&rdquo;</em></p>
<p>Se fosse 20 anos atrás, estariam certos. Mas é 2025 e hoje em dia terminais suportam 256 cores (mais que suficiente), suportam várias resoluções e, mais importante, suportam fontes, em particular <a href="https://www.nerdfonts.com/"target="_blank" rel="noopener">Nerd Fonts</a>, fontes que tem ícones dentro. É assim que funciona uma ferramenta como <a href="https://github.com/eza-community/eza"target="_blank" rel="noopener">eza</a>, que eu uso todo dia com alias pra <code>ls</code> e fica assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_16-50-25.png" alt="eza"  loading="lazy" /></p>
<p>Não é só isso, existem bibliotecas modernas, pra diversas linguagens, como <a href="https://github.com/charmbracelet/bubbletea"target="_blank" rel="noopener"><strong>BubbleTea</strong></a>, que é um framework pra construir TUIs. Chequem o GitHub deles mas tem as ferramentas pra fazer interfaces assim:</p>
<p><img src="https://camo.githubusercontent.com/e2b13cfb4ea64e98028ab21e0dca97a6af46d8eeb78ef092c5cd4669ea7b7bef/68747470733a2f2f73747566662e636861726d2e73682f627562626c657465612f627562626c657465612d6578616d706c652e676966" alt="BubbleTea"  loading="lazy" /></p>
<p>E se quiser só um widget simples pra complementar um script Bash que está fazendo, eles tem o <a href="https://github.com/charmbracelet/gum"target="_blank" rel="noopener"><strong>Gum</strong></a>, que permite interfaces assim:</p>
<p><img src="https://camo.githubusercontent.com/cea695e2df946b14838196a5cfb7d6608f03387ba42c7a7fb5afa6432371635c/68747470733a2f2f7668732e636861726d2e73682f7668732d31715935375272516c584375796473456744703638472e676966" alt="Gum"  loading="lazy" /></p>
<p>E pra usar é só instalar o binário e no seu script fazer linhas assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># exemplos de Gum</span>
</span></span><span class="line"><span class="cl">gum choose <span class="s2">&#34;fix&#34;</span> <span class="s2">&#34;feat&#34;</span> <span class="s2">&#34;docs&#34;</span> <span class="s2">&#34;style&#34;</span> <span class="s2">&#34;refactor&#34;</span> <span class="s2">&#34;test&#34;</span> <span class="s2">&#34;chore&#34;</span> <span class="s2">&#34;revert&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gum input --placeholder <span class="s2">&#34;scope&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gum confirm <span class="s2">&#34;Commit changes?&#34;</span> <span class="o">&amp;&amp;</span> git commit -m <span class="s2">&#34;</span><span class="nv">$SUMMARY</span><span class="s2">&#34;</span> -m <span class="s2">&#34;</span><span class="nv">$DESCRIPTION</span><span class="s2">&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E se usa <a href="https://akitaonrails.com/tags/omarchy/"target="_blank" rel="noopener"><strong>Omarchy</strong></a> eles ainda trazem uma funcionalidade extra pra TUIs. Basta clicar &ldquo;SUPER+ALT+SPACE&rdquo; pra trazer o menu principal, depois escolher &ldquo;INSTALL&rdquo; e &ldquo;TUI&rdquo;. Ou simplesmente abrir o terminal e executar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$OMARCHY_PATH/bin/omarchy-tui-install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-08_23-22-50.png" alt="omarchy-tui-install"  loading="lazy" /></p>
<p>Ele vai pedir um nome pro App e a linha de comando pra executar. Dá pra escolher icones também no <a href="https://dashboardicons.com/"target="_blank" rel="noopener">Dashboard Icons</a>. Registrando dessa forma, quando chamar o launcher Rofi com &ldquo;SUPER+SPACE&rdquo; e digitar &ldquo;lazy&rdquo; vai aparecer o LazyDocker como se fosse um app normal:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hdmi-a-3.png" alt="Rofi LazyDocker"  loading="lazy" /></p>
<p>E como em Hyprland não tem barra de título nem nada, apps TUI ou webapps (tem <code>omarchy-webapp-install</code> também) abrem como se fossem apps &ldquo;nativos&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_00-49-28.png" alt="tiles windows"  loading="lazy" /></p>
<p>Leiam as seções de <a href="https://learn.omacom.io/2/the-omarchy-manual/59/tuis"target="_blank" rel="noopener">TUIs</a> e <a href="https://learn.omacom.io/2/the-omarchy-manual/63/web-apps"target="_blank" rel="noopener">Web-Apps</a> no manual do Omarchy.</p>
<p>Obviamente, isso é só se você é daqueles que prefere a sensação de estar executando &ldquo;Apps&rdquo;, mas é a mesma coisa se abrir um Terminal e simplesmente digitar o <code>lazydocker</code> na linha de comando - que é minha preferência. O que esses scripts do Omarchy fazem é criar um arquivo <code>.desktop</code> em <code>~/.local/share/applications</code>. O exemplo do <code>LazyDocker.desktop</code> gera isto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ cat ~/.local/share/applications/LazyDocker.desktop
[Desktop Entry]
Version=1.0
Name=LazyDocker
Comment=LazyDocker
Exec=alacritty --class TUI.tile -e lazydocker
Terminal=false
Type=Application
Icon=/home/akitaonrails/.local/share/applications/icons/LazyDocker.png
StartupNotify=true</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se não quiser mais este launcher é só apagar o arquivo ou chamar o script <code>$OMARCHY_PATH/bin/omarchy-tui-remove</code>.</p>
<p>Esse recurso é particularmente interessante no Hyprland porque diferente de GNOME ou KDE, a janela não tem barra superior de título, então não entrega que você está só abrindo um terminal ou navegador web. É assim que ele já vem pré-configurado com coisas como ChatGPT ou Grok parecendo que são apps &ldquo;nativos&rdquo;, mas na verdade são só janelas de Chromium abrindo direto as URLs de cada um:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_00-54-52.png" alt="WebApps Omarchy"  loading="lazy" /></p>
<h2>Alguns TUIs que eu gosto<span class="hx:absolute hx:-mt-20" id="alguns-tuis-que-eu-gosto"></span>
    <a href="#alguns-tuis-que-eu-gosto" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Na minha opinião, já que não tem Apps com GUIs bonitas pra Linux, é melhor mesmo usar os respectivos web-apps como Canva ou ChatGPT dessa forma, mas mais interessante é ter TUIs mesmo. Como falei, hoje em dia não é difícil fazer TUIs bonitas. Pra um programador, a vantagem é não tem que lidar, primeiro com frameworks de front-end, segundo, ter que fazer deploy em algum lugar na internet.</p>
<p>TUIs são leves, usam poucos recursos na máquina, são fáceis pra qualquer programador fazer, e você pode ter um home server ou um servidor num cloud qualquer, acessar via SSH e acessar a TUI, sem ter que lidar com X11 ou RDP ou qualquer coisa mais pesada só pra ter um GUI.</p>
<p>Alguns TUIs são óbvios, no <a href="https://akitaonrails.com/2025/09/07/omarchy-2-0-lazyvim-lazyextras/"target="_blank" rel="noopener">post anterior</a> já falei de <a href="https://www.lazyvim.org/"target="_blank" rel="noopener"><strong>LazyVim</strong></a> e como ele é um editor de textos tão competente e completo quando um VS Code, usando só uma fração dos recursos da máquina. Todo mundo deve conhecer ferramentas como HTOP ou BTOP. Além dos <a href="https://github.com/jesseduffield/lazygit"target="_blank" rel="noopener">LazyGit</a> ou <a href="https://github.com/jesseduffield/lazydocker"target="_blank" rel="noopener">LazyDocker</a> também. Esses todos já vem no Omarchy.</p>
<p>Mas tem muito mais, por exemplo, <a href="https://github.com/danvergara/dblab"target="_blank" rel="noopener"><strong>dblab</strong></a> que é um TUI cliente de banco de dados como MySQL ou Postgres:</p>
<p><img src="https://github.com/danvergara/dblab/raw/main/screenshots/dblab-cover.png" alt="dblab"  loading="lazy" /></p>
<p>Outro similar é <a href="https://github.com/jorgerojas26/lazysql"target="_blank" rel="noopener">LazySQL</a>, feito em Go:</p>
<p><img src="https://github.com/jorgerojas26/lazysql/raw/main/images/lazysql.png" alt="LazySQL"  loading="lazy" /></p>
<p>Ou se você prefere ferramentas feitas em Rust, tem o <a href="https://github.com/achristmascarl/rainfrog"target="_blank" rel="noopener"><strong>rainfrog</strong></a>:</p>
<p><img src="https://github.com/achristmascarl/rainfrog/raw/main/vhs/demo.gif" alt="rainfrog"  loading="lazy" /></p>
<p>Pra quem não consegue usar CURL na linha de comando e prefere usar Postman pra chamar e explorar APIs, tem o TUI <a href="https://github.com/darrenburns/posting"target="_blank" rel="noopener"><strong>Posting</strong></a> que faz a mesma coisa:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/422760155-78359ab0-5e0c-4c0b-a60b-dce06b11bbf5.png" alt="Posting"  loading="lazy" /></p>
<p>Pra nós que rodamos muitos containers Docker, já falei do LazyDocker, mas tem também o <a href="https://github.com/mrjackwills/oxker"target="_blank" rel="noopener"><strong>oxker</strong></a>:</p>
<p><img src="https://github.com/mrjackwills/oxker/raw/main/.github/demo_01.webp" alt="oxker"  loading="lazy" /></p>
<p>Se usar Discord, tem o <a href="https://github.com/ayn2op/discordo"target="_blank" rel="noopener"><strong>discordo</strong></a>:</p>
<p><img src="https://github.com/ayn2op/discordo/raw/main/.github/preview.png" alt="Discordo"  loading="lazy" /></p>
<p>Quem faz muito ricing de Linux já teve que gravar ISOs em Pen Drives dezenas de vezes, pra isso tem o <code>dd</code> na linha de comando, mas tem um TUI mais legal que é o <a href="https://github.com/ifd3f/caligula"target="_blank" rel="noopener"><strong>Caligula</strong></a>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2025/09/09/omarchy-2-0-tuis/caligula-burning.png" alt="Caligula gravando uma imagem de disco no terminal: barra “Burning… 251.7 MB” no topo, um gráfico de velocidade de escrita oscilando até 32 MB/s ao longo de ~12 segundos, e um painel de Stats mostrando entrada my_cool.img.lz4, saída /dev/sdb, média de escrita de 20.2 MB/s e ETA de 24 segundos."  loading="lazy" /></p>
<p>Se seu SSD está ficando cheio e você não sabe onde diabos que tá ocupando tanto espaço, tem várias ferramentas pra analisar e te mostrar, um deles é o <a href="https://github.com/dundee/gdu"target="_blank" rel="noopener"><strong>gdu</strong></a>:</p>
<p><img src="https://camo.githubusercontent.com/d8fa7d2f7bdd10dce45a81c2accf26d597b300b82e01b97a1288ff2f1fe06c57/68747470733a2f2f61736369696e656d612e6f72672f612f3338323733382e737667" alt="gdu"  loading="lazy" /></p>
<p>E o último que achei legal pela nostalgia é o <a href="https://github.com/andmarti1424/sc-im"target="_blank" rel="noopener"><strong>sc-im</strong></a>, pense em Excel como TUI ou - pra nós que viemos dos anos 80 - pense Lotus 1-2-3 mas com key bindings de Vim:</p>
<p><img src="https://github.com/andmarti1424/sc-im/raw/main/screenshots/scim-plot-graph.gif?raw=true" alt="sc-im"  loading="lazy" /></p>
<p>Pra que abrir Google Spreadsheets só pra fazer um Todo list se dá pra fazer no Terminal?? TUIs são excepcionais!</p>
<p>Se quiser conhecer mais TUIs olhe o <a href="https://github.com/rothgar/awesome-tuis/?tab=readme-ov-file"target="_blank" rel="noopener"><strong>awesome-tuis</strong></a> no GitHub:</p>
<p><a href="https://github.com/rothgar/awesome-tuis/?tab=readme-ov-file"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2025/09/09/omarchy-2-0-tuis/awesome-tuis-banner.png" alt="awesome tuis"  loading="lazy" /></a>
De novo: zero necessidade de front-ends web pesadas pra coisas simples. Nada supera a simplicidade de uma linha de comando. E com bibliotecas como BubbleTea ou Gum, está muito fácil pra qualquer um fazer um app bonito. E com Hyprland e Omarchy, fica tudo integrado lado-a-lado de qualquer outro app com GUI.</p>
<p>Filosofando um pouco, a história das GUIs começa com interfaces gráficas <em>skeumorphic</em> que tentam imitar objetos do nosso dia a dia, em particular parecendo pseudo-3D, como era um Windows 95. Chegou nos anos 2000 e começaram a migrar pra interfaces <em>flat</em> ou <em>minimalistas</em>, tirando completamente o skeumorphismo, deixando tudo plano. Foi a partir do Metro do Windows 8, principalmente. E eu acho que isso converge pra estilo mais de TUI, dando uma volta completa, voltando a como era nos terminais dos anos 70. Eu particularmente acho isso fascinante.</p>
]]></content:encoded><category>omarchy</category><category>ferramentas-de-desenvolvimento</category><category>linux</category></item><item><title>Instalando Omarchy 2.0 do Zero - Anotações Pessoais</title><link>https://akitaonrails.github.io/2025/08/29/new-omarchy-2-0-install/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/08/29/new-omarchy-2-0-install/</guid><pubDate>Fri, 29 Aug 2025 16:00:00 -0300</pubDate><description>&lt;p&gt;Eu tenho recomendado o &lt;a href="https://omarchy.org/"target="_blank" rel="noopener"&gt;Omarchy 2.0&lt;/a&gt; do DHH pra qualquer um que queira um ambiente de desenvolvimento leve, bonito e totalmente equipado. É um conjunto de configurações pra Hyprland rodando em cima de Arch Linux e vem com padrões inteligentes prontos pra usar. Assista o &lt;a href="https://www.youtube.com/watch?v=TcHY0AEd2Uw"target="_blank" rel="noopener"&gt;video tour&lt;/a&gt; mostrando as principais funcionalidades.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_043020.jpg" alt="meu omarchy" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;No meu caso, eu passei 10 anos usando Manjaro GNOME como meu daily driver. Manjaro que é um derivado de Arch. A razão era a instalação mais simples e configurações pré-prontas. Mas hoje em dia, com &lt;a href="TODO"&gt;archinstall&lt;/a&gt;, acho que não vale mais a pena. Sei que existem outros derivados, mas é melhor ficar no original mesmo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu tenho recomendado o <a href="https://omarchy.org/"target="_blank" rel="noopener">Omarchy 2.0</a> do DHH pra qualquer um que queira um ambiente de desenvolvimento leve, bonito e totalmente equipado. É um conjunto de configurações pra Hyprland rodando em cima de Arch Linux e vem com padrões inteligentes prontos pra usar. Assista o <a href="https://www.youtube.com/watch?v=TcHY0AEd2Uw"target="_blank" rel="noopener">video tour</a> mostrando as principais funcionalidades.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_043020.jpg" alt="meu omarchy"  loading="lazy" /></p>
<p>No meu caso, eu passei 10 anos usando Manjaro GNOME como meu daily driver. Manjaro que é um derivado de Arch. A razão era a instalação mais simples e configurações pré-prontas. Mas hoje em dia, com <a href="TODO">archinstall</a>, acho que não vale mais a pena. Sei que existem outros derivados, mas é melhor ficar no original mesmo.</p>
<p>Pra quem é de Ubuntu ou derivados de Debian, Fedora, etc, vai ter uma pequena curva de aprendizado no gerenciador de pacotes, Pacman e Yay em vez de Apt ou Dnf, mas quando entenderem o AUR, nunca mais vão querer outra coisa. Tem literalmente tudo que você precisa, sem ter que lidar com repositórios de terceiros.</p>
<p>Demorei pra testar o Omarchy porque eu já usava Hyprland antes do Omarchy ter sido criado. O DHH já tinha o Omakub antes, que era um conjunto de scripts pra customizar um Ubuntu pra ser um bom ambiente de desenvolvimento. Eu gostava dele mas como preferia Manjaro, fiz um fork chamado Omakub-MJ. Depois disso resolvi desinstalar o GNOME e GDM e instalar um outro conjunto de scripts chamado <a href="https://www.ml4w.com/"target="_blank" rel="noopener">ML4W</a>, que deixa o Hyprland bem bonito também. Então o Omarchy não tinha tanta novidade, pra mim.</p>
<p>Só que eu uso o mesmo Manjaro já faz anos. Tinha um monte de configs e scripts velhos misturados. Meu Manjaro GNOME já não tinha mais GNOME. Meu Hyprland era Frankenstein. A única barreira era minha configuração de <strong>QEMU/Libvirt</strong> pra rodar Windows com <a href="https://akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt/"target="_blank" rel="noopener">GPU passthrough</a>, que era o que eu usava pra editar videos quando ainda postava no meu <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">canal do YouTube</a>.</p>
<p>Mas como parei de editar videos, esse Windows também não é mais tão importante. Eu tinha outra máquina virtual Windows com GPU passthrough pra games. Mas atualmente tenho um mini-PC separado com Windows que uso só pra games. Também tinha outra VM com outro Linux só pros meus experimentos de I.A., mas desde então migrei pra rodar nativo isolando tudo com Docker. Ou seja, minhas VMs já não são mais importantes. Veja meus blog posts anteriores pra saber sobre I.A. em Docker.</p>
<p>Com o lançamento do Omarchy 2.0, parece uma boa hora pra reinstalar Arch do zero com Omarchy por cima e recomeçar com tudo limpo e bonito.</p>
<h2>1 - Meu Setup<span class="hx:absolute hx:-mt-20" id="1---meu-setup"></span>
    <a href="#1---meu-setup" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250829_113734.jpg" alt="meu PC"  loading="lazy" /></p>
<p>Cada caso vai ser diferente. Este post tem como principal função servir como minha referência pessoal pro futuro, se eu precisar reinstalar tudo de novo.</p>
<p>Não, não vou usar <strong>NixOS</strong>. Eu realmente não acho que compensa. Só vale a pena se você é administrador de sala de computador ou infraestrutura de servidores e seu dia a dia é ter que reconstruir setups idênticos de maneira confiável. Pra um usuário normal, que só vai reinstalar a cada 2 ou 3 anos, não vale nem um pouco a curva de aprendizado nem o trabalho de manutenção. É perda de tempo. Eu vou gastar dias aprendendo agora, terminar o setup, passar 2 anos e já vou ter esquecido tudo. Arch Linux sozinho já é estável o suficiente pra eu não precisar disso.</p>
<p>Meu setup ainda é o mesmo da época que estava editando videos pro canal. É um <strong>Ryzen 9 7850X3D</strong> com 16 cores, 32 threads, pico de 5Ghz. Ainda é um excelente CPU que tem poder de sobra pros meus usos, por isso não me vejo querendo atualizar tão cedo. São raras as vezes que eu consigo saturar tudo isso de core ao mesmo tempo nessas velocidades. Melhor ainda: ele tem GPU integrada AMD que torna instalar Linux mil vezes mais fácil do que se eu tivesse NVIDIA.</p>
<p>Mas, eu também tenho uma GPU discreta, separada, a <strong>NVIDIA RTX 4090</strong> (não, não preciso de uma 5090, tem o mesmo tanto de VRAM). Ela vai ficar pra tarefas secundárias e não pra carregar meu ambiente gráfico. Coisas como render de Cycles no Blender, ou rodar LLMs com ollama da vida. Antes eu usava GPU passthrough pra uma VM em QEMU/Libvirt pra rodar Windows em parelelo ao Linux. Tanto pra editar video quanto pra games.</p>
<p>Como não edito mais videos, não preciso desse setup complicado então nem vou me preocupar mais em instalar Libvirt de volta. Se eu precisar de Windows, vou deixar em dual boot. Se precisar editar video, hoje em dia temos DaVinci Resolve Studio que roda nativo em Linux (mil vezes melhor que Adobe Premiere). Pra games eu tenho um mini PC separado com uma RTX 4060 ligado na minha TV. Melhor coisa é ter um PC totalmente separado pra games.</p>
<p>Então este setup é pra ser um Linux puro, com acesso ao meu <strong>NAS Synology DS 1821+</strong> de 100TB. Única coisa extra que vou precisar é configurar mounts de NFS, como já detalhei <a href="https://akitaonrails.com/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/"target="_blank" rel="noopener">neste outro post</a>.</p>
<p>Um detalhe é que eu tenho <strong>4 NVMEs</strong> neste PC. 2TB pro meu Linux antigo. 2 TB pro dual boot/VM de Windows. 2 TB só como drive de Steam e 1 TB pra coisas aleatórias. Como vou limar a VM de Steam, também não preciso mais dedicar um NVME inteiro só pra isso. Em vez disso vou fazer o diretório &ldquo;/home&rdquo; do novo Linux ser esses 2 TB inteiro.</p>
<p>Isso vai me permitir não me preocupar mais com Docker enchendo meu drive e travando meu sistema inteiro com falta de espaço. Da última vez eu tinha &ldquo;resolvido&rdquo; isso fazendo um <a href="https://akitaonrails.com/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/"target="_blank" rel="noopener">drive remoto com iSCSI</a> no meu NAS (que tem espaço infinito). Faz tirando minha VM de Steam vou ganhar 2TB então resolvo o problema localmente e com menos configurações.</p>
<p>RAM eu tenho de sobra, <strong>96GB de DDR5</strong>. Com isso também não preciso me preocupar com partição de swap. Swap só é importante caso você tenha menos de 32GB de RAM, se estiver rodando muito Docker e ficar batendo no máximo de consumo de RAM muito rápido. Ou, se estiver usando notebook e quer ter a opção de hibernação (poder gravar a RAM em disco, dar power off completo na máquina e conseguir voltar depois exatamente de onde parou - que é diferente de suspender, que ainda usa energia).</p>
<p>Se quiser hibernar precisa ter um pouco mais do que o tamanho da sua RAM. Então se tem 32GB é bom deixar uma margem, uns 34GB ou mais de Swap. Como meu PC é desktop em casa, não preciso de hibernação, só ativar <strong>ZRAM</strong> pros raros casos onde tiver um pico acima de 96GB (que é ultra raro). Fora que é mais fácil eu simplesmente subir pra 128GB de RAM se precisar muito. Swap pra compensar RAM só vale a pena em notebooks que já não tem espaço pra mais RAM de verdade.</p>
<h2>2 - Por que Omarchy?<span class="hx:absolute hx:-mt-20" id="2---por-que-omarchy"></span>
    <a href="#2---por-que-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Como falei, já sou usuário de Arch faz uma década e não vejo nenhum repositório de pacotes maior e mais completo do que o <strong>AUR</strong>. O único outro repositório que compete, de fato, é do NixOS. Mas como Nix está descartado pro meu caso de uso, o AUR continua imbatível.</p>
<p>Arch tem Pacman como gerenciador de pacotes. Pra acessar o AUR precisa de um segundo gerenciador. Existem vários como os antigos Pacaur, Paru e vários outros. Mas o melhor, na minha opinião, é o Yay. Ele tem exatamente a mesma sintaxe que o Pacman e substitui o Pacman. Na prática só usamos Yay pra tudo.</p>
<p>Pra procurar pacotes por nome é só fazer <code>yay -Ss spotify</code>.</p>
<p>Pra remover pacotes é só fazer <code>yay -Rns spotify</code>.</p>
<p>Pra atualizar todos os pacotes do sistema é só fazer <code>yay -Syu</code></p>
<p>Pra remover pacotes órfãos que sobram ao longo do tempo é só fazer <code>sudo pacman -Rns $(pacman -Qdtq)</code></p>
<p>E esses são os comandos que eu mais uso 99% do tempo.</p>
<p>Eu já usei tudo que é tipo de desktop manager, seja GNOME (que tem mais comportamento de Mac), KDE (que tem mais comportamento de Windows pra power users), XFCE (que baseado em GTK e é mais leve), Cinnamon (também baseado em GTK), i3 (que é sistema de tiling windows), Sway (como i3, também tiling mas se não me engano já foi feito pensado em Wayland).</p>
<p>Na prática, todo mundo usa aplicativos de dois jeitos: janela em tela cheia ou 2 janelas verticais dividindo o monitor no meio (editor de um lado, navegador do outro), ou 3 janelas (um editor vertical ocupando metade do monitor, um navegador ocupando 1/4 e um terminal ocupando outro 1/4). Daí organizamos mais apps abertos em desktops virtuais/workspaces. Esse é o jeito mais fácil de trabalhar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_12-12-58.png" alt="layout ideal"  loading="lazy" /></p>
<p>Significa que poder arrastar janelas flutuantes é perda de tempo. Eu prefiro que as janelas já abram dimensionadas num desses layouts que falei acima. Dá pra configurar GNOME ou KDE pra fazer isso. Mas tem cheiro de gambiarra. Em vez disso realmente é melhor usar i3 ou Sway.</p>
<p>Deixando as <a href="https://x.com/LundukeJournal/status/1942389827518808384"target="_blank" rel="noopener">controvérsias</a> de lado (sim, tenho um pouco de pé atrás com Wayland e gosto da existência de XLibre). Mas é um fato que <strong>X11</strong> ficou pra trás e é uma arquitetura muito velha e complicada. Wayland de fato tem um &ldquo;appeal&rdquo; melhor, apesar de ter demorado bastante pra arredondar coisas básicas como compartilhamento de tela (OBS, Zoom). Mas já faz alguns anos que uso Wayland sem ter dores de cabeça. Hoje em dia eu diria que está bem estável. Eu já usava GNOME sobre Wayland faz muito tempo. Eu não deixo de usar coisas por ter pé atrás: com tecnologia vou ser sempre absolutamente promíscuo e usar tudo.</p>
<p>Sabendo que vou usar <strong>Wayland</strong> e sabendo que quero layouts automáticos de janelas, e não preciso de janelas flutuantes, o melhor é um desktop manager com sistema de <strong>tiling windows</strong> (tiles são &ldquo;azulejos&rdquo; e estamos falando de azulejos ficarem lado a lado e nunca um em cima do outro), que organize as janelas lado a lado de forma inteligente e automática.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_12-19-25.png" alt="janelas lado a lado"  loading="lazy" /></p>
<p>Nesse caso só temos 2 opções: <strong>Sway</strong> ou <strong>Hyprland</strong> (qualquer uma das duas é boa). O Hyprland, instalado do zero, é totalmente pelado, não tem absolutamente nada. Precisamos configurar tudo através de arquivos em <code>.config/hypr</code>. É até simples, mas dá trabalho.</p>
<p>A única opção boa que se tinha pra ter um Hyprland pré-configurado e bem feito era o <strong>ML4W</strong>, que eu já usava. Mas agora temos o Omarchy que justamente instala e pré-configura tudo pra funcionar redondo no Hyprland. Essa é a melhor opção que temos hoje em dia.</p>
<p>Além de já configurar Hyprland pra funcionar com <strong>Waybar</strong>, Rofi e outros componentes, ele tem um sistema próprio pra trocar temas e estilos e deixar o sistema com o <em>&ldquo;look and feel&rdquo;</em> que quiser (e todos que já vem nele são super bonitos), mas também já instala coisas que eu uso faz tempo como Neovim com o pacote <strong>LazyVim</strong> (na minha opinião, o jeito mais fácil de fazer o Neovim não deixar nada a desejar comparado a um VS Code), <strong>Mise</strong> (a melhor forma de organizar ambientes de desenvolvimento pra conseguir ter múltiplas versões da sua linguagem de programação favorita).</p>
<p>Se estiver instalando uma distro do zero, as únicas duas coisas que precisa ter são Mise e LazyVim. Além de Docker e LazyDocker. É o suficiente pra começar um ambiente de desenvolvimento produtivo. E o Omarchy já trás tudo isso pré-configurado. Ninguém &ldquo;precisa&rdquo; do Omarchy, mas ele já trás tudo integrado e bonito. Então, pra que me dar ao trabalho de fazer tudo do zero pra ficar quase igual?</p>
<p>Além disso, isso é Linux. Não gostou de 1 dos componentes? Só trocar.</p>
<h2>3 - Faça Backup<span class="hx:absolute hx:-mt-20" id="3---faça-backup"></span>
    <a href="#3---fa%c3%a7a-backup" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A grande maioria de tudo que tenho já fica no servidor remoto, meu NAS Synology de 100TB. Isso já facilita muito. Mesmo assim ainda tem bastante coisa localmente no meu PC, então a primeira coisa a fazer é copiar tudo pro NAS. Se não tiver um NAS, então um HD externo. Qualquer coisa server, o importante é garantir que não vai perder nada antes de instalar um novo OS do zero.</p>
<p>Não esquecer diretórios como <code>.config</code> ou <code>.local</code>. Melhor fazer um <code>rsync -avz --progress .config /mnt/nas/backup/</code>, por exemplo, e copiar tudo. Mesmo o que achar que não vai precisar. Melhor copiar a mais do que faltar coisa. Via de regra, o ideal é copiar tudo que estiver na sua <code>$HOME</code> pra um drive externo ou NAS.</p>
<p>Só pra garantir que não vou esquecer nada importante, também vou gravar uma lista de todos os pacotes que tenho instalado no meu sistema antigo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -Q &gt; pacotes.txt
flatpak list &gt; flatpak.txt</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Também garantir que não vou perder configs velhas de serviços que eu fiz no systemd então também copiar tudo de <code>/etc/systemd/system</code>. Vale também gravar coisas como <code>/etc/fstab</code>. Também não esquecer de fazer backup de suas chaves de SSH com <code>tar cvfz ssh.tgz ~/.ssh</code>!!</p>
<p>Se como eu, usava QEMU/Libvirt pra VMs, é bom salvar as configs das máquinas virtuais que usava. Normalmente a ferramenta pra gerenciar VMs é o virsh então eu só fiz:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo virsh list --all
sudo virsh dumpxml &lt;vmname&gt; &gt; &lt;vmname&gt;.xml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se eu quiser recriar as máquinas no futuro, já vai estar tudo no backup. No meu caso mantenho todos os drives virtuais no meu NAS, mas caso os seus estejam locais, também precisa salvar esses arquivos que ficam em <code>/var/lib/libvirt/images/</code></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>rsync -avh --progress /var/lib/libvirt/images/ /backup/libvirt-images/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>4 - É difícil instalar Arch?<span class="hx:absolute hx:-mt-20" id="4---é-difícil-instalar-arch"></span>
    <a href="#4---%c3%a9-dif%c3%adcil-instalar-arch" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Arch, menos que um Gentoo, sempre teve reputação de ser a distro Linux mais brutal pra um iniciante instalar. Isso não é mentira, mas a verdade é que Arch é simultaneamente a distro mais difícil e também a mais fácil.</p>
<p>Baixe <a href="https://archlinux.org/download/"target="_blank" rel="noopener">a ISO oficial</a> e grave num pen drive (em Windows, usando <strong>Rufus</strong> ou Balena Etcher). Agora aperte F12 (depende da sua BIOS) ou configure na BIOS direto pra bootar do pen drive. Fazendo isso ele carrega o live environment do Arch que é literalmente cair numa linha de comando. Sem mais nenhuma ajuda.</p>
<p>A partir deste ponto tem que seguir o <a href="https://wiki.archlinux.org/title/Installation_guide"target="_blank" rel="noopener">Wiki de Instalação</a> do excelente ArchWiki e aprender a instalar um Linux do absoluto zero usando somente linha de comando, passo a passo, na tentativa e erro (mais errando do que acertando) e, ao final, você vai ter um &ldquo;feeling&rdquo; muito melhor de como uma distro Linux funciona de verdade.</p>
<p>Eu não fiz video de Arch mas fiz videos de <a href="https://www.youtube.com/watch?v=iQkBbRPkASo"target="_blank" rel="noopener">Slackware</a> pra explicar como um gerenciador de pacotes funciona. Também fiz um video de <a href="https://www.youtube.com/watch?v=cSyTjCUFx2A"target="_blank" rel="noopener">Gentoo</a> pra mostrar como uma distro que compila tudo do zero funciona. E fiz um video de como o <a href="https://www.youtube.com/watch?v=5F6BbhgvFOE"target="_blank" rel="noopener">Boot do Linux Funciona</a> pra entender onde fica a kernel e como ela é estruturada. Mas não fiz video de Arch porque sempre deixei como exercício pra iniciantes.</p>
<p>O jeito manual é viável, mas se está acostumado a um instalador gráfico, como o <strong>Calamaris</strong>, que tem na maioria das distros mais populares como Ubuntu ou Fedora, você vai ficar totalmente perdido. Mas aqui vem o bônus: não precisa passar por tudo isso pra ter um Arch Linux na sua máquina.</p>
<p>Pra isso existe o <a href="https://wiki.archlinux.org/title/Archinstall"target="_blank" rel="noopener"><strong>archinstall</strong></a>.</p>
<p>Quando bootar o pen drive do Arch e cair na linha de comando, basta digitar archinstall e pronto, só seguir o passo a passo que ele dá - que eu considero ainda mais simples do que Calamaris - e ele vai instalar e configurar tudo de forma automática pra você, do mesmo jeito que é instalar Fedora ou qualquer outra distro. Sério, não é pra demorar mais que 15 minutos.</p>
<p>O archinstall tem esta cara:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_045255.jpg" alt="archinstall"  loading="lazy" /></p>
<p>Basta entrar em cada um dos ítens da esquerda e preencher suas escolhas. Configure coisas como layout de teclado (normalmente eu escolho &lsquo;us-acentos&rsquo;). Edite o hostname da sua máquina. Escolha &ldquo;NetworkManager&rdquo; em configuração de rede (ele vai configurar tanto ethernet quanto wifi pra você), adicionar suporte a Bluetooth e Audio (escolha &ldquo;Pipewire&rdquo;). Escolha o tipo &ldquo;desktop&rdquo; em vez de &ldquo;minimal&rdquo; pra já ter os principais pacotes necessários pra um desktop. Escolha a timezone (&ldquo;America/Sao_Paulo&rdquo; no meu caso).</p>
<p>Recomendo também escolher a opção de Hyprland acho que em Applications ou Adicional Packages. Ele já vai instalar o SDDM pra ter um login gráfico.</p>
<p>Só tem duas opções que precisa de mais atenção: pra bootloader, muitos vão ficar tentados a usar um <strong>Limine</strong>, que é mais moderno e leve. Mas eu recomendo ainda usar <strong>GRUB</strong>, já explico porque.</p>
<p>A segunda opção importante é &ldquo;Disk Configuration&rdquo;. Não se preocupe, diferente de fdisk, ele é super intuitivo. Basta escolher o drive correto onde quer instalar (se tiver dual boot, cuidado pra não apagar seu Windows). Daí deixe ele fazer o layout automático das partições (só edite manualmente se tiver alguma necessidade muito especial). O mais importante, escolha <a href="https://wiki.archlinux.org/title/Btrfs"target="_blank" rel="noopener"><strong>BTRFS</strong></a> em vez do tradicional ext4.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_045312.jpg" alt="btrfs"  loading="lazy" /></p>
<p>Todo Linux sempre formata suas partições Linux com ext4, que é o mais antigo, mais estável e mais bem documentado file system. Hoje em dia eu recomendo BTRFS, é muito mais moderno, mais seguro (tem sistemas de proteção contra corrupção melhores), usa menos espaço (aprenda sobre CoW - <strong>Copy on Write</strong>), e o principal: tem suporte a snapshots baratos (pense o Apple Time Machine, só que melhor). Tem a maioria das funcionalidades de um sistema de arquivos dedicado pra servidor de arquivos mas muito mais simples e intuitivo de usar do que o monstro que é o ZFS (não recomendo pra usuários normais).</p>
<p>Ele tem algumas necessidades de manutenção que é bom ler no Wiki pra aprender. Mas no geral é plug and play, instala e esquece. Nas próximas seções vou mostrar algumas coisas que precisa saber, mas vai por mim: escolha BTRFS.</p>
<p>Eu sei que o Omarchy 2.0 tem opção de uma <a href="https://iso.omarchy.org/omarchy-online.iso"target="_blank" rel="noopener">ISO pré-configurada</a> que imagino que seja o Arch normal com archinstall e já o Omarchy por cima tudo de uma só vez. Mas eu não sei se eles permitem essa escolha por isso preferi ir direto no Arch puro antes. No final o efeito é o mesmo. Depois comentem abaixo se a ISO do Omarchy também tem as mesmas opções. Só garanta que no final tenha BTRFS. O futuro você vai te agradecer.</p>
<h2>5 - HOME separada<span class="hx:absolute hx:-mt-20" id="5---home-separada"></span>
    <a href="#5---home-separada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Como expliquei antes, eu usava um dos meus 4 NVMEs pra ser o drive de Linux. Mas agora liberei outro NVME e quero mover minha $HOME pra lá e ter 2 TB dedicado só pra user space.</p>
<p>Não vou explicar como particionar e formatar esse outro drive. É simples: basta usar <a href="https://wiki.archlinux.org/title/Fdisk"target="_blank" rel="noopener">fdisk</a> ou depois instalar <strong>Gparted</strong> que é a versão gráfica da ferramenta <a href="https://wiki.archlinux.org/title/Parted"target="_blank" rel="noopener">parted</a> que dá pra usar pela linha de comando. São várias opções. O importante é só zerar minha partição antiga Windows e formatar também com BTRFS.</p>
<p>Cada um vai ter nomes de dispositivos e partições diferentes do meu. Pra saber quais são os seus use o comando <code>lsblk</code>. Veja o meu exemplo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_12-54-42.png" alt="lsblk"  loading="lazy" /></p>
<p>No meu exemplo <code>nvme1n1</code> é o drive de 1TB pra coisas aleatórias. <code>nvme2n1</code> é meu novo Arch Linux. <code>nvme0n1</code> é o drive de dual boot de Windows antigo. <code>nvme3n1</code> é o drive de Steam que formatei com BTRFS pra virar meu novo drive de HOME.</p>
<p>Se não entendeu, HOME é o diretório <code>/home/akitaonrails</code>. Ele vai ficar num drive separado. Depois que o archinstall terminar e eu bootar no novo Linux, é só fazer o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mount -t btrfs /dev/nvme3n1p1 /mnt
sudo btrfs subvolume create /mnt/@home
rsync -aAXHv /home/ /mnt/@home/
sudo umount /mnt</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai montar a nova partição, daí usamos o comando &ldquo;btrfs&rdquo; pra criar um novo sub-volume chamado &ldquo;@home&rdquo; (de novo, leia o Wiki de BTRFS pra entender conceitos como sub-volumes).</p>
<p>Os comandos acima criam o sub-volume e copiam tudo que estava na HOME antiga pra nova. Agora precisamos que, no boot, o <code>/home</code> seja montado nessa nova partição. Pra isso precisamos saber o UUID dessa partição:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~ ❯ sudo blkid /dev/nvme3n1p1
/dev/nvme3n1p1: LABEL=&#34;HOME&#34; UUID=&#34;84e9a1b7-a30e-...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O comando <code>blkid</code> devolve o UUID único de cada partição e, com isso, podemos editar o arquivo <code>/etc/fstab</code> usando <code>sudo nvim</code> ou <code>sudo nano</code> ou o editor que preferir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># /etc/fstab
...
# /dev/nvme2n1p2
# UUID=da3aea43-...-aeaf8c7224d7 /home      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@home 0 0

# /dev/nvme3n1p1
UUID=84e9a1b7-...-7a6a17fd0395 /home      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@home 0 0
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja como comentei a linha antiga que montava o <code>/home</code> e adicionei a nova linha com o novo UUID. O resto é tudo igual. E é só isso, basta bootar e a partir de agora meu <code>/home</code> vai montar no novo drive. Agora eu tenho 2 TB dedicados só pros meus downloads lixo kkkkkk 🤣</p>
<p>Mas não só isso, também tenho espaço pra coisas grandes como imagens de Docker ou modelos de LLM, sem correr o risco de encher o drive do sistema operacional e correr o risco dele não bootar mais por falta de espaço - como já me aconteceu várias vezes no passado. Sempre lembre de dar manutenção em Docker e LLMs e apagar coisas velhas.</p>
<p>Falando nisso, BTRFS tem suporte a snapshots. E um dos erros básicos é esquecer que diretórios de Docker ou LLM podem acabar nos snapshots e ficar ocupando espaço sem você saber. Pra evitar isso é melhor marcar esses diretórios pra não entrarem nos snapshots. Pra isso temos que dizer ao BTRFS pra ignorar esses diretórios.</p>
<p>A forma correta é criando subvolumes separados pra eles. Pra isso vou montar aquela minha mesma partição no drive separado onde montei minha $HOME:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~ ❯ sudo mkdir /mnt/temp
~ ❯ sudo mount -o subvolid=5 -t btrfs /dev/nvme3n1p1 /mnt/temp

~ ❯ ls /mnt/temp
 @home   timeshift-btrfs

~ ❯ sudo btrfs subvolume create /mnt/temp/@docker
Create subvolume &#39;/mnt/temp/@docker&#39;

~ ❯ sudo btrfs subvolume create /mnt/temp/@ollama
Create subvolume &#39;/mnt/temp/@ollama&#39;

~ ❯ sudo umount /mnt/temp</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora é só editar o <code>/etc/fstab</code> pra montar corretamente igual fizemos com a HOME:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># /etc/fstab
...
# /dev/nvme3n1p1
UUID=84e9a1b7-...-7a6a17fd0395 /home      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@home 0 0
UUID=84e9a1b7-...-7a6a17fd0395 /var/lib/docker      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@docker 0 0
UUID=84e9a1b7-...-7a6a17fd0395 /var/lib/ollama      btrfs      rw,relatime,ssd,discard=async,space_cache=v2,subvol=/@ollama 0 0
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto, subvolumes limpos. Mais importante no meu caso: movi diretórios que tem tendência a crescer sem controle pra fora da partição do sistema operacional, tornando mais difícil acabar com um sistema inacessível por causa de falta de espaço. Faça a mesma coisa com outros diretórios parecidos com coisas como Kubernetes por exemplo. Qualquer coisa que fique baixando arquivos grandes, como imagens de sistemas.</p>
<p>Agora é só montar e trazer as coisas de volta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># toda vez que edita o fstab, rode isso:
sudo systemctl daemon-reload

# crie os mount points
sudo mkdir /var/lib/docker
sudo mkdir /var/lib/ollama

# monte
sudo mount /var/lib/docker
sudo mount /var/lib/ollama

# copiar o conteúdo do diretório antigo de volta
sudo rsync -aHAX --inplace /var/lib/docker.old/ /var/lib/docker/
sudo systemctl start docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Além de gerenciamento de espaço, qual a outra vantagem de fazer subvolumes separados? Porque a próxima seção vai ser pra tornar o sistema ainda mais robusto adicionando capacidade de snapshots e rollback pra snapshots do passado. E o ideal é tirar arquivos gigantes de snapshots que vão ficar no passado. Imagens de Docker são fáceis de recuperar: só dar <code>docker pull</code> de novo, não precisa fazer backup.</p>
<h2>6 - Instalar Omarchy<span class="hx:absolute hx:-mt-20" id="6---instalar-omarchy"></span>
    <a href="#6---instalar-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa é a parte mais fácil. Quando terminar de instalar com archinstall, se escolheu a opção de Hyprland, vai aparecer o SDDM pra fazer login. E quando entrar você vai ficar chocado com quão <strong>cru</strong> é o Hyprland. Não dá pra fazer basicamente nada.</p>
<p>Pra isso não virar um dia inteiro de configuração na mão, usaremos o Omarchy. Clique &ldquo;Super Enter&rdquo; pra abrir o terminal e não ligue pra quão feio as coisas estão agora. Vai melhorar.</p>
<p>No terminal só faça isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>curl -fsSL https://omarchy.org/install | bash</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Só seguir as instruções na tela e deixar ele fazer o resto. E pronto, acabou, não tem próximo passo.</p>
<p>Recomendo MUITO que leia o <a href="https://learn.omacom.io/2/the-omarchy-manual"target="_blank" rel="noopener">MANUAL ONLINE DO OMARCHY</a> que o DHH fez. É muito limpo, simples e direto. Todas as combinações de tecla, por exemplo, estão na <a href="https://learn.omacom.io/2/the-omarchy-manual/53/hotkeys"target="_blank" rel="noopener">seção HOTKEYS</a>. Gaste uma hora lendo o básico pra conseguir operar. Mas a essa altura você já tem praticamente tudo que precisa: Alacritty como terminal, Chromium como navegador (pode instalar Brave se preferir, como é meu caso).</p>
<p>&ldquo;Super Alt Space&rdquo; abre o menu principal do Omarchy. &ldquo;Super Space&rdquo; abre o Rofi pra digitar o nome do app que quer abrir. &ldquo;Super Enter&rdquo; abre o terminal. &ldquo;Super B&rdquo; abre o navegador e assim por diante.</p>
<h2>7 - Customizando Omarchy<span class="hx:absolute hx:-mt-20" id="7---customizando-omarchy"></span>
    <a href="#7---customizando-omarchy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Omarchy é um Hyprland customizado. Podemos editar o que quisermos diretamente em <code>~/.config/hypr</code>. E como eu estava acostumado com ML4W, resolvi mexer um pouco pra ficar bom pro meu setup. Seu setup vai variar dependendo das suas necessidades.</p>
<p>Logo que terminei de instalar Omarchy e tentei usar Gparted pra particionar aquele meu segundo NVME pra HOME, ele deu problema de autenticação. Não lembro se esta foi a solução mas é bom garantir que tenha Polkit instalado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># make sure polkit &#43; agent exist
sudo pacman -S --needed polkit polkit-gnome xorg-xhost

# Hyprland: export full env to systemd user bus once
dbus-update-activation-environment --systemd --all</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Voltando, a primeira coisa que eu preciso resolver é que eu tenho 2 monitores. Um Asus 32&quot; 1440p 144fps como principal e outro Samsung 24&quot; 4K 60fps na vertical do lado esquerdo. A forma de configurar isso no Hyprland é editando um arquivo como <code>~/.config/hypr/monitors.conf</code> que normalmente é carregado pelo <code>~/.config/hypr/hyprland.conf</code>.</p>
<p>Em vez de fazer isso manualmente, prefiro instalar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S nwg-displays</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_13-46-04.png" alt="nwg-displays"  loading="lazy" /></p>
<p>Aqui eu configuro coisas como transform 90 graus pra girar o monitor vertical, scaling de resolução, suporte a 10-bits de cor, etc.</p>
<p>Ele vai salvar dois arquivos: <code>~/.config/monitors.conf</code> e <code>~/.config/workspaces.conf</code>. O Omarchy não carrega esse segundo arquivo então temos que editar em <code>~/.config/hypr/hyprland.conf</code> pra ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
# Change your own setup in these files (and overwrite any settings from defaults!)
source = ~/.config/hypr/monitors.conf
source = ~/.config/hypr/workspaces.conf`
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Já aproveitei pra adicionar algumas configurações extras nesse arquivo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
# animation
animations {
  enabled = yes
  bezier = easeOutQuint, 0.23, 1, 0.32, 1
  animation = workspaces, 1, 4, easeOutQuint, slide
  # optional extras:
  animation = specialWorkspace, 1, 4, easeOutQuint, slide
  animation = windows, 1, 4, easeOutQuint, popin 80%
  animation = fade, 1, 4, easeOutQuint
}

xwayland {
    force_zero_scaling = false # careful with mixing scaling wayland and x11
}

input {
  numlock_by_default = true
}

# dialogs/splash/utility ONLY
windowrulev2 = float,  xwayland:1, windowtype:^(dialog|splash|utility)$
windowrulev2 = center, xwayland:1, windowtype:^(dialog|splash|utility)$

# never center menus/tooltips
windowrulev2 = tile,   xwayland:1, windowtype:^(popup_menu|dropdown_menu|tooltip)$</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem gente que não gosta mas eu gosto de ter uma animação rápida quando mudo de um workspace pra outro, por isso adicionei o bloco <code>animations</code>.</p>
<p>Quando se usa aplicativos feitos pra X11, eles vão rodar em cima do XWayland. Uma coisa chata é que alguns deles abrem &ldquo;dialog boxes&rdquo;, que são janelas flutuantes. E eles podem acabar em lugares difíceis de clicar, especialmente num setup com múltiplos monitores. Por isso adicionei blocos como <code>xwayland</code> e regras <code>windowrulev2</code> pra forçar esses dialog boxes a sempre aparecerem centralizados na tela.</p>
<p>FInalmente, eu tenho um teclado numpad da 8bitdo conectado via Bluetooth, mas ele não liga com numlock carregado. Pra corrigir isso tem esse bloco <code>input</code>.</p>
<p>Voltando ao layout de monitor. Depois que o nwg-displays gerou os arquivos, ainda precisei modificar um pouco por meu caso, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># ~/.config/monitors.conf
# Generated by nwg-displays on 2025-08-28 at 14:30:17. Do not edit manually.

#monitor=HDMI-A-3,2560x1440@143.97,1080x407,1.25,bitdepth,10
#monitor=DP-5,3840x2160@60.0,0x0,1.875,bitdepth,10
#monitor=DP-5,transform,1
#monitor=DP-1,0x0@60.0,-1x-1,1.0
#monitor=DP-1,disable

monitor=HDMI-A-3,2560x1440@143.97,2813x948,1.0,bitdepth,10
monitor=DP-5,3840x2160@60.0,1373x350,1.5,bitdepth,10
monitor=DP-5,transform,1
monitor = DP-1,disable</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O arquivo de monitor não precisei mexer nada, na real, só lembrar de desabilitar esse DP-1 que aparece no meu monitor principal (acho que é a DisplayPort dele, mas eu uso HDMI, então tem que desabilitar a DP-1).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># ~/.config/workspaces.conf
# Generated by nwg-displays on 2025-08-28 at 13:07:02. Do not edit manually.

workspace=5,monitor:DP-5,persistent:true
workspace=1,monitor:HDMI-A-3,persistent:true
workspace=2,monitor:HDMI-A-3,persistent:true
workspace=3,monitor:HDMI-A-3,persistent:true
workspace=4,monitor:HDMI-A-3,persistent:true</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esta também não precisei mudar muito, no próprio nwg-displays eu disse quais workspaces eu quero em cada monitor. Na minha preferência pessoal prefiro deixar o monitor vertical somente com a workspace 5 e os outros no monitor principal. A diferença aqui foi que adicionei essas opções <code>persistent:true</code> em todas, pra garantir que vai tudo funcionar direito com as workspaces &ldquo;pinadas&rdquo; nos lugares corretos. Workspaces em Hyprland são bem dinâmicos, vale ler a documentação depois, caso queira casos mais complicados.</p>
<p>Isso resolve meu setup de dois monitors do jeito que eu prefiro. Experimente mexer em coisas como scaling até ficar tudo ok pro seu setup.</p>
<p>Na sequência eu precisava resolver as hotkeys (key bindings). Eu estou acostumado com algumas combinações que vinha no ML4W então resolvi copiar de lá direto em <code>~/.config/hypr/bindings.conf</code>. Primeiro, trocar o chromium pelo Brave:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Application bindings
$terminal = uwsm app -- alacritty
$browser = brave # omarchy-launch-browser
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí adicionei um tanto de binds no final do mesmo arquivo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>unbind = SUPER, V
bind = SUPER, V, swapsplit
bind = SUPER, M, fullscreen, 1                                                           # Maximize Window
bind = SUPER, T, togglefloating                                                          # Toggle active windows into floating mode
bind = SUPER SHIFT, right, resizeactive, 100 0                                           # Increase window width with keyboard
bind = SUPER SHIFT, left, resizeactive, -100 0                                           # Reduce window width with keyboard
bind = SUPER SHIFT, down, resizeactive, 0 100                                            # Increase window height with keyboard
bind = SUPER SHIFT, up, resizeactive, 0 -100                                             # Reduce window height with keyboard

# Display
bind = SUPER SHIFT, mouse_down, exec, hyprctl keyword cursor:zoom_factor $(awk &#34;BEGIN {print $(hyprctl getoption cursor:zoom_factor | grep &#39;float:&#39; | awk &#39;{print $2}&#39;) &#43; 0.5}&#34;) # Increase display zoom
bind = SUPER SHIFT, mouse_up, exec, hyprctl keyword cursor:zoom_factor $(awk &#34;BEGIN {print $(hyprctl getoption cursor:zoom_factor | grep &#39;float:&#39; | awk &#39;{print $2}&#39;) - 0.5}&#34;) # Decrease display zoom
bind = SUPER SHIFT, Z, exec, hyprctl keyword cursor:zoom_factor 1 # Reset display zoom

# Workspaces
bind = SUPER, 1, workspace, 1 on HDMI-A-3 # Open workspace 1
bind = SUPER, 2, workspace, 2 on HDMI-A-3  # Open workspace 2
bind = SUPER, 3, workspace, 3 on HDMI-A-3 # Open workspace 3
bind = SUPER, 4, workspace, 4 on HDMI-A-3 # Open workspace 4
bind = SUPER, 5, workspace, 5 on DP-5 # Open workspace 5

bind = SUPER SHIFT, 1, movetoworkspace, 1  # Move active window to workspace 1
bind = SUPER SHIFT, 2, movetoworkspace, 2  # Move active window to workspace 2
bind = SUPER SHIFT, 3, movetoworkspace, 3  # Move active window to workspace 3
bind = SUPER SHIFT, 4, movetoworkspace, 4  # Move active window to workspace 4
bind = SUPER SHIFT, 5, movetoworkspace, 5  # Move active window to workspace 5

bind = SUPER, Tab, workspace, m&#43;1       # Open next workspace
bind = SUPER SHIFT, Tab, workspace, m-1 # Open previous workspace

bind = SUPER CTRL, 1, exec, $HOME/.config/hypr/moveTo.sh 1  # Move all windows to workspace 1
bind = SUPER CTRL, 2, exec, $HOME/.config/hypr/moveTo.sh 2  # Move all windows to workspace 2
bind = SUPER CTRL, 3, exec, $HOME/.config/hypr/moveTo.sh 3  # Move all windows to workspace 3
bind = SUPER CTRL, 4, exec, $HOME/.config/hypr/moveTo.sh 4  # Move all windows to workspace 4
bind = SUPER CTRL, 5, exec, $HOME/.config/hypr/moveTo.sh 5  # Move all windows to workspace 5</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note que o último bloco de scripts precisa de um script pra mover todas as janelas de uma só vez pra outro workspace. Eu copiei este <a href="https://github.com/mylinuxforwork/dotfiles/blob/main/dotfiles/.config/hypr/scripts/moveTo.sh"target="_blank" rel="noopener">moveTo.sh</a> do GitHub do ML4W e coloquei em <code>~/.config/hypr</code> direto. Não esquecer de dar permissão de execução com <code>chmod +x moveTo.sh</code> depois.
Os vários de workspace abaixo é como eu prefiro mover workspaces e mover apps pra diferentes workspaces.</p>
<p>Omarchy já vem com &ldquo;SUPER J&rdquo; pra mudar a orientação das janelas, por exemplo, se está duas janelas na horizontal um em cima do outro, com SUPER J eles ficam verticais, mas faltava o &ldquo;SUPER V&rdquo; que adicionei pra &ldquo;swapsplit&rdquo; que é trocar as janelas de lugar.</p>
<p>Eu também gosto de &ldquo;SUPER M&rdquo; pra temporariamente fazer uma das janelas ficar em tela cheia, e &ldquo;SUPER T&rdquo; pra temporariamente fazer uma janela ficar flutuante (como uma calculadora, por exemplo). Tive que dar unbind no SUPER M anterior que abria o Spotify, mas eu quase não uso Spotify no desktop então não preciso dela. Quando quero abrir esse app, vou no SUPER SPACE pra abrir o Rofi e escrevo lá.</p>
<p>Se tiver dúvidas sobre as combinações padrão, o Omarchy tem &ldquo;SUPER K&rdquo; pra abrir uma janela com uma ajuda pra todas.</p>
<p>Ainda não modifiquei combinações pra aplicativos específicos. Os que vem no Omarchy já são muito úteis como SUPER A pra abrir o ChatGPT como se fosse um app separado ou SUPER CTRL A pra abrir o Grok numa janela separada. SUPER O pra abrir o Obsidian também é muito útil pra mim. Recomendo brincar com esses binds pra fazer bindings específicos e, de novo, ler o manual pra aprender tudo mais que já vem nele. Mas até aqui é o suficiente pra deixar o Hyprland do jeito que eu gosto de usar.</p>
<h2>8 - Timeshift, Snapshots e GRUB<span class="hx:absolute hx:-mt-20" id="8---timeshift-snapshots-e-grub"></span>
    <a href="#8---timeshift-snapshots-e-grub" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Lembram que eu falei pra escolher GRUB no archinstall? É pra agora. O objetivo: deixar o sistema fazer snapshots periódicos do sistema e, caso instale algum pacote corrompido ou faça coisas no seu sistema que impeçam ele de bootar, em vez de ter que recorrer a um pendrive pra tentar bootar e diagnosticar o erro, é mais fácil só bootar, escolher um snapshot logo antes da operação destrutiva e voltar intacto.</p>
<p>Antes de prosseguir, um ajuste que acho importante: meu monitor 4K faz o GRUB ficar com letras pequenas demais pra eu conseguir ler. Prefiro letras maiores. Felizmente o GRUB usa fontes bitmap então posso usar qualquer uma. Eu fiz o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo grub-mkfont -s 48 -o /boot/grub/fonts/caskaydia48.pf2 /usr/share/fonts/TTF/CaskaydiaMonoNerdFont-Regular.ttf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso vai gerar uma fonte no formato que o GRUB consegue carregar. Escolha qualquer uma que gostar, o Omarchy já pré-instala várias muito boas como as da JetBrains. Se tamanho 48 for muito grande, diminua pra 32, experimente até ficar do jeito que gosta.</p>
<p>Daí é só editar <code>/etc/default/grub</code> pra, no final ter esta linha:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>GRUB_FONT=/boot/grub/fonts/caskaydia48.pf2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E agora é só regerar o bootloader:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo grub-mkconfig -o /boot/grub/grub.cfg</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Toda vez que o <code>yay -Syu</code> trouxer atualização de kernel ou drivers, o GRUB vai ser regerado com a mesma fonte. Olha como ficou pra mim, acho que está bem melhor (isso é um monitor 4K):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250828_124009.jpg" alt="Grub Font"  loading="lazy" /></p>
<p>Agora a razão toda pra ter escolhido GRUB: permitir escolher snapshots de BTRFS pra poder bootar no passado, antes de alguma operação destrutiva, como mostra na foto acima. Pra isso precisamos do seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pacman -S timeshift grub-btrfs
yay -S timeshift-autosnap
systemctl enable --now grub-btrfs.service</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto. A partir de agora, toda vez que o GRUB for reconstruído, vai ter uma nova opção pra escolher snapshots. Só precisamos criar esses snapshots automaticamente e, pra isso, temos o app Timeshift. Abra e siga o passo a passo dele. O importante é escolher a opção de BTRFS (sim, dá pra usar RSYNC se estiver em ext4, mas isso ocupa muito espaço, porque ele vai literalmente ficar fazendo cópias dos arquivos do seu sistema inteiro. A beleza de BTRFS é que snapshots dele não duplicam arquivos).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_15-37-52.png" alt="Timeshift Schedule"  loading="lazy" /></p>
<p>Eu deixo ele fazendo snapshots e acumulando versões das últimas horas, últimos dias, últimas semanas e meses. Se lá na frente eu lembrar <em>&ldquo;puuuuts, tinha aquele arquivo que eu apaguei mês passado mas agora eu preciso&rdquo;</em> Basta abrir o Timeshift e explorar um snapshot do mês passado. Igual povo de Mac faz com Time Machine, mas ocupando quase nada de espaço extra.</p>
<p>Além disso, todo upgrade de pacotes vai gerar um snapshot antes. Assim, se por acaso vier um pacote corrompido ou que quebre alguma configuração sua importante, dá pra voltar exatamente como era antes da atualização. E se quebrar de forma catastrófica, onde seu Linux não boota mais, basta ir no menu extra do GRUB e bootar do snapshot anterior, que funcionava. E tudo volta igualzinho como estava antes.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_15-38-24.png" alt="Timeshift Snapshots"  loading="lazy" /></p>
<p>Não existe nada mais importante pro seu sistema do ter snapshots automáticos e que não ocupam quase nada de espaço extra. Só isso já torna o BTRFS superior a qualquer outra alternativa.</p>
<p><strong>Isso não substitui Backup externo!!</strong></p>
<p>Esse rollback e capacidade de recuperar coisas de forma fácil é bom pra acidentes do dia a dia. Mas digamos que seu NVME quebre de alguma forma, ou seu PC quebre de alguma forma. Pra isso servem backups externos (seja HD externo, seja NAS, seja Cloud). Você quer ter certeza que, numa situação catastrófica, ainda seja possível recuperar seus arquivos.</p>
<p>Eu tenho todas as camadas de proteção: snapshots locais via BTRFS e Timeshift, backups feitos remotamente pro meu NAS, e backup de partes mais importantes do meu NAS na <a href="https://aws.amazon.com/pt/s3/storage-classes/glacier/"target="_blank" rel="noopener">Amazon Glacier</a> que, diferente de Amazon S3 é pra guardar backup, onde o custo de armazenamento é ultra baixo mas o custo de transferência é mais alto (S3 o armazenamento é mais caro mas a transferência é barata, por isso serve pra servir assets de websites).</p>
<p>Eu gosto de estar preparado pra, caso minha casa pegue fogo, eu não perca nada importante.</p>
<h2>9 - NFS no NAS<span class="hx:absolute hx:-mt-20" id="9---nfs-no-nas"></span>
    <a href="#9---nfs-no-nas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Falando em NAS, uma coisa que configurei diferente - já que uso primariamente Linux em vez de Windows - foi habilitar servidor de NFS.</p>
<p>Mas cuidado que alguns NAS tem uma característica que pode incomodar: o seu usuário no NAS vai ter UID e GID diferente de 1000!</p>
<p>Quando se cria um usuário em qualquer Linux, o padrão é atribuir o UID 1000 pro primeiro usuário, 1001 pro segundo e assim por diante. Mas como no NAS Synology o primeiro usuário recebe UID 1026, se eu montar com NFS (que transparece as permissões de UNIX), vou ter problemas de autorização.</p>
<p>Existem duas soluções: tentar mudar o UID no NAS de 1026 pra ser 1000. <a href="https://akitaonrails.com/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/"target="_blank" rel="noopener">Eu tentei isso</a> e já adianto que é perda de tempo, você pode acabar quebrando as configurações do NAS de forma catastrófica.</p>
<p>É muito mais fácil mudar o usuário do seu novo Linux local pra ser 1026 e pronto. É bem fácil.</p>
<p>Primeiro, precisamos deslogar e sair do Hyprland e voltar pra tela de login do SDDM. Daí precisamos mudar pra outro TTY com <strong>CTRL + ALT + F3</strong> (pra ir pra tty3, por exemplo). De lá temos que fazer login como <strong>root</strong>.</p>
<p>Agora rodamos: <code>systemctl isolate multi-user.target</code> pra rodar em modo de recuperação isolado, onde nenhum outro usuário tem processos rodando. Já que vamos mexer no usuário, ele não pode estar ativo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>usermod -u 1026 akitaonrails
groupmod -g 1026 akitaonrails
chown -R 1026:1026 /home/akitaonrails</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com os comandos acima mudamos de 1000 pra 1026 e acertamos as permissões nos arquivos locais pra refletir essa mudança. Agora sim, podemos editar o <code>/etc/fstab</code> pra montar os diretórios compartilhados no NAS com NFS:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># NFS
192.168.0.x:/volume1/GIGACHAD /mnt/gigachad nfs4 _netdev,noauto,nofail,bg,x-systemd.automount,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.idle-timeout=60s,noatime,nodiratime,async,rsize=65536,wsize=65536,actimeo=1  0 0
192.168.0.x:/volume1/TERACHAD /mnt/terachad nfs4 _netdev,noauto,nofail,bg,x-systemd.automount,x-systemd.requires=network-online.target,x-systemd.after=network-online.target,x-systemd.idle-timeout=60s,noatime,nodiratime,async,rsize=65536,wsize=65536,actimeo=1  0 0</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não esquecer de criar os mount points:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkdir /mnt/gigachad
mkdir /mnt/terachad</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pronto, só rebootar e tudo vai funcionar corretamente. As permissões dos arquivos no NAS - que estão pro UID 1026 e GID 1026 - vão ser acessíveis pro usuário local sem problemas.</p>
<h2>10 - Utilitários<span class="hx:absolute hx:-mt-20" id="10---utilitários"></span>
    <a href="#10---utilit%c3%a1rios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui são as últimas dicas pequenas pra ficar tudo num lugar só. A primeira é substituir o comando <code>ls</code> pelo <code>eza</code> que oferece uma cara mais bonita, com ícones e tudo mais, assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_16-50-25.png" alt="eza"  loading="lazy" /></p>
<p>Só fazer <code>yay -S eza</code> e adicionar isto no seu <code>.zshrc</code> ou <code>.bashrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>alias ls=&#39;eza --icons --color=auto --group-directories-first&#39;
alias ll=&#39;eza -l --icons --color=auto --group-directories-first&#39;
alias la=&#39;eza -la --icons --color=auto --group-directories-first&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Segunda dica é instalar <a href="https://atuin.sh/"target="_blank" rel="noopener">Atuin</a>. Pra quem mexe com infra, isso é extremamente importante: backup de toda o histórico de comandos que você usou no seu terminal até hoje.</p>
<p>Instale com <code>yay -S atuin</code> e faça:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>atuin login
atuin sync</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Leia o site oficial, mas primeiro precisa criar uma conta e uma chave de encriptação secreta - sim, você pode rodar localmente num servidor na sua casa, mas a opção cloud deles é bem cômoda e segura porque fica toda encriptada, então não tem problema se subir senhas do seu histórico.</p>
<p>Só precisa colocar isso no seu <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export ATUIN_NOBIND=true
eval &#34;$(atuin init zsh)&#34;
bindkey &#34;^R&#34; atuin-search

# incremental history search with arrow keys
bindkey &#34;^[[A&#34; history-beginning-search-backward
bindkey &#34;^[[B&#34; history-beginning-search-forward]]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se estiver usando ZSH, o auto-complete dele vai funcionar normalmente mas, se digitar CTRL+R, vai abrir o histórico do Atuin. Ele vai guardar todos os seus comandos num SQLite e sincronizar. Assim você pode sincronizar diversas máquinas e servidores que usa no dia-a-dia ou, como no meu caso, recuperar o histórico todo numa nova instalação.</p>
<p>E pra ficar melhor, podemos configurar ele pra auto-sincronizar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkdir -p ~/.config/atuin
sed -i &#39;s/^#\?auto_sync.*/auto_sync = true/; s/^#\?sync_frequency.*/sync_frequency = &#34;30m&#34;/&#39; ~/.config/atuin/config.toml 2&gt;/dev/null || \
printf &#39;auto_sync = true\nsync_frequency = &#34;30m&#34;\n&#39; &gt;&gt; ~/.config/atuin/config.toml</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Terceira dica: como temos Docker já instalado e configurado, eu gosto de usar o <a href="https://github.com/tbckr/sgpt"target="_blank" rel="noopener"><strong>SGPT</strong></a> na linha de comando pra quando quero lembrar a sintaxe de algum comando que uso pouco, em vez de abrir um app ou navegador. Basta adicionar isto no meu <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export OPENROUTER_API_KEY=&#34;sk-or-v1-8b8...17&#34;
export OPENAI_API_KEY=&#34;sk-or-v1-db...f3&#34;
export OPENAI_API_BASE=&#34;https://openrouter.ai/api/v1&#34;

alias sgpt=&#39;docker run --rm -e OPENAI_API_BASE=${OPENAI_API_BASE} -e OPENAI_API_KEY=${OPENROUTER_API_KEY} ghcr.io/tbckr/sgpt:latest txt &#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu uso <a href="https://openrouter.ai/"target="_blank" rel="noopener">OpenRouter</a> pra ter acesso a diferentes LLMs comerciais numa única API, mas você pode só usar sua chave da OpenAI. Feito isso eu usaria no terminal parecido com isto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>~ ❯ sgpt &#34;what is the pacman command in Arch to find out if a particular file came from an installed package?&#34;
To find out if a particular file came from an installed package in Arch Linux, you can use the following pacman command:

``bash
pacman -Qo /path/to/file
``

Replace &#34;/path/to/file&#34; with the actual path to the file you want to check. This command will show the package which owns the file, or report that the file does not belong to any package if that is the case.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se quiser, ele suporta sessões, então vai lembrar da conversa a cada nova chamada. Leia o Github do projeto.</p>
<p>Como fiz backup do meu <code>~/.config</code> antigo, coisas como abas abertas do meu navegador Brave abriram normalmente nessa nova instalação de Omarchy e posso continuar fazendo minhas coisas exatamente do ponto onde parei. Não perdi nada. Mesma coisa vale pra todos os outros apps como emails que faço backup local via Thunderbird. Não preciso mandar baixar tudo de novo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-08-29_15-59-34.png" alt="Audio Output"  loading="lazy" /></p>
<p>Se como eu, você tem vários dispositivos de saída de áudio, é possível que logo que terminar de instalar note que parece que não tem som. Mas não é um problema porque pode ser só que a saída está apontando pro dispositivo errado ou no perfil errado. Use SUPER ALT SPACE pra abrir o menu do Omarchy, escolha SETUP e AUDIO pra abrir esse programa que é em modo texto (eu gosto de programas em modo texto).</p>
<p>No meu caso, uso um DAC Topping D70 pra processar meu áudio e o perfil &ldquo;Default&rdquo; não funciona mas mudando pra &ldquo;Pro Audio&rdquo; funcionou. Também cuidado que tem configuração de volume global e controle individual de volume por programa. No geral, graças ao Pipewire, tudo deve funcionar normalmente.</p>
<p>Eu testei apps como Discord, Blender, Bambu Studio, VLC e tudo funcionando perfeitamente bem. Só VLC que precisei instalar os codecs de um pacote separado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S extra/vlc-plugin-ffmpeg</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Blender também, o melhor é instalar deste pacote:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S blender-bin</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se usar o pacote &ldquo;blender&rdquo; normal, alguns add-ons dele não instalam direito. Eu tive esse problema com MACHIN3Tools e MESHMachine, por exemplo.</p>
<p>Falando em Blender, ele é um app X11. Wayland roda aplicativos X11 usando uma camada adaptadora chamada <strong>XWayland</strong>. Eu tive vários problemas com posicionamento de menus e dialog boxes, mesmo adicionando <code>windowrule</code>. Uma coisa que sei lá porque funcionou foi instalar Blender e Bambu Studio via Flatpak:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S flatpak
flatpak install org.blender.Blender
flatpak install com.bambulab.BambuStudio</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Finalmente, se você é novo em LazyVim, não esqueça de rodar o comando <a href="https://www.lazyvim.org/extras"target="_blank" rel="noopener"><code>:LazyExtras</code></a> pra instalar os plugins extras pras suas linguagens favoritas, como LSP, Linter, syntax highlight, snippets e mais. O LazyVim puro já é completo mas sem suporte a muitas linguagens pra não ficar pesado desnecessariamente, você precisa instalar separado depois.</p>
<p>Também aprenda a usar <a href="https://mise.jdx.dev/dev-tools/"target="_blank" rel="noopener"><strong>Mise-en-place</strong></a>. No menu do Omarchy tem uma opção pra instalar suporte a várias linguagens e ele vai usar Mise, que é a forma correta de gerenciar suas linguagens.</p>
<p>Mise é muito fácil, no geral, de dentro do diretório do seu projeto, é só fazer algo como:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mise use ruby@3.4.3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com isso, só dentro do seu projeto vai estar disponível Ruby 3.4.3, mas fora pode estar outra versão global. Não esquecer de ativar no seu <code>.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>eval &#34;$(mise activate zsh)&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h2>Impressões e Conclusão<span class="hx:absolute hx:-mt-20" id="impressões-e-conclusão"></span>
    <a href="#impress%c3%b5es-e-conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Comparado ao meu Manjaro antigo, muita coisa que não precisei configurar mais. Por exemplo, por causa do tanto de espaço que Docker e LLMs precisavam, eu tinha configurado um drive iSCSI no meu NAS só pra isso. Mas como eu liberei 2 TB de NVME local, não preciso mais lidar com isso, basta usar os novos sub-volumes de BTRFS que criei acima.</p>
<p>Também não preciso mais configurar QEMU/Libvirt porque não preciso mais usar Windows junto com Linux lado a lado. Mantive o NVME com as partições Windows e posso fazer dual boot pra ela se precisar muito de alguma coisa. No geral nunca preciso, então é menos uma configuração complicada.</p>
<p>Eu passei meses usando apps via Flatpak, com o intuito de ser mais seguro e isolado do sistema. É como rodar apps via Docker. Mas é chato demais. Por ser isolado significa que ele não obedece os temas do sistema, no final sou obrigado a ficar abrindo permissões pros apps. E se for pra ficar abrindo, é mais fácil instalar nativamente via <code>yay</code> e tudo funciona perfeitamente.</p>
<p>Acho que isso completa minhas anotações de tudo que precisei fazer pra deixar o Omarchy exatamente do jeito que eu gosto. Como falei repetidas vezes, o que funciona pra mim não quer dizer que vai funcionar pra você. Pra isso existe documentações como o <a href="https://wiki.archlinux.org/title/Main_page"target="_blank" rel="noopener">ArchWiki</a> que é &ldquo;O&rdquo; melhor lugar pra aprender tudo que sobre Linux. Use e abuse dessa documentação.</p>
<p>Como podem ver, não teve tanta coisa assim que eu customizei no Omarchy, por isso eu gosto dele: 95% do caminho ele já fez pra mim, só precisei mexer em poucos ajustes pra deixar do jeito que eu preciso. E agora sem o tanto de lixo que foi acumulando no meu Manjaro velho. Recomendo que todo mundo instale Omarchy como primeiro Linux e aprenda a usar de verdade.</p>
]]></content:encoded><category>omarchy</category><category>linux</category><category>armazenamento-e-backup</category></item><item><title>Configurando meu NAS Synology com NFS no Linux</title><link>https://akitaonrails.github.io/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/</guid><pubDate>Thu, 17 Apr 2025 20:40:00 -0300</pubDate><description>&lt;p&gt;3 posts no mesmo dia, mas é que tirei o dia pra resolver meus problemas de Linux hoje, então segue mais uma anotação pra mim mesmo do futuro.&lt;/p&gt;
&lt;p&gt;Todos que me acompanham, em particular no &lt;a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener"&gt;Instagram&lt;/a&gt;, sabem da minha saga com NAS. Em particular meu novo Synology DS1821+ com mais de &lt;strong&gt;100TB&lt;/strong&gt; (4 HDs de 12TB e 4 HDs de 20TB e já tenho 4 outros HDs de 20TB pra atualizar no futuro quando precisar). Uso pra muita coisa, em particular meu &lt;a href="https://www.akitaonrails.com/2024/04/03/meu-netflix-pessoal-com-docker-compose"target="_blank" rel="noopener"&gt;Netflix Pessoal&lt;/a&gt; que expliquei nesse outro post como eu configurei com Docker.&lt;/p&gt;</description><content:encoded><![CDATA[<p>3 posts no mesmo dia, mas é que tirei o dia pra resolver meus problemas de Linux hoje, então segue mais uma anotação pra mim mesmo do futuro.</p>
<p>Todos que me acompanham, em particular no <a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener">Instagram</a>, sabem da minha saga com NAS. Em particular meu novo Synology DS1821+ com mais de <strong>100TB</strong> (4 HDs de 12TB e 4 HDs de 20TB e já tenho 4 outros HDs de 20TB pra atualizar no futuro quando precisar). Uso pra muita coisa, em particular meu <a href="https://www.akitaonrails.com/2024/04/03/meu-netflix-pessoal-com-docker-compose"target="_blank" rel="noopener">Netflix Pessoal</a> que expliquei nesse outro post como eu configurei com Docker.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4egervwxarfvls2uw3kg5k13i3hd" alt="Synology DSM"  loading="lazy" /></p>
<p>Nos destaques e post sobre isso no Insta já discuti detalhes desse NAS mas resumindo:</p>
<ul>
<li>prefiro Synology porque no fundo ele é um Linux com filesystem BTRFS (que eu gosto), que é quase tão flexível mas mais fácil de usar do que ZFS. E sim, eu sei que existe unraid, TrueNAS e outros. Mas eu acho que o Synology DSM é bem competente (tem versão open source se quiserem), e ele faz tudo que eu preciso.</li>
<li>prefiro Plex do que Emby ou Jellyfin. Já testei eles, mas o suporte do Plex a apps de TV e celular eu acho muito melhor, acho o player mais estável, também acho que as funcionalidades de organização dele são mais robustas.</li>
<li>porque não usar só Cloud? Primeiro porque é lento. Minha fibra é só 1Gbps e upload é metade disso. Segundo porque minha rede interna é ultra veloz (10Gbps) e muito melhor. Terceiro porque nada no &ldquo;Cloud&rdquo; tem garantia de nada, podem te bloquear, podem te invadir, podem apagar por engano. Quarto, tudo que é meu, eu gosto sendo <strong>realmente</strong> meu, no meu alcance, no meu total controle, sem ninguém pra fazer m&amp;rda com minha propriedade. Custa mais caro, é uma escolha. Eu literalmente tenho mais filmes e séries melhores que a Netflix com conteúdo que você não vai mais achar fácil em nenhum streaming, por exemplo.</li>
</ul>
<p>Na verdade, eu confio tão pouco em &ldquo;cloud&rdquo; que eu uso mas faço uma cópia de tudo no meu NAS local usando o app Cloud Sync que tem na loja da Synology:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/deednjd3ii774b5b7jdxu1azox3x" alt="Cloud Sync"  loading="lazy" /></p>
<p><strong>&ldquo;Ah, mas e se der pau irreversível no seu NAS e você perder TUDO!!??&rdquo;</strong></p>
<p>Nenhum backup é completo se não segue o <strong>Mantra 3-2-1</strong>, lembram??</p>
<ul>
<li><strong>3 cópias, 2 mídias, 1 off-site</strong></li>
</ul>
<p>Eu tenho algumas coisas gravadas em Millenial Discs (mdiscs, que são como Blu-Rays mas de material inorgânico que nunca estraga). E eu faço cópias das coisas mais importantes do meu NAS pra Amazon Glacier usando o app &ldquo;Glacier Backup&rdquo; que também tem na loja da Synology.</p>
<p>Glacier é como S3 só que bem mais barato porque ele não tem acesso fácil nem transferência rápida. É feito pra backups mesmo, que você grava uma vez e quase nunca acessa de novo. <strong>ENCRIPTADO</strong>, lógico.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/y83uw9rtu8p5hgsq0ugyanb5cibj" alt="Glacier Backup"  loading="lazy" /></p>
<p>Notem como eu confio tão pouco em cloud, como dropbox, que eu faço uma cópia no meu NAS e mando pra outro cloud. Tem coisas com múltiplas cópias mesmo. Eu não confio no serviço de ninguém, sempre algum humano vai cometer um erro catastrófico algum dia, é só questão de tempo.</p>
<p>Ah sim, e eu baixo mesmo muita coisa de torrent e eu me considero Power User e sei o que é malware/virus do que não é e sei como funcionam. Mas eu também sou humano então todos os meus Windows tem no mínimo o Windows Defender ativo, Malwarebytes que eu gosto também e mesmo assim eu ainda rodo mais um antivirus no NAS também, que também tem na loja da Synology. Eu não confio em nada sem redundância de tudo, sob meu total controle.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bekpbla77t4o4uxcysqks18racwc" alt="antivirus"  loading="lazy" /></p>
<p>Mais importante e porque escolho BTRFS tanto no NAS quanto no meu PC: <strong>snapshots</strong>. Ele faz snapshots de tudo periodicamente. Assim, se alguma hora eu rodar um <code>rm -Rf</code> da vida errado, eu não perco nada, basta voltar pro snapshot anterior e vai estar tudo lá, bonitinho. Snapshots são inteligentes, eles quase não ocupam espaço, porque no BTRFS o mesmo binário do arquivos iguais, apontam pro mesmo lugar físico. Não é como um <code>rsync</code> que realmente duplica tudo e duplica espaço ocupado.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zhbq7dv8ai8jupqwj3zqvmetq5yk" alt="Synology Snapshots"  loading="lazy" /></p>
<p>No seu PC/notebook Linux, com BTRFS, instale o programa Timeshift pra fazer a mesma coisa. A melhor coisa: se uma hora você atualizar seu sistema e ele não bootar ou alguma coisa quebrar depois de update, é fácil: só rebootar e no GRUB escolher o snapshot anterior e pronto, vai voltar exatamente igual antes do update. No Arch/Manjaro ele é inteligente e já faz snapshots automaticamente toda vez antes do <code>pacman</code> rodar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n20yo00ztofrjsu6p6678wmfphdd" alt="Timeshift"  loading="lazy" /></p>
<p>Enfim, outro dos usos é pra projetinhos pessoais de código mesmo no meu Linux. No mini-PC que uso na TV pra media center, eu rodo Windows 11 mesmo. Porque uso ligado a outra eGPU com uma RTX 4060 e eu uso Steam pra games. Sim, muitos jogos de Steam rodam em Linux, tem Bazzite, etc. Mas tem coisas que só rodam no Windows e eu preferi comodidade mesmo. Só uso pra games e media então ph0da-se, funciona plug and play.</p>
<p>Meu outro home server mini-PC é onde rodam meus Docker pro media center, torrent, plex server e tudo mais. É um Ubuntu LTS headless. Além dos meus handhelds como o Rog Ally X que roda Windows. Todos eles tem acesso ao meu NAS da Synology, onde exponho pastas compartilhadas com SMB e é isso. Plug and play.</p>
<p>Eu fazia a mesma coisa no meu Manjaro Linux: montava as pastas com SMB. Mas isso tem um problema se eu quero codar alguma coisa e quero usar Git: em SMB não existem atributos de chmod (como flag +x pra script executável) e toda vez que faço git push, zoa todas as permissões de tudo. É um enorme saco. Quem já usou Git no Windows sabe disso.</p>
<p>Eu não consigo deixar scripts executáveis nesse diretório compartilhado. Então dificulta minha vida no Linux. Então resolvi consertar isso de uma vez e fazer a coisa certa: parar de usar SMB e passar a usar NFS que é a forma nativa de Linux pra acessar pastas compartilhadas na rede de um outro servidor Linux (se o servidor fosse Windows, aí tinha que ser SMB mesmo, mas ninguém em sã consciência monta um NAS com Windows Server).</p>
<p>No SMB, o serviço ignora ownership e permissões do filesystem. Eu crio usuários e atribuo ao serviço, e não aos arquivos. Basta logar com esse usuário e ele vai ter acesso aos arquivos. Então não tem como fazer <code>chown</code> e mudar ownership e nem fazer <code>chmod</code> e mudar permissões individualmente nos arquivos, é tudo normalizado.</p>
<p>Em NFS, o serviço expõe exatamente o ownership e permissões no filesystem direto, sem um intermediário de ACL pra checar permissões por fora. Então eu tenho o mesmo nível de acesso pra esses atributos que eu tenho nos arquivos locais do meu Linux, que é o que eu quero.</p>
<p>Mas a Synology faz uma coisa irritante não sei porque. Em qualquer Linux, o primeiro usuário e o primeiro grupo criados costumam ser UID: 1000 e GID: 1000. Faça o teste, vai no seu terminal e faça <code>id [seu usuário]</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ id akitaonrails
uid=1000(akitaonrails) gid=1000(akitaonrails) groups=1000(akitaonrails),998(wheel),996(audio),994(input),...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então tanto faz fazer <code>chown akitaonrails:akitaonrails foo.txt</code> ou <code>chown 1000:1000 foo.txt</code>. Na prática é o número identificador que é importante e não o nome.</p>
<p>Com NFS, se no lado da Synology tem um usuário com mesmo número de UID e GID, eu automaticamente tenho permissão. Mas quando se cria usuários na Synology, não sei porque cargas d&rsquo;água, ele começa criando com IDs acima de <strong>1020 em vez de 1000</strong>. Então eu tenho um usuário &ldquo;akitaonrails&rdquo; no Synology com UID 1026 em vez de 1000.</p>
<p>Mesmo conseguindo montar a pasta compartilhada com NFS, se tento usar Git, por exemplo, ele detecta que meu UID local é 1000 e os arquivos estão com ownership do UID 1026:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git st fatal: detected dubious ownership in repository at &#39;/mnt/gigachad/Projects/dotfiles&#39; To add an exception for this directory, call: 

	git config --global --add safe.directory /mnt/gigachad/Projects/dotfiles</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não dá pra ficar usando com UIDs misturados, vira uma zona tão ruim quanto SMB. Mas tem formas de mapear e outras gambiarras, mas só tem duas soluções mais &ldquo;limpas&rdquo;:</p>
<ul>
<li>mudar meu UID/GID do meu Linux pra ser 1026 também</li>
<li>criar um novo usuário no Synology, mudar o ID dele pra 1000 e fazer chown em todos os arquivos pra esse novo usuário.</li>
</ul>
<p>Escolhi a segunda opção. Primeira coisa é habilitar acesso a SSH no NAS (o ideal é deixar coisas assim desligadas, por segurança, mas estou numa rede local privada então o risco é mínimo):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jpanifwk7guaxaekpoph0hty61ve" alt="SSH Synology"  loading="lazy" /></p>
<p>No mínimo eu sempre troco a porta padrão do SSH, costume, mas tanto faz. Precisa saber usar SSH, não ensinar aqui, tem ChatGPT pra isso.</p>
<p>Agora, criar um novo usuário padrão:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/l6surkfy60whkaqri8m2rzqrq7d2" alt="Novo Usuário"  loading="lazy" /></p>
<p>Vamos garantir que o serviço de NFS está ligado também, já que vou passar a usar ele (por padrão vem desligado, só SMB ligado porque maioria dos clientes são PCs Windows mesmo):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/tpyix3dfkc0h2i4x4iaad99djffj" alt="Enable NFS"  loading="lazy" /></p>
<p>Agora é logar com SSH no NAS e editar o UID e GID, primeiro editando <code>/etc/passwd</code> e achando uma linha como:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>akitaonrails-nfs:x:10xx:10xx:...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E trocando pra:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>akitaonrails-nfs:x:1000:1000:...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora editando <code>/etc/group</code> e mesma coisa, trocando o grupo <code>akitaonrails-nfs</code> recém criado pra <code>1000</code> também. Pronto, agora eu tenho um usuário com UID 1000 e GID 1000 criado. O nome não é o mesmo do meu PC mas isso não importa, só os números.</p>
<p>Na GUI da Synology DSM, não esquecer de adicionar permissões pra cada pasta compartilhada, pra NFS:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/x5q0fmb5u6qxd7ahe7f7nquoy73k" alt="NFS permissions"  loading="lazy" /></p>
<p>Ainda via SSH, tem que alterar o ownership e consertar as permissões de <strong>TODOS OS ARQUIVOS</strong> e no meu caso, com terabytes, levou BASTANTE tempo, só deixar lá rodando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chown -R 1000:1000 /volume1/GIGACHAD
sudo chmod -R 0755 /volume1/GIGACHAD</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se eu quiser desconectar do SSH e deixar lá rodando (já que vai levar um tempão), basta rodar desconectado da sessão SSH com <code>nohup</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>nohup chown -R 1000:1000 /volume1/GIGACHAD &gt; /tmp/chown.log 2&gt;&amp;1 &amp;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Um cuidado que precisa tomar é não fazer algo como <code>chmod -R -x</code> em tudo. Primeiro: diretórios precisam ter o bit de execução e se remover, não vai mais conseguir navegar por diretórios, então tem que separar correção de diretórios e arquivos. Segundo: é bom passar esse conserto em diretórios pra garantir que não ficou arquivos com bit executável (o que pode gerar problemas de segurança, imagine arquivos aleatórios de diretórios de Node modules). Terceiro: diretórios como &ldquo;#snapshot&rdquo; são pra controle do filesystem e são read-only, tentar mexer neles só vai comer MUITO tempo, então é melhor filtrar e não mexer neles.</p>
<p>Ou fazer um script com isso e rodar com nohub pra poder sair do SSH enquanto espera ele rodar (no meu caso, com terabytes, leva horas):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>nohup /tmp/fix-terachad-perms.sh &gt; /dev/null 2&gt;&amp;1 &amp;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem alguns probleminhas escondidos ainda. Ainda no SSH da Synology tem que mudar o <code>/etc/exports</code> que o NFS usa porque ele ainda mapeia por default pro usuário UID 1026 e 100, tem que mudar os dois pra 1000:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># antigo
sudo cat /etc/exports
Password:

/volume1/GIGACHAD       192.168.0.0/24(rw,async,no_wdelay,no_root_squash,insecure_locks,sec=sys,anonuid=1025,anongid=100)
/volume1/TERACHAD       192.168.0.0/24(rw,async,no_wdelay,crossmnt,no_root_squash,insecure_locks,sec=sys,anonuid=1025,anongid=100</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Problema de mexer em arquivos de sistema como esse é que não se sabe se mexer na GUI de permissões do NFS, ou se vier updates, vai voltar como era antes. Se alguém souber de alguma solução mais permanente, avisem.</p>
<p>E pronto, com tudo isso, basta desconfigurar a configuração de CIFS/SMB que eu tinha e substituir por NFS. Eu prefiro usar o <a href="https://wiki.archlinux.org/title/Autofs"target="_blank" rel="noopener">AutoFS</a> pra fazer isso. Eu tinha um <code>auto.cifs</code> antes, que agora posso deletar. E criar um novo <code>/etc/autofs/auto.nfs</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gigachad  -fstype=nfs4,rw,nosuid,noatime 192.168.0.xx:/volume1/GIGACHAD
terachad  -fstype=nfs4,rw,nosuid,noatime 192.168.0.xx:/volume1/TERACHAD</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E trocar a linha de <code>auto.cifs</code> em <code>/etc/autofs/auto.master</code> por isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/mnt  /etc/autofs/auto.nfs --timeout=60 --ghost --browse</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora é reiniciar o serviço:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl daemon-reexec
sudo systemctl restart autofs</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E pronto, tudo montado com NFS e agora eu tenho suporte completo a ownership e permissions nativas de Linux, sem o overhead e incompatibilidades da porcaria de SMB. Deu um &ldquo;medinho&rdquo; ficar fazendo chown e chmod em massa, mas é um NAS com BTRFS. Tem RAID pra aguentar corrupção de dados, tem snapshots se precisar reverter alguma coisa. Essa é a vantagem de ter sistemas de segurança: dá pra fazer as coisas sabendo que se algo der errado, tem como reverter. Essa paz de espírito não tem preço.</p>
]]></content:encoded><category>homelab</category><category>linux</category><category>redes</category></item><item><title>NVIDIA e Wayland - Problemas pra PCI Passthrough em VMs</title><link>https://akitaonrails.github.io/2025/04/17/nvidia-e-wayland-problemas-pra-pci-passthrough-em-vms/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/17/nvidia-e-wayland-problemas-pra-pci-passthrough-em-vms/</guid><pubDate>Thu, 17 Apr 2025 19:35:00 -0300</pubDate><description>&lt;p&gt;Mais um artigo que é mais pra eu não me esquecer e servir de anotação pra mim.&lt;/p&gt;
&lt;p&gt;Em Fevereiro de 2023 eu fiz um longo video sobre como eu configurei Windows pra rodar emulado em VM de QEMU/Libvirt e conseguindo passar minha 2a GPU, que é a RTX 4090 pra dentro da VM, possibilitando rodar jogos pesados mesmo emulando o Windows. Se não assistiu, está &lt;a href="https://www.akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt"target="_blank" rel="noopener"&gt;neste link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Em resumo, eu tenho uma máquina parruda, um Ryzen 9 7950X3D com 96GB de RAM. Essa CPU tem uma iGPU, uma GPU integrada fraquinha da AMD. Fraquinha mas mais do que suficiente pra rodar GNOME em dois monitores, com suporte a HDR e em 4K sem engasgar nem nada. Pra uso do dia a dia que é mais abrir navegador, é mais que suficiente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Mais um artigo que é mais pra eu não me esquecer e servir de anotação pra mim.</p>
<p>Em Fevereiro de 2023 eu fiz um longo video sobre como eu configurei Windows pra rodar emulado em VM de QEMU/Libvirt e conseguindo passar minha 2a GPU, que é a RTX 4090 pra dentro da VM, possibilitando rodar jogos pesados mesmo emulando o Windows. Se não assistiu, está <a href="https://www.akitaonrails.com/2023/02/01/akitando-137-games-em-maquina-virtual-com-gpu-passthrough-entendendo-qemu-kvm-libvirt"target="_blank" rel="noopener">neste link</a>.</p>
<p>Em resumo, eu tenho uma máquina parruda, um Ryzen 9 7950X3D com 96GB de RAM. Essa CPU tem uma iGPU, uma GPU integrada fraquinha da AMD. Fraquinha mas mais do que suficiente pra rodar GNOME em dois monitores, com suporte a HDR e em 4K sem engasgar nem nada. Pra uso do dia a dia que é mais abrir navegador, é mais que suficiente.</p>
<p>Essa GPU carrega no Linux com o driver que já vem na kernel, <code>amdgpu</code>e é montada como <code>/dev/dri/card1</code>. Daí eu coloquei uma 2a GPU, a parruda RTX 4090 com 24GB de VRAM GDDR6X. Não é suficiente pra rodar as LLMs mais parrudas, mas consegue rodar o suficiente. Comprei primariamente pra jogos, mas acabo usando pra IA e render 3D também.</p>
<p>Agora o conceito é simples: no boot, o Linux precisa ignorar essa placa e não carregar os drivers da NVIDIA. Se fizer isso, eu não consigo depois passar ela pra dentro da VM do Windows. Eu editava os videos do meu canal no DaVinci Resolve, que usava plugins que só funcionam em Windows, esse era um dos motivos na época. O Resolve em si funciona no Linux também, mas muitos plugins não. Eu também preferia usar Photoshop, que também só tem no Windows.</p>
<p>Pra isso configuramos o Linux pra carregar um driver de mentira, chamado de <code>vfio-pci</code> no lugar do driver da Nvidia (ou a alternativa open source, Nouveau). No outro artigo eu explico em mais detalhes, mas é um enorme saco configurar isso na mão. Não sei se ainda funciona, mas na época também usei um script que automatiza isso, o <a href="https://github.com/uwzis/GPU-Passthrough-Manager"target="_blank" rel="noopener">GPU-Passthrough-Manager</a>. Ele vai configurar tudo pra impedir os drivers da NVIDIA de carregar e trocar pra vfio-pci corretamente. Acho que também funciona em notebooks com Optimus, quando você tem uma iGPU mais fraca (pra economizar bateria) e uma GPU discreta separada, mas eu não testo isso faz anos então nem me perguntem, perguntem ao ChatGPT.</p>
<p>Desde 2023 isso funciona super bem. Eu tenho um script chamado <code>nvidia-locker</code> que tá no outro artigo. Se eu quiser rodar LLMs ou Blender no meu Linux nativo, rodo esse script e ele tira o vfio-pci e recolocar o driver da NVIDIA em tempo real, sem precisar rebootar. Daí quando termino e eu preciso bootar no Windows, rodo o script de novo e ele descarrega o driver também em tempo real, possibilitando passar via PCI Passthrough pra VM do QEMU. Tudo bonito.</p>
<p>Isso é, até poucos meses atrás, quando o Arch e Manjaro atualizaram pra trocar o antigo legado do <strong>Xorg pra Wayland</strong>. Hoje em dia Wayland é bem mais estável, os portals dele funcionam direitinho e eu consigo usar webcam, microfone, tablet e tudo mais sem nenhum problema. Tudo funciona e, teoricamente, Wayland é pra ser melhor. O novo GNOME 48 também ficou muito bom, com suporte a HDR e tudo mais. Enfim, tá tudo bonitão mesmo, recomendo.</p>
<p>Menos pra combinação de GNOME/Wayland com GPU NVIDIA especificamente nesse cenário de PCI Passthrough pra VM. Em resumo: o boot continua normal, vfio-pci carrega e esconde a GPU da NVIDIA. Meu script <code>nvidia-locker</code> também consegue recarregar o driver da NVIDIA a quente. O problema é quando eu tento descarregar o driver pra abrir a VM: o GNOME não deixa.</p>
<p>O Wayland é super agressivo e assim que ele detecta que surgiu uma nova GPU, ele carrega o processo <code>gnome-shell</code> nessa GPU. E como tem um processo pendurado, não tem como tirar mais o driver a quente. Só rebootando, e isso é um saco.</p>
<p>Não tem solução fácil. Eu tentei dezenas de coisas diferentes como permissões, regras de UDEV, serviços de SystemD, opções de kernel mas nada impede a porcaria do processo <code>gnome-shell</code> ou <code>gjs</code> (portal de Flatpak) de se pendurarem na GPU.</p>
<p>Só tem um jeito, meio gambiarra:</p>
<ul>
<li>primeiro, configurar o GDM (que carrega o GNOME e é o processo pai) pra não auto-restartar automaticamente se morrer</li>
<li>segundo, ir pra outro TTY (Ctrl+Alt+F3, por exemplo), matar o GDM de lá e só assim descarregar o driver da NVIDIA.</li>
<li>terceiro, quando trocar o driver por vfio-pci, agora sim a GPU está desligada e dá pra reiniciar o GDM e logar novamente. Só um saco que os apps gráficos abertos vão morrer, mas é mais rápido do que um reboot inteiro do PC.</li>
</ul>
<h2>1 - desligar auto-restart do GDM<span class="hx:absolute hx:-mt-20" id="1---desligar-auto-restart-do-gdm"></span>
    <a href="#1---desligar-auto-restart-do-gdm" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Disable GDM auto-restart
sudo mkdir -p /etc/systemd/system/gdm.service.d
sudo tee /etc/systemd/system/gdm.service.d/10-no-restart.conf &lt;&lt;EOF
[Service]
Restart=no
EOF

# Update systemd
sudo systemctl daemon-reload</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Essa config impede o GDM de auto-reiniciar. Agora eu consigo matar ela. Sem fazer isso, se eu matar a GDM, ela abre de novo rápido demais e não dá tempo de tirar o driver da NVIDIA antes.</p>
<h2>2 - Tentando evitar o Wayland de pegar a GPU<span class="hx:absolute hx:-mt-20" id="2---tentando-evitar-o-wayland-de-pegar-a-gpu"></span>
    <a href="#2---tentando-evitar-o-wayland-de-pegar-a-gpu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Isso o script de GPU-Passthrough-Manager teoricamente já faz, mas pra garantir é bom ter o seguinte:</p>
<p>Primeiro, em <code>/etc/modprobe.d/blacklist-nvidia.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>blacklist nvidia
blacklist nvidia_drm
blacklist nvidia_uvm
blacklist nvidia_peer_mem
blacklist nvidia_modeset
blacklist nvidia-nvlink
blacklist xhci_pci
blacklist ucsi_ccg
options nvidia NVreg_EnableGpuFirmware=0</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Segundo, em <code>/etc/modprobe.d/nvidia.conf</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>options nvidia_drm modeset=0
options nvidia NVreg_InitializeSystemMemoryAllocations=0</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A opção modeset é pra tentar fazer o nvidia_drm não se pendurar na GPU, mas isso não funcionou também. É opcional, não sei se tem problema deixar ou tirar pra modeset=1 de novo. Acho que é mais importante isso se você usa o GPU da Nvidia como GPU primária onde realmente sua interface gráfica vai rodar, especialmente pra suportar o Wayland corretamente.</p>
<p>A outra tentativa é com regras de UDEV em <code>/etc/udev/rules.d/80-nvidia-composite.rules</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Unified NVIDIA/AMD GPU management
ACTION==&#34;add&#34;, SUBSYSTEM==&#34;drm&#34;, KERNEL==&#34;card*&#34;, ATTRS{vendor}==&#34;0x10de&#34;, \
    RUN&#43;=&#34;/bin/sh -c &#39;chmod 000 /dev/dri/%k; echo GNOME_MUTTER_IGNORE_DEVICE=1 &gt;&gt; /etc/environment&#39;&#34;

ACTION==&#34;add&#34;, SUBSYSTEM==&#34;drm&#34;, KERNEL==&#34;card*&#34;, ATTRS{vendor}==&#34;0x1002&#34;, \
    RUN&#43;=&#34;/bin/sh -c &#39;chmod 660 /dev/dri/%k; chown root:video /dev/dri/%k&#39;&#34;

# New critical addition:
ACTION==&#34;add&#34;, SUBSYSTEM==&#34;pci&#34;, ATTR{vendor}==&#34;0x10de&#34;, ATTR{class}==&#34;0x030000&#34;, ATTR{enable}=&#34;0&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A idéia é tentar mudar os atributos chmod pra 000, mas isso também não funcionou.</p>
<p>A última tentativa foi tentar um serviço Systemd em <code>/etc/systemd/system/nvidia-lock.service</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Hardened NVIDIA Lock
Before=display-manager.service
Conflicts=multi-user.target

[Service]
Type=oneshot
# Merge of both prepare scripts
ExecStart=/bin/sh -c &#39;echo 0 &gt; /sys/class/vtconsole/vtcon0/bind&#39;
ExecStart=/bin/sh -c &#39;echo 0 &gt; /sys/class/vtconsole/vtcon1/bind&#39;
ExecStart=/bin/sh -c &#39;chmod 000 /dev/dri/card0&#39;
ExecStart=/bin/sh -c &#39;for card in /dev/dri/card*; do [ &#34;$(cat /sys/class/drm/${card##*/}/device/vendor)&#34; == &#34;0x10de&#34; ] &amp;&amp; chmod 000 &#34;$card&#34;; done&#39;

[Install]
WantedBy=graphical.target</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mesma idéia de tentar bloquear o Wayland via permissões mais duras, mas também não funcionou. Eu acho que não tem problemas eu deixar isso rodando mas podem pular pra próxima seção</p>
<h2>3. O que funcionou<span class="hx:absolute hx:-mt-20" id="3-o-que-funcionou"></span>
    <a href="#3-o-que-funcionou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de muito testar, agora eu tenho dois scripts (só pra ficar menor macarrônico). Eis o script <code>nvidia-unlock</code> que descarrega o driver da NVIDIA e tem que rodar de outro TTY diferente de onde roda o GDM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># /usr/local/bin/nvidia-unlock - Improved version with session handling</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Store current TTY</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT_TTY</span><span class="o">=</span><span class="k">$(</span>tty <span class="p">|</span> sed <span class="s1">&#39;s/\/dev\/tty//&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">IS_GDM_RUNNING</span><span class="o">=</span><span class="k">$(</span>systemctl is-active gdm<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 1. Stop GDM gracefully</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$IS_GDM_RUNNING</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;active&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Stopping GDM...&#34;</span>
</span></span><span class="line"><span class="cl">  sudo systemctl stop gdm
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 2. Kill all graphical processes</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Terminating graphical sessions...&#34;</span>
</span></span><span class="line"><span class="cl">sudo pkill -9 -f <span class="s2">&#34;gnome-shell|gjs|Xwayland&#34;</span>
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 3. Nuclear GPU unbind</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Removing PCI devices...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;1&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/devices/0000:01:00.0/remove &gt;/dev/null
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;1&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/devices/0000:01:00.1/remove &gt;/dev/null
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 4. Purge drivers</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Unloading NVIDIA modules...&#34;</span>
</span></span><span class="line"><span class="cl">sudo rmmod nvidia_drm nvidia_modeset nvidia_uvm nvidia 2&gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 5. Rescan PCI</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Rescanning PCI bus...&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;1&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/rescan &gt;/dev/null
</span></span><span class="line"><span class="cl">sleep <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 6. Restore GDM if it was running</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$IS_GDM_RUNNING</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;active&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Restarting GDM...&#34;</span>
</span></span><span class="line"><span class="cl">  sudo systemctl start gdm
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Return to original TTY</span>
</span></span><span class="line"><span class="cl">  sleep <span class="m">3</span> <span class="c1"># Wait for GDM to initialize</span>
</span></span><span class="line"><span class="cl">  sudo chvt <span class="nv">$CURRENT_TTY</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># 7. Final verification</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! lsmod <span class="p">|</span> grep -q nvidia<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;✅ NVIDIA GPU successfully unlocked&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;❌ Failed to fully unlock GPU&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse script mata todo mundo embaixo do GDM, o GNOME Shell, os programas gráficos abertos, tudo, então não deixe seu Libreoffice aberto sem salvar se rodar isso, vai matar tudo. Por isso tem que fazer Ctrl+Alt+F3 e rodar do terminal separado.</p>
<p>E agora, este é meu novo <code>nvidia-locker</code> :</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#!/bin/bash</span>
</span></span><span class="line"><span class="cl"><span class="c1"># nvidia-locker - Final working version with proper TTY handling</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get the directory where this script lives</span>
</span></span><span class="line"><span class="cl"><span class="nv">SCRIPT_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Configuration</span>
</span></span><span class="line"><span class="cl"><span class="nv">NVIDIA_GPU</span><span class="o">=</span><span class="s2">&#34;0000:01:00.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NVIDIA_AUDIO</span><span class="o">=</span><span class="s2">&#34;0000:01:00.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">PCI_PATH</span><span class="o">=</span><span class="s2">&#34;/sys/bus/pci/devices&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">DRM_PATH</span><span class="o">=</span><span class="s2">&#34;/dev/dri&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">UNLOCK_SCRIPT</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">SCRIPT_DIR</span><span class="si">}</span><span class="s2">/nvidia-unlock&#34;</span> <span class="c1"># Look in same directory</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  Core Functions </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">get_driver<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">local</span> <span class="nv">device</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[</span> -d <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$device</span><span class="s2">/driver&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    basename <span class="k">$(</span>readlink <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$device</span><span class="s2">/driver&#34;</span><span class="k">)</span> 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;none&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">is_gui_session<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Returns true if running in a graphical session</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$DISPLAY</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$WAYLAND_DISPLAY</span><span class="s2">&#34;</span> <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">print_status<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> -e <span class="s2">&#34;\nCurrent GPU Status:&#34;</span>
</span></span><span class="line"><span class="cl">  lspci -nnk -d 10de:2684
</span></span><span class="line"><span class="cl">  lspci -nnk -d 10de:22ba
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">load_vfio<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;🔒 Binding to vfio-pci...&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;vfio-pci&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_GPU</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;vfio-pci&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/bind &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/bind &gt;/dev/null
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">load_nvidia_headless<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;🔓 Loading NVIDIA in headless mode...&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Clear any existing overrides</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_GPU</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;&#34;</span> <span class="p">|</span> sudo tee <span class="s2">&#34;</span><span class="nv">$PCI_PATH</span><span class="s2">/</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">/driver_override&#34;</span> &gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Load modules with headless options</span>
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia <span class="nv">NVreg_InitializeSystemMemoryAllocations</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia_modeset
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia_uvm
</span></span><span class="line"><span class="cl">  sudo modprobe nvidia_drm <span class="nv">modeset</span><span class="o">=</span><span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Bind devices</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/nvidia/bind &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/nvidia/bind &gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Lock DRM nodes</span>
</span></span><span class="line"><span class="cl">  sudo chmod <span class="m">000</span> <span class="s2">&#34;</span><span class="nv">$DRM_PATH</span><span class="s2">/card0&#34;</span> 2&gt;/dev/null <span class="c1"># NVIDIA</span>
</span></span><span class="line"><span class="cl">  sudo chmod <span class="m">660</span> <span class="s2">&#34;</span><span class="nv">$DRM_PATH</span><span class="s2">/card1&#34;</span> 2&gt;/dev/null <span class="c1"># AMD</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  Command Handlers </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vm_mode<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">current_driver</span><span class="o">=</span><span class="k">$(</span>get_driver <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$current_driver</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;nvidia&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> is_gui_session<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️  Please switch to TTY (Ctrl+Alt+F2) and run:&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;   sudo systemctl stop gdm &amp;&amp; sudo nvidia-unlock&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="nb">echo</span> <span class="s2">&#34;⚠️  Already in TTY - running nvidia-unlock...&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="o">[</span> -x <span class="s2">&#34;</span><span class="nv">$UNLOCK_SCRIPT</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        sudo <span class="s2">&#34;</span><span class="nv">$UNLOCK_SCRIPT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;❌ Error: nvidia-unlock not found at </span><span class="si">${</span><span class="nv">UNLOCK_SCRIPT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;ℹ️  Make sure both scripts are in the same directory&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">      <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  load_vfio
</span></span><span class="line"><span class="cl">  print_status
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">host_mode<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">current_driver</span><span class="o">=</span><span class="k">$(</span>get_driver <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$current_driver</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;vfio-pci&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;🔄 Releasing vfio-pci binding...&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_GPU</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/unbind &gt;/dev/null
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$NVIDIA_AUDIO</span><span class="s2">&#34;</span> <span class="p">|</span> sudo tee /sys/bus/pci/drivers/vfio-pci/unbind &gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  load_nvidia_headless
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="o">&amp;&amp;</span> nvidia-smi
</span></span><span class="line"><span class="cl">  print_status
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#  Main </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> in
</span></span><span class="line"><span class="cl">on<span class="o">)</span> vm_mode <span class="p">;;</span>
</span></span><span class="line"><span class="cl">off<span class="o">)</span> host_mode <span class="p">;;</span>
</span></span><span class="line"><span class="cl">status<span class="o">)</span> print_status <span class="p">;;</span>
</span></span><span class="line"><span class="cl">*<span class="o">)</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Usage: </span><span class="nv">$0</span><span class="s2"> [on|off|status]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;  on     - Prepare GPU for VM (may require nvidia-unlock)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;  off    - Enable NVIDIA for host (headless mode)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;  status - Show current GPU status&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra rodar é a mesma coisa: <code>nvidia-locker off</code> vai tirar o bloqueio da vfio-pci e carregar os drivers da Nvidia quando eu quero rodar uma LLM de IA nativo no meu Linux ou renderizar alguma coisa no Blender com Cycles.</p>
<p>Depois que terminar, e eu quiser rodar Windows na VM. Ctrl-Alt-F3, novo login no terminal puro texto, e <code>nvidia-locker on</code>. Isso vai matar a GDM, descarregar os drivers da NVIDIA, recarregar o bloqueio do vfio-pci e reiniciar o GDM pra eu logar de novo no modo gráfico. E de lá posso abrir via comando <code>virsh start</code> ou usando a interface gráfica Virtual Machine Manager se quiser usar Display Spice, por exemplo.</p>
<p>E é isso, o artigo é mais pra eu não me esquecer do que eu fiz, pra no futuro usar de referência.</p>
]]></content:encoded><category>linux</category><category>hardware</category></item><item><title>Configuração da BIOS do meu PC - X670E Aorus Xtreme</title><link>https://akitaonrails.github.io/2025/04/17/configuracao-da-bios-do-meu-pc-x670e-aorus-xtreme/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/17/configuracao-da-bios-do-meu-pc-x670e-aorus-xtreme/</guid><pubDate>Thu, 17 Apr 2025 18:50:00 -0300</pubDate><description>&lt;p&gt;Esta é uma anotação pra mim mesmo, pra eu não esquecer e talvez ajude alguém.&lt;/p&gt;
&lt;p&gt;Eu sei que toda placa-mãe vem pré-configurada em &amp;ldquo;defaults&amp;rdquo; seguros e conservadores, o mais estável e não o mais performático. Então se não entrar na BIOS pra mexer, vai estar deixando performance na mesa.&lt;/p&gt;
&lt;p&gt;Eu tenho uma CPU AMD Ryzen 9 7950X3D. O processador é um pacote com um pacote que funciona do dois &amp;ldquo;dies&amp;rdquo;, não sei se é &amp;ldquo;chip&amp;rdquo; um jeito de visualizar. Cada um deles tem 8 cores, totalizando os 16 cores e cada core com 2 threads, então 32 threads possíveis em paralelo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Esta é uma anotação pra mim mesmo, pra eu não esquecer e talvez ajude alguém.</p>
<p>Eu sei que toda placa-mãe vem pré-configurada em &ldquo;defaults&rdquo; seguros e conservadores, o mais estável e não o mais performático. Então se não entrar na BIOS pra mexer, vai estar deixando performance na mesa.</p>
<p>Eu tenho uma CPU AMD Ryzen 9 7950X3D. O processador é um pacote com um pacote que funciona do dois &ldquo;dies&rdquo;, não sei se é &ldquo;chip&rdquo; um jeito de visualizar. Cada um deles tem 8 cores, totalizando os 16 cores e cada core com 2 threads, então 32 threads possíveis em paralelo.</p>
<p>Mas tem uma diferença, cada die tem caches L3 diferentes. Um dos dies tem só 32MB e o outro tem 96MB, então programas mais pesados se beneficiam se rodarem nos cores do die que tem mais L3, e dá pra configurar isso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/gcbhrzj8wuc38z3lonisck0ooeck" alt="lstopo"  loading="lazy" /></p>
<p>Além disso ele tem clock base acho que de 3Ghz com capacidade de dar turbo boost até 5.7Ghz. Não pode tudo rodar nessa velocidade senão esquenta muito rápido e pode dar &ldquo;thermal throttle&rdquo; e aí ele derruba tudo pra baixo de 3Ghz. Por isso que, principalmente em notebooks que não tem capacidade térmica adequada, o melhor não é dar overclocking e sim &ldquo;undervolting&rdquo; (cortar a energia pra ir menos rápido). Porque é melhor todo mundo um pouco menos rápido do que todo mundo super rápido mas imediatamente super-aquecer e todo mundo ficar lento. É complexo isso.</p>
<p>Pra complicar mais eu tenho 96GB de RAM DDR5 6000. Capaz do máximo teórico de 6Ghz, mas isso é teórico porque pode dessincronizar tudo e causar instabilidades até crashear um kernel panic. Por isso o default da placa mãe é a velocidade base acho que de 4Ghz e CPU no máximo indo até 4Ghz mais ou menos também.</p>
<p>Mas dá pra subir isso um pouco sem deixar instável. Em resumo eu fiz o seguinte:</p>
<h2>PBO - Precision Boost Overdrive<span class="hx:absolute hx:-mt-20" id="pbo---precision-boost-overdrive"></span>
    <a href="#pbo---precision-boost-overdrive" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Os CPUs Ryzen tem PBO pra dar turbo boost no clock dos cores. Ele costuma vir ou desativado ou numa configuração conservadora, então seu CPU de 5.7Ghz pode estar operando só em 4Ghz ou menos e nunca entregando o máximo.</p>
<p>Pra ajustar isso, se quiser ser avançado, precisa ajustar parâmetros como PPT (Package Power Tracking) pra 200W ou mais. TDC (Thermal Design Current) pra 160A ou mais. EDC (Electrical Design Current) pra 200A ou mais. E pode configurar uma otimizador de curva, por core, pra negativo (-10 ou -20) pra reduzir voltagem, o que pode permitir clocks de boost mais altos.</p>
<p>Mas isso é muito avançado, e precisa saber bem mais de elétrica antes de
tentar na não. Melhor não arriscar e não mexer nisso. A tela a seguir, não deixe em Avançado e sim em Auto mesmo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vxeurat4imakwzdl6artkzuqymmq" alt="PBO Advanced"  loading="lazy" /></p>
<p>Em vez disso é melhor mexer em PBO Enhancement Presets, que é essa tela abaixo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a1vgigv3ka64xs6nclq9z2hh21w7" alt="PBO Presets"  loading="lazy" /></p>
<p><strong>70/80/90</strong> se refere à temperatura máxima que permitimos os cores de chegarem. Isso vai depender se você tem uma boa solução de resfriamento. Seja um AIO com resfriamento líquido ou um <a href="https://noctua.at/en/nh-d15-g2"target="_blank" rel="noopener"><strong>Noctua NH-D15 G2</strong></a> que é o que eu uso. Ventoinhas da Noctua são hiper silenciosas, eu quase nunca ouço e tem menos manutenção e possibilidade de falhas do que refriamento líquido. Eu prefiro menos peças móveis pra garantir.</p>
<p>[<img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4d10atf2k9rb5pntb9eec5uzqk7u" alt="Noctua NH-D15 G2"  loading="lazy" /></p>
<p>Agora esses <strong>Level 1 a 5</strong> são as tunagens de PPT/TDC/EDC que eu falei, mas já pré-definidas e testadas pelo fabricante. 1 é menos agressivo, o mais estável já 5 é o mais agressivo, pode ser instável. O certo é começar em <strong>90 level 5</strong> e ir diminuindo até achar o mais estável pro seu sistema. Isso vai variar, por exemplo, quando mais RAM tiver, menos agressivo parece que pode ser, especialmente quanto mais rápido essa RAM for.</p>
<p>No meu caso, logo no boot do meu Manjaro Linux, já deu kernel panic, que se parece com algo assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f25kqct9urmereleunhurknw40d4" alt="kernel panic"  loading="lazy" /></p>
<p>Então voltei pra BIOS e fui testando diferentes níveis e, pra minha configuração, consegui chegar só até <strong>90 Level 2</strong>. Esse tem sido estável e até agora não tive nenhum crash aleatório.</p>
<p>Além disso, recentemente (começo de 2025) fabricantes de placa-mãe soltaram atualizações de firmware que abriram acesso a uma funcionalidade chamada <strong>X3D Turbo Mode</strong>. Se você ver isso, não pense que é mais performance grátis. Na realidade, a menos que use a máquina mais pra games, vai sentir até que ela ficou mais lenta.</p>
<p>Como expliquei antes, Ryzen 9 tem 1 CCD (core complex), que é o die com 96MB de cache L3 (V-Cache). Turbo mode tenta forçar mais programas pro CCD que <strong>NÃO</strong> tem V-Cache (o de 32MB), e que por isso consegue ter um boost maior de clock mas tem mais latência. Isso é bom pra games - que normalmente não se beneficiam de ter mais cache e sim mais clock.</p>
<p>Mas ao mesmo tempo, programas que precisam do V-Cache vão acabar caindo mais vezes no Core com <strong>MENOS</strong> cache L3 e aí vão performar pior que antes. Exemplos disso são programas de modelagem 3D como Blender, justamente o que eu quero usar mais agora. Por isso teste, mas em princípio pode deixar desligado.</p>
<p>Essa opção no meu firmware se chama &ldquo;X3D Turbo Mode&rdquo; mas em outros lugares pode ser &ldquo;3D V-Cache Performance Optimizer&rdquo;, é a mesma coisa.</p>
<h2>XMP/EXPO RAM<span class="hx:absolute hx:-mt-20" id="xmpexpo-ram"></span>
    <a href="#xmpexpo-ram" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora, a RAM DDR5 boota no clock base de uns 4.8Ghz. Mas dependendo do modelo da RAM ela pode ir muito mais, no meu caso pra 6Ghz. Toda memória moderna também tem diversas configurações como a frequência, latência, voltagem e mais. De novo, não vale a pena tentar parametrizar isso na mão. É melhor usar perfis pré-definidos (profiles). A Intel chama esse suporte de <strong>XMP</strong> e a AMD chama de <strong>EXPO</strong>.</p>
<p>Esqueci de tirar foto da minha BIOS, mas essa foto do Google Images um pouco mais antiga é a mesma coisa. XMP/EXPO vem desligado (conservador) mas é só escolher o profile que já existe:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ypwo0rsbr1bmu6w6ujm7b1qyjlof" alt="XMP/EXPO Profile"  loading="lazy" /></p>
<p>Note que logo embaixo tem coisas como <strong>Low Latency Support</strong> e <strong>High Bandwidth Support</strong>. Teoricamente pra diminuir a latência e aumentar a banda de transferência. Tudo que soa muito bom, mas no meu caso não funciona. Se eu tento ligar esses dois, meu sistema não dá boot. Fui obrigado a limpar a CMOS pra reiniciar a BIOS sem isso.</p>
<p>O problema: possivelmente muita memória (96GB). Talvez se você tiver menos, 32GB, por aí, isso funcione, mas parece que quanto mais se tem, mais instável fica se tenta ir rápido demais.</p>
<p>Outro problema, em DDR5, quanto mais memória, mais lento é o Cold Boot, o boot inicial quando o PC estava desligado. A BIOS precisa fazer uma checagem nas memórias pra garantir esses parâmetros, tempo de resposta e tudo mais. E isso acontece em <strong>TODO</strong> boot. Pode levar alguns minutos e alguns podem achar até que a máquina travou, mas é só essa checagem.</p>
<p>Na BIOS tem uma opção chamada <strong>Memory Context Restore</strong> onde ele usa os parâmetros que gravou da última vez que fez essa checagem. Mas não recomendo, pra mim inclusive nem boota mais depois de habilitar isso. Teoricamente ele iria cortar o tempo de checagem e reusar dados antigos, mas esses dados podem não bater (os valores podem flutuar) e isso torna o sistema todo instável. Melhor esperar um minuto a mais no boot do que ficar com o sistema instável.</p>
<p>Em configurações avançadas de memória tem mais uma coisa que devemos mexer: <strong>DF C-States</strong>, que precisa estar <strong>DESABILITADO</strong>. Em teoria essa funcionalidade é pra reduzir a latência entre CPU e memória. Mas em sistemas com muita RAM (96GB ou mais como a minha) isso também pode causar instabilidade a custo de não muito ganho notável. Prefiro ficar estável nesse caso.</p>
<p>Outra configuração pra mudar é <strong>Power Supply Idle Control</strong> tem que mudar pra &ldquo;Typical Current Idle&rdquo;, e até agora (pode surgir um update de correção da firmware), é mandatório pra processadores Ryzen (mesmo AM5). isso previne estado de dormir &ldquo;Deep Power State&rdquo;, o que pode causar kernel panics aleatórios.</p>
<p>Mesma coisa pra <strong>Global C-State Control</strong> que também precisa desabilitar pelo mesmo motivo: evitar &ldquo;deep sleep states&rdquo; (C6/C10) quando a CPU fica em &ldquo;idle&rdquo; (sem fazer nada) por muito tempo. Essas funcionalidades existem pra economizar energia, mas parece que AMD tem bugs quando a CPU é forçada a esse estado e quando acorda, volta instável ou crasheando. Então ele vai consumir mais energia quando não tá fazendo nada (por volta de menos de 20W, em vez de perto de zero), mas pelo menos não vai ficar instável. Se quiser economizar energia, melhor é dar shut-down e desligar a força mesmo, quando não estiver usando.</p>
<h2>Resumo da BIOS (pra mim)<span class="hx:absolute hx:-mt-20" id="resumo-da-bios-pra-mim"></span>
    <a href="#resumo-da-bios-pra-mim" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>PBO Enhancement: <strong>90 Level 2</strong> (clock boost um pouco mais agressivo que o default)</li>
<li>Power Supply Idle Control: <strong>Typical Current Idle</strong> (prevenir o bug de crash em idle)</li>
<li>Global C-State Control: <strong>Desabilitado</strong> (evita dormir profundo, causando instabilidade)</li>
<li>DF C-States: <strong>Desabilitado</strong> (também evita dormir, melhorando latência da Infinity Fabric)</li>
<li>XMP/EXPO: <strong>Profile XMP1</strong> se tiver (aumenta clock da memória)</li>
</ul>
<p>Tem 2 coisas importantes, especialmente se estiver usando GPUs discretas:</p>
<ul>
<li>Above 4G Decoding e Re-Size Bar Support. No meu caso ambas estão ligadas por padrão, se na sua estiver desligada, ligue.</li>
</ul>
<p>No Linux, dá pra checar se está ligada. Rode o seguinte:</p>
<p><code>sudo lspci -vv | grep -A 15 VGA</code></p>
<p>Tem que aparecer &ldquo;Resizable BAR&rdquo; assim:</p>
<p><code>Capabilities: [XX] PCI Express, ... Resizable BAR: Supported (Enabled)</code></p>
<p>Se não aparecer, vá pra BIOS e procure pra habilitar. Sem isso você não está usando o máximo da sua placa de video. Acho que em iGPUs (que são GPUs que já vem embutida junto com a CPU) aí não tem essa opção mesmo.</p>
<h2>Kernel Linux-Zen<span class="hx:absolute hx:-mt-20" id="kernel-linux-zen"></span>
    <a href="#kernel-linux-zen" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em sempre estive usando a kernel padrão LTS mais estável, agora era Linux66, se não me engano. Mas lembrei que posso usar a kernel Zen, que é a mesma kernel padrão do Arch com tunings de performance pra melhorar latência, especialmente em multi-thread. Ele muda pro scheduler CFS com melhorias. Teoricamente, melhora a responsividade geral do sistema. Acho que vale a pena usar. No Arch/Manjaro é assim:</p>
<p><code>yay -S linux-zen-versioned-bin linux-zen-versioned-headers-bin</code></p>
<p>Isso vai desinstalar a kernel antiga e, se estiver usando GPUs NVIDIA, também vai desinstalar o pacote de drivers. Por isso precisa instalar o seguinte pacote também:</p>
<p><code>yay -S nvidia-dkms</code></p>
<p>E depois, com tudo instalado, rodar o seguinte comando:</p>
<p><code>sudo dkms autoinstall</code></p>
<p>Isso vai instalar de novo os binários dos drivers da nvidia pro novo kernel Zen. No caso do Manjaro, não precisa mexer no GRUB porque no meu caso ele está configurado pra <code>GRUB_DEFAULT=saved</code> em <code>/etc/default/grub</code>. Isso significa que quando reboota, ele vai gravar qual kernel eu escolhi por último, e no próximo reboot, se eu não escolher nada, vai carregar de novo o último kernel que eu escolhi. Prefiro assim do que deixar &ldquo;hard-coded&rdquo; qual kernel é o padrão.</p>
<p>Feito isso, no próximo boot só checar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ uname -r
6.14.2-zen1-1-zen</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Outra coisa é checar se <strong>CPPC</strong> está ativo. Isso é <strong>Collaborative Processor Performance Control</strong> que é uma funcionalidade que expõe pra kernel do OS um ranking de cores (qual tem boost mais alto, quem tem mais cache, etc). Lembra o X3D Turbo Mode que falei antes? Mesmo conceito: pra facilitar o OS escolher pra qual core mandar qual programa pra rodar melhor.</p>
<p>Nem todo firmware de placa-mãe expõe isso como uma opção, no caso do meu X670E da Gigabyte, ele não existe. Significa que provavelmente já vem ligado por padrão. Pra checar se está ligado, basta ir no terminal do Linux e rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cat /sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_highest_perf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se voltar algum resultado, é porque está ligado e o firmware está informando pra kernel sobre as características de cada core, assim o scheduler tem mais informação pra gerenciar pra onde mandar cada thread.</p>
<p>Outra forma de checar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ls /sys/devices/system/cpu/cpu0/cpufreq/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Precisa retornar uma lista contendo nomes como estes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>amd_pstate_highest_perf
amd_pstate_nominal_perf
amd_pstate_preferred_core</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Que, novamente, é sinal que o Linux está reconhecendo CPPC direito.</p>
<p>O próprio Manjaro, além das kernels estáveis, tem as versões &ldquo;linux-rt&rdquo; que é pra sistemas real-time. Tecnicamente, não só é pra ter latência mais baixa, mas sim latências estáveis e mais previsíveis. <strong>NÃO USE ESSE TIPO DE KERNEL</strong>. Isso é pra sistemas de fábrica ou coisas assim que precisam de determinismo do processamento e não tem tolerância pra latência aleatória. Não significa que vai rodar mais responsivo, pode até ser que rode mais lento, mas o importante é ser um lento previsível.</p>
<p>Outros podem falar da kernel <strong>Linux Liquorix</strong>, que muitos comparam com a Zen. Mas enquanto a Zen é baseada no próprio kernel que o Arch usa, o do Liquorix é baseada na versão de Debian/Ubuntu. Então não é recomendado usar no Arch/Manjaro. Além disso ele é mais experimental e teoricamente menos estável também. Use se estiver num Ubuntu ou derivados da vida. Em Arch, melhor ficar no Zen.</p>
<h2>check_ryzen_perf.sh<span class="hx:absolute hx:-mt-20" id="check_ryzen_perfsh"></span>
    <a href="#check_ryzen_perfsh" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se tiver um sistema Ryzen, copie o seguinte script e rode na sua máquina:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🔍 Verifying Ryzen 7950X3D Tuning on Linux&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPU Driver: &#34;</span>
</span></span><span class="line"><span class="cl">grep . /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ EPP Mode: &#34;</span>
</span></span><span class="line"><span class="cl">cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPPC Detected: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_highest_perf <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Preferred Core Ranking: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_prefcore_ranking <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Running Kernel: &#34;</span>
</span></span><span class="line"><span class="cl">uname -r
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;\n✅ All key Linux-side Ryzen tuning options appear active.\n&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Na minha máquina ele agora dá este resultado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>🔍 Verifying Ryzen 7950X3D Tuning on Linux
✅ CPU Driver: amd-pstate-epp
✅ EPP Mode: performance
✅ CPPC Detected: Yes
✅ Preferred Core Ranking: Yes
✅ Running Kernel: 6.14.2-zen1-1-zen

✅ All key Linux-side Ryzen tuning options appear active.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dizendo que, teoricamente, tudo de performance parece estar ativo e funcionando. Caso um deles não esteja, pergunte ao ChatGPT o que fazer pra sua configuração.</p>
<h2>Checar versão da BIOS<span class="hx:absolute hx:-mt-20" id="checar-versão-da-bios"></span>
    <a href="#checar-vers%c3%a3o-da-bios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Muitos fabricantes de placa-mãe oferecem software, pra Windows, pra automaticamente baixar e atualizar o firmware. Mas no caso da Gigabyte, não tem suporte pra Linux. Pra checar que versão está instalada agora, sem precisar entrar na BIOS, no Linux tem este comando:</p>
<p><code>sudo dmidecode -s bios-version</code></p>
<p>No meu caso, agora volta F33 (versão de março de 2025). Agora, pra comparar se essa é a versão mais nova, só indo manualmente na porcaria do site da Gigabyte que, no meu caso, é <a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support"target="_blank" rel="noopener">este link</a>. Pro seu modelo específico, e fabricante, lógico, esse link vai variar.</p>
<p><a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-1x/support#support-dl"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/72l1if05q22u1ogowfzws9rtz95j" alt="x670e support page"  loading="lazy" /></a></p>
<p>Pra piorar, parece que o conteúdo é carregado assincronamente por javascript, então se tentar usar <code>wget</code>ou <code>curl</code> pela linha de comando, não vai ter carregado o conteúdo ainda.</p>
<p>Resolvi fazer um script idiota pra resolver isso. Primeiro, caso não use <code>mise</code> como eu (veja meu projeto no Github <a href="https://github.com/akitaonrails/omakub-mj"target="_blank" rel="noopener">Omakub-MJ</a> pra ter um setup igual o meu), instale Node manualmente assim:</p>
<p><code>sudo pacman -S nodejs npm</code></p>
<p>Crie algum diretório pro projeto e de lá faça:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>npm init -y
npm install -g playwright
npx playwright install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crie um arquivo <code>check_bios_playwright.js</code> com este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">chromium</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;playwright&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">chromium</span><span class="p">.</span><span class="nx">launch</span><span class="p">({</span> <span class="nx">headless</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Load support page
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s1">&#39;domcontentloaded&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Click the BIOS tab manually (it triggers JS to fetch content)
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;id=bios-count&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Wait for BIOS version element to load
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">60000</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Grab the first BIOS version listed
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">biosVersion</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$eval</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="nx">el</span> <span class="p">=&gt;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;🌐 Latest BIOS Version on Gigabyte site:&#39;</span><span class="p">,</span> <span class="nx">biosVersion</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora crie um script bash <code>check_bios_update.sh</code> com este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get directory of this script (absolute path)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SCRIPT_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the Playwright JS script in the same directory</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span>node <span class="s2">&#34;</span><span class="nv">$SCRIPT_DIR</span><span class="s2">/check_bios_playwright.js&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🖥  Current BIOS: </span><span class="nv">$CURRENT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🌐 Latest BIOS:  </span><span class="nv">$LATEST</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;✅ You are up to date.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;⬆  BIOS update available!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Faça um <code>chmod +x</code> pra tornar executável e, se tudo der certo, terá uma resposta como esta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[sudo] password for akitaonrails:
🖥  Current BIOS: F33
🌐 Latest BIOS:  F33
✅ You are up to date.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É um tiro de canhão pra matar uma mosca, mas isso vai usar o Chromium headless pra puxar a página, carregar o javascript e aí usar seletores de CSS pra achar a versão da BIOS, comparar com o da sua máquina e ver se está atualizado.</p>
<p>Caso não esteja, não tem jeito, tem que ir manualmente no site, baixar o arquivo ZIP, extrair num pen drive e bootar na BIOS pra atualizar manualmente. Mas pelo menos consigo checar rapidamente de tempos em tempos só com uma linha de comando agora.</p>
<h2>Checagem automática de versão de BIOS<span class="hx:absolute hx:-mt-20" id="checagem-automática-de-versão-de-bios"></span>
    <a href="#checagem-autom%c3%a1tica-de-vers%c3%a3o-de-bios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Resolvi que não quero lembrar rodar esse script manualmente, então a melhor coisa é criar um serviço systemd de usuário, e checar automaticamente toda semana. Se tiver versão nova, criar uma notificação ao GNOME pra eu poder ver visualmente e automaticamente mandar abrir a página de download pra mim.</p>
<p>Primeiro, preciso fazer meu usuário ter acesso ao comando dmidecode pra checar a versão atual da minha BIOS sem precisar digitar senha com <code>sudo</code>, pra isso vamos criar uma nova regra de sudoers assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo visudo -f /etc/sudoers.d/bios-check</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Vai abrir um arquivo vazio onde preencho assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo /usr/sbin/dmidecode -s bios-version</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E não esquecer de colocar a permissão correta nesse arquivo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod 0440 /etc/sudoers.d/bios-check</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com isso não vai mais pedir senha pra rodar esse comando, já que vamos colocar num serviço em background rodando automaticamente. Obviamente não é seguro permitir rodar <code>sudo</code> sem senha, por isso limitamos exclusivamente pra permitir isso somente nesse binário específico e nada mais.</p>
<p>No mesmo diretórios dos outros arquivos com playwright instalado e tudo mais, crio um <code>bios-update-check.sh</code> com este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Path to your Playwright BIOS scraper script</span>
</span></span><span class="line"><span class="cl"><span class="nv">JS_CHECKER</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/bios-check/check_bios_playwright.js&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NODE_BIN</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>which node<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># URL of your motherboard&#39;s BIOS support page</span>
</span></span><span class="line"><span class="cl"><span class="nv">URL</span><span class="o">=</span><span class="s2">&#34;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support#support-dl-bios&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get latest BIOS version from Gigabyte site</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span><span class="nv">$NODE_BIN</span> <span class="s2">&#34;</span><span class="nv">$JS_CHECKER</span><span class="s2">&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -z <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send <span class="s2">&#34;BIOS Check&#34;</span> <span class="s2">&#34;⚠️ Failed to fetch latest BIOS version from Gigabyte&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send -u normal <span class="s2">&#34;🔔 BIOS Update Available!&#34;</span> <span class="s2">&#34;Current: </span><span class="nv">$CURRENT</span><span class="s2"> → Latest: </span><span class="nv">$LATEST</span><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Opening the download page...&#34;</span> --app-name<span class="o">=</span><span class="s2">&#34;BIOS Checker&#34;</span>
</span></span><span class="line"><span class="cl">  xdg-open <span class="s2">&#34;</span><span class="nv">$URL</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É a mesma coisa do script anterior mas agora usando <code>notify-send</code> pra mandar mensagens pro Gnome (em KDE é diferente) e usando <code>xdg-open</code> pra abrir a URL no navegador default do sistema. Configure diferente pro seu sistema. Não esquecer de tornar executável e testar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>chmod &#43;x ~/.local/bin/bios-update-check.sh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora é criar o serviço systemd pro meu usuário (diferente de criar um pra sistema que roda com mais permissões. Só preciso disso rodando no nível do meu usuário), então crio o arquivo <code>~/.config/systemd/user/bios-check.service</code> com o seguinte conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Weekly BIOS version checker

[Service]
Type=oneshot
ExecStart=/home/akitaonrails/.local/bin/bios-check/bios-update-check.sh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora o serviço de timer pra rodar toda semana chamado <code>~/.config/systemd/user/bios-check.timer</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Run BIOS version check weekly

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=default.target</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E habilitar o timer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>systemctl --user daemon-reexec
systemctl --user daemon-reload
systemctl --user enable --now bios-check.timer</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por convenção no Systemd, um timer [blabla].timer vai executar o serviço com mesmo nome [blabla].service. Por isso precisa dos dois. Se você habilitar o serviço direto, ele vai rodar só uma vez e sair. Pra saber se tá ativado certo faça o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>✦ ❯ systemctl --user list-timers

NEXT                          LEFT LAST PASSED UNIT             ACTIVATES
Mon 2025-04-21 00:00:00 -03 2 days -         - bios-check.timer bios-check.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que você pode automatizar algumas coisas no seu sistema e aproveitar seu Gnome melhor.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Só comecei essa jornada desta vez porque me ocorreu que fazia meses que eu não atualizava o firmware da placa-mãe e aí aproveitei pra tunar algumas coisas que não tinha feito antes. Coisas como PBO estava no automático (então eu estava deixando performance pra trás, lembro que da última vez tinha ficado instável mas eu não fui atrás pra saber porque), coisas como XMP/EXPO já tava ligado. Mas agora eu acho que realmente liguei tudo que podia.</p>
<p>Também fazia anos que eu não experimentava a kernel Linux-Zen, então já aproveitei pra testar agora. E como falei, por enquanto tá estável. Como comecei a aprender Blender e puxar mais CPU e memória, achei que valia a pena gastar um tempo tentando extrair o máximo da minha máquina. Modelagem 3D é totalmente CPU/RAM, mais que GPU.</p>
<p>Ah sim, falando em Blender, uma última coisa. Lembra que meu Ryzen tem 1 CCD, que é aquele 1 die com 96MB de cache L3. Melhor coisa a fazer é criar afinidade do Blender com esse die e evitar que ele caia no core com só 32MB de cache L3.</p>
<p>No Linux, pra fazer isso, é esta linha de comando:</p>
<p><code>taskset -c 0-15 blender-4.3 &amp;</code></p>
<p>O comando taskset vai dar dica pro scheduler que o blender deve ficar nas threads de 0 a 15 (onde tem V-Cache) e não de 16 a 31 (que só tem 32MB). Dessa forma, em teoria, estou dando as maiores chances ao Blender de funcionar na melhor performance possível nesta máquina. Programas pouco importantes como o navegador, terminal, se cair no core de 32MB, não tem problema.</p>
<p>Eu faço a mesma coisa no libvirt/qemu quando rodo Windows emulado: eu configuro o libvirt pra subir a VM atrelado ao CCD de 96MB e deixo o outro core pro Linux host. Aliás, falando em BIOS, pra rodar VMs tem que habilitar SVM e IOMMU, que costumam vir desligados porque suporte a máquinas virtuais rouba um pouco da performance da CPU. Máxima performance é com eles <strong>desligados</strong>. Se você não vai usar VMs, deixe desligado.</p>
<p>E se alguém notou que estou rodando explicitamente o Blender 4.3 em vez do mais novo Blender 4.4 é porque alguns addons ainda não funcionam na versão mais nova, então é melhor ficar uma versão pra trás pra ter mais compatibilidade com addons que não atualizam rápido.</p>
]]></content:encoded><category>hardware</category><category>performance</category><category>linux</category></item><item><title>Omakub pra Manjaro e ArchWSL</title><link>https://akitaonrails.github.io/2024/10/30/omakub-pra-manjaro-e-archwsl/</link><guid isPermaLink="true">https://akitaonrails.github.io/2024/10/30/omakub-pra-manjaro-e-archwsl/</guid><pubDate>Wed, 30 Oct 2024 12:41:00 -0300</pubDate><description>&lt;p&gt;Algum tempo atrás o DHH lançou uma ferramenta open source chamada &lt;a href="https://omakub.org"target="_blank" rel="noopener"&gt;Omakub&lt;/a&gt; que instala diversas ferramentas e configura um ambiente de desenvolvimento moderno em Ubuntu. Ele vai além de coisas só de terminal e também configura vários aspectos do ambiente GNOME. Vale a pena experimentar, nem que seja numa Virtual Machine, pra ver como fica.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1ddbjucxzkah963tou557907bkmr" alt="Omakub - demo" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Eu gostei bastante das escolhas do DHH, ele sempre foi um cara de Mac, eu também. Mas eu migrei de volta pra Linux quase 10 anos atrás. O motivo foi porque eu evangelizava tech pra desenvolvedores júniors e no Brasil ficou inviável incentivar Macs. E não queria que parecesse que sem usar Macs você estaria automaticamente em grande desvantagem. Então passei a usar exclusivamente Linux e no blog fiz vários posts explicando sobre isso, como estes:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Algum tempo atrás o DHH lançou uma ferramenta open source chamada <a href="https://omakub.org"target="_blank" rel="noopener">Omakub</a> que instala diversas ferramentas e configura um ambiente de desenvolvimento moderno em Ubuntu. Ele vai além de coisas só de terminal e também configura vários aspectos do ambiente GNOME. Vale a pena experimentar, nem que seja numa Virtual Machine, pra ver como fica.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1ddbjucxzkah963tou557907bkmr" alt="Omakub - demo"  loading="lazy" /></p>
<p>Eu gostei bastante das escolhas do DHH, ele sempre foi um cara de Mac, eu também. Mas eu migrei de volta pra Linux quase 10 anos atrás. O motivo foi porque eu evangelizava tech pra desenvolvedores júniors e no Brasil ficou inviável incentivar Macs. E não queria que parecesse que sem usar Macs você estaria automaticamente em grande desvantagem. Então passei a usar exclusivamente Linux e no blog fiz vários posts explicando sobre isso, como estes:</p>
<ul>
<li><a href="https://www.akitaonrails.com/2015/11/10/situacao-brasil-no-macs-for-the-rest-of-us"target="_blank" rel="noopener">2015 - Situação Brasil: No Macs for the Rest of Us</a></li>
<li><a href="https://www.akitaonrails.com/2016/04/12/o-ano-do-linux-no-desktop-pela-microsoft"target="_blank" rel="noopener">2016 - The Year of Linux on the Desktop, by Microsoft ??</a></li>
<li><a href="https://www.akitaonrails.com/2017/01/12/off-topic-por-que-estamos-todos-migrando-para-longe-da-apple"target="_blank" rel="noopener">2017 - Off-Topic - Why are we all moving away from Apple?</a></li>
</ul>
<p>Sem contar os diversos videos com setups que fiz no meu canal, como estes:</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=epiyExCyb2s"target="_blank" rel="noopener">O Guia Definitivo de Ubuntu pra Devs Iniciantes</a></li>
<li><a href="https://www.youtube.com/watch?v=sjrW74Hx5Po"target="_blank" rel="noopener">O Melhor Setup Dev com Arch e WSL2</a></li>
</ul>
<p>Em 2024, com exceção se precisa desenvolver apps pra iOS ou coisas específicas de .NET, usar distros Linux é superior a usar Windows ou MacOS. Antigamente eu diria um &ldquo;mas&rdquo; se quiser jogar, aí seria obrigado a fazer dual-boot com Windows. Mas até isso Linux já superou Macs graças aos esforços da Valve e as comunidades open source em torno de projetos como Proton. Hoje, uma quantidade absurda de jogos novos de PC rodam liso em Linux, basta instalar Steam e já era.</p>
<p>Não é o tema do artigo, mas já que toquei no assunto, estava justamente  assistindo um review do <a href="https://bazzite.gg/"target="_blank" rel="noopener">BazziteOS</a> rodando no <strong>Asus ROG Ally</strong> (meu favorito), criando um Steam Deck ainda mais potente. É Fedora rodando num handheld, com uma interface tão boa que ninguém sabe que é Linux por baixo, rodando jogos com mesma performance que em Windows. É impressionante mesmo. Quem ficou totalmente pra trás em games foi a Apple.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/OwWRCrGoXV0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Isso tudo dito, o DHH mudou pra um hardware muito bacana de uma empresa pequena e nova chamada <a href="https://frame.work/"target="_blank" rel="noopener">Framework</a>, que faz notebooks modulares. A idéia é não precisar jogar tudo fora quando quiser fazer upgrades de hardware. Só trocar o módulo pra um mais novo. E não só coisas simples como trocar uma porta USB-A por uma USB-C, mas até trocar GPU, trocar placa-mãe, monitor.</p>
<p>É um conceito muito bacana que eu gostaria que mais fabricantes se comprometessem a fazer. Evita lixo eletrônico desnecessário, pra começar, muito mais fácil de reparar partes, sem ter que jogar tudo fora, como acontece com um Macbook. Vi isso pela primeira vez no canal do Linus Tech Tips porque o Linus Sebastian se tornou um dos primeiros investidores anjo deles.</p>
<p><img src="https://images.prismic.io/frameworkmarketplace/Zv77LrVsGrYSwWcu_3A-marketplace-parts-2x.png?auto=format,compress" alt="Framework"  loading="lazy" /></p>
<p>A maioria dos fabricantes que suportam Linux, costumam oferecer algum tipo de suporte a Ubuntu. Ele acabou meio que se tornando o &ldquo;padrão&rdquo; de Linux pra usuários comuns. Na dúvida, instale Ubuntu. E não é uma má escolha. Se escolher Ubuntu, use o projeto <a href="https://github.com/basecamp/omakub"target="_blank" rel="noopener">Omakub</a> oficial do DHH, que vai configurar tudo bonitinho pra você.</p>
<p>Eu não gosto de Ubuntu, é uma escolha pessoal (e cada um de Linux vai ter sua própria opção. Já sei que o primeiro comentário vai ser alguém falando de porque todo mundo não usa NixOS de uma vez &hellip;). Não precisa ficar preocupado nem se sentir mal por usar Ubuntu, não tem absolutamente nada de errado com ele. Escolher distros é que nem escolher marca de carro.  Qual a diferença de Ferrari, Lambo, Porsche, McLaren? Você vai achar ruim se prefere Lambo e eu prefiro McLaren?</p>
<p>Além disso, puristas vão dizer que é pra ficar no Arch Linux puro, mas faz anos que prefiro <a href="https://manjaro.org/products/download/x86"target="_blank" rel="noopener"><strong>Manjaro GNOME</strong></a> no meu PC desktop e em notebook prefiro ficar com Windows 11 mesmo e WSL2 rodando <a href="https://github.com/yuk7/ArchWSL"target="_blank" rel="noopener"><strong>ArchWSL</strong></a>. Ainda recomendo brincar com Arch puro pra aprender. Mas depois de tantos anos, como é meu caso, prefiro um instalador que faz igual Ubuntu: vai do zero ao desktop em poucos cliques.</p>
<p>Então não teve jeito, fui obrigado a fazer um fork e ajustar o Omakub num novo projeto, o <a href="https://github.com/akitaonrails/omakub-mj"target="_blank" rel="noopener"><strong>OMAKUB-MJ</strong></a> (o nome não tá muito correto, deveria ser algo como OMAKAMJ ou algo assim que seria Omakase com Manjaro, mas fica feio, mandem sugestões nos comentários). Migrei todos os comandos de Apt pra Yay, além de diversos outros ajustes. Rodei no meu PC principal (depois de ter testado bastante numa VM, claro) e agora ficou bonitão assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8sq62mkbc4kxuzqk5opej275xpuc" alt="Omakub-MJ"  loading="lazy" /></p>
<p>Igual à versão de Ubuntu, ele ativa o tema Tokyo Night, mas dá pra mudar pra vários outros temas. Depois de instalado, vai ter um comando <code>omakub</code> no terminal que abre um menu com várias opções, incluindo mudar tamanho das fontes ou trocar temas:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7qvbv50ipzifo3cv5bhe3lb64kq8" alt="Omakub trocar tema"  loading="lazy" />
Tem <strong>MUITAS</strong> coisas legais pré-instaladas, por isso repito pra ler <a href="https://manual.omakub.org/"target="_blank" rel="noopener">o manual</a> que é muito bonito, bem escrito, e eu não mudei nenhuma das configurações principais (só o zellij, já explico), então tudo que vale pra versão Ubuntu também vale pra Manjaro. E eu sei que a maioria de vocês tem preguiça de abrir o manual, mas uma coisa que o DHH sabe fazer é uma documentação atraente:</p>
<p><a href="https://manual.omakub.org/1/read#leaf_29"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xdsu6gghf5ncic9rh852hv1c7bcz" alt="Manual Omakub"  loading="lazy" /></a></p>
<h3>Migrando pra Manjaro<span class="hx:absolute hx:-mt-20" id="migrando-pra-manjaro"></span>
    <a href="#migrando-pra-manjaro" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Enfim, pra migrar de Ubuntu pra Manjaro, a primeira coisa que fiz foi trocar comandos apt por yay, coisa simples assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">- sudo apt install -y alacritty
</span></span></span><span class="line"><span class="cl"><span class="gi">+ yay -S alacritty --noconfirm
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas rapidamente encontramos as partes de porque eu prefiro o <strong>YAY</strong> do que <strong>APT</strong> como gerenciador de pacotes:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">-  sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
</span></span></span><span class="line"><span class="cl"><span class="gd">-  echo &#34;deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main&#34; \| sudo tee /etc/apt/sources.list.d/brave-browser-release.list
</span></span></span><span class="line"><span class="cl"><span class="gd">-  sudo apt update -y
</span></span></span><span class="line"><span class="cl"><span class="gd">-  sudo apt install -y brave-browser
</span></span></span><span class="line"><span class="cl"><span class="gi">+  yay -S brave-browser --noconfirm
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Em Ubuntu, tudo que a própria Canonical não controla, fica em repositórios de terceiros, então precisamos explicitamente adicionar esses repositórios antes de conseguir instalar seus pacotes.</p>
<p>Em Arch é a mesma coisa, com o Pacman só conseguimos instalar os pacotes oficiais que o grupo do Arch controla e testa. Mas existe um repositório de terceiros chamado AUR, o Arch User Repository. Daí instalamos ferramentas como Pamac (que vem instalado em muitas distros) ou o Yay (que eu gosto mais). Por baixo eles rodam Pacman, mas oferecem um único comando que serve pros dois: instalar pacotes oficiais e instalar pacotes do AUR, com os mesmos comandos. Então não tem que ficar manualmente gerenciando trocentos repositórios externos.</p>
<p>Foi bem fácil converter porque no AUR tem tudo que precisamos. É muito raro precisar de um software e não achar no AUR. Além disso fiz outra modificação: eu prefiro manter aplicativos gráficos como Spotify ou Discord instalados via <strong>Flatpak</strong>.</p>
<p>Não é nem por razões de segurança. Prefiro que aplicativos de terceiros mexam no sistema operacional abaixo o menos possível. E muitos deles precisam de várias dependências. Quando preciso fazer um upgrade geral, demora mais porque vem um monte de pacotes de dependência junto que eu nem precisava. Pelo menos com Flatpak, as dependências ficam isoladas em imagens de containers.</p>
<p>Enfim, quando preciso atualizar o sistema só faço <code>yay -Syyuu</code> e quando quero atualizar aplicativos, basta fazer <code>flatpak update -y</code>, assim posso rodar separadamente. As únicas exceções são editores de texto como Visual Studio Code, nesse caso prefiro que ele tenha acesso ao sistema de arquivos todo mesmo, e não ter que lidar com permissões de acesso em containers de Flatpak, por isso instalo via Yay.</p>
<h3>GNOME<span class="hx:absolute hx:-mt-20" id="gnome"></span>
    <a href="#gnome" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Adicionei dois aplicativos opcionais, o Bitwarden (o DHH prefere o 1password, ambos são bons) e o Discord. Aplicativos de Flatpak são fáceis de instalar, nem precisa estar num instalador como esse, mas coloquei só mais esses por conveniência pra mim mesmo. Obviamente você pode instalar depois o que quiser. Basta procurar com <code>flatpak search</code> e instalar com <code>flatpak install</code>.</p>
<p>No lado Gnome, o DHH escolheu apps como <a href="https://flameshot.org/"target="_blank" rel="noopener">Flameshot</a> pra substituir o app de screenshot do Gnome. Uma das vantagens é que depois de tirar a foto de tela, dá pra imediatamente editar a imagem antes de salvar. Pra ter preview rápido de imagens no gerenciador de arquivos Nautilus, ele escolheu o <a href="https://gitlab.gnome.org/GNOME/sushi"target="_blank" rel="noopener">Gnome Sushi</a>. Todas essas opções são muito boas e você pode ver tudo que é instalado no desktop no diretório <a href="https://github.com/akitaonrails/omakub-mj/tree/master/install/desktop"target="_blank" rel="noopener">install/desktop</a>. Cada app é um script separado.</p>
<p>De todos, o único que realmente não conhecia é o <a href="https://play.google.com/store/apps/details?id=org.localsend.localsend_app&amp;hl=en"target="_blank" rel="noopener"><strong>Localsend</strong></a>. Mas agora já instalei no meu Linux, Windows, smartphone e tudo mais. É o jeito mais fácil de transferir arquivos entre máquinas na mesma rede. Igual o Airdrop de iPhone. Vale a pena ter instalado em tudo.</p>
<p><a href="https://localsend.org/pt-BR"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fnpyyqis498wth9do5qodt7xli0x" alt="Localsend"  loading="lazy" /></a></p>
<p>Outro app que eu não conhecia é o <a href="https://ulauncher.io/"target="_blank" rel="noopener">Ulauncher</a> que é um executador de aplicações, igual a barra de Spotlight de Mac. Basta apertar <code>Super/Tecla de Windows + Espaço</code> e ele abre uma barra onde podemos digitar o nome do app que queremos abrir:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ttbw39v9y0r17rw16x4s99p45epe" alt="Ulauncher"  loading="lazy" /></p>
<p>Se fosse só isso, não é muito útil porque em Gnome, só de apertar <code>Super</code> já dá pra digitar o nome de apps. Mas o interessante é que ele faz mais que executar apps. Depois fuce a <a href="https://ext.ulauncher.io/"target="_blank" rel="noopener">Página de Extensions</a> pro Ulauncher. Tem coisas como calculadoras, controlador de Spotify, pesquisador de arquivos, facilitador pra editores com Obsidian ou Notion, pesquisador de snippets de código pra VS Code, tradutores, integradores pra serviços de GPT e um dos meus favoritos, facilitador de Emojis:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dsjsfzdr6rddfrqqtlcmj3uu9891" alt="Ulauncher Emojis"  loading="lazy" /></p>
<p>O Omakub ajusta bastante coisa do Gnome. Em particular adiciona várias extensions que mudam o comportamento do ambiente gráfico:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ocm6xifnbygf4cagdb5icjusz1t6" alt="Gnome Extensions"  loading="lazy" /></p>
<p>Depois vale fuçar as configurações de um por um. Por exemplo, ele instala e já configura a extension <a href="https://extensions.gnome.org/extension/4548/tactile/"target="_blank" rel="noopener"><strong>Tactile</strong></a> que é provavelmente a melhor pra organizar as janelas em &ldquo;tiles&rdquo; ou &ldquo;azulejos&rdquo;, ou seja, pra facilitar alinhar uma janela em cima ou do lado da outra de forma proporcional. Ele faz isso pré-dividindo a tela em áreas e atribuindo letras a cada área, assim fica fácil de fazer uma janela se encaixar numa dessas áreas.</p>
<p>Por exemplo, com o comando <code>Super + T</code> aparece esse grid. Se digitar &ldquo;w&rdquo; duas vezes, minha janela de Alacritty se encaixa no topo, no meio, na área marcada com &ldquo;W&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ofqh86tf3l3p6eviesq1dhosbio2" alt="Tactile W"  loading="lazy" /></p>
<p>Normalmente, eu gosto de ter a janela aberta no meio do monitor, maximizado na vertical, sem esticar pros lados, especialmente pra ler ou digitar textos. Então posso fazer <code>Super + T</code> e logo depois <code>W e S</code> pra marcar duas áreas:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ijvlxzdvykxn12nbkvcwy73s5i6p" alt="Tactile W S"  loading="lazy" /></p>
<p>Mas também continuo podendo teclar <code>F11</code> ou <code>Super + seta pra cima</code> pra maximizar a janela na tela inteira. Ou usar as combinações clássicas de Windows como <code>Super + seta esquerda</code> ou <code>Super + seta direita</code> pra fazer a janela usar metade vertical da tela, desta forma:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jjznbiwblc0zdlfydrmdqqxuxwkk" alt="Tactile lado a lado"  loading="lazy" /></p>
<p>Vale fuçar a documentação e as configurações da extension Tactile pra dividir as áreas do jeito que mais gosta. Isso vai variar bastante caso use monitor secundário na vertical, ou use monitor Ultra Wide. E nesse caso é onde esse tipo de extensão vai fazer mais diferença pra manter suas janelas organizadas.</p>
<h3>Neovim e LazyVim<span class="hx:absolute hx:-mt-20" id="neovim-e-lazyvim"></span>
    <a href="#neovim-e-lazyvim" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu mencionei Visual Studio Code, e ele está aqui pra quem gosta, mas quem acompanha meus videos e artigos, sabe que sou usuário de Vim de longa data. E a comunidade Vim não pára quieta. Hoje em dia tem um fork mais moderno, o <strong>NEOVIM</strong>, que se tornou o mais usado.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/k00pryy0c676m4ryssrt7d48aqqr" alt="LazyVim"  loading="lazy" /></p>
<p>Em cima do NeoVim podemos instalar plugins manualmente e ficar ajustando configuração pela eternidade, ou usar configs já prontas. Até poucos meses atrás eu usava o LunarVim, mas ele meio que caiu em desuso. Depois passei a usar o <a href="https://astronvim.com/"target="_blank" rel="noopener">AstroNVIM</a>, que resolve muito bem, mas parece que a maioria das pessoas passou a convergir pro <a href="https://www.lazyvim.org/"target="_blank" rel="noopener"><strong>LazyVim</strong></a>. Confesso que fico em dúvida entre qual é o melhor, AstroNvim ou LazyVim, mas vou ficar com o LazyVim por enquanto.</p>
<p>No fundo vão usar muitos dos mesmos plugins, análise de código vai ter plugins pra LSP, navegação vai ter o mesmo neo-tree, o mesmo telescope e assim por diante. Por isso vai ser muito subjetivo mesmo. Acho que nenhum dos dois tem como errar.</p>
<p>Muita gente acha que esses pacotes são contra a filosofia de &ldquo;aprenda a configurar você mesmo&rdquo;. E de fato, se puder, recomendo mesmo que monte sua configuração do zero. Mas faça isso só uma vez, desfaça e use um desses pacotes. Na realidade, essa noção começa a falhar porque esses plugins, sejam eles escritos no antigo VimScript ou na mais moderna linguagem Lua, são programas completos. Pegue plugins como Neo-Tree ou Telescope. Você não fez nem nunca vai fazer nenhum deles do zero, isso é pra poucos.</p>
<p>Na prática, o NeoVim já poderia vir pré-instalado com eles, pra você não faria diferença, porque são modernos, bem escritos, customizáveis, e não ficam no seu caminho. Não vem porque a idéia toda de um Vim é ser hiper-leve, pra podermos usar rapidamente dentro de um container, ou conectando num outro servidor via SSH, sem GUI, sem dependências, se bugs de interface nem nada disso. Só no nosso desktop, em ambiente controlado, gostaríamos dele tão pesado e funcional quanto um VS Code.</p>
<p>Depois tente dar uma olhada na configuração de um LazyVim, é muito mais coisa do que uma única pessoa deveria se preocupar. E saem plugins novos todos os dias, plugins antigos deixam de ser usados, não tem como você ficar de olho em tudo isso. Melhor ser um esforço dividido pela comunidade e a gente ter a oportunidade de usar o que tem de melhor só dando um update.</p>
<p>Sinceramente, seja Astronvim ou LazyVim, eu não tenho nem vontade e nem disposição de tentar montar do zero uma config como essa. E o que eles me oferecem &ldquo;out-of-the-box&rdquo; está perfeito já, eu não preciso ficar mudando. Basta usar. Você tem que ser muito chato pra achar que pode fazer melhor. Se achar isso, faça um pull request pra esses projetos então. Será tempo melhor gasto.</p>
<h3>ZelliJ<span class="hx:absolute hx:-mt-20" id="zellij"></span>
    <a href="#zellij" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu sou usuário de Tmux de longa data. E meus amigos usavam o antigo Screen, antes disso ainda. Eu lembro quando falávamos do Tmux como o software mais moderno. Agora ele é o legado e o mais moderno é o ZelliJ.</p>
<p>Todos eles são gerenciadores de sessão. Pense quando você se conecta num servidor remoto via SSH e fica trabalhando nele. Daí temos uma pane na internet ou cai a luz. O que acontece? Não só a conexão fecha, mas seja lá o que você estava rodando remotamente vai crashear porque o app está atrelado - é um filho - da sua shell (bash, zsh). E sua shell estava atrelada à sessão SSH. Quando a conexão cai, o SSH mata o shell que mata sua app. Você perde o que estava fazendo ou, pior, se o programa estava no meio, ele vai crashear no meio e pode corromper alguma coisa.</p>
<p>Em vez de correr esse risco, quando logar via SSH, imediatamente abra uma sessão de Screen, Tmux ou Zellij. Agora o shell vai abrir atrelado ao Zellij. Se a conexão cair e o SSH fechar, o ZelliJ vai continuar rodando, ele fica atrelado a um daemon na máquina remota. Seu shell e seu app ficam atrelados ao ZelliJ e não à sua sessão SSH.</p>
<p>Quando a luz voltar ou a internet voltar, basta conectar via SSH de novo e rodar o comando <code>zellij attach</code> ou <code>tmux attach</code>  e ele vai &ldquo;re-anexar&rdquo; à última sessão de apps, e você vai voltar ao seu shell, como se nunca tivesse saido de lá. Quaisquer apps que estivessem rodando e processando, ia continuar até acabar, normalmente, como qualquer app em background. Essa é a vantagem principal de um gerenciador como ZelliJ ou Tmux.</p>
<p>Mesmo se não estiver trabalhando numa máquina remota via SSH. Mesmo no seu desktop. Digamos que esteja brincando de editar configurações, instalar extensions e coisas assim. E do nada seu GNOME ou seu X resolvam dar pau, crashear e reiniciar. Todos os apps gráficos vão crashear junto, incluindo seu app de terminal, como Alacritty e junto com ele, qualquer shell e programa em linha de comando que estivesse rodando, como seu NeoVim, podendo perder código que estava editando e não salvou.</p>
<p>Não mais. Quando o X reiniciar, basta reabrir seu Alacritty e, mesma coisa, fazer um <code>zellij attach</code> pra voltar exatamente onde estava. Inclusive, se dentro do zellij você estava remotamente conectado a um servidor, monitorando com htop ou algo assim, nada vai fechar, vai continuar tudo conectado. Isso é mais uma camada de estabilidade pra você.</p>
<p>Veja este exemplo abaixo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/wmxxa8a061mg6ius1chgukb4k86m" alt="ZelliJ"  loading="lazy" /></p>
<p>O Omakub já configura o ícone do Alacritty pra abrir uma janela de ZelliJ. Se seguir a configuração padrão, teclando <code>Ctrl P D</code>, sendo &ldquo;P&rdquo; pra Painel e &ldquo;D&rdquo; pra &ldquo;D&quot;ividir a janela horizontalmente, dá pra abrir dois programas um em cima do outro.</p>
<p><strong>ATENÇÃO:</strong> este é o único app do Omakub que fui obrigado a mudar os shortcuts, por isso o que tem na documentação oficial não vai bater.</p>
<p>O problema é o seguinte: vários apps de terminal, como NeoVim, tem vários shortcuts de navegação. Se digitar &ldquo;Espaço + E&rdquo; vai abrir o painel de exploração do Neo-Tree na esquerda. Dá pra navegar por diretórios usando &ldquo;J&rdquo; ou &ldquo;K&rdquo; pra ir pra cima ou pra baixo e &ldquo;Enter&rdquo; pra abrir os diretórios ou abrir arquivos.</p>
<p>Quando o arquivo abre, ele abre num painel na direita. Mas como você navega entre os painéis? Basta digitar <code>Ctrl + H</code> ou <code>Ctrl + L</code> (lembrando que em Vim as &ldquo;setas&rdquo; são as telas da home row da mão direita <code>H J K L</code>). Só que se você estiver dentro do ZelliJ, isso não vai funcionar. Isso porque o ZelliJ em si usa o modificador <code>Ctrl</code> pra ele. Então ele engole o comando e não passa pro NeoVim.</p>
<p>Uma das soluções é que o ZelliJ, assim como NeoVim, tem &ldquo;modes&rdquo;. Vim tem modo normal, modo de edição e modo visual. ZelliJ tem vários modos, como modo normal, modo de scroll, modo de painel e cada modo tem seu sub-conjunto de shortcuts/combinações de teclas. Um desses modos é &ldquo;Locked&rdquo;. Podemos travar o zellij, daí usar o programa dentro do painel isoladamente, e lembrar de ficar &ldquo;desbloqueando&rdquo; o zellij. É meio chatinho de ficar fazendo isso.</p>
<p>Outra solução é o que eu fiz no Omakub-MJ. Eu adicionei as configurações do usuários de GitHub &ldquo;shoukoo&rdquo; que fez um artigo um tempo atrás chamado <a href="https://shoukoo.github.io/blog/zellij-love-neovim/"target="_blank" rel="noopener">ZelliJ 💓 NeoVim</a>. A configuração completa dele está <a href="https://github.com/shoukoo/dotfiles/blob/master/zellij.kdl"target="_blank" rel="noopener">neste arquivo</a>. Mas em resumo, ele cria um modo especial que batizou de &ldquo;modo tmux&rdquo;.</p>
<p>Pra entrar nesse modo, precisa digitar primeiro <code>Ctrl f</code> e na barra inferior, dá pra ver mudando de modo Normal pra Tmux:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ecr3jpesab4l2ilalp24vf7f9n4c" alt="zellij tmux"  loading="lazy" /></p>
<p>E agora sim, podemos digitar comandos como <code>P D</code> pra dividir painel. E fazendo isso, enquanto estiver no modo Normal mesmo, dentro do Neovim, combinações como <code>Ctrl H</code> ou <code>Ctrl L</code> vão funcionar normalmente. Win-win!</p>
<p>Só lembrar que, se na documentação oficial do zellij/omakub ele mandar digitar <code>Ctrl P W</code> pra abrir um painel flutuante, agora vai ser <code>Ctrl + F</code> e depois <code>P W</code>. E ele chama de &ldquo;modo tmux&rdquo; porque em Tmux, todo comando é depois de digitar <code>Ctrl + B</code> antes. No caso ele escolheu <code>Ctrl + F</code> pro ZelliJ, mas é arbitrário, poderia ser qualquer combinação inicial.</p>
<p>O resto continua funcionando igual. Pra navegar entre os painéis de ZelliJ, só digitar <code>Alt + H</code> ou <code>Alt + K</code> como no NeoVim, mas usando Alt em vez de Ctrl. Pra salvar a sessão atual e sair do ZelliJ, só fazer <code>Ctrl + F, O D</code> e quando voltar, só digitar <code>Ctrl + F, O W</code> pra listar as sessões salvas (sim, dá pra ter várias), e restaurar a que quiser com Enter.</p>
<p>Como o manual do Omakub recomenda, vale <a href="https://zellij.dev/documentation/"target="_blank" rel="noopener">estudar a documentação oficial do ZelliJ</a>.</p>
<h3>ASDF pra MISE<span class="hx:absolute hx:-mt-20" id="asdf-pra-mise"></span>
    <a href="#asdf-pra-mise" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Finalmente, uma das ferramentas que eu usei por mais anos foi o ASDF. Quem já programa em Node, Ruby, Python, etc já teve que usar algo como NVM, RVM ou RBenv, VENV ou Conda e assim por diante.</p>
<p>O mundo ideal é que todo mundo sempre usasse a versão mais nova de tudo, toda linguagem, toda biblioteca, tudo. Mas isso é inviável. Um bom programador vai acumular vários projetos na sua máquina, cada um vai estar numa versão diferente.</p>
<p>Ano retrasado ele trabalhou num projeto em Node na versão 16. Hoje está trabalhando num projeto novo na versão 22. Mas do nada, teve a necessidade de rodar esse projeto antigo. E agora? Ele atualizou tudo e só tem o node 22. Se desinstalar e instalar o 16 pra rodar o antigo, como fica o projeto novo? E as bibliotecas, como fica seu diretório de NPM? Uma zona completa.</p>
<p>Pra conseguir ter várias versões de node instaladas em paralelo na mesma máquina, recomenda-se usar NVM, que é o Node Version Manager.</p>
<p>Mas você é um programador poliglota. Trabalha em projetos em várias diferentes linguagens. Vai ter que aprender os comandos individuais de gerenciar versão pra cada linguagem? NVM pra Node. VENV pra Python. RVM pra Ruby, o que usa pra Java? Fodeu.</p>
<p>Pra isso criaram projetos como o antigo ASDF. Com ele era muito simples, bastava fazer <code>asdf install nodejs 14.21.3</code> pra instalar uma versão antiga de node e depois <code>asdf local nodejs 14.21.3</code> dentro do diretório do projeto antigo. Ele cria uma arquivo <code>.tool-versions</code> declarando a versão de nodejs pra esse diretório e quando entramos nele, o asdf detecta e já muda o que precisa pra poder usar a versão certa. Super conveniente.</p>
<p>Mais conveniente ainda porque podemos usar o mesmo conjunto de comandos pra qualquer linguagem. Basta instalar o plugin da linguagem com <code>asdf plugin add java</code> por exemplo. E ele suporta dezenas de linguagens, até as mais exóticas que você nunca ouviu falar. É o verdadeiro canivete suíço de gerenciamento de linguagens.</p>
<p>E esse seria o fim da história. Hoje mesmo, se você já usa ASDF, pode continuar usando, não tem nenhum problema com ele. Mas você conhece o povo de Rust: eles querem reescrever tudo em Rust. O Omakub mesmo instala vários substitutos como o &ldquo;exa&rdquo; que substitui &ldquo;ls&rdquo; e é feito em Rust. Tem vários assim.</p>
<p>No caso, existe o novo <a href="https://mise.jdx.dev/dev-tools/"target="_blank" rel="noopener"><strong>Mise</strong></a> de &ldquo;mise-en-place&rdquo;, pra quem já assistiu Masterchef. É exatamente a mesma coisa que ASDF só que feito em Rust. Inclusive ele é compatível com o mesmo arquivo <code>.tool-versions</code> que mencionei antes. Se não tiver, ele gera arquivo com nome próprio <code>.mise.local.toml</code> porque, claro, YAML ou JSON são antigos, o negócio é usar TOML &hellip;</p>
<p>Ele simplifica os comandos, mas não necessariamente acho que ficaram mais intuitivos. Mas é bem simples. Digamos que eu precise do tal Nodejs antigo, basta fazer <code>mise install node@14.21.3</code> ou direto <code>mise use node@14.21.3</code> pra instalar e já escolher pra usar.</p>
<p>Dá pra configurar versões locais pra cada diretório de projeto com <code>mise use --env local node@14.21.3</code>. E essa sintaxe de &ldquo;@&rdquo; é pra permitir instalar mais de uma versão num único comando, por exemplo <code>mise install ruby@3.1 nodejs@22.3.0</code>.</p>
<p>Mesma coisa como asdf, também podemos configurar a versão global padrão, caso não tenha arquivos como <code>.tool-versions</code> no diretório, fazendo <code>mise use --global nodejs@22.3.0</code>. Pra listar o que tem instalado, só fazer <code>mise ls</code>.</p>
<p>Enfim, vale <a href="https://mise.jdx.dev/getting-started.html"target="_blank" rel="noopener">estudar a documentação oficial</a>. No geral é uma ótima ferramenta e um bom substituto pra ASDF. Mas ele é tão parecido com ASDF que, se já estiver acostumado, não tem necessidade de mudar. Não tem nada a mais que ele faça que eu fosse sentir falta se não tivesse. Povo de Rust tem que parar de ficar substituindo as coisas pra fazer exatamente a mesma coisa. Era melhor gastar esse tempo ajustando e mandando patches pro próprio ASDF. Só dividiu os esforços da comunidade em duas ferramentas que fazem a mesma coisa.</p>
<h3>Últimas Considerações - Atuin<span class="hx:absolute hx:-mt-20" id="últimas-considerações---atuin"></span>
    <a href="#%c3%baltimas-considera%c3%a7%c3%b5es---atuin" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Uma última coisa, é que eu adicionei a ferramenta <a href="https://atuin.sh/"target="_blank" rel="noopener"><strong>Atuin</strong></a> &ldquo;Magical Shell History&rdquo;. Ele substitui o histórico do seu shell (sabe, aquele que você pode digitar &ldquo;Ctrl + R&rdquo; e procurar a linha de comando?) que normalmente é gravado em arquivo texto e manda pra um banco de dados SQLite3.</p>
<p>Só isso já seria legal, pra tirar as limitações de arquivo texto e evitar corromper e perder histórico. Mas além disso ele tem um serviço que permite sincronizar seu histórico entre diferentes máquinas. Digamos, seu PC desktop de casa, seu notebook de trabalho, seu servidor remoto. Hoje em dia todos tem históricos isolados e diferentes. E se desse pra sincronizar todos?</p>
<p>Aquele dia que você esquece seu notebook no trabalho, está no PC de casa e fica <em>&ldquo;puts, quais eram mesmo aqueles comandos de docker que eu fiz no trabalho??&rdquo;</em> Agora tem com, basta fazer <code>atuin sync</code> e pronto, seus históricos sincronizam!</p>
<p>Pra isso, no script de instalação eu rodo o <code>atuin register</code> que pede um username, email e uma senha (que, se não me engano, vai servir pra encriptar seus dados). E pronto, já vai sozinho se virar pra ficar sincronizando. Se já tinha conta, numa máquina nova basta rodar <code>atuin login</code> e logar. Só isso.</p>
<p>Ele substitui o <code>Ctrl + R</code> pra pesquisar no sqlite3 e agora seu histórico será muito mais robusto. Eu imagino que isso é mais importante ainda pro povo de devops. Eu lembro que rodava trocentos comandos de pods e containers e usava <strong>MUITO</strong> o histórico pra lembrar das coisas e repetir comandos. Fucem a documentação, tem como montar seu próprio servidor de Atuin pra onde sincronizar. Mesmo estando encriptado, talvez você queira 100% de controle. Tem como.</p>
<p>E por hoje é isso. Eu gostei bastante de brincar com o Omakub-MJ e meu PC pessoal com Manjaro GNOME e meu notebook com Windows 11 + ArchWSL já estão usando tudo exatamente como expliquei aqui. Novamente, leiam o <a href="https://manual.omakub.org/1/read#leaf_29"target="_blank" rel="noopener">Manual do Omakub</a> escrito pelo DHH, usem o Omakub dele caso estejam em Ubuntu, usem o <a href="https://github.com/akitaonrails/omakub-mj"target="_blank" rel="noopener">meu fork</a> pra Manjaro e Arch e não deixem de mandar Pull Requests ou Issues lá.</p>
]]></content:encoded><category>linux</category><category>ferramentas-de-desenvolvimento</category><category>open-source</category></item><item><title>[Akitando #139] - Entendendo Como Containers Funcionam</title><link>https://akitaonrails.github.io/2023/03/02/akitando-139-entendendo-como-containers-funcionam/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/03/02/akitando-139-entendendo-como-containers-funcionam/</guid><pubDate>Thu, 02 Mar 2023 09:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/85k8se4Zo70"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Finalmente vou pagar promessa. Este é o video sobre containers e Docker que venho falando faz vários videos atrás, que ia fazer. Mas eu precisava explicar a instalação, como Linux boota, como funciona virtualização, pra finalmente chegar em containers. Então este episódio vai amarrar tudo que vocês aprenderam nos videos de Linux, de armazenamento, etc e finalmente vão entender containers do jeito certo.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/85k8se4Zo70"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Finalmente vou pagar promessa. Este é o video sobre containers e Docker que venho falando faz vários videos atrás, que ia fazer. Mas eu precisava explicar a instalação, como Linux boota, como funciona virtualização, pra finalmente chegar em containers. Então este episódio vai amarrar tudo que vocês aprenderam nos videos de Linux, de armazenamento, etc e finalmente vão entender containers do jeito certo.</p>
<h2>Erratas<span class="hx:absolute hx:-mt-20" id="erratas"></span>
    <a href="#erratas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>5:12 - tá errado, isso é em outros comandos, como top eu acho, que mostraria 400% se tivesse usando 4 CPU. Do jeito que eu falei ficou parecendo que naquele quadrado vermelho mostraria isso, mas ali é POR cpu/thread, por isso vai só até 100%. Só em linhas de comando que mostra num número só quanto que tá se usando que apareceria, por exemplo, 400%.</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro: qual a diferença de VMs e Containers?</li>
<li>01:49 - CAP 1 - Limitando Processos - cgroups</li>
<li>07:43 - CAP 2 - Enganando Processos - namespaces</li>
<li>14:45 - CAP 3 - Recapitulando Containers - LXC, Docker, OCI</li>
<li>17:34 - CAP 4 - A Base de Containers - RunC</li>
<li>22:29 - CAP 5 - Hello World de Docker - O que tem dentro?</li>
<li>33:20 - CAP 6 - Restringindo Processos Ainda Mais - Capabilities</li>
<li>37:18 - CAP 7 - Orquestrando Containers - Docker Compose</li>
<li>41:26 - CAP 8 - Bônus do Episódio &ldquo;Tornando sua App Web mais Rápida&rdquo;  - Load Balancer com Docker</li>
<li>43:25 - CAP 9 - Orquestra de Gente Grande - K8S e Terraform</li>
<li>47:17 - CAP 10 - Outros Usos de Containers - Apps em Containers?</li>
<li>51:28 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://github.com/akitaonrails/episode0139"target="_blank" rel="noopener">https://github.com/akitaonrails/episode0139</a></li>
<li><a href="https://www.zsiegel.com/2022/01/17/load-balancing-in-docker-with-nginx"target="_blank" rel="noopener">https://www.zsiegel.com/2022/01/17/load-balancing-in-docker-with-nginx</a></li>
<li><a href="https://www.mo4tech.com/unshare-command-details-and-examples.html"target="_blank" rel="noopener">https://www.mo4tech.com/unshare-command-details-and-examples.html</a></li>
<li><a href="https://ericchiang.github.io/post/containers-from-scratch/"target="_blank" rel="noopener">https://ericchiang.github.io/post/containers-from-scratch/</a></li>
<li><a href="https://wiki.archlinux.org/title/cgroups"target="_blank" rel="noopener">https://wiki.archlinux.org/title/cgroups</a></li>
<li><a href="https://nodejs.org/en/docs/guides/nodejs-docker-webapp/"target="_blank" rel="noopener">https://nodejs.org/en/docs/guides/nodejs-docker-webapp/</a></li>
<li><a href="https://docs.docker.com/compose/gettingstarted/"target="_blank" rel="noopener">https://docs.docker.com/compose/gettingstarted/</a></li>
<li><a href="https://flast101.github.io/docker-privesc/"target="_blank" rel="noopener">https://flast101.github.io/docker-privesc/</a></li>
<li><a href="https://blog.alexellis.io/runc-in-30-seconds/"target="_blank" rel="noopener">https://blog.alexellis.io/runc-in-30-seconds/</a></li>
<li><a href="https://man7.org/linux/man-pages/man7/capabilities.7.html"target="_blank" rel="noopener">https://man7.org/linux/man-pages/man7/capabilities.7.html</a></li>
<li><a href="https://blog.container-solutions.com/linux-capabilities-in-practice"target="_blank" rel="noopener">https://blog.container-solutions.com/linux-capabilities-in-practice</a></li>
<li><a href="https://github.com/containers/bubblewrap"target="_blank" rel="noopener">https://github.com/containers/bubblewrap</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>O penúltimo video sobre o setup do meu PC foi pesado em configuração mas se conseguiu chegar até o fim, deve ter entendido um pouco mais sobre como virtualização funciona, em particular usando o emulador QEMU acelerado com KVM. Aquilo é virtualização: criar uma máquina virtual que roda outro sistema operacional, outra kernel, por cima.</p>
<p>Hora de pagar a promessa que vim fazendo em diversos videos. Finalmente falar um pouco sobre os tais &ldquo;containers&rdquo; que até hoje muita gente confunde com virtualização. As duas coisas &ldquo;parece&rdquo; que é rodar um programa dentro de uma máquina virtual. Mas containers são completamente diferentes de máquinas virtuais. Vamos lá.</p>
<p>(&hellip;)</p>
<p>Se fosse fazer um TLDR, uma única frase que resume a diferença, é até simples: uma máquina virtual roda um novo kernel, possivelmente numa outra arquitetura, como x86 em cima de ARM, já um container reaproveita o mesmo kernel, necessariamente na mesma arquitetura de hardware. É essa a diferença. Agora vamos ver na prática.</p>
<p>Vou começar falando só de containers de Linux. BSD, Windows, MacOS tem infraestrutura diferente pra containers. Windows tem como rodar container pra rodar apps de Windows. Isso é leve. Mas quando Windows ou MacOS precisam rodar apps de Linux, na prática o que acontece é que eles de fato sobem uma máquina virtual de Linux primeiro, como o WSL que já fiz dois videos a respeito. Portanto envolve sim virtualização. E é dentro dessa máquina que um Docker Desktop cria containers de Linux. Só em Linux nativo que containers são realmente &ldquo;leves&rdquo;.</p>
<p>Em Linux precisamos entender dois componentes fundamentais: cgroups e namespaces. Cgroups já mencionei no episódio anterior quando mostrei como isolar threads da CPU só pra máquina virtual e só pro host. Cgroups ou control groups é um mecanismo da kernel pra agrupar processos e limitar recursos como cpu e memória.</p>
<p>Existem várias formas de listar cgroups. A mais simples acho que é só dar <code>ls /sys/fs/cgroup</code>. Lembra o script chamado &ldquo;suspend&rdquo; que fiz pra limitar AllowedCPUs pro host? Usei o systemd pra limitar esse parâmetro pros cgroups init.scope, system.slice e user.slice. Vejam na listagem que todos são diretórios. Se listar um deles, vem vários arquivos como &ldquo;cpu.weight&rdquo; ou &ldquo;memory.pressure&rdquo;. É como se parametriza os recursos pra esses cgroups.</p>
<p>É tudo organizado como uma árvore, pra variar olha só árvores de novo, onde um cgroup herda configurações de outro cgroup. Honestamente, não sei os detalhes de como é configurado cada um que já vem de cada distro e imagino que cada um configura essa árvore de formas um pouco diferente. Essa listagem é no meu Manjaro. Num Ubuntu é semelhante, tem a maioria dos mesmos slices.</p>
<p>Se listarmos o que tem em system.slice, tem novos subdiretórios, um pra cada daemon que o systemd inicia, e também cgroups pra cada um dos aplicativos instalados via Snap, que vou falar um pouco mais pra frente. Pra enxergamos isso melhor podemos rodar o comando <code>systemctl status</code>. Agora temos a hierarquia toda de cgroups.  Como falei antes, olha aqui o system.slice e os sub-grupos de cada daemon. E mais pra baixo temos o user.slice onde os programas que estamos rodando como navegador, terminal e tudo mais ficam em seus próprios sub-grupos também. Todos os processos rodando estão organizados em cgroups. Você nem sabia disso, mas toda vez que executa um programa, ele vai estar atrelado a pelo menos um cgroup.</p>
<p>Lembra que quando usamos comandos com <code>ps</code> ou programas como <code>htop</code> eles pesquisam os metadados no sistema de arquivos virtual <code>/proc</code>? Por exemplo, vamos pegar o PID do meu navegador Edge. Tem vários processos, todo navegador separa cada aba num processo diferente pra isolar, ter mais segurança, e se uma aba crashear, não derrubar todas as outras junto. Podemos fazer <code>cat /proc/PID/group</code> e aqui vamos em qual sub-grupo de cgroup esse process está. No escopo do edge, dentro de app.slice, dentro de user service, dentro de user.slice.</p>
<p>Existem várias formas de controlar cgroups. Uma delas é usando o sistema de arquivos virtual disponível no diretório <code>/sys/fs/cgroups</code>. Isso é um ponto de montagem. Se fizermos <code>mount | grep cgroup</code> olha como aparece que o tipo do sistema de arquivos é &ldquo;cgroup2&rdquo;. É como se fosse um dispositivo virtual montado. Lembra que já expliquei nos episódios de Linux, como nos de Slackware, Gentoo e no que explico como funciona o boot, que existem diversos pontos de montagem especiais como &ldquo;/proc&rdquo; ou &ldquo;/dev&rdquo;?</p>
<p>Pra mostrar na prática pra que diabos saporra serve, deixa criar um script idiota em Ruby. Podia ser em Python, podia ser em Java, faça no que quiser. É só um loop infinito printando ponto pra sempre. Vamos rodar e em outro terminal abro o &ldquo;htop&rdquo;. Veja aqui no topo que meu script tá usando 100% de 1 CPU. Se eu tivesse feito suportar múltiplas threads, cada 100% seria uma CPU. Um programa que usa 4 threads inteiras ia aparecer aqui como 400%.</p>
<p>No topo do programa lista todas as threads independentes e veja que tem uma de 100% que é meu script. Mas se esperar um pouquinho vai ver que ele muda de lugar. Um programa não fica sempre fixo no mesmo thread. Pra isso serve o scheduler da kernel. Expliquei sobre scheduler no episódio de concorrência e paralelismo e é a razão de porque no episódio anterior tive o trabalho de mostrar como mandei a máquina virtual usar as threads de 16 a 31 e limitei o host a usar só de 8 a 15. Lembra deste script aqui usando systemd e configurando o parâmetro AllowedCPUs em todos os diretórios raíz de slices?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">command</span><span class="o">=</span><span class="nv">$2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$command</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;started&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- system.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-15
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- user.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-15
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- init.scope <span class="nv">AllowedCPUs</span><span class="o">=</span>0-15
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$command</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;release&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- system.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-31
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- user.slice <span class="nv">AllowedCPUs</span><span class="o">=</span>0-31
</span></span><span class="line"><span class="cl">    systemctl set-property --runtime -- init.scope <span class="nv">AllowedCPUs</span><span class="o">=</span>0-31
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ctrl-C pra parar o loop infinito e agora podemos criar sub-diretórios e arquivos usando comandos normais como &ldquo;mkdir&rdquo;, &ldquo;echo&rdquo; ou podemos continuar usar o SystemD pra facilitar, que é o que vou fazer agora. Vou criar um slice pra mim chamado &ldquo;akita.slice&rdquo; em &ldquo;/etc/systemd/system/akita.slice&rdquo;. Só abrir com qualquer editor de textos usando &ldquo;sudo&rdquo; e no caso quero limitar o uso de CPU em só 30% pra processos neste cgroup. Pra isso coloco este conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Slice]
CPUQuota=30%</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Posso configurar outros limites, como memória, mas pra esse exemplo, só isso já funciona. Agora vou abrir um shell dentro desse cgroup com este comando <code>systemd-run --slice=akita.slice --uid=akitaonrails --shell</code> e nessa linha de comando que abriu, vou repetir o mesmo comando <code>ruby loop.rb</code>.</p>
<p>Olha o htop. Tá vendo aqui no topo da lista? Antes o script consumia 100% de uma thread, mas como agora reiniciei a partir do meu novo cgroup,que só permiti usar 30% da CPU, a kernel obriga a usar no máximo 30% da CPU. E com isso conseguimos controlar granuladamente o que cada processo no sistema pode ou não consumir.</p>
<p>E por que eu iria querer limitar um programa forçado assim? No Wiki do site do ArchLinux tem um exemplo no fim. Ele menciona o Matlab, que é um programa de cálculo numérico e, dependendo da complexidade do cálculo, pode consumir toda a RAM e toda CPU do sistema. Se um programa usar todos os recursos, vai faltar CPU e RAM até pra kernel e o sistema inteiro vai ficar lento ou crashear. É um tipo de ataque de DoS. DoS ou Denial of Service é um programa saturar todos os recursos da máquina até tudo parar de funcionar. Mas se colocar o Matlab num cgroup onde limito CPU e RAM pra, digamos, deixar sobrar uns 10%, a kernel vai ter a possibilidade de tentar se corrigir.</p>
<p>Depois leia mais sobre cgroups no Wiki do Arch. Mas o importante foi pra explicar que existe esse recurso em todo Linux moderno. Agora vou pular pro segundo grande recurso pra limitar o que processos podem fazer, que se chama &ldquo;Namespaces&rdquo;. Quando listo o que tem em &ldquo;/proc&rdquo; consigo enxergar todos os PIDs de todos os processos rodando no sistema. Se rodar o comando &ldquo;mount&rdquo; consigo ver todos os pontos de montagem ativos no sistema. E existem comandos ou locais onde posso listar a stack de rede, IPC que é comunicação inter-processos, IDs de usuários e grupos.</p>
<p>Mas é possível esconder todos esses detalhes de um processo. Pra isso existe o comando <code>unshare</code>, literalmente &ldquo;descompartilhar&rdquo;. Num terminal normal, se executarmos o comando <code>ps aux</code> isso vai nos devolver uma lista com todos os processos ativos neste momento, e costuma ser uma lista grande num desktop normal. Vamos abrir um novo shell, mas fazendo <code>sudo unshare --fork --pid --mount-proc /bin/bash</code>. Parece nada demais né? Mas se executarmos <code>ps aux</code> olha só, cadê todos os meus processos? Sumiu? O bash agora tem PID 1.</p>
<p>É isso que se chama namespace, é um espaço separado de nomes, no caso de PIDs. Se abrir outro terminal e executar <code>ps aux</code> tá tudo aqui bonitinho e lá no final olha o comando unshare e a instância de bash rodando com um PID alto. A diferença é que o bash que acabamos de abrir não enxerga mais o mesmo &ldquo;/proc&rdquo; global. Ele tem um &ldquo;/proc&rdquo; local só pra ele, isolado e separado que começa do 1 de novo. De dentro daquele bash, vamos listar o que tem em &ldquo;/proc&rdquo;, só os dois PIDs que listou. No outro terminal normal se listar &ldquo;/proc&rdquo; tem todos os PIDs.</p>
<p>Entenderam? Namespace faz o processo puxar a informação de outro lugar. Estamos mentindo, mas ele continua executando no mesmo sistema, igual qualquer outro programa, tanto que tem um PID de verdade. Pra quem é de Javascript, uma analogia é a diferença de criar uma variável com o antigo &ldquo;var&rdquo; pra cair no escopo global da função ou usar &ldquo;let&rdquo; e manter a variável no escopo local do bloco, manja? Não é exatamente a mesma coisa, mas é parecido.</p>
<p>Com o recurso de namespaces podemos mentir pros processos pra não enxergar mais o resto do sistema do lado de fora e assim limitar o que podem fazer. Por padrão, todos os recursos do sistema operacional são globais, todo processo tem acesso a praticamente tudo que outro processo tem, salvo permissões de leitura e execução por usuário, que você tá mais acostumado a pensar, tipo, root tem acesso a tudo. Mas com <code>unshare</code> inclusive note que ele engana meu processo fazendo achar que é root. Se executarmos o comando <code>id</code> olha, sou o usuário zero.</p>
<p>Como executamos unshare com sudo, esse root tem o mesmo nível de privilégio do root de verdade, o que não é muito seguro. Vamos dar exit e executar a mesma linha de comando mas com a opção &ldquo;&ndash;user&rdquo; e pronto. Se executarmos &ldquo;id&rdquo; agora sou nobody, literalmente um zé ninguém. Mais útil vai ser rodar como root, só que não tendo acesso ao sistema de arquivos todo. Vamos consertar isso.</p>
<p>Se assistiu meus episódios de Linux, viu minha explicação sobre chroot. Um container de verdade não usa chroot, mas pra demonstração dos conceitos vai servir. Teve um palestrante que já demonstrou isso anos atrás e deixou alguns arquivos que vamos precisar. Vou deixar o link na descrição abaixo, vamos baixar o tarball &ldquo;rootfs.tar.gz&rdquo;.</p>
<p><code>wget https://github.com/ericchiang/containers-from-scratch/releases/download/v0.1.0/rootfs.tar.gz</code></p>
<p>Pronto, baixei lá no &ldquo;/tmp&rdquo; mesmo porque é só pra demonstração. Agora é só descomprimir com <code>tar xvfz</code>, esperar um segundo &hellip; e tá lá. Se listarmos o que tem, parece com a raíz do seu HD, não? É uma mini-distribuição do que chamamos de &ldquo;userland&rdquo;. Se listar o &ldquo;rootfs/boot&rdquo; não tem a kernel nem a initram que deveria. E o que fazemos com isso? É como na instalação do Gentoo, fazemos <code>sudo chroot rootfs /bin/bash</code>. Ele vai fazer &ldquo;change root&rdquo;, mudar a raíz, pra esse diretório rootfs e executar o bash dentro do sub-diretório &ldquo;rootfs/bin&rdquo; e não o meu de verdade.</p>
<p><code>ls</code> dá pau agora. Isso porque esse shell não carregou perfil nenhum, então não tem nenhuma variável de ambiente configurado. Vamos exportar o path de novo com <code>export PATH=/sbin:/bin:/usr/bin</code> e agora sim temos acesso às ferramentas nesses diretórios. É assim que sua distro Linux faz, só que isso já vem configurado automático pra você, mas na mão é assim que fazemos.</p>
<p>Essa variável provavelmente está no seu arquivo &ldquo;/etc/profile&rdquo; ou .bashrc ou .zshrc da vida. Executando <code>ls /</code> parece que não aconteceu nada, mas faz <code>ls /boot</code>, viu? Vazio. Estamos dentro do diretório &ldquo;rootfs&rdquo; que está sendo mascarado pra gente como se fosse a nova raíz. Esse shell não tem mais acesso ao meu sistema de arquivos de verdade.</p>
<p>Agora tentamos executar <code>ps</code> e dá pau. Isso porque não existe nenhum mount de diretórios especiais, como o &ldquo;/proc&rdquo; que é onde comandos como &ldquo;ps&rdquo; pesquisam processos. Veja o erro, ele manda justamente dar mount nisso. Mas agora é que vamos juntar esse comando de chroot com o unshare. Então damos exit e executamos o comando <code>sudo unshare -p -f --mount-proc=/tmp/rootfs/proc chroot rootfs /bin/bash</code></p>
<p>A opção &ldquo;-p&rdquo; é o jeito mais curto pra &ldquo;&ndash;pid&rdquo; e &ldquo;-f&rdquo; é pra &ldquo;&ndash;fork&rdquo;. Daí montamos o proc e damos chroot via unshare. Não podemos esquecer de exportar o PATH de novo. Pronto. Se fizermos <code>ls /boot</code> tá vazio. Se fizermos <code>ls /home</code>, tá vazio. Ou seja, esse não é seu sistema, ele está enxergando a raíz a partir do diretório &ldquo;/tmp/rootfs&rdquo;. Mas agora se rodarmos <code>ps aux</code>, sucesso! Só enxergamos os processos dentro do novo namespace. Não só escondemos os PIDs de todo o sistema como escondemos o sistema de arquivos e você pensa que está num sistema completamente diferente.</p>
<p>Esse é o ponto que confunde todo mundo. O comportamento dentro desse shell faz parecer que estou dentro de uma máquina virtual. Mas isso não é uma máquina virtual, não tem um hypervisor como KVM com um emulador como QEMU simulando uma máquina nova do zero e fazendo boot de um novo kernel. Por isso estou reforçando que o diretório falso &ldquo;/boot&rdquo; tá vazio, não tem outra kernel. Isso não é uma máquina virtual. E se voltarmos pra outro terminal e executarmos <code>ps aux</code> esse bash que abrimos dentro do chroot tá aqui, aparecendo normalmente como se fosse qualquer outro processo.</p>
<p>Se não ficou claro. Alt-tab pro shell dentro do chroot. Executando <code>ls /usr/bin</code> vamos abrir um programa que tem aqui. Olha só, veio um python naquele tarball, vamos executar. Esse é o REPL de python que vocês já devem ter visto. Alt-tab pro terminal normal e <code>ps aux</code> de novo. Olha só aparecendo aqui python. Se fosse uma máquina virtual, teria um  único processo, por exemplo de QEMU e só, eu não estaria vendo os processos dentro da máquina virtual misturado com os do meu sistema. E isso porque esses processos ESTÃO rodando no meu sistema normalmente, sem emulação, nem nada disso, é nativo.</p>
<p>Se não fosse assim, chamaríamos tudo de máquina virtual. Mas por isso inventamos esse termo novo chamado &ldquo;container&rdquo; justamente pra ser distinto. O processo está só sendo enganado, achando que está num outro sistema porque cortamos os olhos dele, as mãos dele, o nariz dele. E só apontamos tudo pra outros namespaces É isso que unshare e chroot fizeram com o shell do bash: isolaram num jarro, num container. Tudo que executa com unshare está na Matrix.</p>
<p>Nessa demonstração simples só escondemos os PIDs, o diretório &ldquo;/proc&rdquo; e o sistema de arquivos, mas dá pra mudar a rede, dá pra mudar todos os outros mounts como &ldquo;/dev&rdquo; ou &ldquo;/sys&rdquo;, dá pra fazer ele enxergar outros cgroups, outro clock do sistema, outros usuários e grupos, outro hostname e domainname, ou seja, o processo realmente vai achar que está em outra máquina, sem realmente estar. Ele pára de ter acesso ao resto do sistema e efetivamente roda &ldquo;isolado&rdquo;, que é todo o objetivo.</p>
<p>Namespaces são super flexíveis. Por exemplo, se isolar o container numa rede virtual, é assim que o processo num container consegue dar bind, digamos na porta 80, e um outro processo em outro container também consegue subir na porta 80, cada um numa rede isolada que daí configuro pra expor um pra fora como porta 4001 e outra como 4002. Mas também posso adicionar esses dois containers ambos no mesmo namespace de rede e assim crio uma rede virtual só entre os dois, sacaram? Pra isso serve o comando <code>nsenter</code> que é literalmente &ldquo;entrar num namespace&rdquo;, depois pesquisem.</p>
<p>Pesquisem no Google por &ldquo;container from scratch&rdquo; ou &ldquo;container do zero&rdquo; e vão esbarrar em artigos como do site da RedHat e diversos outros artigos explicando sobre <code>unshare</code>. Mas pra hoje esses exemplos que dei já servem pra continuar a explicação. As coisas começam a ficar confusas porque existem várias ferramentas que funcionam em cima dessa infraestrutura de cgroups e namespaces que expliquei. No começo, no mundo Linux surgiu Linux Containers ou LXC e depois Docker.</p>
<p>Em 2015 a empresa do Docker criou a Open Container Initiative ou OCI, um grupo com várias outras empresas pra padronizar e tornar mais portável todos os componentes que fazem containers funcionar, em particular o formato de imagens que é o formato OCI image, é o que você baixa do site dockerio.com. Os padrões estão todos na opencontainers.org e no mundo Linux a implementação desse padrão é a tecnologia &ldquo;runC&rdquo;. Vamos ver como funciona.</p>
<p>O runC precisa de um arquivo &ldquo;config.json&rdquo; que define as características do container. E vamos aproveitar aquele diretório &ldquo;rootfs&rdquo; ainda. O próprio comando runc gera esse arquivo executando <code>runc spec</code> dentro do diretório rootfs mesmo.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;ociVersion&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.2-dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;process&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;terminal&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;user&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;uid&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;gid&#34;</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/bin/bash&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;env&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;TERM=xterm&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;capabilities&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;bounding&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;effective&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;permitted&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">],</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;ambient&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_AUDIT_WRITE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_KILL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;CAP_NET_BIND_SERVICE&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;rlimits&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;RLIMIT_NOFILE&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;hard&#34;</span><span class="p">:</span> <span class="mi">1024</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;soft&#34;</span><span class="p">:</span> <span class="mi">1024</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;noNewPrivileges&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;/tmp/rootfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;readonly&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;hostname&#34;</span><span class="p">:</span> <span class="s2">&#34;runc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;mounts&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/proc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;proc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;proc&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;tmpfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;tmpfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;strictatime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mode=755&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;size=65536k&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/pts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;devpts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;devpts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;newinstance&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ptmxmode=0666&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mode=0620&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;gid=5&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/shm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;tmpfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;shm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;mode=1777&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;size=65536k&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/dev/mqueue&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;mqueue&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;mqueue&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/sys&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;sysfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;sysfs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ro&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;destination&#34;</span><span class="p">:</span> <span class="s2">&#34;/sys/fs/cgroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;cgroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;source&#34;</span><span class="p">:</span> <span class="s2">&#34;cgroup&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nosuid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;noexec&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nodev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;relatime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;ro&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">],</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;linux&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;resources&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;devices&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;allow&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;access&#34;</span><span class="p">:</span> <span class="s2">&#34;rwm&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;namespaces&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;pid&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;network&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ipc&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;uts&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;mount&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;cgroup&#34;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;maskedPaths&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/acpi&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/asound&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/kcore&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/keys&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/latency_stats&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/timer_list&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/timer_stats&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/sched_debug&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/sys/firmware&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/scsi&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;readonlyPaths&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/bus&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/fs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/irq&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/sys&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="s2">&#34;/proc/sysrq-trigger&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora que vocês viram a demonstração com unshare, esse arquivo não deveria parecer muito alienígena. Inclusive foi por isso que fiz videos explicando Linux antes, porque precisa entender o sistema operacional pra entender como containers são configurados. Esse arquivo começa declarando a versão do padrão OCI, daí qual processo vamos rodar, que é o bash, e com qual usuário e grupo, no caso zero quer dizer root.  Olha como já configura o PATH pra mim. &ldquo;CWD&rdquo; é Current Working Directory que é o diretório onde o programa vai abrir.</p>
<p>Capabilities pula que vou explicar depois. Daí &ldquo;root&rdquo; quer dizer a raíz do sistema de arquivos, que vai ser dentro daquele /tmp/rootfs onde fizemos chroot. Hostname é o nome da máquina, que é definido pelo namespace UTS, assim o container pode ter um nome de máquina diferente do nome da máquina de verdade.</p>
<p>No atributo de mounts aparece a mesma coisa que fizemos com a opção &ldquo;&ndash;mount-proc&rdquo; do comando unshare, onde mandamos dar mount no sistema de arquivos especial de processos. Assim, dentro do container não vai mais enxergar os pids de verdade do lado de fora. Mas aqui tá mais completo porque vai dar mount pra &ldquo;/dev&rdquo;, pros pseudo terminais em &ldquo;/dev/pts&rdquo;, pra memória compartilhada que é &ldquo;/dev/shm&rdquo;, as filas de mensagem pra comunicação entre processos em &ldquo;/dev/mqueue&rdquo;, o &ldquo;/sys&rdquo; que espelha funcionalidades e informações da kernel e finalmente o mount de cgroup que mostrei no começo do video. Tudo isolado dos de verdade do lado de fora do container. Estamos deixando esse jarro o mais realista possível pro processo dentro.</p>
<p>E por fim quais namespaces vou isolar também que são os mesmos do unshare. Pid sabemos que esconde os processos, mas temos network pra rede virtual, ipc que é a fila pra comunicação interprocessos, uts que permite mudar o hostname interno e mount que permite criar mounts diferentes do seu sistema.</p>
<p>Com tudo configurado, podemos executar, a partir de rootfs, <code>sudo runc start hello</code>. Hello você pode substituir por um nome qualquer pra esse container, contanto que seja um nome único, daí podemos criar quantos containers quisermos. Como aconteceu antes quando rodamos unshare com chroot, caímos no bash dentro do jarro, com todos os seus sentidos sequestrados. RunC está mentindo pro bash, vamos ver?</p>
<p>Se digitarmos <code>id</code> estamos como root. Se executarmos <code>ls /boot</code>, tá vazio, estamos dentro do diretório rootfs como nova raíz de arquivos. Melhor ainda, se digitarmos <code>mount</code>, temos só os pontos de montagem que definimos no <code>config.json</code>. Ele não enxerga nenhum dos pontos de montagem de verdade do meu sistema. E se digitarmos <code>ps aux</code>, como antes, só enxergamos o bash que está rodando. É como se fosse uma máquina de verdade vazia. Estamos dentro da Matrix, mas repetindo, isso não é uma máquina virtual. Container, na prática é outro nome dado a um processo em execução com menos acessos que um processo normal, mas no fundo é só isso: a execução de um processo.</p>
<p>O que eu falei no começo do episódio? Vou repetir: Se fosse fazer um TLDR, uma única frase que resume a diferença, é até simples: uma máquina virtual roda um novo kernel em qualquer arquitetura, um container reaproveita o mesmo kernel, nas mesma arquitetura. É essa a diferença. Mas agora vamos ver na prática. Estou repetindo o tempo todo que &ldquo;/boot&rdquo; está vazio. Não tem outra kernel, estamos compartilhando a mesma kernel. Hypervisors compartilham o hardware de verdade, como CPU, GPU e dispositivos pra máquinas virtuais que rodam qualquer outro sistema operacional, qualquer nova kernel isolados um dos outros. Posso rodar Windows do lado de BSD. Mas um container compartilha o mesmo kernel que existe já carregado. Ele não pode rodar sistemas operacionais diferentes, só outra distro Linux.</p>
<p>Vamos voltar pro que eu disse no começo: como Windows e MacOS fazem pra ter containers de Docker de Linux? Eles sim, primeiro precisam usar o hypervisor deles, o Hyper-V ou o Hypervisor.framework pra bootar um Linux de verdade e em cima desse Linux rodar o RunC pra conseguir ter containers de Linux. Por isso Docker é sempre mais pesado em cima de Windows do que direto num Linux. Numa distro Linux não tem a necessidade de rodar um hypervisor antes. Entenderam essa diferença? É muito importante, porque sua performance e uso de recursos como RAM sempre vai ser pior no Docker pra Windows ou pra Mac. Sempre, não tem exceção.</p>
<p>Mas eu falei Docker e fiquei mostrando RunC, qualé? É porque depois que a OCI foi formada, o Docker, Podman, Kubernetes, todos que usam containers, provavelmente usam RunC por baixo, que é a infraestrutura que usa cgroups, unshare e outras coisas que vem em todo Linux. Mas se tem RunC então pra que precisamos de Docker?</p>
<p>A parte importante do Docker não é mais a capacidade de executar containers. Hoje em dia isso vem nativo na kernel do Linux através do RunC que mostrei. O grande lance de Docker é o ecossistema de imagens, o DockerHub. Como é o tutorial de hello world de Docker logo depois que instala? <code>docker run hello-world</code>, vamos executar no terminal. Ele imprime esse texto e termina. O que aconteceu?</p>
<p>A ferramenta começa checando no registry local se tem uma imagem com nome &ldquo;hello-world&rdquo;. Como não tinha, deu &ldquo;pull&rdquo;, como se fosse o comando <code>git clone</code> ou mais especificamente, <code>git pull</code>. Já explico porquê, mas na prática baixou o binário do site hub.docker.com. Podemos ir no site deles e ver o registro dessa imagem lá, tão vendo? Na dúvida ele baixa da tag &ldquo;latest&rdquo;. É o equivalente à branch &ldquo;main&rdquo; ou a antiga &ldquo;master&rdquo;.</p>
<p>Essa imagem baixada é que nem aquele nosso diretório &ldquo;/tmp/rootfs&rdquo; que eu tinha baixado o tarball, onde demos chroot. Com essa imagem o runC sobe um container. Pra ver o container que executou basta rodar o comando <code>docker container ls --all</code>. Sem essa opção a lista seria vazia porque o container executa e termina. Com essa opção &ldquo;all&rdquo; ele mostra mesmo os terminados. Lembra no comando &ldquo;runc&rdquo; que dei o nome de &ldquo;hello&rdquo; pro container? No comando de <code>docker run</code> poderia passar a opção &ldquo;&ndash;name&rdquo; mas como não fiz isso ele deu um nome aleatório pro container.</p>
<p>Agora uma coisa que a maioria que já usou Docker nunca deve ter feito. Vamos executar o comando <code>docker export NOME_CONTAINER &gt; rootfs.tar</code>. Sacaram? Podemos criar um diretório novo &ldquo;mkdir -p /tmp/rootfs2&rdquo; e fazer <code>tar xvf rootfs.tar --directory /tmp/rootfs2</code>. Agora damos &ldquo;cd&rdquo; pra dentro e não tem quase nada lá, alguns diretórios pra fazer mount de procs e outras coisas e esse executável &ldquo;hello&rdquo; que podemos executar diretamente com &ldquo;./hello&rdquo; e olha só, a mesma saída que vimos antes. É isso que o <code>docker run</code> executou e é assim que podemos pegar o conteúdo de uma imagem de Docker. Uma imagem de Docker não é muito diferente de um mero tarball.</p>
<p>Vamos entender melhor essa tal imagem. Pra começar, de volta no site do DockerHub vamos escolher a imagem do banco de dados Postgres. Agora vamos abrir do lado outro navegador com o arquivo Dockerfile no Github do repositório docker-library. Se clicarmos na tag &ldquo;latest&rdquo; podemos ver o que ele chama de layers. Sem ir linha a linha, só de bater o olho, conseguem ver onde um lado bate com o outro? Por exemplo, no Dockerfile tem esse comando &ldquo;ENV GOSU_VERSION&rdquo;. O comando ENV de Dockerfile serve pra configurar uma variável de ambiente, equivalente ao comando <code>export</code> num Bash. Todos esses &ldquo;/bin/sh&rdquo; são esses comandos RUN e assim por diante.</p>
<p>O que é um Dockerfile? É como se fosse um script de comandos que diz passo a passo como montar a tal imagem de Postgres. Voltando pra primeira linha, começa com o comando FROM. Não precisamos começar uma imagem do zero, podemos começar com uma que já existe e construir por cima. É como se fizéssemos <code>git clone</code> de um projeto e começássemos a adicionar coisas e ir fazendo <code>git commit</code>.</p>
<p>A grosso modo, cada comando do Dockerfile, esses em letra maiúscula no começo da linha, vão gerar um dos layers que vemos aqui no site do DockerHub. Com exceção de layers de configurar variável de ambiente, que é irrisório, a maioria dos comandos modifica ou baixa arquivos e por isso ocupa espaço. É que nem commit de Git. Por isso é comum num Dockerfile ter comandos RUN que executam vários comandos de shell concatenados, que nem logo no começo que fazemos <code>apt update; apt-get install ...; rm -Rf</code>. Por que?</p>
<p>Se o <code>rm -Rf</code> estivesse separado em outro comando RUN embaixo, o comando do <code>apt-get install</code> ia baixar e instalar pacotes, como em qualquer Debian ou Ubuntu que você usa. Digamos que instalou 50 megabytes, tô chutando. Mas vem um lixo opcional que não preciso, daí no comando seguinte rodo <code>rm</code> e apaga 10 megabytes. Mas a imagem vai ter um layer com 50 megabytes. Porque o <code>rm</code> ficou num layer separado.</p>
<p>Em vez disso eu baixo os 50 mega, na sequência já apago, e aí fecho a camada com 40 megabytes. Por isso fica tudo dentro do mesmo comando RUN, como se fosse num mesmo commit, entenderam? Quanto menos comando RUN no Dockerfile, melhor. Olha esse outro comando RUN embaixo, um monte de linhas um atrás do outro, e o resultado final de executar tudo é que vai no layer da imagem.</p>
<p>Essa é a importância da OCI que o Docker fundou. Ela define esse formato chamado imagem OCI. Dentro dessa imagem, que é um binariozão parecido com um zipão, tem alguns arquivos e diretórios obrigatórios. Como um <code>index.json</code> que tem o nome, ID, tamanho, lista de layers e histórico. Tem o <code>oci-config.json</code> que é o equivalente ao <code>config.json</code> do RunC que mostrei antes e, o mais importante, o diretório &ldquo;blobs&rdquo; que é parecido com diretório &ldquo;.git/objects&rdquo; de um repositório Git.</p>
<p>Ah, vocês não assistiram meu video de Git né? Lá eu expliquei que o importante é só o que tem no diretório &ldquo;.git&rdquo;, todo o resto pode jogar fora porque dá pra recriar tudo com o que tem aqui. Se listarmos <code>ls .git/objects</code> vem esse monte de subdiretórios. Vamos listar um deles e encontramos um arquivo com nome que é um SHA1. Esse é o SHA1 do commit de git. Os blobs da imagem de Docker é similar só que otimizados pra layers binários numa árvore, enquanto Git é otimizado pra árvore de commits de texto.</p>
<p>Lembram do formato QCOW2 que mostrei no episódio anterior? O QEMU Copy-on-Write versão 2 que é a imagem de HD virtual que o QEMU usa e onde instalamos o Windows virtualizado? É similar. O formato de imagem OCI também suporta copy-on-write. Que é o mesmo recurso que tem disponível em sistemas de arquivos modernos como o BTRFS. Um snapshot de BTRFS é similar com snapshot de QCOW2 que é parecido com layers de imagem OCI. COW permite criar layers só realmente com a diferença dos blocos que mudaram num arquivo.</p>
<p>Sabe diff que fazemos em Git e ele grava no commit só as linhas que foram adicionadas e as que foram removidas e não o arquivo todo? COW é como se fosse diff de arquivo binário. E lembra que eu venho repetindo em todos os videos da mini-série de armazenamento como arquivos são nada mais do que um conjunto de blocos binários organizados numa árvore? Se um arquivo tem 100 megabytes, divididos em 100 blocos de 1 megabyte, digamos que só 10 blocos desse arquivos foram alterados. Na próxima camada, ou commit, não precisa repetir os 100 megabytes inteiros do arquivo, só os 10 blocos modificados ocupando 10 megabytes. É por isso que imagens de Docker não são pesados.</p>
<p>Pra construir uma imagem nova só precisamos ter o arquivo Dockerfile como esse do Postgres e rodar o comando <code>docker build</code>. Se resolvermos mudar o Dockerfile a partir da metade, e repetirmos o comando <code>docker build</code>. Todas as camadas geradas pela primeira metade de comandos do arquivo permanecem as mesmas, jogamos fora as últimas camadas e ele executa os comandos a partir da metade. Por isso que quanto mais pro começo de um Dockerfile você mexer, mais o <code>docker build</code> vai ter que trabalhar. Se mexer só na última linha, só ela que vai ter que rodar.</p>
<p>Então uma imagem de OCI ou Docker é um conjunto de camadas binárias, como commits de um git. Pra montar dentro do container o Docker usa um sistema de arquivos chamado overlay2 que é um Union Filesystem. Union significa que unifica as diversas camadas num sistema de arquivos único. O container em si não sabe que tem essas camadas todas por baixo. Isso só importa pra construção da imagem, não pra rodar.</p>
<p>Eu menti agora pouco pra vocês. RunC é a infraestrutura de containers no Linux que sistemas como Kubernetes e Podman usam, mas o Docker propriamente dito na verdade usa outra coisa, que é o &ldquo;containerd&rdquo;. Pense assim, RunC tem menos funcionalidades, é mais simples, mais leve e pode rodar em userland, ou &ldquo;rootless&rdquo;, sem permissões de root ou daemons. Já containerd tem mais funcionalidades e foi feito pra funcionar com um daemon que sobe como root.</p>
<p>Por isso que quando se instala Docker, todo tutorial vai te falar pra executar &ldquo;systemctl start docker&rdquo; e habilitar o serviço pra subir no boot com &ldquo;systemctl enable docker&rdquo;. E isso pode se tornar um problema de segurança. Se sair pesquisando sobre buracos no Docker, vai achar várias formas de exploit dependendo de como o container foi configurado pra subir. O caso mais besta é subir um container com um comando como esse: <code>docker run -tid -v /:/mnt/ alpine</code>. Estão enxergando o erro?</p>
<p>O problema é essa opção &ldquo;-v&rdquo; que é pra mapear volumes. Lembra que quando estamos dentro da imagem o processo dentro não enxerga nada do sistema de arquivos fora? Pois bem, no mundo real o container é mais útil se ele tem &ldquo;algum&rdquo; acesso ao mundo externo. É melhor ser o Neo, saber que o mundo real existe, e ter superpoderes dentro da Matrix e um deles é conseguir enxergar arquivos do sistema de verdade.</p>
<p>Essa opção &ldquo;-v&rdquo; monta um diretório do lado de fora pra um diretório do lado de dentro, no caso do exemplo vai montar a raíz de verdade do meu HD pra um diretório &ldquo;/mnt&rdquo; no container. Mas isso é uma burrice, nunca faça um ponto de montagem tão amplo. Como o daemon que roda o container, o containerd que o Docker usa, sobe como root, ele tem permissão pra editar qualquer arquivo do sistema de verdade a partir desse ponto de montagem.</p>
<p>Daí é game-over, posso fazer o que quiser, inclusive editar o arquivo &ldquo;/etc/passwd&rdquo; pra injetar um novo usuário com permissão de root na máquina de verdade e invadir ela. Eu acho que ninguém em sã consciência jamais subiria um container de Docker num servidor de produção apontando pra raíz do HD do servidor. Só um sem noção faria isso. Seria certificado de estupidez.</p>
<p>É uma das razões de porque muitos de nós preferimos usar Podman em vez de Docker. Podman foi feito pra ter todos os mesmos comandos, mesmas opções e mesma sintaxe de Docker. Então comandos como <code>docker build</code> ou <code>docker run -it bla bla</code> posso rodar com <code>podman build</code> ou <code>podman run bla bla</code>. Se quiser posso fazer um alias no shell chamado docker que aponta pro podman e tudo vai funcionar praticamente igual. Podman usa o mesmo arquivo Dockerfile com a mesma sintaxe, a única diferença é aquele comando FROM no começo do arquivo.</p>
<p>Por exemplo, no caso do postgres começa com &ldquo;FROM debian&rdquo;. Pro Podman suportar precisa reescrever como &ldquo;FROM docker.io/debian&rdquo;. Só especificar o site da imagem porque ele suporta outros repositórios em vez de só o dockerhub. Outra diferença é que Docker é construído em cima do containerd, mas Podman usa o RunC como mostrei antes. Diferente do containerd não precisa ter um daemon rodando como root. Na verdade RunC, e por consequência o Podman, tem modo &ldquo;rootless&rdquo;. Ou seja, nem preciso iniciar com &ldquo;sudo&rdquo;. Portanto o container consegue rodar sem privilégio nenhum e por padrão é mais seguro. Veja documentação deles de como funciona esse modo porque não vou detalhar hoje.</p>
<p>E falando em segurança e permissões, pra concluir, faltou falar daquele bloco no arquivo do <code>config.json</code> que falei pra pular, lembram? Capacidade ou Linux Capabilities. Toda vez que se fala em permissões em Linux, automaticamente pensamos em coisas como os comandos &ldquo;chown&rdquo; pra mudar a propriedade de algum arquivo ou &ldquo;chmod&rdquo; pra mudar um arquivo pra ser só de leitura por exemplo. Ou, usar ou não usar &ldquo;sudo&rdquo; pra executar alguma coisa. E é isso, acabou.</p>
<p>Só que não. De cara vocês já aprenderam sobre cgroups e como eles limitam os recursos do processo, ou namespaces e como o sistema mente pros processos, independente se tem permissão de root ou não. E tem muito mais em cima. Linux capabilities é a habilidade da kernel de dar ou remover capacidades com o comando <code>setcap</code>.</p>
<p>Vou tentar não descer em detalhe desnecessário demais, mas num terminal o primeiro comando pra entender capabilities é o <code>capsh</code>. Execute num terminal e vemos dois campos no começo: Current e Bounding Set. Current vazio significa que um usuário normal começa sem nenhuma capacidade por default, mas tem várias capacidades que podem ser habilitadas, que é essa lista enorme chamada de Bounding Set.</p>
<p>Existem dois tipos de capacidades em Linux: capacidades efetivas e capacidades permitidas. Capacidades efetivas determinam quais operações um processo pode performar, e capacidades permitidas são quais podem ser habilitadas ou desabilitadas. Bounding Set é um sub-conjunto de capacidades permitidas. E quem controla esse set é o comando <code>prctl</code>, depois pesquise porque não quero entrar em detalhes sobre granularidade de segurança de Linux hoje.</p>
<p>Próximo comando vai ser <code>grep Cap /proc/$BASHPID/status</code>. Essa variável BASHPID é o pid do bash. Aí aparece essa lista bizarra, mas é fácil. O primeiro são inherited capabilities, ou capacidades herdadas no caso zero. Segundo são capacidades permitidas que eu falei, zero. Terceiro capacidades efetivas que também expliquei, zero. Finalmente capacidades bounding set.</p>
<p>Com o comando <code>capsh --decode=</code> esse hexadecimal e a lista é, obviamente, a mesma que vimos no comando <code>capsh --print</code> antes. Isso fica guardado no objeto proc do processo. E esse hexadecimal é um bit set. Um conjunto de bits. Se você não conhece a técnica de guardar propriedade num bit dentro de um inteiro e pegar os valores com uma máscara binária, um bit mask, pesquise. É essencial pra economizar bits em coisas como protocolos de rede.</p>
<p>De qualquer forma veja de novo aquele bloco de capabilities em <code>config.json</code>, agora você sabe o que é efetivo ou permitido. Esses cap_alguma coisa tem que ler no manual de capabilities. Só digitar <code>man capabilities</code> no terminal. Pronto, cap_audit_write é capacidade de escrever no log do sistema, cap_kill é capacidade de enviar sinais tipo SIGTERM ou SIGKILL e cap_net_bind_service é pra conseguir dar bind em portas privilegiadas abaixo de 1024, como a porta 80 sem precisar fazer sudo.</p>
<p>Com isso vocês tem pelo menos noção agora do que são containers do ponto de vista da kernel do Linux. Eles não são máquinas virtuais. É simplesmente um jarro que a kernel cria ao redor de um processo que roda nativamente pra restringir o que ele pode ver ou acessar, fazendo esse processo acreditar que está sozinho em outra máquina. A kernel usa cgroups e capabilities pra limitar os recursos e capacidades que esse processo tem acesso e usa namespaces pra limitar os sentidos desse processo, fazendo enxergar coisas como pids, mounts, rede, e tudo mais que são falsos, dentro da Matrix.</p>
<p>Docker é implementado em cima de containerd que é um serviço que precisa rodar como root. Podman e Kubernetes são implementados em cima de RunC que é mais leve e sem necessidade de um daemon auxiliar. Falando nisso, pode ser que Kubernetes ou Terraform confundam pra quem nunca mexeu. Qual a diferença disso pra Docker ou Docker Compose?</p>
<p>Vamos lá. Dockerfile vocês já entenderam, servem pra construir imagens OCI. Docker Compose é hiper útil, tem o Podman Compose que é exatamente a mesma coisa pra quem escolher usar Podman. Ambos começam com um arquivo <code>docker-compose.yml</code>. Deixa eu dar um exemplo besta aqui. Digamos que está trabalhando num projeto em Node. Comece criando um Dockerfile que constrói a imagem do seu projeto Node com seguinte conteúdo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-docker" data-lang="docker"><span class="line"><span class="cl"><span class="c"># Use the latest LTS (long-term support) version of Node.js as the base image</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">docker.io/node:lts</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Set the working directory to the project root</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Copy the package.json and package-lock.json files to the working directory</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> package*.json ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Install the dependencies</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> npm install<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Copy the rest of the project files to the working directory</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Expose the app&#39;s port</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">3000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Run the app when the container is started</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span> <span class="s2">&#34;npm&#34;</span><span class="p">,</span> <span class="s2">&#34;start&#34;</span> <span class="p">]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Começamos a partir da imagem do Node versão lts em vez do mais novo que é latest, lembram? Projetos em produção precisam de estabilidade e não da versão mais nova que pode ser a mais bugada. Sempre use a mesma versão de tudo na sua máquina de desenvolvimento e nos servidores de produção e isso começa pela image de docker.</p>
<p>Dentro do container, o código fonte do seu projeto vai ficar nesse WORKDIR, em /app.  Daí copiamos o arquivo package.json pra dentro do container e rodamos <code>npm install</code> pra criar o <code>node_modules</code> e baixar todas as dependências. Terminamos de copiar todos o códigos fonte do projeto com esse comando COPY. Além disso esse container vai expôr pra fora a porta 3000 que é onde o servidor de node vai dar bind quando rodar o comando que está em CMD, no caso <code>npm start</code>.</p>
<p>Não esquecer de também criar um arquivo <code>.dockerignore</code> praquele comando COPY não copiar o <code>node_modules</code> que está no seu ambiente de desenvolvimento. Dentro do container já rodamos <code>npm install</code> então não precisa copiar tudo de novo. É importante entender onde ficam os arquivos em cada ambiente.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>node_modules
*.log</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Só com isso já dá pra fazer <code>docker build . -t seuusuario/seuprojeto</code> E já dá pra rodar com <code>docker run -p 3000:3000 seuusuario/seuprojeto</code>. Digamos que seu projeto precisa ter Redis e Postgres instalado. Isso costuma ser um problema, porque se você for um programador bem ocupado e experiente, na sua máquina deve ter diversos projetos. E alguns mais antigos podem depender de Postgres mais antigo, outros de Postgres mais novo. Mas se instalar na sua máquina via o gerenciador de pacotes da sua distro, sempre vai estar com a versão mais nova só.</p>
<p>Pra ter várias versões de node, muita gente usa o NVM ou o meu favorito que é o ASDF, mas e pra ter várias versões de postgres, de redis, de mysql, de kafka e todos esses outros componentes? E é pra isso que entra o Docker Compose. Este é um exemplo de arquivo <code>docker-compose.yml</code> que também ficaria na raíz do seu projeto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000:3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_HOST=db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">REDIS_HOST=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/redis:latest</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Docker Compose orquestra e sobe vários containers Docker que ele chama de services. O primeiro é sua aplicação.  Ele começa com o atributo build sendo ponto, que é o diretório atual. Vai subir a imagem que seria construída com o Dockerfile que tá no seu projeto nesse diretório. Daí configuramos o projeto com variáveis de ambiente, por exemplo dizemos que DATABASE_HOST se chama &ldquo;db&rdquo;. Normalmente você colocaria &ldquo;localhost&rdquo;, mas cada serviço é como se estivesse rodando numa máquina separada graças ao Namespace UTS que permite que cada um tenha seu hostname separado, por isso o service de Postgres vai se chamar &ldquo;db&rdquo;.</p>
<p>Logo embaixo começa o bloco &ldquo;db&rdquo; onde dizemos que é pra baixar a imagem <code>docker.io/postgres:latest</code> e configuramos o usuário e senha, que é o mesmo que o serviço de node da sua aplicação vai usar. Mesma coisa no terceiro service que chamamos de &ldquo;redis&rdquo; e baixa imagem docker.io/redis. Latest é a versão mais nova, mas num projeto de verdade é bom fixar uma versão em específico pra não confundir.</p>
<p>Veja também que o serviço app, que é sua aplicação, tem um bloco dizendo &ldquo;depends_on&rdquo;, ou seja, o docker compose só vai subir sua aplicação depois que os serviços de postgres e redis carregarem. E pronto. Mesmo que você seja um programador que acabou de entrar no projeto, depois de fazer <code>git clone</code> do repositório, é só rodar <code>docker compose up</code> e pronto! Vai subir todos os serviços e ao final pode ir no navegador a apontar pra localhost:3000 e que vai estar tudo de pé e funcionando. O ideal é manter essa configuração atualizada, porque o que mais acontece é ficar ajustando só na sua máquina e não subir a atualização pro repositório, daí pessoas novas entram na equipe e baixam uma configuração desatualizada e dá vários problemas. Mantenha tudo atualizado.</p>
<p>De bônus, lembra do video de otimização web onde explico sobre coisas como NGINX? Podemos simular o que eu falei naquele video com docker compose. Não é uma coisa que faríamos num projeto de verdade mas só pra ilustrar como aquele diagrama conceitual viraria código de Docker Compose:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app_1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_HOST=db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">REDIS_HOST=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app_2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_HOST=db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATABASE_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">REDIS_HOST=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_USER=app_user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_PASSWORD=app_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/redis:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">load_balancer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker.io/nginx:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8080:80&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./nginx.conf:/etc/nginx/nginx.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app_1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app_2</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Os serviços app, redis e postgres são iguaizinhos o do exemplo anterior mas agora adicionei um &ldquo;load balancer&rdquo; que puxa a imagem do nginx. Lembram que expliquei naquele video que ele é quem realmente responde na porta 80? E pro nginx funcionar precisa ter um arquivo <code>nginx.conf</code> no seu projeto que instruímos o docker compose copiar pra dentro do container de nginx. Esse arquivo diz o seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>user                 nginx;
worker_processes     auto;

events {
    worker_connections  65535;
    multi_accept        on;
}

http {
    charset              utf-8;
    sendfile             on;
    tcp_nopush           on;
    tcp_nodelay          on;
    client_max_body_size 4m;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    map $remote_addr $proxy_forwarded_elem {

        # IPv4 addresses can be sent as-is
        ~^[0-9.]&#43;$        &#34;for=$remote_addr&#34;;

        # IPv6 addresses need to be bracketed and quoted
        ~^[0-9A-Fa-f:.]&#43;$ &#34;for=\&#34;[$remote_addr]\&#34;&#34;;

        # Unix domain socket names cannot be represented in RFC 7239 syntax
        default           &#34;for=unknown&#34;;
    }

    map $http_forwarded $proxy_add_forwarded {

        # If the incoming Forwarded header is syntactically valid, append to it
        &#34;~^(,[ \\t]*)*([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?(;([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?)*([ \\t]*,([ \\t]*([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?(;([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;=([!#$%&amp;&#39;*&#43;.^_`|~0-9A-Za-z-]&#43;|\&#34;([\\t \\x21\\x23-\\x5B\\x5D-\\x7E\\x80-\\xFF]|\\\\[\\t \\x21-\\x7E\\x80-\\xFF])*\&#34;))?)*)?)*$&#34; &#34;$http_forwarded, $proxy_forwarded_elem&#34;;

        # Otherwise, replace it
        default &#34;$proxy_forwarded_elem&#34;;
    }

    upstream www {
        least_conn;
        server app_1:3000;
        server app_2:3000;
    }
    
    server {
    
        listen 80;
    
        location / {
            proxy_pass http://www;

            proxy_http_version                 1.1;
            proxy_cache_bypass                 $http_upgrade;
            
            # Proxy headers
            proxy_set_header Upgrade           $http_upgrade;
            #proxy_set_header Connection        $connection_upgrade;
            proxy_set_header Host              $host;
            proxy_set_header X-Real-IP         $remote_addr;
            proxy_set_header Forwarded         $proxy_add_forwarded;
            proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host  $host;
            proxy_set_header X-Forwarded-Port  $server_port;
        }
    
    }
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O bloco server, tem a configuração do servidor que vai escutar na porta 80 e fazer o tal proxy reverso pra <a href="http://app"target="_blank" rel="noopener">http://app</a> e app está configurado acima, são os hostnames app_1 e app_2 na porta 3000 de cada container. Lembrem que containers tem rede virtual separada, namespace de portas separado. Então cada um, quando carregar, se penduram na porta 3000 e o nginx vai fazer round robin entre esses dois servidores.</p>
<p>app_1 e app_2 herdam do serviço app que é sua aplicação em Node. Aqui estamos subindo duas instâncias pra ter balanceamento de carga entre os dois. Note que o serviço load_balancer depende desse app_1 e app_2 estarem de pé, e esses dois, por herdarem de app, dependem de redis e postgres estarem de pé e é assim que o docker compose sabe quem tem que subir primeiro pra tudo estar funcionando. E pronto! Isso é uma pequena demonstração de uma aplicação node com dois processos sendo balanceados por um nginx.</p>
<p>Docker Compose foi feito pra infraestrutura bem simples, em particular pra rodar na sua máquina de desenvolvimento e facilitar a vida de programadores. Ele até serve pra fazer deploy em servidores pequenos, tipo numa Digital Ocean da vida. Antigamente tinha um projeto chamado Docker Machine que servia pra conversar com provedores de servidores, mas acho que ninguém usa mais.</p>
<p>Veja como nesse arquivo, pra subir duas instâncias da aplicação, precisou manualmente digitar app_1, app_2, colocar eles no depends_on do load_balancer. Imagina se precisasse subir 10 instâncias. Ou se precisasse configurar replica de Postgres. Ou se precisasse configurar uma fila e vários servidores separados de jobs assíncronos como falei no video do ChatGPT. Esse arquivo não escala, ia ficar complicado ainda mais porque também não sabe só aumentar ou diminuir só os containers de web. Ele não tem noção do que está rodando nos servidores já e só dar manutenção.</p>
<p>É pra isso que entra Kubernetes e Terraform. Kubernetes é um conjunto de tecnologias de infraestrutura elástica. A parte que se aproxima de Docker é o que ele chama de pods, que são containers que rodam com RunC. Mas o Kubernetes faz bem mais, configura VPC que são redes privadas virtuais. Eu posso criar uma rede isolada só pros containers web, outra rede isolada separada só pra servidores de jobs, por exemplo. Sabe lidar com balanceador de carga, o Ingress, adicionar e remover pods dinamicamente, e sabe que está ou na AWS ou no Google Cloud ou na Azure e com isso consigo adicionar serviços exclusivos como o RDS da AWS como banco de dados ou o Cloud SQL do Google Cloud. É bem mais flexível e, por isso, mais complicado.</p>
<p>Entenda, um IaaS ou Infraestrutura como Serviço, como a AWS, me fornece os componentes básicos de infraestrutura. O Kubernetes é melhor pra lidar mais com os pods, os containers, depois que já tem a infra de máquinas e rede pronta. Pra subir essa infra inicial, podemos ou fazer na mão via interface web, o que não recomendamos, ou orquestrar com Terraform.</p>
<p>Por exemplo, máquinas virtuais que o AWS chama de EC2. Esses servidores tem tamanhos diferentes, por exemplo t2.nano que só tem 1 vcpu ou t2.xlarge de 4 vcpus ou a1.metal com 16 cpus, ou m4.16xlarge de 64 vcpus, cada um com um preço diferente por hora. Tem planos diferentes pra S3, pra RDS, pra SQS, e assim vai. E isso só na AWS. Pra organizar tudo, usamos Terraform. Deixa mostrar só alguns trechos de um exemplo de arquivo de configuração, que pode ficar super comprido.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Set up the provider for the desired cloud provider
provider &#34;aws&#34; {
  region = &#34;us-east-1&#34;
}

# Create a VPC to host the resources
resource &#34;aws_vpc&#34; &#34;app_vpc&#34; {
  cidr_block = &#34;10.0.0.0/16&#34;
}

# Create a public subnet in the VPC
resource &#34;aws_subnet&#34; &#34;public_subnet&#34; {
  vpc_id            = aws_vpc.app_vpc.id
  cidr_block        = &#34;10.0.1.0/24&#34;
  availability_zone = &#34;us-east-1a&#34;
}

# Create an Internet Gateway and attach it to the VPC
resource &#34;aws_internet_gateway&#34; &#34;app_igw&#34; {
  vpc_id = aws_vpc.app_vpc.id
}

# Create a route table and a route for the public subnet
resource &#34;aws_route_table&#34; &#34;public_route_table&#34; {
  vpc_id = aws_vpc.app_vpc.id
  route {
    cidr_block = &#34;0.0.0.0/0&#34;
    gateway_id = aws_internet_gateway.app_igw.id
  }
}

# Associate the public subnet with the route table
resource &#34;aws_route_table_association&#34; &#34;public_subnet_association&#34; {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_route_table.id
}

# Create an EC2 instance to host the Node.js application
resource &#34;aws_instance&#34; &#34;app_server&#34; {
  ami           = &#34;ami-12345678&#34;
  instance_type = &#34;t2.micro&#34;
  vpc_id        = aws_vpc.app_vpc.id
  subnet_id     = aws_subnet.public_subnet.id
  key_name      = &#34;app-key&#34;
  user_data     = &#34;${file(&#34;user_data.sh&#34;)}&#34;
}

# Create a security group for the EC2 instance
resource &#34;aws_security_group&#34; &#34;app_server_sg&#34; {
  name        = &#34;app_server_sg&#34;
  description = &#34;Security group for the application server&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;0.0.0.0/0&#34;]
  }
}

# Create an RDS instance for Postgres
resource &#34;aws_db_instance&#34; &#34;app_db&#34; {
  engine               = &#34;postgres&#34;
  engine_version       = &#34;11.5&#34;
  instance_class       = &#34;db.t2.micro&#34;
  name                 = &#34;app_db&#34;
  username             = &#34;app_user&#34;
  password             = &#34;app_password&#34;
  vpc_security_group_ids = [aws_security_group.app_db_sg.id]
  vpc_id               = aws_vpc.app_vpc.id
  subnet_group_name    = &#34;app_db_subnet_group&#34;
}

# Create a security group for the RDS instance
resource &#34;aws_security_group&#34; &#34;app_db_sg&#34; {
  name        = &#34;app_db_sg&#34;
  description = &#34;Security group for the application database&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 5432
    to_port     = 5432
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;10.0.1.0/24&#34;]
  }
}

# Create a security group for Redis
resource &#34;aws_security_group&#34; &#34;app_redis_sg&#34; {
  name        = &#34;app_redis_sg&#34;
  description = &#34;Security group for Redis&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 6379
    to_port     = 6379
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;10.0.1.0/24&#34;]
  }
}

# Create an Elasticache cluster for Redis
resource &#34;aws_elasticache_cluster&#34; &#34;app_redis&#34; {
  engine            = &#34;redis&#34;
  cluster_id        = &#34;app_redis&#34;
  node_type         = &#34;cache.t2.micro&#34;
  num_cache_nodes   = 1
  security_group_ids = [aws_security_group.app_redis_sg.id]
  vpc_id            = aws_vpc.app_vpc.id
}

# Create an Nginx load balancer
resource &#34;aws_lb&#34; &#34;app_lb&#34; {
  name            = &#34;app_lb&#34;
  internal        = false
  security_groups = [aws_security_group.app_lb_sg.id]
  subnets         = [aws_subnet.public_subnet.id]
  load_balancer_type         = &#34;application&#34;
  idle_timeout               = 60
  enable_deletion_protection = false
}

# Create a security group for the Nginx load balancer
resource &#34;aws_security_group&#34; &#34;app_lb_sg&#34; {
  name        = &#34;app_lb_sg&#34;
  description = &#34;Security group for the application load balancer&#34;
  vpc_id      = aws_vpc.app_vpc.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = &#34;tcp&#34;
    cidr_blocks = [&#34;0.0.0.0/0&#34;]
  }
}

# Create a target group for the Nginx load balancer
resource &#34;aws_lb_target_group&#34; &#34;app_tg&#34; {
  name     = &#34;app_tg&#34;
  port     = 80
  protocol = &#34;HTTP&#34;
  vpc_id   = aws_vpc.app_vpc.id
  target_type = &#34;instance&#34;
}

# Register the EC2 instance with the target group
resource &#34;aws_lb_target_group_attachment&#34; &#34;app_tg_attachment&#34; {
  target_group_arn = aws_lb_target_group.app_tg.arn
  target_id        = aws_instance.app_server.id
  port             = 80
}

# Create a listener for the Nginx load balancer
resource &#34;aws_lb_listener&#34; &#34;app_listener&#34; {
  load_balancer_arn = aws_lb.app_lb.arn
  port              = &#34;80&#34;
  protocol          = &#34;HTTP&#34;
  default_action {
    type             = &#34;forward&#34;
    target_group_arn = aws_lb_target_group.app_tg.arn
  }
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja, o Terraform sabe que o provedor é AWS, e em qual região geográfica, no caso leste dos Estados Unidos, tipo Nova Iorque. Veja configuração de rede privada, subnets, gateway, tabela de rotas, e mais.</p>
<p>Daí contrato um servidor EC2, tamanho t2.micro, e configuro pra rede que tinha declarado antes. Embaixo configuramos grupo de segurança. Depois tenho configuração de banco de dados, depois de Redis que é o serviço que a AWS chama de Elasticache, depois tem o load balancer. E esse arquivo de exemplo é bem simplório e incompleto. Entre Kubernetes e Terraform, tem bastante detalhe pra realmente deployar uma aplicação em produção que esteja configurado de forma eficiente e com segurança.</p>
<p>Numa configuração de verdade, se tudo estiver direitinho declarado, seria só digitar <code>terraform init</code>  pra fazer download e instalar os plugins necessários. Daí <code>terraform plan</code> pra criar o plano do deployment pra esse provedor e a gente checar se tá tudo realmente certo. Coisa que já tá instalada ele não vai instalar de novo, vai só reconfigurar e instalar o que falta.  E no final fazer <code>terraform apply</code> pra realmente contratar os serviços, configurar e colocar nos lugares certos na infra.</p>
<p>Video de hoje não é sobre devops mas já que estou falando de containers e serviços, achei legal só mostrar um exemplo pra quem nunca viu. Mas o ponto é: compare uma configuração longa de Terraform com um simples Docker Compose. Nem chega perto, porque o objetivo das duas ferramentas é diferente. Um é pra orquestrar uma infra de verdade, a outra é pra rodar só na sua máquina pra facilitar desenvolvimento. Pra configurar uma AWS use Kubernetes e Terraform. Pra configurar serviços na sua máquina de desenvolvimento, use Docker Compose. Todo bom projeto deveria ter isso.</p>
<p>Pra finalizar, não vou mostrar mas queria só explicar outras tecnologias de containers que existem. Expliquei sobre imagens OCI de Docker, mas tem outro projeto que recomendo que pesquisem chamado OSTree. Lembra que falei que uma imagem meio que parece montado com commits binários de um Git? Existe de fato um projeto que é um Git de sistema de arquivos, o OSTree. A idéia é a seguinte: imagina que você tem 100 servidores numa AWS da vida, coisa grande. Imagina se sai atualização do Ubuntu Server que roda lá, o que você vai fazer? Mandar rodar <code>apt update; apt upgrade -y</code> e rezar pra não dar pau?</p>
<p>Na verdade não, vai montar imagens com ostree. O sistema de arquivos de todo servidor vai ser como um Git gigante. Daí sai atualização, você atualiza só em uma imagem. O OSTree nos servidores vai baixar a nova imagem e trocar a imagem antiga pela imagem nova. É tipo dar um git pull e trocar pro branch mais novo com um git checkout manja? É uma explicação grosseira, mas é bom saber que isso existe.</p>
<p>Outra coisa é sobre capacidades. Além de Linux capabilities que expliquei antes existem outra formas de limitar acesso a recursos da máquina, especialmente no contexto de segurança. Sabe em aplicativo Android ou iOS hoje em dia que pergunta se você quer dar acesso da camera pra um app ou acesso à sua localização e coisas assim? No mundo Linux isso é responsabilidade de componentes como Seccomp de Secure Computing Mode, que é outra funcionalidade da kernel do Linux pra restringir chamadas de sistema. Ele normalmente é usado em combinação com outros mecanismos de segurança, mas o seccomp em si oferece a infra na kernel pra aplicar filtros nos programas. Se alguém tentar fazer alguma coisa que não deveria, a kernel recusa.</p>
<p>Em cima disso temos projetos como AppArmor e SE-Linux. Olhando só por cima, eles se complementam. Ambos servem pra forçar políticas de segurança nos programas. O AppArmor permite especificar que recursos, como arquivos, dispositivos e sockets de rede um programa tem permissão pra usar. SE-Linux significa Security Enhanced Linux que também força políticas, por exemplo, pra acesso a hardware externo. É ele quem pode proibir um aplicativo de usar sua webcam.</p>
<p>Além disso, do jeito que falei, parece que todo container vai usar Namespaces de Linux com o comando unshare, mas existem outras formas de fazer containers, como o projeto Bubblewrap que é um container mais simples e usa outro recurso da kernel do Linux que são namespaces unprivileged e seccomp-bpf.</p>
<p>Mencionei ostree, seccomp, apparmor, se-linux e bubblewrap porque se você juntar todos eles num único projeto, começa a surgir com coisas como Flatpak. Sabe, aquele instalador de aplicativos? Tecnicamente é como se fosse um Docker ou Podman, mas feito pra funcionar melhor com aplicativos, especialmente aplicativos gráficos, com janela e que precisam acessar seus arquivos, USB e se comunicar com outros programas, precisando ter acesso a serviços como dbus. Tem gente que fica pensando em usar Docker pra instalar um Gimp ou LibreOffice. Não né, pra isso serve Flatpak.</p>
<p>Flatpak roda aplicativos como Spotify, Discord, Zoom, dentro de containers semi-isolados. Menos isolados que um container de Docker, mas um pouco mais seguros do que se fosse instalado direto via apt ou pacman. E o Snap de Ubuntu é similar, um gerenciador de containers. Em vez dos pacotes serem tipo zips como expliquei no video de Slackware, Flatpak e Snap baixam imagens, como imagens de Docker, só que em vez de imagem OCI é OSTree.</p>
<p>Entenda que Flatpak e Snap são primos de Docker e Kubernetes. As tecnologias por baixo também dependem da kernel do Linux e sempre caímos em cgroups, namespaces, capabilities e algum formato de HD virtual dentro de um linguição de bytes binários que chamamos de imagens. Espero que tenha dado uma luz no entendimento de containers e espero que ninguém mais tenha a pachorra de confundir containers com máquinas virtuais. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>containers</category><category>linux</category><category>devops</category><category>akitando</category></item></channel></rss>