<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Homelab | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/homelab/</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/homelab/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>Desastre no meu NAS! Como quase perdi 90 TB de dados</title><link>https://akitaonrails.github.io/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/</guid><pubDate>Tue, 06 Oct 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Ontem, segunda-feira, fui acordado às 8 da manhã por um bipe alto e insistente. Eu sabia que era o NAS antes de abrir o olho. Corri pra ver, e quase perdi o chão: dois discos marcados como críticos, num array que nunca pode ter mais do que um.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-2-discos-criticos.png" alt="Painel do Synology mostrando o Storage Pool 1 em estado crítico, com os discos 1 e 8 marcados como Critical" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Naquele momento eu sabia que estava a um soluço de perder tudo. Este texto é o post-mortem do que aconteceu, e a primeira parte foi escrita no calor do desastre, com a cópia de salvamento ainda rodando.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ontem, segunda-feira, fui acordado às 8 da manhã por um bipe alto e insistente. Eu sabia que era o NAS antes de abrir o olho. Corri pra ver, e quase perdi o chão: dois discos marcados como críticos, num array que nunca pode ter mais do que um.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-2-discos-criticos.png" alt="Painel do Synology mostrando o Storage Pool 1 em estado crítico, com os discos 1 e 8 marcados como Critical"  loading="lazy" /></p>
<p>Naquele momento eu sabia que estava a um soluço de perder tudo. Este texto é o post-mortem do que aconteceu, e a primeira parte foi escrita no calor do desastre, com a cópia de salvamento ainda rodando.</p>
<blockquote>
  <p><strong>Atualização de 11 de outubro:</strong> a cópia terminou e o final foi feliz. Nenhum arquivo se perdeu por acidente, os dois NAS estão saudáveis, cada um com seu RAID-6. A recuperação inteira está na <a href="#seis-dias-depois-o-final-da-hist%c3%b3ria">segunda metade deste texto</a>. A primeira parte fica como foi publicada, sem retoques.</p>

</blockquote>
<h2>Eu uso NAS faz muito tempo<span class="hx:absolute hx:-mt-20" id="eu-uso-nas-faz-muito-tempo"></span>
    <a href="#eu-uso-nas-faz-muito-tempo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não sou marinheiro de primeira viagem. Fiz uma <a href="https://www.youtube.com/playlist?list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt"target="_blank" rel="noopener">minissérie inteira de vídeos sobre armazenamento e sistemas de arquivos</a>, e recomendo assistir antes de vir me perguntar &ldquo;qual um bom NAS pra iniciante?&rdquo;. Não existe isso.</p>
<p>Uso armazenamento externo desde a segunda metade dos anos 2000, quando era usuário de Mac e existia o Drobo. A empresa não existe mais, mas tive três DAS deles antes de migrar pra Synology, primeiro num modelo de entrada e depois no DS1821+, intermediário, que uso há mais de cinco anos. Já escrevi sobre ele aqui, <a href="/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/">configurando NFS no Linux</a> e <a href="/2025/04/24/acessando-seu-nas-usando-iscsi-em-vez-de-smb/">acessando por iSCSI</a>.</p>
<p>Ele foi muito valioso pra mim, e você não tem que questionar por que eu preciso de tanto espaço. Não importa: cada um decide o que guarda no seu.</p>
<p>Quando eu produzia vídeo pro YouTube, editava material 4K direto do NAS, graças ao cache NVMe e à rede 10 GbE. Me acostumei com armazenamento grande a 10 gigabits e não consigo mais voltar atrás.</p>
<p>O array estava organizado em SHR, o RAID híbrido da Synology, que na prática se comporta como RAID-5 (mais sobre isso adiante): aguenta a perda de um disco sem perder dado nenhum. Sempre achei que isso bastava.</p>
<h2>HD morre. Sempre<span class="hx:absolute hx:-mt-20" id="hd-morre-sempre"></span>
    <a href="#hd-morre-sempre" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não se engane: disco rígido estraga. Nunca, jamais confie num HD solitário esquecido num armário. Ele vai falhar, e você vai perder tudo o que está nele.</p>
<p>O melhor dado público sobre isso é o da Backblaze, que opera centenas de milhares de discos e publica a taxa de falha todo trimestre. No <a href="https://www.backblaze.com/blog/backblaze-drive-stats-for-q2-2026/"target="_blank" rel="noopener">relatório do segundo trimestre de 2026</a>, com 354.415 discos monitorados, a taxa anualizada foi de 1,73%, e a taxa acumulada da frota inteira está em 1,41% ao ano.</p>
<p>Parece pouco. Traduzindo pra escala de gente:</p>
<ul>
<li><strong>Um disco sozinho, por 5 anos:</strong> a chance de ele morrer nesse período fica perto de 7%. É mais ou menos a chance de tirar um número específico num dado de 14 lados. Você apostaria todas as fotos da sua família nisso?</li>
<li><strong>Um disco sozinho, por 10 anos:</strong> uns 13%, um em cada oito.</li>
<li><strong>Oito discos, como no meu NAS, por um ano:</strong> a chance de pelo menos um falhar é de uns 11%. Em cinco anos, mais de 40%.</li>
</ul>
<p>E esses números são de datacenter, com temperatura controlada, energia limpa e disco girando o tempo todo. HD de gaveta, que fica anos parado e leva tranco na mudança, não tem estatística tão bonita.</p>
<p>Cartão de memória é pior, e nem existe uma estatística equivalente pra consultar. O que existe é teste de tortura: o <a href="https://www.bahjeez.com/the-great-microsd-card-survey-one-year-later/"target="_blank" rel="noopener">Great MicroSD Card Survey</a> testou 216 cartões ao longo de um ano de escrita contínua, e 52 deles, um em cada quatro, já tinham morrido.</p>
<p>Cartão legítimo começou a dar erro, em média, depois de uns 2.500 ciclos de regravação; cartão falsificado, depois de 700. Cartão de memória é mídia de transporte. Guardar a única cópia de alguma coisa num microSD é pedir pra perder.</p>
<p>A conclusão prática é a mesma da minissérie: dado importante precisa de redundância. Um array com paridade existe justamente pra que a morte de um disco, que é questão de tempo, não leve nada junto.</p>
<h2>O que aconteceu<span class="hx:absolute hx:-mt-20" id="o-que-aconteceu"></span>
    <a href="#o-que-aconteceu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com tudo isso, eu deveria estar tranquilo com a minha configuração. Não estava.</p>
<p>Começou anteontem, domingo. Fazia tempo que eu via o volume chegando no teto.</p>
<p>No começo eram 8 discos de 10 TB. Com os anos fui trocando, um a um, por discos de 20 TB (o DSM mostra em base binária; na etiqueta são 12 e 22 TB). É um processo lento: cada troca leva dias, porque o array precisa reconstruir e redistribuir os dados.</p>
<p>Decidi trocar mais um, o sétimo. A Synology suporta hot-swap, então é simples: tira o disco velho, encaixa o novo, adiciona ao array e deixa o DSM cuidar da reconstrução.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-pool-reparando.png" alt="Storage Pool 1 do Synology em reparo, 0,01% concluído, com 108 TB alocados de 121,8 TB"  loading="lazy" /></p>
<p>No domingo à noite estava assim: reconstrução começando, os oito discos saudáveis, 108 TB alocados de 121,8 TB.</p>
<p>Na segunda, às 8 da manhã, o bipe. O disco 1, o novo, com 13 horas de uso, marcado como crítico:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-drive1-critico.png" alt="Disco 1 do Synology em estado crítico, com erro de I/O e 13 horas ligado"  loading="lazy" /></p>
<p>E o disco 8, um dos antigos, com 22.095 horas ligado (dois anos e meio), também crítico, com erro de leitura às 08:05:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-drive8-critico.png" alt="Disco 8 do Synology em estado crítico, com erro de leitura e a mensagem de que o número de discos com falha excede a tolerância do RAID"  loading="lazy" /></p>
<p>A mensagem do pool não deixava margem: &ldquo;erros irrecuperáveis ocorreram porque erros de disco aconteceram depois da degradação do storage pool. Faça backup dos seus dados imediatamente.&rdquo;</p>
<h3>Por que dois discos é o fim<span class="hx:absolute hx:-mt-20" id="por-que-dois-discos-é-o-fim"></span>
    <a href="#por-que-dois-discos-%c3%a9-o-fim" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em RAID-5, se dois discos caem, a redundância é zero, e em teoria o array inteiro vai junto. É uma situação irrecuperável.</p>
<p>Quem assistiu a minha playlist sabe o motivo. Um array desses não guarda &ldquo;arquivos&rdquo;, um por disco. Um arquivo é uma coleção de blocos, e os blocos ficam espalhados por todos os discos, junto com a paridade. Perder um disco é perder um pedaço de cada arquivo. Não adianta os outros sete estarem saudáveis, porque eles só têm partes.</p>
<p>A paridade serve pra recalcular o pedaço que falta quando um disco some. Com dois faltando, a conta não fecha.</p>
<p>E tem um detalhe cruel na reconstrução: pra recriar o disco novo, o array precisa ler todos os outros discos de ponta a ponta. São mais de 100 TB de leitura contínua. Se algum disco antigo tinha setor ruim escondido num canto que ninguém lia fazia tempo, é nessa hora que ele aparece. Foi o que aconteceu com o disco 8.</p>
<h2>Pra onde copiar 90 TB?<span class="hx:absolute hx:-mt-20" id="pra-onde-copiar-90-tb"></span>
    <a href="#pra-onde-copiar-90-tb" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A única saída, naquele ponto, era agradecer aos céus por os volumes ainda estarem acessíveis, com os arquivos aparecendo, e usar a janela pra copiar tudo pra fora imediatamente. Eu não podia encostar em mais nada.</p>
<p>O problema: copiar uns 90 TB pra onde? Ninguém tem um NAS de 100 TB vazio sobrando em casa.</p>
<p>Normalmente eu compraria esse tipo de peça nos Estados Unidos, numa viagem de turismo, e traria comigo. Sai muito mais barato. Não compensa comprar eletrônico caro no Brasil: o país cobra imposto de importação pior que as tarifas do Trump faz décadas, e o preço final costuma passar do dobro do preço de tabela americano.</p>
<p>Só que eu tinha que escolher. Quanto vale perder 90 TB de dados que levei anos juntando e que são quase impossíveis de recuperar de outro lugar? É uma daquelas situações em que a resposta é &ldquo;não tem preço&rdquo;, e então, qualquer que seja o preço, eu pago.</p>
<p>Achei um revendedor de storage perto de casa, a <a href="https://www.controle.net"target="_blank" rel="noopener">Controle Net</a>. Gente boa, muito ágil na resposta, e me mandaram uma proposta de equipamento com entrega no mesmo dia.</p>
<p>A primeira sugestão deles foi um QNAP TS-832PX. Fui pesquisar antes de aceitar. Ele tem as mesmas 8 baias e já vem com 10 GbE, mas o processador é um ARM Cortex-A57 de 1,7 GHz (Annapurna Labs AL324) com 4 GB de RAM, metade do mínimo de 8 GB que o QuTS hero pede. Pra RAID-6 em ZFS, com checksum e paridade dupla em cada bloco, é hardware fraco, como eu explico mais abaixo. E seria um downgrade em relação ao DS1821+ que eu já tinha, que roda num Ryzen V1500B.</p>
<p>Pedi outra opção, e depois de algumas idas e vindas fechei num QNAP TS-873A, de 8 baias, com o mesmo Ryzen V1500B do meu Synology, 8 GB de RAM, e 10 discos Toshiba MG11 de 24 TB, classe enterprise.</p>
<p>É por isso que você tem que fazer a sua própria pesquisa e saber, objetivamente, o que quer fazer com o equipamento. O revendedor não agiu de má-fé: sugeriu um NAS de 8 baias pra quem pediu um NAS de 8 baias. Quem sabia que ia rodar ZFS em RAID-6 era eu. Se eu tivesse aceitado a primeira proposta no susto, teria pago caro por uma máquina pior do que a que estava substituindo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-proposta-qnap.png" alt="Proposta do revendedor: QNAP TS-873A, 10 discos Toshiba MG11ACA24TE de 24 TB, instalação, treinamento e suporte por 6 anos, total de R$ 185.320,00"  loading="lazy" /></p>
<p>R$ 185.320,00. De fazer o olho lacrimejar.</p>
<h3>Quanto custaria nos Estados Unidos<span class="hx:absolute hx:-mt-20" id="quanto-custaria-nos-estados-unidos"></span>
    <a href="#quanto-custaria-nos-estados-unidos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Fui conferir o preço de tabela americano de hoje. Sobre o câmbio: o dólar está em R$ 4,97 agora, mas isso é efeito do resultado do primeiro turno da eleição de domingo, que derrubou a cotação mais de 4% na segunda. Até sexta ele fechava em R$ 5,22, e é essa a cotação justa pra comparar, porque foi nela que o estoque do revendedor foi formado:</p>
<table>
  <thead>
      <tr>
          <th>Item</th>
          <th style="text-align: right">Preço nos EUA</th>
          <th style="text-align: right">Total</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>QNAP TS-873A-8G (<a href="https://store.qnap.com/ts-873a-8g-us.html"target="_blank" rel="noopener">loja da QNAP</a>, Newegg)</td>
          <td style="text-align: right">US$ 1.199</td>
          <td style="text-align: right">US$ 1.199</td>
      </tr>
      <tr>
          <td>10 × Toshiba MG11ACA24TE 24 TB (<a href="https://www.bhphotovideo.com/c/product/1889900-REG/toshiba_mg11aca24te_24tb_mg10_series_7200.html"target="_blank" rel="noopener">B&amp;H</a>)</td>
          <td style="text-align: right">US$ 999,99 cada</td>
          <td style="text-align: right">US$ 9.999,90</td>
      </tr>
      <tr>
          <td>Placa 10 GbE QXG-10G2T-X710 (<a href="https://www.bhphotovideo.com/c/product/1643883-REG/qnap_qxg_10g2t_x710_dual_port_10gbase_t_10gbe_network.html"target="_blank" rel="noopener">B&amp;H</a>)</td>
          <td style="text-align: right">US$ 351,99</td>
          <td style="text-align: right">US$ 351,99</td>
      </tr>
      <tr>
          <td><strong>Total</strong></td>
          <td style="text-align: right"></td>
          <td style="text-align: right"><strong>~US$ 11.550</strong></td>
      </tr>
  </tbody>
</table>
<p>A R$ 5,22, uns R$ 60 mil. Eu paguei R$ 185 mil, mais de três vezes isso.</p>
<p>A comparação não é perfeita: a proposta brasileira inclui instalação, treinamento e seis anos de suporte técnico, e o equipamento estava na minha mesa no fim do mesmo dia. Mas dá a medida do efeito Brasil.</p>
<p>Se a regra de bolso é &ldquo;o dobro do preço americano&rdquo;, o normal seria uns R$ 120 mil. Os R$ 65 mil a mais são, numa estimativa grosseira minha, a soma de serviço, estoque local de disco enterprise e a minha urgência. Quem compra numa segunda de manhã com o array morrendo não está em posição de pechinchar.</p>
<h3>E o disco está caro no mundo inteiro<span class="hx:absolute hx:-mt-20" id="e-o-disco-está-caro-no-mundo-inteiro"></span>
    <a href="#e-o-disco-est%c3%a1-caro-no-mundo-inteiro" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra piorar, o momento é o pior possível pra comprar HD. A bolha de IA está sugando a produção.</p>
<table>
  <thead>
      <tr>
          <th>Indicador</th>
          <th>Antes</th>
          <th>Agora</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WD Blue 4 TB (histórico do Camelcamelcamel)</td>
          <td>US$ 67 a 85</td>
          <td>US$ 99, <a href="https://winbuzzer.com/2026/02/18/wd-seagate-2026-hard-drive-shortage-ai-data-centers-xcxwbn/"target="_blank" rel="noopener">quase 50% a mais em cinco meses</a> (fevereiro de 2026)</td>
      </tr>
      <tr>
          <td>Capacidade de produção</td>
          <td>disponível no varejo</td>
          <td>WD: 2026 praticamente esgotado, com contratos até 2027 e 2028; Seagate: capacidade nearline toda alocada</td>
      </tr>
      <tr>
          <td>Margem bruta da WD</td>
          <td>41,0% um ano antes</td>
          <td><a href="https://datacenterdisk.com/news/hard-drive-prices-up-50-percent-2026"target="_blank" rel="noopener">54,1% no trimestre encerrado em julho de 2026</a></td>
      </tr>
  </tbody>
</table>
<p>Eu achava que a Seagate tinha saído do mercado consumidor. Fui conferir, e não é bem isso: ela não saiu, mas em fevereiro 87% das vendas de HD dela já eram disco nearline, de datacenter, e a WD tira 89% da receita de clientes de nuvem e só 5% do varejo. O CEO da WD disse que a empresa está &ldquo;praticamente esgotada para o ano de 2026&rdquo;.</p>
<p>A Seagate declarou que não vai ampliar a capacidade de produção; o crescimento vem só de disco de maior densidade. Pra quem compra disco em casa, o efeito prático é parecido com terem saído: sobra o que os hyperscalers não quiseram, pelo preço que o vendedor pedir.</p>
<h2>QNAP em vez de outra Synology<span class="hx:absolute hx:-mt-20" id="qnap-em-vez-de-outra-synology"></span>
    <a href="#qnap-em-vez-de-outra-synology" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de passar a manhã e a tarde entre negociação, burocracia e boleto, no fim do dia eu tinha o QNAP ligado ao lado do Synology.</p>
<p>A escolha pela QNAP foi além da disponibilidade. A Synology vem tomando decisão de negócio questionável.</p>
<p>Em 2025 ela lançou a linha Plus <a href="https://www.tomshardware.com/pc-components/nas/synology-walks-back-controversial-compatibility-policy-for-2025-nas-units-third-party-hdd-and-ssd-support-returns-with-diskstation-manager-7-3-update"target="_blank" rel="noopener">exigindo discos da própria marca ou certificados por ela</a>.</p>
<p>Os da marca são discos de Seagate, Toshiba e WD rebatizados com firmware próprio, e quem usasse outro ficava sem criar pool, sem monitoramento de saúde, sem deduplicação e sem atualização de firmware. Depois da gritaria, <a href="https://www.howtogeek.com/synology-is-walking-back-its-drive-requirements/"target="_blank" rel="noopener">voltou atrás no DSM 7.3</a> e liberou HD e SSD SATA de outras marcas.</p>
<p>Só que pool em M.2 continua exigindo a lista de compatibilidade. Ficou a incerteza no ar: o que impede de fazerem de novo?</p>
<p>Configurar o QNAP é muito fácil. O QuTS hero, o sistema deles baseado em ZFS, é tão simples de usar quanto o DSM da Synology.</p>
<h2>Corrigindo o meu erro: RAID-6 e ZFS<span class="hx:absolute hx:-mt-20" id="corrigindo-o-meu-erro-raid-6-e-zfs"></span>
    <a href="#corrigindo-o-meu-erro-raid-6-e-zfs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Dessa vez eu sabia que tinha que configurar em RAID-6, que aguenta dois discos falhando ao mesmo tempo. A desvantagem é menos capacidade total, porque o equivalente a dois discos vira paridade em vez de um. A vantagem é que eu não deveria mais passar por uma emergência como esta.</p>
<p>Um dos pontos fortes da QNAP é suportar ZFS de fábrica, bem integrado e fácil de usar.</p>
<p>Pra quem não conhece: o ZFS nasceu na Sun Microsystems, pro Solaris, em meados dos anos 2000, e hoje vive como OpenZFS em Linux e FreeBSD. Ele junta num sistema só o que antes eram três camadas, o RAID, o gerenciador de volumes e o sistema de arquivos. Guarda um checksum de cada bloco, então percebe quando o disco devolve dado corrompido sem avisar, e conserta sozinho a partir da paridade. E nunca sobrescreve dado no lugar: toda escrita vai pra um bloco novo, o que torna snapshot praticamente gratuito.</p>
<p>A fama de devorador de RAM vem de duas coisas. A primeira é o ARC, o cache dele, que por desenho ocupa quase toda a memória livre da máquina. Isso assusta quem olha o gráfico, mas é cache: ele devolve a memória quando outro processo pede. A segunda é a desduplicação, que mantém uma tabela enorme com o hash de cada bloco e precisa dela na RAM pra não ficar inutilizável de tão lenta. A velha regra de &ldquo;1 GB de RAM por TB de disco&rdquo; vem daí, e só vale pra quem liga desduplicação.</p>
<p>Mesmo sem ela, ZFS pesa mais que um ext4 ou um Btrfs simples. Calcular checksum, compressão e paridade dupla de cada bloco gasta CPU, e com pouca memória o cache encolhe e tudo fica lento. Em hardware fraco, tipo um NAS de entrada com processador ARM e 2 GB de RAM, ele sofre.</p>
<p>A QNAP pede no mínimo 8 GB pro QuTS hero. O meu veio com 8 GB e um Ryzen de quatro núcleos, o que serve pro meu uso, que é guardar arquivo grande e servir pela rede. Desduplicação fica desligada.</p>
<p>Comprei 10 discos pra um NAS de 8 baias, então sobram dois na gaveta, de reserva, pro dia em que um falhar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-qnap-criando-pool-raid6.png" alt="Assistente de criação de storage pool do QNAP: 8 discos em RAID 6, 126 TB, over-provisioning de 5%, espaço garantido de snapshot de 5% e alerta em 85%"  loading="lazy" /></p>
<p>Pra minha configuração simples, três ajustes:</p>
<ul>
<li><strong>Over-provisioning de 5%.</strong> É um pedaço do pool que o sistema reserva e nunca entrega pra dado. ZFS escreve sempre em espaço novo e fica lento quando o pool enche perto do limite; essa folga mantém o desempenho estável. Em pool de SSD ela também prolonga a vida útil, mas aqui são HDs, então o mínimo já serve.</li>
<li><strong>Espaço garantido de snapshot de 5%.</strong> É a reserva pra snapshot, as &ldquo;fotos&rdquo; do estado dos arquivos que permitem voltar atrás num apagão ou num ransomware. Garantir o espaço significa que os snapshots continuam existindo mesmo que eu encha o resto. Meus dados mudam pouco (é arquivo morto, na maior parte), então snapshot ocupa quase nada e 5% sobra.</li>
<li><strong>Alerta em 85%.</strong> O sistema me avisa quando o pool passar de 85% de uso, com folga pra eu agir antes de bater no teto. Foi exatamente a falta de folga que me fez sair trocando disco às pressas no Synology.</li>
</ul>
<p>Somando as duas reservas, uns 13 TB ficam separados e sobram 112,9 TB pra uso.</p>
<p>Um detalhe do meu caso: estou copiando os 90 TB com os snapshots desligados, e só ligo o agendamento depois que a cópia terminar. Snapshot protege o estado anterior de um arquivo, e num NAS vazio que está recebendo a primeira carga não existe estado anterior que valha proteger. A fonte da verdade ainda é o Synology.</p>
<p>E tem um custo prático. Eu parei e recomecei o job várias vezes, apaguei pasta que tinha entrado por engano e copiei de novo. Com snapshot ligado, cada coisa apagada continuaria ocupando espaço dentro de algum snapshot, e eu estaria enchendo o pool com lixo da própria migração. Quando tudo estiver do lado de cá e conferido, aí sim: snapshot agendado, pra me proteger de apagar o que não devia e de ransomware.</p>
<p>Depois disso criei uma partição gigante &ldquo;thick&rdquo;. Em ZFS dá pra criar pasta &ldquo;thin&rdquo;, que só ocupa o que tem dentro e pode prometer mais espaço do que existe, ou &ldquo;thick&rdquo;, que reserva o tamanho inteiro na criação. Como é um pool com um dono só e um uso só, thick é mais previsível: o espaço é meu e ninguém compete por ele.</p>
<p>Por fim, criei os compartilhamentos e instalei o app HBS 3.</p>
<h2>Copiando 90 TB<span class="hx:absolute hx:-mt-20" id="copiando-90-tb"></span>
    <a href="#copiando-90-tb" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O HBS 3 (Hybrid Backup Sync) é o aplicativo da QNAP pra backup e sincronização. Ele conecta em outro NAS, em rsync, em FTP, em SMB ou em nuvem.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-hbs3-criar-job.png" alt="Tela de criação de job de sincronização do HBS 3, com as opções de destino: NAS local, NAS remoto, Rsync, FTP e CIFS/SMB"  loading="lazy" /></p>
<p>Conectei no compartilhamento SMB do Synology e mandei sincronizar tudo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-hbs3-job-sync.png" alt="HBS 3 sincronizando do Synology TERACHAD para o QNAP TS-873A"  loading="lazy" /></p>
<p>Tive que parar e recomeçar algumas vezes, mexendo nas configurações do job, porque estava absurdamente lento. E a contagem de arquivos não parava de crescer:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-hbs3-snapshot-explosao.png" alt="Status do job no HBS 3 mostrando 45 milhões de arquivos e 865 TB calculados, com média de 350 MB/s"  loading="lazy" /></p>
<p>865 TB num NAS de 108 TB, e aí eu entendi o que estava acontecendo.</p>
<p>Excluí dois tipos de caminho do job:</p>
<ul>
<li><strong>Os diretórios <code>#snapshot</code>.</strong> No Synology, quando você marca &ldquo;tornar snapshot visível&rdquo;, cada pasta compartilhada ganha uma subpasta <code>#snapshot</code> com uma visão completa da pasta em cada ponto no tempo. Dentro do Synology isso não ocupa espaço, porque snapshot só guarda a diferença. Visto de fora, por SMB, cada snapshot parece uma cópia inteira de tudo, e o HBS estava tentando copiar todas.</li>
<li><strong>Os diretórios de backup do restic.</strong> O restic guarda tudo em centenas de milhares de arquivos de pack de poucos megabytes cada. Cópia por rede é rápida com arquivo grande e sofre com arquivo pequeno, porque cada um custa abertura, metadado e fechamento. E esses backups eu refaço do zero no NAS novo.</li>
</ul>
<p>Com isso a vazão subiu pra uma média de 400 MB/s.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-qnap-400mbs.png" alt="Monitor de recursos do QNAP mostrando 414,8 MB/s de recebimento na interface de 10 GbE"  loading="lazy" /></p>
<p>Isso é menos da metade do que uma rede 10 GbE entrega, mas é mais de três vezes o gigabit que a maioria das pessoas tem em casa. Os dois lados estão de fato a 10 gigabits:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-qnap-10gbe.png" alt="Adaptador 10 GbE do QNAP conectado a 10 Gbps"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-10gbe.png" alt="Interface LAN 5 do Synology a 10000 Mbps, full duplex"  loading="lazy" /></p>
<p>Por que não chega nos 1.000 MB/s? Minha hipótese principal é o disco 8. O array de origem está degradado, e cada leitura que passa pelos pedaços afetados precisa ser recalculada a partir da paridade, com um disco que ainda por cima devolve erro e repete tentativa. Faz sentido o Synology não conseguir ir mais rápido que isso.</p>
<p>Tem um segundo fator, que os meus próprios prints entregam: o Synology está com jumbo frame (MTU 9000), e a imagem do QNAP mostra o MTU padrão de 1500. Esse print é de antes; eu já mudei o QNAP pra 9000 também.</p>
<p>Por que isso importa: MTU é o tamanho máximo de cada pacote que trafega na rede. No padrão de 1500 bytes, encher um link de 10 gigabits significa processar mais de 800 mil pacotes por segundo, e cada pacote custa CPU dos dois lados, com cabeçalho, checksum e interrupção. Com jumbo frame cada pacote carrega seis vezes mais dado, então é um sexto dos pacotes pro mesmo volume, e sobra processador pra mover arquivo. Só funciona se todo mundo no caminho estiver igual: as duas placas e o switch. Se um lado fica em 1500, a conexão negocia pelo menor e o jumbo frame do outro lado não serve de nada.</p>
<p>A 400 MB/s, 90 TB levam uns dois dias e meio, e eu prefiro dois dias e meio garantidos.</p>
<h2>O que a Synology explicou<span class="hx:absolute hx:-mt-20" id="o-que-a-synology-explicou"></span>
    <a href="#o-que-a-synology-explicou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em paralelo, abri um chamado no suporte da Synology. Depois de algumas trocas, mandei os logs. Responderam rápido, no mesmo dia, e isso eu reconheço. A explicação do engenheiro, resumida:</p>
<ul>
<li>O disco 8 vinha registrando erro de mídia <strong>há mais de um mês</strong>. Erro de mídia é quando o NAS manda ler um setor e o disco responde que não conseguiu, mesmo tentando várias vezes. É sempre falha interna do disco, e ele deveria ter sido trocado.</li>
<li>Foi a saúde ruim do disco 8 que o derrubou durante a reconstrução do disco 1.</li>
<li>Na prática eu perdi dois discos, e o pool deveria ter caído por completo. Não caiu por causa do jeito como o SHR funciona.</li>
</ul>
<p>O SHR não é um RAID-5 só. Pra aproveitar discos de tamanhos diferentes sem desperdiçar espaço, ele fatia os discos e monta vários RAIDs menores, um por faixa de tamanho, e junta tudo num volume. O engenheiro mandou este diagrama de exemplo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-shr-particoes.png" alt="Diagrama do suporte da Synology mostrando como o SHR divide discos de tamanhos diferentes em partições 1, 2 e 3"  loading="lazy" /></p>
<p>No meu caso, o pool é feito de duas partições. Na primeira, a reconstrução do disco 1 já tinha avançado o bastante e a falha do disco 8 foi leve o bastante pra que os dois continuassem funcionando. Na segunda, os dois estão ausentes. É por isso que o pool não caiu inteiro e eu ainda enxergo os arquivos.</p>
<p>A recomendação dele foi a que eu já estava seguindo: copiar tudo pra fora antes de qualquer outra coisa, porque não dá pra saber se a situação piora nem se tem conserto. Depois do backup ele até pode tentar forçar o disco 8 de volta pro pool pra reconstrução continuar, mas não há garantia de que o disco aceite, nem de que aguente o processo. Nas palavras dele, copiar e recriar o pool do zero é provavelmente o caminho mais rápido.</p>
<p>RAID-5 clássico, aliás, exige todos os discos do mesmo tamanho, e pra aumentar a capacidade eu teria que trocar todos de uma vez. O SHR foi o que me permitiu ir trocando um por vez ao longo dos anos. Por ironia, foi também o que me salvou de perder tudo de uma vez.</p>
<h2>Os dois erros<span class="hx:absolute hx:-mt-20" id="os-dois-erros"></span>
    <a href="#os-dois-erros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Do jeito que eu entendi, são dois erros, um meu e um deles, e é aí que mora o desastre inteiro.</p>
<p><strong>O meu:</strong> subestimei o RAID-6, que na Synology se chama SHR-2. Com dois discos de paridade, a falha do disco 8 no meio da reconstrução seria só um susto. Eu troquei segurança por 20 TB de espaço e passei cinco anos achando que tinha feito bom negócio.</p>
<p><strong>O deles:</strong> pelos logs, o software sabia que o disco 8 estava falhando havia pelo menos um mês, e eu nunca recebi notificação nenhuma. Eu tenho notificação por e-mail configurada. E ela funciona: quando a situação crítica estourou, recebi uma enxurrada de e-mails. Este aqui, sobre os setores ruins do disco 8, chegou à uma da tarde de segunda, cinco horas depois do desastre:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261006120000_nas-synology-email-bad-sectors.png" alt="E-mail do Synology avisando que o número de setores ruins aumentou no disco 8, um Seagate ST22000NT001"  loading="lazy" /></p>
<p>Antes disso, nada. Nenhum aviso sobre o disco 8 até ele falhar de forma catastrófica.</p>
<p>Se eu tivesse recebido, jamais teria tentado trocar o disco 1 antes de substituir o 8. A situação toda era evitável, e foi resultado de um erro humano somado a algum bug de software.</p>
<p>Esse é o outro motivo de eu não ter comprado outra Synology. Tenho um princípio: se o cavalo me derruba, eu sacrifico o cavalo. Não dá pra confiar de novo.</p>
<h2>Onde estou agora<span class="hx:absolute hx:-mt-20" id="onde-estou-agora"></span>
    <a href="#onde-estou-agora" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Até aqui copiei uns 20 TB dos 90 TB originais, e o HBS 3 segue trabalhando. Ainda não sei se perdi algum dado de forma permanente. Espero que não, mas o mais provável é que eu tenha perdido o que estava na fatia de RAID que o engenheiro descreveu como caída.</p>
<p>Tenho backup offsite no AWS Glacier do que é mais importante, mas não de tudo.</p>
<p>Muita gente pergunta por que eu não uso S3 ou outro armazenamento em nuvem pra tudo. Devolvo a pergunta: pensa por dez segundos. Quanto tempo leva pra transferir 90 TB pela sua internet? Faz a conta:</p>
<ul>
<li>Com 1 gigabit de upload cravado, sem cair um segundo: mais de 8 dias.</li>
<li>Com 500 megabits: quase 17 dias.</li>
<li>Com 100 megabits, que é upload bom pra muita casa: 83 dias.</li>
</ul>
<p>E isso é pra subir. Na hora do desastre você precisa baixar tudo de volta, com o relógio correndo. Sem contar o aluguel: 90 TB em S3 padrão custam uns US$ 2 mil por mês no preço de tabela. Nuvem é ótima pro subconjunto crítico, que é como eu uso. Pro acervo inteiro, a física e a fatura não deixam.</p>
<h2>Lições<span class="hx:absolute hx:-mt-20" id="lições"></span>
    <a href="#li%c3%a7%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Fui colocado numa situação impossível numa segunda de manhã, agi na hora do melhor jeito que consegui, e provavelmente vou sair dessa quase ileso. Vamos ver, o HBS 3 ainda está sincronizando. O que eu levo disso:</p>
<ol>
<li><strong>Paridade simples não basta pra disco grande.</strong> Com discos de 20 TB, a reconstrução leva dias e lê tudo. É nesse intervalo que o segundo disco costuma aparecer. RAID-6, SHR-2 ou RAIDZ2.</li>
<li><strong>Não confie só na notificação do fabricante.</strong> Olhe o SMART e o log dos discos antes de começar qualquer troca. Eu não olhei, porque confiava que seria avisado.</li>
<li><strong>Troque primeiro o disco doente, depois o disco pequeno.</strong> Upgrade de capacidade em array com disco suspeito é roleta.</li>
<li><strong>Deixe folga.</strong> Eu estava trocando disco porque o volume estava no teto. Alerta em 85% existe pra isso.</li>
<li><strong>Tenha pra onde copiar.</strong> A minha saída custou R$ 185 mil porque eu não tinha plano B. Um segundo NAS, mesmo modesto, com o que importa, teria me dado tempo pra comprar com calma.</li>
<li><strong>RAID não é backup.</strong> Eu sei disso, ensino isso, e mesmo assim só tinha offsite de uma parte.</li>
</ol>
<p>A cópia terminou e eu já sei o tamanho do estrago: zero. A recuperação completa vem a seguir.</p>
<h2>Seis dias depois: o final da história<span class="hx:absolute hx:-mt-20" id="seis-dias-depois-o-final-da-história"></span>
    <a href="#seis-dias-depois-o-final-da-hist%c3%b3ria" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando publiquei a primeira parte, eu tinha uns 20 TB copiados e nenhuma certeza. O desfecho: terminei de evacuar os ~91 TB, verifiquei arquivo por arquivo, e não perdi nada por acidente. Nem o que estava na fatia de RAID caída, porque ela continuou legível até o fim.</p>
<p>Quem operou a recuperação foram os meus dois agentes. O agente do desktop controla meu PC e os dois NAS por SSH, e o agente do homelab cuida do servidor onde rodam meus 57 containers. Os dois se coordenam por mensagem: um deixa relatório na caixa do outro, e eu acompanho a conversa.</p>
<p><img src="nas-ai-memory-agentes.png" alt="Terminal do agente do desktop consultando a caixa de mensagens do ai-memory, onde o agente do homelab deixa seus relatórios"  loading="lazy" /></p>
<p>Essa parte importa porque nada do que vem a seguir foi eu digitando comando. Eu tomei as decisões; eles executaram, mediram e erraram feio algumas vezes, como você vai ver.</p>
<h3>O HBS 3 estava me traindo quieto<span class="hx:absolute hx:-mt-20" id="o-hbs-3-estava-me-traindo-quieto"></span>
    <a href="#o-hbs-3-estava-me-traindo-quieto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na primeira parte eu contei que o HBS 3, puxando os compartilhamentos do Synology por CIFS, chegou a 400 MB/s depois dos ajustes. O que eu não sabia: velocidade era o menor dos problemas dele.</p>
<p>Dois defeitos apareceram quando o agente conferiu o inventário da origem contra o que o HBS reportava:</p>
<ul>
<li><strong>Ele não entrava onde não tinha permissão e não contava pra ninguém.</strong> O HBS monta o Synology como o meu usuário. O diretório <code>Bitcoin/data</code>, 901 GB com a wallet e o chainstate do meu nó, pertence ao uid 101 e é fechado pra qualquer outro. O CIFS não conseguia entrar ali, e o HBS reportava sucesso sem registrar a omissão. Um backup &ldquo;verde&rdquo; que deixa o nó de Bitcoin inteiro pra trás.</li>
<li><strong>Ele pulava arquivos com nome &ldquo;não suportado&rdquo;.</strong> 113 arquivos, registrados só numa tela de detalhe que ninguém abre durante uma cópia de dias.</li>
</ul>
<blockquote>
  <p>Ferramenta de backup que reporta sucesso omitindo o que não conseguiu ler é pior que uma que falha gritando. Verifique o backup contra um inventário independente da origem, nunca contra o log da própria ferramenta.</p>

</blockquote>
<p>Abandonamos o HBS. O agente abriu SSH nos dois NAS, habilitou o servidor rsync no QNAP e o Synology passou a empurrar direto pro rsyncd dele, sem nada passar pelo PC. O wrapper de cada job: <code>rsync -a --numeric-ids -W --partial --timeout=600</code>, tentativas com retry, e <strong>nunca <code>--delete</code></strong>. O <code>--numeric-ids</code> preservou os uids originais sem precisar de chown depois, o <code>--partial</code> fez qualquer interrupção virar continuação, e a ausência de <code>--delete</code> fez do destino sempre um superconjunto: re-shardar ou matar um job nunca custava dados.</p>
<h3>O gargalo não era o disco quebrado<span class="hx:absolute hx:-mt-20" id="o-gargalo-não-era-o-disco-quebrado"></span>
    <a href="#o-gargalo-n%c3%a3o-era-o-disco-quebrado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um contexto que faltou na primeira parte. Esse Synology nasceu com oito Seagate IronWolf de linha consumidora, e eu vinha trocando um por um por IronWolf Pro de 20 TB ao longo dos anos. A desgraça aconteceu exatamente num desses upgrades: eu troquei o disco 1 pelo Pro novo e, durante a reconstrução, o disco 8 falhou. Sobrou um único disco antigo no array: o disco 2.</p>
<p>E foi ele, não o disco 8, o gargalo da evacuação. Medido sob carga:</p>
<ul>
<li>Disco 2 (o consumidor antigo, com 37 mil horas de uso): 91% de utilização, entregando 27 MB/s, cada I/O demorando 4 a 15 vezes mais que os irmãos.</li>
<li>Os seis Pro: entre 34% e 45% de utilização, parados esperando ele.</li>
</ul>
<p>Em RAID-5 (e no SHR), uma leitura sequencial atravessa o stripe inteiro, e o stripe só completa quando o membro mais lento responde. Um disco lento limita o array inteiro: por causa dele, o array não passava de uns 200 MB/s enquanto seis discos bons sobravam. E o SMART dele estava limpo, zero setores realocados. Disco consumidor costuma vir sem limitação de tempo de recuperação de erro (ERC/TLER) ativa, então ele tenta reler setor marginal por segundos, internamente, sem contabilizar nada. O cache NVMe do Synology desviava parte da leitura dos pratos e ajudava a maquiar o sintoma.</p>
<blockquote>
  <p>O disco que falha e o disco que te atrasa não precisam ser o mesmo, e SMART &ldquo;PASSED&rdquo; não diz nada sobre latência. Array lento se diagnostica com utilização e tempo de resposta por disco, não com SMART.</p>

</blockquote>
<h3>Paralelizar rsync é ciência, não chute<span class="hx:absolute hx:-mt-20" id="paralelizar-rsync-é-ciência-não-chute"></span>
    <a href="#paralelizar-rsync-%c3%a9-ci%c3%aancia-n%c3%a3o-chute" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Primeiro instinto: mais streams. Errado, e por duas vezes.</p>
<p>Na primeira, um bug nosso: um relançamento &ldquo;limpo&rdquo; de jobs não matou nada, porque o <code>ps</code> do busybox do Synology trunca a linha de comando e o grep de verificação não achou os processos velhos. Resultado: 32 rsyncs rodando pra 7 destinos, quatro a cinco escritores por árvore, o QNAP com os buffers de recepção entalados, e o cabo transmitindo 516 MB/s enquanto o array lia 140. Depois da limpeza (enumerando <code>/proc/*/cmdline</code>, nunca mais confiando no <code>ps</code>), a vazão útil <strong>subiu</strong> de 140 pra 181 MB/s com a carga total desabando.</p>
<p>Na segunda, a medida inverteu o senso comum: com árvores de arquivo grande, menos streams eram mais rápidos, porque o tamanho médio de cada requisição de leitura triplicava e o array fazia corridas sequenciais longas em vez de ficar pulando de posição.</p>
<p>Só que árvore de arquivo pequeno é outro esporte. O <code>TERACHAD/Emulators</code> tem 21 TB em 3,4 milhões de arquivos com média de 6 MB: trabalho limitado por latência (stat, criação, metadados), não por banda. E ali a paralelização agressiva funcionou, com um detalhe: o shard certo se escolhe por <strong>contagem de arquivos</strong>, não por tamanho. Um único diretório, <code>ROMS_FINAL/xbox-hdd-ready</code>, segurava 995 mil dos 1,59 milhão de arquivos do ROMS_FINAL, com média de 1,7 MB por arquivo. Isolando ele dos ISOs multi-gigabyte:</p>
<table>
  <thead>
      <tr>
          <th>Configuração</th>
          <th style="text-align: right">GB/hora</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>stream único, média de 6 horas</td>
          <td style="text-align: right">~157</td>
      </tr>
      <tr>
          <td>dois shards</td>
          <td style="text-align: right">272</td>
      </tr>
      <tr>
          <td><strong>três shards</strong></td>
          <td style="text-align: right"><strong>436 a 501</strong></td>
      </tr>
  </tbody>
</table>
<p>De 2,8 a 3,2 vezes a vazão média do stream único, na mesma máquina, na mesma rede. E tem um motivo bonito: com o array faltando um membro, leituras de dados que não moram no disco 2 não precisam dele, então a concorrência extra deixava os outros discos trabalharem enquanto ele engasgava.</p>
<blockquote>
  <p>&ldquo;Mais paralelismo&rdquo; e &ldquo;menos paralelismo&rdquo; são as duas respostas erradas como regra geral. Trabalho limitado por banda se divide pouco; trabalho limitado por latência se divide muito, e por contagem de arquivos. Meça antes de escolher.</p>

</blockquote>
<h3>Copiar é metade do trabalho; provar é a outra metade<span class="hx:absolute hx:-mt-20" id="copiar-é-metade-do-trabalho-provar-é-a-outra-metade"></span>
    <a href="#copiar-%c3%a9-metade-do-trabalho-provar-%c3%a9-a-outra-metade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Cada job que terminava passava por um dry-run do mesmo rsync, com os mesmos excludes: zero arquivos precisando transferência significa destino idêntico por tamanho e data. E mesmo assim quase deu errado, porque <strong>a verificação herdava os excludes da cópia</strong>: uma árvore excluída da transferência ficava excluída da própria prova. Refazendo a checagem sem excludes, apareceram 952 arquivos e 191 GB faltando no Downloads (exatamente os lotes que eu tinha separado como &ldquo;única cópia existente&rdquo;) e os repositórios restic antigos, que um nome de diretório parecido no QNAP fingia cobrir: o ID interno dos repositórios provou que eram outros, não cópias.</p>
<p>Teve também deriva de origem: uma árvore verificada como completa recebeu 16 GB de checkpoints novos no Synology depois da checagem, e só a re-verificação final pegou. Verificação tem prazo de validade.</p>
<p>E uma decisão consciente, registrada pra ninguém me acusar de esconder: os históricos antigos de backup (uns 2 TB de snapshots restic e um set de duplicity de 2025) ficaram pra trás por escolha minha, porque os repositórios novos no QNAP já cobrem o mesmo conteúdo a partir de agora. Perda deliberada de histórico, não de dado.</p>
<p>Fecho da conta: <strong>17 jobs de rsync, todos exit 0, todos verificados, uns 5,9 milhões de arquivos byte-idênticos</strong>, e o desligamento do Synology só aconteceu depois de cada árvore provada.</p>
<p><img src="nas-homelab-containers-status.png" alt="Tabela de status da recuperação no meio do processo: os containers já liberados e os que seguiam segurados esperando a verificação do rsync de cada árvore"  loading="lazy" /></p>
<h3>Enquanto isso, no homelab<span class="hx:absolute hx:-mt-20" id="enquanto-isso-no-homelab"></span>
    <a href="#enquanto-isso-no-homelab" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Do outro lado da casa, o servidor marvin, que monta o NAS por NFS e roda os 57 containers, quase morreu de pé. NAS morto mais montagem NFS &ldquo;hard&rdquo; mais 57 containers reiniciando sozinhos deu uma fila infinita de I/O ininterruptível: a máquina respondia ping, mas o sshd não conseguia completar um handshake. E cada tentativa insistente de SSH só piorava, porque o OpenSSH pune fonte repetitiva aumentando o banimento. A gente batia na porta e a porta aumentava a tranca.</p>
<p>A saída foi parar de bater: pausei os torrents pela API do qBittorrent (que continuava respondendo), fui no console físico, desabilitei o Docker, reinicie, e o servidor voltou. Um watchdog que eu nem lembrava que existia tinha, sozinho, derrubado os 14 containers dependentes do NAS antes de qualquer banco corromper: o LevelDB do bitcoind abriu limpo na primeira tentativa.</p>
<p>A migração em si foi elegante: o fstab apontou pro QNAP e os pontos de montagem antigos viraram symlinks pra dentro do novo. <strong>Nenhum docker-compose mudou.</strong> Aí veio a pegadinha do NFS da QNAP: permissão é por cliente. O desktop estava liberado sem squash, mas as escritas do marvin caíam como uid 1001, bloqueando tudo que escreve no NAS. A primeira tentativa de correção ainda virou a montagem pra read-only no meio do primeiro backup restic, que sobreviveu na base do retry. Configuração final: leitura e escrita, sem squash pra ninguém, testada com <code>touch</code> e <code>ls -ln</code> de cada cliente, porque &ldquo;funcionou no desktop&rdquo; não prova nada sobre o servidor.</p>
<p><img src="nas-qnap-configurando-nfs.png" alt="Tela de permissões NFS do QNAP, com a regra por host pro servidor do homelab"  loading="lazy" /></p>
<p>Depois disso foi ressurreição árvore por árvore: cada serviço só subia quando a árvore dele estava verificada no QNAP. Em dois dias, 57 de 57 containers de volta, e um backup restic novinho rodando diariamente pro QNAP.</p>
<h3>O kernel novo derrubou o NVMe<span class="hx:absolute hx:-mt-20" id="o-kernel-novo-derrubou-o-nvme"></span>
    <a href="#o-kernel-novo-derrubou-o-nvme" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No meio da recuperação, um susto de graça do servidor. Sete minutos depois do backup noturno começar, o controlador NVMe sumiu em silêncio: todo binário tomou SIGBUS, Docker e sshd morreram, e nada restou gravado em lugar nenhum, porque o log mora no dispositivo que morreu. Nem kernel panic: só silêncio.</p>
<p>O mesmo workload tinha rodado bem horas antes no kernel 7.2.6 e morreu no 7.2.9. Desligar o APST (o gerenciamento de energia do NVMe) não resolveu: segunda queda idêntica, regressão do 7.2.9 confirmada por eliminação. O MicroOS é transacional e baseado em snapshot, então o rollback foi um reboot pra linhagem 7.2.6, não uma reinstalação. As atualizações automáticas ficaram propositalmente quebradas naquela linhagem pra ninguém voltar pro 7.2.9 sem supervisão, e o teste do 7.2.8 fica pra quando eu puder sentar do lado.</p>
<blockquote>
  <p>Sistema operacional transacional transforma regressão de kernel em reboot. Sem snapshot de sistema, essa noite teria custado muito mais caro.</p>

</blockquote>
<h3>O susto do thick provisioning<span class="hx:absolute hx:-mt-20" id="o-susto-do-thick-provisioning"></span>
    <a href="#o-susto-do-thick-provisioning" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No QNAP, a cópia inteira rodou sem snapshot nenhum: numa migração em massa, snapshot é peso morto. Terminada a cópia, fui ligar a agenda de snapshots do ZFS e tomei um erro seco: &ldquo;pool free space is not enough&rdquo;. Com 20 TB livres.</p>
<p><img src="nas-qnap-thick-thin-erro-snapshot.png" alt="Erro do QNAP ao tentar criar snapshot: espaço livre do pool insuficiente, mesmo com terabytes livres"  loading="lazy" /></p>
<p>As duas afirmações eram verdade sobre coisas diferentes. A pasta CHAD tinha sido criada como <strong>thick</strong>: dos 100 TB de quota, o ZFS reserva os 100 TB, e pra tirar snapshot de um dataset com reserva, o pool precisa garantir espaço pra sobrescrever todos os blocos referenciados enquanto o snapshot prende as cópias velhas. Na conta: 80 TB escritos mais 100 TB reservados, contra 126 TB utilizáveis do pool. Não fecha. E o <code>df</code> mente dos dois lados, porque mede a pasta contra a quota, não contra o pool.</p>
<p>A correção foi converter a pasta pra <strong>thin</strong>, que o QuTS hero faz in-place, escondido na tela de Management da pasta, não no diálogo de edição onde &ldquo;Thick&rdquo; parece texto estático. Em thin, o número vira teto em vez de reserva: a reserva de 10 TB evaporou, o pool passou a ter 39 TB livres, e o primeiro snapshot saiu consumindo 334 KB sobre 79,9 TB referenciados. O ZFS não precisava de quase nada; a reserva é que exigia garantia de 100 TB pra autorizar.</p>
<p><img src="nas-qnap-chad-properties.png" alt="Propriedades da pasta CHAD depois da conversão, já como thin"  loading="lazy" /></p>
<p>Agora o QNAP tem agenda de snapshots com política de retenção, como devia ser desde o primeiro dia:</p>
<p><img src="nas-qnap-snapshot-schedule.png" alt="Agenda de snapshots da pasta CHAD no QNAP"  loading="lazy" /></p>
<p><img src="nas-qnap-snapshot-retention.png" alt="Política de retenção dos snapshots: quantos guardar por dia, semana e mês"  loading="lazy" /></p>
<p>Duas notas de rodapé pra quem montar um desses. Se a sua pasta thin tiver alerta de 80%, suba a quota pra folga estética, porque o alerta mede contra a quota e não contra o pool. E o gargalo real desse QNAP é RAM: 7 GB pra um pool de 174 TB. O mínimo oficial do QuTS hero é 8 GB, e a prática recomendada pra esse porte é 16 pra cima. O tamanho do cache ARC governa a velocidade de metadados, e foi por isso que as varreduras de árvore inteira durante a migração eram lentas. Upgrade de RAM vale mais que qualquer ajuste fino.</p>
<h3>O Synology nasce de novo<span class="hx:absolute hx:-mt-20" id="o-synology-nasce-de-novo"></span>
    <a href="#o-synology-nasce-de-novo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com tudo verificado no QNAP, o Synology finalmente pôde ser operado. Troquei os discos 2 e 8 por IronWolf Pro novos, apaguei o pool ferido e recriei do zero, desta vez em <strong>SHR-2</strong>, o modo com dois discos de paridade, o RAID-6 deles. O pool novo saiu com 100 TB, todos os discos saudáveis:</p>
<p><img src="nas-synology-pool-novo-saudavel.png" alt="Storage Manager do Synology mostrando o pool novo SYNOCHAD em SHR com tolerância a dois discos, 100 TB e todos os discos Healthy"  loading="lazy" /></p>
<p>Ele volta a trabalhar como alvo de réplica, e não mais como ponto único de falha. O QNAP segue como principal, em RAIDZ2, com 87 TB dos 126 TB usados e os snapshots agendados:</p>
<p><img src="nas-qnap-storage-manager-final.png" alt="Storage Manager do QNAP com o pool RAID 6 Ready e a pasta CHAD em thin com 79,9 TB de 95 TB"  loading="lazy" /></p>
<h3>O que eu levo da segunda metade<span class="hx:absolute hx:-mt-20" id="o-que-eu-levo-da-segunda-metade"></span>
    <a href="#o-que-eu-levo-da-segunda-metade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ol>
<li><strong>Desconfie do sucesso silencioso.</strong> O HBS reportava verde enquanto deixava 901 GB pra trás. Backup se verifica contra inventário da origem.</li>
<li><strong>SMART não mede lentidão.</strong> O disco que quase matou a evacuação estava &ldquo;saudável&rdquo;. Utilização e latência por disco contam a verdade.</li>
<li><strong>Paralelismo se calibra medindo.</strong> Bandwidth-bound divide pouco, latency-bound divide muito, e o shard se escolhe por contagem de arquivos.</li>
<li><strong>Verificação não herda exclude.</strong> Se a prova usa os mesmos filtros da cópia, ela só reafirma as suposições da cópia. E verifique de novo imediatamente antes de destruir a origem.</li>
<li><strong>Permissão de NFS é por cliente.</strong> Teste escrita de cada máquina que vai usar.</li>
<li><strong>Não bata repetidamente num sshd morrendo.</strong> Cada tentativa alonga o banimento. Uma tentativa paciente, ou console físico.</li>
<li><strong>O reparo perigoso vem por último.</strong> O array ficou degradado e lento até o último byte estar provado em outro lugar; só então reconstruir deixou de custar qualquer coisa.</li>
</ol>
<p>Saí dessa melhor protegido do que entrei: dois NAS, cada um com seu RAID-6, snapshots agendados no QNAP, restic diário, e o Glacier pro que é crítico. O desastre me custou um QNAP, dois discos e uma semana. Podia ter custado 90 TB.</p>
]]></content:encoded><category>armazenamento-e-backup</category><category>homelab</category><category>hardware</category></item><item><title>Bitcoin no Home Server: Soberania e Privacidade com Coldcard, Sparrow e Fulcrum</title><link>https://akitaonrails.github.io/2026/04/01/bitcoin-no-home-server-soberania-e-privacidade-com-coldcard-sparrow-e-fulcrum/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/01/bitcoin-no-home-server-soberania-e-privacidade-com-coldcard-sparrow-e-fulcrum/</guid><pubDate>Wed, 01 Apr 2026 19:00:00 -0300</pubDate><description>&lt;p&gt;Este post é complemento direto dos meus artigos recentes sobre o &lt;a href="https://akitaonrails.github.io/2026/03/31/migrando-meu-home-server-com-claude-code/"&gt;novo home server com openSUSE MicroOS&lt;/a&gt; e sobre o &lt;a href="https://akitaonrails.github.io/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/"&gt;Minisforum MS-S1 Max&lt;/a&gt;. Naqueles textos eu falei da base. Aqui eu quero mostrar um uso concreto dela: montar uma stack decente de Bitcoin em casa, com foco em privacidade, soberania operacional e transações seguras do meu lado.&lt;/p&gt;
&lt;p&gt;Antes de mais nada: isto aqui não é texto de evangelismo nem chamada pra day trade. Muito pelo contrário. No momento em que escrevo, em 1 de abril de 2026, o Bitcoin está na faixa de US$ 68 mil e perto de R$ 391 mil, abaixo dos picos de 2025. Muita gente olha pra isso e entra em pânico ou começa a fantasiar trade alavancado. Eu acho as duas reações erradas. Existe uma tese de &amp;ldquo;super cycle&amp;rdquo; baseada em demanda institucional, ETFs spot e efeito defasado do halving. Pode ser. Pode não ser. O que eu sei é que candle de curto prazo não muda a parte que realmente me interessa: infraestrutura. Se você precisa de alavancagem pra &amp;ldquo;acelerar ganhos&amp;rdquo;, você provavelmente está só acelerando sua chance de ser liquidado.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post é complemento direto dos meus artigos recentes sobre o <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">novo home server com openSUSE MicroOS</a> e sobre o <a href="/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/">Minisforum MS-S1 Max</a>. Naqueles textos eu falei da base. Aqui eu quero mostrar um uso concreto dela: montar uma stack decente de Bitcoin em casa, com foco em privacidade, soberania operacional e transações seguras do meu lado.</p>
<p>Antes de mais nada: isto aqui não é texto de evangelismo nem chamada pra day trade. Muito pelo contrário. No momento em que escrevo, em 1 de abril de 2026, o Bitcoin está na faixa de US$ 68 mil e perto de R$ 391 mil, abaixo dos picos de 2025. Muita gente olha pra isso e entra em pânico ou começa a fantasiar trade alavancado. Eu acho as duas reações erradas. Existe uma tese de &ldquo;super cycle&rdquo; baseada em demanda institucional, ETFs spot e efeito defasado do halving. Pode ser. Pode não ser. O que eu sei é que candle de curto prazo não muda a parte que realmente me interessa: infraestrutura. Se você precisa de alavancagem pra &ldquo;acelerar ganhos&rdquo;, você provavelmente está só acelerando sua chance de ser liquidado.</p>
<p>Pra mim, a pergunta útil não é &ldquo;vai subir amanhã?&rdquo;. A pergunta útil é: &ldquo;se eu quiser guardar e movimentar Bitcoin sem terceirizar tudo pra exchange, wallet web e API pública, como eu monto isso direito aqui em casa?&rdquo;</p>
<h2>O problema real: conveniência demais custa privacidade demais<span class="hx:absolute hx:-mt-20" id="o-problema-real-conveniência-demais-custa-privacidade-demais"></span>
    <a href="#o-problema-real-conveni%c3%aancia-demais-custa-privacidade-demais" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O fluxo padrão da maioria das pessoas é simples: compra em exchange, deixa parado lá, ou então instala uma wallet qualquer no celular e pronto. Funciona. Também concentra risco e vaza metadado pra todo lado.</p>
<p>Se você deixa saldo em exchange, você tem risco de custódia. Se você usa wallet desktop apontando pra servidor público, você tem risco de privacidade. Se você usa hardware wallet de forma relaxada, comprada no Mercado Livre de segunda mão, você tem risco de supply chain. Se mistura tudo isso com pressa, pior ainda.</p>
<p>Por isso eu acabei chegando numa combinação que, pra quem é técnico e quer montar a própria infra, me parece bastante sólida:</p>
<ul>
<li>Coldcard pro armazenamento frio</li>
<li>Sparrow Wallet no Linux como carteira desktop e coordenadora das transações</li>
<li>Fulcrum no home server como servidor Electrum privado</li>
<li>bitcoind no mesmo servidor como full node de verdade, validando a cadeia e fazendo broadcast sem depender de terceiros</li>
</ul>
<p>Não é o caminho mais fácil. Mas esse é justamente o ponto. Segurança de verdade raramente vem do caminho mais fácil.</p>
<h2>Os conceitos que confundem quem está começando<span class="hx:absolute hx:-mt-20" id="os-conceitos-que-confundem-quem-está-começando"></span>
    <a href="#os-conceitos-que-confundem-quem-est%c3%a1-come%c3%a7ando" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de entrar no stack, vale alinhar quatro termos que normalmente são jogados no ar como se todo mundo já soubesse:</p>
<table>
  <thead>
      <tr>
          <th>Conceito</th>
          <th>O que é</th>
          <th>Por que importa</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Airgap</td>
          <td>Dispositivo que nunca toca na internet nem por USB de dados</td>
          <td>Reduz a superfície de ataque do signer</td>
      </tr>
      <tr>
          <td>PSBT</td>
          <td>Partially Signed Bitcoin Transaction</td>
          <td>Formato padrão pra preparar, assinar e finalizar transações em etapas</td>
      </tr>
      <tr>
          <td>Watch-only wallet</td>
          <td>Carteira que enxerga saldo/endereço mas não guarda chave privada</td>
          <td>Ótima pra desktop: observa e monta a transação, mas não assina</td>
      </tr>
      <tr>
          <td>Full node</td>
          <td>Nó que valida blocos e regras do protocolo localmente</td>
          <td>Você não precisa &ldquo;acreditar&rdquo; em API de ninguém</td>
      </tr>
      <tr>
          <td>Electrum server</td>
          <td>Camada de índice que responde rápido consultas de carteira</td>
          <td>Sem isso, wallets desktop ficam dependentes de servidores públicos</td>
      </tr>
  </tbody>
</table>
<p>Em português claro, o fluxo fica assim:</p>
<ol>
<li>A Sparrow, no desktop, monta a transação.</li>
<li>Essa transação vira um PSBT.</li>
<li>O PSBT vai pra Coldcard por microSD.</li>
<li>A Coldcard assina offline.</li>
<li>O arquivo assinado volta pra Sparrow.</li>
<li>A Sparrow faz o broadcast via seu próprio servidor, não via infraestrutura pública de terceiros.</li>
</ol>
<p>É isso que as pessoas querem dizer quando falam em &ldquo;airgapped workflow&rdquo;. Não é magia. É só separação de papéis feita de forma disciplinada.</p>
<h2>Coldcard: signer frio, offline, chato do jeito certo<span class="hx:absolute hx:-mt-20" id="coldcard-signer-frio-offline-chato-do-jeito-certo"></span>
    <a href="#coldcard-signer-frio-offline-chato-do-jeito-certo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu uso <a href="https://coldcard.com/"target="_blank" rel="noopener">Coldcard</a> como cold storage. O motivo é simples: ela foi pensada desde o começo como dispositivo Bitcoin-only, com foco forte em operação airgapped por microSD. Isso já elimina uma categoria enorme de &ldquo;comodidades&rdquo; que muita gente acha prática, mas que eu prefiro não ter perto das minhas chaves.</p>
<p><a href="https://coldcard.com/mk4"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/bitcoin-sovereignty/coldcard-mk4-official.png" alt="Coldcard Mk4 no site oficial da Coinkite"  loading="lazy" /></a></p>
<p>Na prática, a Coldcard fica com a parte mais importante do sistema: a chave privada. Ela não precisa saber de servidor, de Electrum, de API pública, de exchange, de nada disso. O trabalho dela é um só: assinar transação offline.</p>
<p>Esse desacoplamento é ótimo por dois motivos:</p>
<ul>
<li>O desktop pode ser conveniente sem virar ponto único de desastre.</li>
<li>O signer continua isolado mesmo se sua máquina principal der problema.</li>
</ul>
<p>E aqui entra um aviso que eu realmente quero deixar em caixa alta mental:</p>
<p><strong>Nunca compre hardware wallet de segunda mão. Nunca.</strong></p>
<p>Não é exagero. Você não tem como saber, de verdade, o que aconteceu com aquele dispositivo antes de chegar na sua mão. Pode ter seed pré-gerada, firmware adulterado, componente trocado, embalagem refeita, supply chain comprometida, ou simplesmente algum truque bobo esperando você baixar a guarda. Hardware wallet é uma daquelas categorias em que economizar R$ 300 comprando usado é insanidade. Compre sempre do site oficial do fabricante ou de revendedor oficialmente autorizado pelo fabricante. E mesmo assim confira lacres, procedência e firmware.</p>
<h2>Dá pra fazer parecido com um celular velho?<span class="hx:absolute hx:-mt-20" id="dá-pra-fazer-parecido-com-um-celular-velho"></span>
    <a href="#d%c3%a1-pra-fazer-parecido-com-um-celular-velho" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Dá. Mas eu trataria isso como alternativa de estudo ou de orçamento, não como substituto óbvio de uma Coldcard.</p>
<p>O caminho mais sério hoje pra isso é o <a href="https://airgap.it/"target="_blank" rel="noopener">AirGap Vault</a>, que foi justamente desenhado pra usar um smartphone antigo como signer offline, via QR code, mantendo o aparelho fora da rede. A ideia é boa, e pra muita gente pode ser a porta de entrada correta.</p>
<p>Mas tem trade-off:</p>
<ul>
<li>smartphone velho não foi desenhado como hardware wallet dedicada</li>
<li>histórico anterior do aparelho importa</li>
<li>bateria envelhecida, tela ruim e Android abandonado são problemas reais</li>
<li>o modelo de ameaça é menos claro do que num dispositivo dedicado</li>
</ul>
<p>Então minha visão é simples: dá pra usar? Dá. Eu recomendaria como solução principal pra guardar patrimônio relevante? Não. Pra isso eu ainda prefiro hardware dedicado comprado da fonte correta.</p>
<h2>Sparrow Wallet: a melhor peça de desktop desse quebra-cabeça<span class="hx:absolute hx:-mt-20" id="sparrow-wallet-a-melhor-peça-de-desktop-desse-quebra-cabeça"></span>
    <a href="#sparrow-wallet-a-melhor-pe%c3%a7a-de-desktop-desse-quebra-cabe%c3%a7a" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No Linux, eu uso <a href="https://sparrowwallet.com/"target="_blank" rel="noopener">Sparrow Wallet</a>. Pra mim, hoje, ela é uma das melhores peças de software nesse ecossistema.</p>
<p><a href="https://www.sparrowwallet.com/features/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/01/bitcoin-sovereignty/sparrow-transactions.png" alt="Sparrow Wallet em execução, mostrando o histórico detalhado da carteira e das transações"  loading="lazy" /></a></p>
<p>O que eu gosto nela:</p>
<ul>
<li>funciona muito bem em desktop Linux</li>
<li>suporta hardware wallets direito</li>
<li>entende PSBT sem drama</li>
<li>deixa muito claro o que está acontecendo na transação</li>
<li>é ótima como watch-only wallet</li>
</ul>
<p>No meu fluxo, a Sparrow faz três coisas:</p>
<ol>
<li>Guarda a carteira watch-only.</li>
<li>Monta a transação com os outputs e taxas.</li>
<li>Recebe de volta a assinatura da Coldcard e faz o broadcast.</li>
</ol>
<p>Essa separação é elegante. O desktop vira coordenador. O signer continua frio.</p>
<h2>Por que Coldcard + Sparrow funciona tão bem<span class="hx:absolute hx:-mt-20" id="por-que-coldcard--sparrow-funciona-tão-bem"></span>
    <a href="#por-que-coldcard--sparrow-funciona-t%c3%a3o-bem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa combinação é boa porque cada peça faz o que faz melhor:</p>
<ul>
<li>a Coldcard protege a chave</li>
<li>a Sparrow organiza o uso humano da carteira</li>
<li>o servidor cuida da infraestrutura</li>
</ul>
<p>Muita wallet tenta fazer tudo. Eu prefiro esse desenho modular. É menos &ldquo;mágico&rdquo;, mais explícito, e mais fácil de raciocinar sobre ele sem autoengano.</p>
<p>Se eu estou no desktop, eu quero visibilidade. Se eu estou no signer, eu quero isolamento. Se eu estou no servidor, eu quero validação e índice local. Essa divisão é limpa.</p>
<h2>O problema da Sparrow sem infra própria<span class="hx:absolute hx:-mt-20" id="o-problema-da-sparrow-sem-infra-própria"></span>
    <a href="#o-problema-da-sparrow-sem-infra-pr%c3%b3pria" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora vem o detalhe importante. A Sparrow sozinha não resolve privacidade.</p>
<p>Se você instalar, abrir e simplesmente usar servidores públicos, alguém do outro lado passa a aprender bastante coisa sobre sua carteira: conjunto de endereços, xpubs ou derivadas, saldo, histórico, comportamento de consulta, broadcast. Não é custódia, mas ainda é exposição.</p>
<p>Esse é o buraco que o Fulcrum preenche.</p>
<h2>Fulcrum: o servidor Electrum privado da casa<span class="hx:absolute hx:-mt-20" id="fulcrum-o-servidor-electrum-privado-da-casa"></span>
    <a href="#fulcrum-o-servidor-electrum-privado-da-casa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="https://github.com/cculianu/Fulcrum"target="_blank" rel="noopener">Fulcrum</a> é um servidor Electrum. Em vez de deixar a Sparrow perguntando coisas pra servidor público de terceiros, ela pergunta pro meu próprio servidor.</p>
<p>Na prática, isso significa:</p>
<ul>
<li>consulta de saldo local</li>
<li>histórico local</li>
<li>descoberta de endereços local</li>
<li>broadcast local</li>
</ul>
<p>Ou seja: a wallet desktop para de &ldquo;telefonar&rdquo; pro mundo toda vez que você abre o programa.</p>
<p>No meu setup atual, a Sparrow aponta pra um Fulcrum rodando no home server da LAN, com porta <code>50001</code> na rede interna e <code>50002</code> com TLS.</p>
<h2>E por que Fulcrum não basta sozinho<span class="hx:absolute hx:-mt-20" id="e-por-que-fulcrum-não-basta-sozinho"></span>
    <a href="#e-por-que-fulcrum-n%c3%a3o-basta-sozinho" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Porque Fulcrum não substitui um full node. Ele indexa em cima de um full node.</p>
<p>Quem realmente valida bloco, regra de consenso, script, transação e cadeia é o <code>bitcoind</code>. O Fulcrum fica na frente como camada de índice, porque Bitcoin Core puro não foi feito pra servir carteira desktop com esse tipo de consulta rápida.</p>
<p>Então a arquitetura correta é:</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">Coldcard (offline signer)
</span></span><span class="line"><span class="cl">        ^
</span></span><span class="line"><span class="cl">        | microSD / PSBT
</span></span><span class="line"><span class="cl">        v
</span></span><span class="line"><span class="cl">Sparrow Wallet (desktop watch-only + coordinator)
</span></span><span class="line"><span class="cl">        |
</span></span><span class="line"><span class="cl">        v
</span></span><span class="line"><span class="cl">Fulcrum (Electrum server privado)
</span></span><span class="line"><span class="cl">        |
</span></span><span class="line"><span class="cl">        v
</span></span><span class="line"><span class="cl">bitcoind (full node)</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>O que eu subi no home server de verdade<span class="hx:absolute hx:-mt-20" id="o-que-eu-subi-no-home-server-de-verdade"></span>
    <a href="#o-que-eu-subi-no-home-server-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No meu home server, a stack mora numa pasta dedicada do Docker Compose e é composta por dois containers:</p>
<ul>
<li><code>bitcoin-bitcoind</code></li>
<li><code>bitcoin-fulcrum</code></li>
</ul>
<p>O compose é simples. E isso é bom. Infra sensível não ganha nada ficando esperta demais no YAML.</p>
<p>O desenho principal é 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-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">bitcoin</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">lncm/bitcoind:v28.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">bitcoin-bitcoind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${BITCOIN_UID}:${BITCOIN_GID}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">label:disable</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">/srv/bitcoin/data:/data/.bitcoin</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;8333:8333&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">stop_grace_period</span><span class="p">:</span><span class="w"> </span><span class="l">5m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;bitcoin-cli&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-datadir=/data/.bitcoin&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;ping&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">start_period</span><span class="p">:</span><span class="w"> </span><span class="l">60s</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">fulcrum</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">cculianu/fulcrum:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">bitcoin-fulcrum</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">label:disable</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">/srv/bitcoin/fulcrum:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/srv/bitcoin/data:/bitcoin:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;Fulcrum&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/data/fulcrum.conf&#34;</span><span class="p">]</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;50001:50001&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;50002:50002&#34;</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="nt">bitcoin</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">service_healthy</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 meu caso, a restrição do <code>50001</code> à LAN acontece na camada de rede do host. O YAML acima é o esqueleto do stack, não a política inteira de firewall.</p>
<p>As partes mais importantes disso:</p>
<ul>
<li><code>restart: always</code> porque isso é serviço de longa duração</li>
<li>volume explícito pra não perder estado</li>
<li><code>user: &quot;${BITCOIN_UID}:${BITCOIN_GID}&quot;</code> porque o diretório persistente precisa casar com o ownership real do storage, então eu prefiro fixar UID/GID de forma explícita em vez de confiar no default da imagem</li>
<li>o RPC não é publicado no host; ele fica só na rede interna do Compose, que é tudo o que o Fulcrum precisa</li>
<li>o healthcheck usa o próprio <code>.cookie</code> local do Bitcoin Core, então não precisa espalhar senha fixa em comando</li>
<li>o Fulcrum monta o datadir do node em leitura apenas para autenticar via <code>.cookie</code> sem inventar credencial paralela</li>
<li>no <code>fulcrum.conf</code>, isso vira uma configuração simples: falar com <code>bitcoin:8332</code> e ler o <code>.cookie</code> montado, em vez de repetir credencial em texto puro</li>
<li><code>security_opt: label:disable</code> porque neste host com MicroOS, SELinux e bind mounts sensíveis eu preferi a rota pragmática de desarmar esse atrito específico em vez de perder tempo brigando com label em volume que já está sendo tratado de forma controlada</li>
<li><code>depends_on</code> com <code>service_healthy</code> pra o Fulcrum só subir depois que o RPC do bitcoind responder</li>
<li><code>stop_grace_period: 5m</code> porque bitcoind precisa tempo real pra flushar estado em shutdown gracioso</li>
</ul>
<h2>A versão final<span class="hx:absolute hx:-mt-20" id="a-versão-final"></span>
    <a href="#a-vers%c3%a3o-final" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Hoje, o desenho que eu quero manter é esse: <code>bitcoind</code> com <code>txindex</code>, <code>dbcache=1024</code>, volume persistente, parada graciosa de 5 minutos, autenticação por <code>.cookie</code>, e Fulcrum na frente servindo Sparrow pela LAN ou via TLS.</p>
<p>A stack atual está assim:</p>
<table>
  <thead>
      <tr>
          <th>Componente</th>
          <th>Estado</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bitcoin Core</td>
          <td><code>28.0</code></td>
      </tr>
      <tr>
          <td>Fulcrum</td>
          <td><code>2.1.0</code></td>
      </tr>
      <tr>
          <td>Stop timeout do container</td>
          <td><code>300</code> segundos</td>
      </tr>
      <tr>
          <td>Data dir do node</td>
          <td>volume persistente dedicado montado em <code>/data/.bitcoin</code></td>
      </tr>
      <tr>
          <td>Rede</td>
          <td><code>8333</code> pra P2P, RPC só na rede interna do Compose, <code>50001/50002</code> pro Electrum privado</td>
      </tr>
  </tbody>
</table>
<p>Não me interessa transformar isso em espetáculo. O ponto é mais simples: a infra final precisa ser chata, previsível e estável.</p>
<h2>Os tunings que importam de verdade<span class="hx:absolute hx:-mt-20" id="os-tunings-que-importam-de-verdade"></span>
    <a href="#os-tunings-que-importam-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não tem mágica aqui. Tem alguns parâmetros que fazem diferença real e um monte de coisa que só serve pra enfeitar compose.</p>
<p>O <code>stop_grace_period: 5m</code> existe porque bitcoind não é container descartável de stateless API. Ele mantém chainstate, índices e cache em memória. Se você não dá tempo pro processo encerrar direito, você cria trabalho desnecessário no próximo start.</p>
<p>O <code>user: &quot;${BITCOIN_UID}:${BITCOIN_GID}&quot;</code> está ali por um motivo bem menos glamouroso e muito mais importante: storage persistente com permissão errada é um jeito excelente de quebrar serviço bom. Então eu prefiro alinhar o container ao ownership real do volume em vez de deixar isso implícito.</p>
<p>O <code>dbcache=1024</code> é o ponto que eu acho mais sensato pra node doméstico sempre ligado. Grande o suficiente pra não ficar sofrendo em I/O o tempo todo, pequeno o suficiente pra não transformar cada restart num parto.</p>
<p>O <code>txindex=1</code> eu mantenho porque quero o node completo, não uma instalação minimalista só pra dizer que &ldquo;roda Bitcoin&rdquo;. Se a ideia aqui é autonomia operacional, eu prefiro ter o índice completo.</p>
<p>O <code>rpcworkqueue=512</code> e <code>rpcthreads=16</code> são o tipo de ajuste que faz sentido quando você sabe que vai colocar Fulcrum perguntando pro node o dia inteiro e quer alguma folga.</p>
<p>No lado do Fulcrum, os parâmetros principais são:</p>
<ul>
<li><code>db_mem = 8192</code></li>
<li><code>db_max_open_files = -1</code></li>
<li><code>bitcoind_clients = 8</code></li>
<li><code>worker_threads = 0</code></li>
<li><code>peering = false</code></li>
</ul>
<p>De novo: nada de esoterismo. Só cache suficiente, paralelismo razoável e nada de anunciar esse servidor como serviço público.</p>
<p>No meu <code>bitcoin.conf</code> atual, o núcleo importante 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-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">server</span><span class="o">=</span><span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">txindex</span><span class="o">=</span><span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">prune</span><span class="o">=</span><span class="s">0</span>
</span></span><span class="line"><span class="cl"><span class="na">rpcbind</span><span class="o">=</span><span class="s">0.0.0.0</span>
</span></span><span class="line"><span class="cl"><span class="na">rpcallowip</span><span class="o">=</span><span class="s">172.0.0.0/8</span>
</span></span><span class="line"><span class="cl"><span class="na">rpcthreads</span><span class="o">=</span><span class="s">16</span>
</span></span><span class="line"><span class="cl"><span class="na">rpcworkqueue</span><span class="o">=</span><span class="s">512</span>
</span></span><span class="line"><span class="cl"><span class="na">dbcache</span><span class="o">=</span><span class="s">1024</span>
</span></span><span class="line"><span class="cl"><span class="na">maxmempool</span><span class="o">=</span><span class="s">512</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>Tudo isso faz sentido num servidor com RAM decente e NVMe rápido. Mas o detalhe que mais importa continua sendo o shutdown limpo. Infra de wallet não tem espaço pra mentalidade de &ldquo;depois a gente vê&rdquo;.</p>
<h2>O tamanho real disso tudo<span class="hx:absolute hx:-mt-20" id="o-tamanho-real-disso-tudo"></span>
    <a href="#o-tamanho-real-disso-tudo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse é outro ponto que muita gente subestima.</p>
<p>Se você olhar documentação mais antiga do Bitcoin Core, vai encontrar números como 350 GB de disco para um node com configuração padrão. Isso já ficou pra trás. Dados mais atuais do tamanho da blockchain apontam algo em torno de <strong>725.82 GB em 11 de março de 2026</strong>, e isso é só a cadeia bruta, sem contar os índices extras que muita gente técnica vai querer manter.</p>
<p>E aqui entra a pegadinha: o stack que eu estou descrevendo não é &ldquo;Bitcoin Core pelado só pra dizer que roda um node&rdquo;. É <code>bitcoind</code> com <code>txindex</code>, mais Fulcrum, mais margem pra rebuild, logs, snapshots e crescimento normal da rede.</p>
<p>Por isso, pra montar algo parecido hoje, eu pensaria assim:</p>
<ul>
<li>abaixo de 1 TB: eu nem começaria</li>
<li>1 TB: mínimo pragmático</li>
<li>2 TB: faixa confortável</li>
<li>acima disso: se você quer folga de longo prazo, snapshots e menos ansiedade operacional</li>
</ul>
<p>E aqui vai a observação mais importante de todas em self-hosting: não assuma que persistência, montagem e backup estão certos só porque o YAML está bonito. Verifique de verdade.</p>
<p>Outra coisa que eu não esqueceria num host com btrfs: colocar o banco do Fulcrum (<code>fulc2_db</code>) em subvolume separado. O motivo é bem mundano. Esse diretório cresce, muda o tempo inteiro e não tem nada a ver com snapshot automático genérico de <code>/var</code>. Se você mistura tudo, acaba arrastando índice grande e reconstruível junto com snapshot de sistema, queimando espaço e deixando a manutenção mais irritante do que precisava. Índice do Fulcrum não é configuração delicada. É dado pesado, volátil e reconstruível. Eu trato exatamente assim.</p>
<h2>Hardening: o que já deixei aplicado<span class="hx:absolute hx:-mt-20" id="hardening-o-que-já-deixei-aplicado"></span>
    <a href="#hardening-o-que-j%c3%a1-deixei-aplicado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui é onde aparece a diferença entre &ldquo;rodou no meu notebook&rdquo; e &ldquo;eu confiaria nisso pra operar minha carteira&rdquo;.</p>
<p>No estado atual da stack, os pontos que eu considero importantes ficaram assim:</p>
<ul>
<li>o RPC do Bitcoin Core não depende mais de exposição desnecessária no host; o Fulcrum fala com o <code>bitcoind</code> pela rede interna do Docker, que é o que interessa de verdade</li>
<li><code>50001</code> fica restrita ao uso interno da LAN</li>
<li><code>50002</code> fica disponível com TLS, que é a forma certa quando você precisa sair do plaintext</li>
<li>o shutdown é gracioso, com <code>stop_grace_period: 5m</code>, pra o <code>bitcoind</code> ter tempo de flushar estado em vez de morrer de qualquer jeito</li>
<li>a montagem do storage não fica na base do &ldquo;depois eu vejo&rdquo;; existe checagem de mount antes do Docker subir, justamente pra evitar drift silencioso</li>
</ul>
<p>Cada um desses itens existe por um motivo bem concreto.</p>
<p>Tirar o RPC da superfície do host reduz ataque sem custo nenhum. O Fulcrum já está no mesmo Compose e já consegue falar com o serviço pelo nome interno. Não existe ganho real em deixar essa porta aparecendo onde ela não precisa aparecer.</p>
<p>Separar <code>50001</code> e <code>50002</code> também ajuda a manter a casa em ordem. Dentro de LAN controlada, plaintext é aceitável. Fora disso, o mínimo razoável é TLS. Misturar os dois cenários costuma virar bagunça.</p>
<p>O <code>stop_grace_period: 5m</code> parece detalhe de container, mas não é. Quem já teve banco de dados, índice ou node de blockchain encerrado no tapa sabe o quanto isso vira horas de trabalho depois. Serviço stateful precisa de parada decente.</p>
<p>E a checagem de mount é uma daquelas coisas chatas que salvam você de si mesmo. O YAML pode estar lindo. Se o storage não montou e o serviço subiu gravando onde não devia, você acabou de fabricar um problema bem irritante.</p>
<p>Tem também um ponto que eu gosto bastante nesta versão final do stack: o Fulcrum autentica no <code>bitcoind</code> via arquivo <code>.cookie</code>, não via senha fixa em texto puro. Isso é interessante por dois motivos:</p>
<ul>
<li>você não precisa deixar credencial estática aparecendo em compose, inspect, healthcheck ou documentação</li>
<li>a autenticação fica mais alinhada com o próprio jeito que o Bitcoin Core já sabe operar localmente</li>
</ul>
<p>Em termos práticos, isso reduz vazamento acidental de segredo operacional. Não é uma solução mágica pra tudo, mas é muito melhor do que espalhar <code>rpcuser</code> e <code>rpcpassword</code> em arquivo, log e comando.</p>
<p>O único tipo de hardening que eu tento evitar aqui é o hardening performático demais no YAML e frouxo demais na operação. Eu prefiro menos &ldquo;engenharia de palco&rdquo; e mais disciplina básica:</p>
<ul>
<li>rede mínima</li>
<li>segredo mínimo</li>
<li>privilégio mínimo</li>
<li>shutdown limpo</li>
<li>storage verificado</li>
<li>subvolume separado pra dado grande e reconstruível, como o índice do Fulcrum</li>
</ul>
<p>E, de novo, documente tudo. Infra boa não é a que só funciona hoje. É a que continua funcionando quando você volta nela seis meses depois.</p>
<h2>Por que isso melhora transações do seu lado<span class="hx:absolute hx:-mt-20" id="por-que-isso-melhora-transações-do-seu-lado"></span>
    <a href="#por-que-isso-melhora-transa%c3%a7%c3%b5es-do-seu-lado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando eu monto uma transação na Sparrow e assino na Coldcard, a cadeia de confiança fica muito melhor definida:</p>
<ul>
<li>a chave privada não toca internet</li>
<li>a wallet desktop não precisa confiar em servidor público</li>
<li>o broadcast pode sair do meu próprio node</li>
<li>o histórico de endereços não precisa ir parar em Electrum server de terceiro</li>
</ul>
<p>Isso não torna nada invulnerável. Ainda existe risco de malware no desktop, seed mal armazenada, erro humano, engenharia social e desastre físico. Mas o desenho fica bem mais coerente.</p>
<h2>E o Lightning? Especialmente no Brasil?<span class="hx:absolute hx:-mt-20" id="e-o-lightning-especialmente-no-brasil"></span>
    <a href="#e-o-lightning-especialmente-no-brasil" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aí eu separo as coisas.</p>
<p>Reserva e transação de valor maior eu trato de um jeito. Gasto cotidiano eu trato de outro.</p>
<p>Pra gasto cotidiano, especialmente no Brasil, eu acho burrice operacional carregar muito saldo em wallet quente. Wallet de Lightning e app de gasto têm que ser quase &ldquo;carteira de bolso&rdquo;: só o suficiente pro dia a dia.</p>
<p>Isso vale em dobro se você usar solução híbrida ou custodial, como <a href="https://www.redotpay.com/"target="_blank" rel="noopener">RedotPay</a>. Eu entendo por que ela é interessante pra brasileiro: empresa de Hong Kong, foco internacional, bridge razoavelmente prática entre cripto e gasto com cartão. Pra viagens, compra online e vida fora do eixo bancário brasileiro, faz sentido. Mas eu jamais trataria isso como lugar de guardar patrimônio. Isso é ferramenta de spending, não cofre.</p>
<p>Mesma lógica pro <a href="https://www.bitrefill.com/br/pt/"target="_blank" rel="noopener">Bitrefill Brasil</a>. Eu acho o serviço interessante justamente porque ele resolve uma dor real no Brasil: transformar sats em utilidade concreta sem vender toda posição nem depender de integração bancária o tempo todo. Gift card, recarga, pequenas despesas. Como ferramenta de uso, faz bastante sentido.</p>
<p>Pra wallet Lightning no celular, eu olharia primeiro para:</p>
<ul>
<li><a href="https://phoenix.acinq.co/"target="_blank" rel="noopener">Phoenix</a> pra quem quer algo muito bom e simples</li>
<li><a href="https://breez.technology/"target="_blank" rel="noopener">Breez</a> pra quem quer experiência boa de pagamentos</li>
<li><a href="https://zeusln.com/"target="_blank" rel="noopener">ZEUS</a> se você é mais técnico e eventualmente pretende operar seu próprio node Lightning</li>
</ul>
<p>Todas elas, na minha cabeça, entram na categoria &ldquo;carteira de bolso&rdquo;. Saldo pequeno. Uso diário. Nada de transformar app de celular em cofre de aposentadoria.</p>
<h2>Notícias recentes que reforçam esse raciocínio<span class="hx:absolute hx:-mt-20" id="notícias-recentes-que-reforçam-esse-raciocínio"></span>
    <a href="#not%c3%adcias-recentes-que-refor%c3%a7am-esse-racioc%c3%adnio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu não monto esse tipo de stack porque acho bonito. Eu monto porque terceirização demais dá ruim com frequência demais.</p>
<p>Dois exemplos recentes:</p>
<ul>
<li>o <a href="https://www.cnbc.com/2025/02/21/hackers-steal-1point5-billion-from-exchange-bybit-biggest-crypto-heist.html"target="_blank" rel="noopener">hack da Bybit em 2025</a> mostrou, de novo, o risco básico de deixar custódia relevante em exchange</li>
<li>o <a href="https://techcrunch.com/2025/05/15/coinbase-says-customers-personal-information-stolen-in-data-breach/"target="_blank" rel="noopener">vazamento de dados de clientes da Coinbase em 2025</a> mostrou o outro lado do problema: mesmo quando a custódia não é o foco imediato, sua identidade, saldo e histórico viram superfície de ataque</li>
</ul>
<p>Uma stack como Coldcard + Sparrow + Fulcrum + full node não elimina todo risco do mundo. Mas evita duas classes de problema bem reais:</p>
<ul>
<li>perder soberania de custódia</li>
<li>entregar privacidade de carteira e transação de bandeja pra terceiros</li>
</ul>
<h2>Então vale a pena?<span class="hx:absolute hx:-mt-20" id="então-vale-a-pena"></span>
    <a href="#ent%c3%a3o-vale-a-pena" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra maioria das pessoas, honestamente, provavelmente não no primeiro mês. É trabalhoso, tem curva de aprendizado, e exige disciplina.</p>
<p>Mas pra programador, engenheiro e qualquer pessoa técnica que quer aprender a não depender sempre de serviço alheio, eu acho um exercício excelente.</p>
<p>Você aprende sobre:</p>
<ul>
<li>separação de responsabilidades</li>
<li>persistência e estado</li>
<li>shutdown gracioso</li>
<li>observabilidade</li>
<li>isolamento de segredo</li>
<li>trade-off entre conveniência e segurança</li>
</ul>
<p>E tudo isso vale além de Bitcoin.</p>
<p>No fim das contas, é isso que mais me interessa nesse stack. Não é sair pregando &ldquo;hyperbitcoinization&rdquo; nem posar de profeta de preço. É montar um sistema em casa em que eu consigo confiar mais porque fui eu que instalei, medi, quebrei, consertei e documentei.</p>
<p>Dá trabalho? Dá.</p>
<p>Mas esse tipo de trabalho ensina exatamente o que software moderno tenta fazer você esquecer: depender menos dos outros dá mais trabalho no começo, mas costuma comprar muito mais controle no longo prazo.</p>
]]></content:encoded><category>bitcoin-e-criptomoedas</category><category>homelab</category><category>seguranca</category></item><item><title>Migrando meu Home Server com Claude Code | openSUSE MicroOS</title><link>https://akitaonrails.github.io/2026/03/31/migrando-meu-home-server-com-claude-code/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/31/migrando-meu-home-server-com-claude-code/</guid><pubDate>Tue, 31 Mar 2026 16:00:00 -0300</pubDate><description>&lt;p&gt;Meu home server antigo era uma bagunça. Um Intel NUC com Ubuntu Server que eu fui remendando ao longo de dois anos. Containers com caminhos hardcoded, volumes montados em locais aleatórios (&lt;code&gt;/home/akitaonrails/docker/&lt;/code&gt;, &lt;code&gt;/home/akitaonrails/sonarr/&lt;/code&gt;, &lt;code&gt;/mnt/terachad/&lt;/code&gt;), docker-compose files espalhados sem padrão nenhum. Funcionava, mas se eu perdesse o disco, levaria dias pra reconstruir tudo de memória.&lt;/p&gt;
&lt;p&gt;Com o &lt;a href="https://akitaonrails.github.io/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/"&gt;novo Minisforum MS-S1 Max&lt;/a&gt; que comprei, decidi fazer a migração direito. E decidi usar Claude Code desde o início pra acelerar o processo. É um servidor caseiro, só eu uso, o risco de fazer besteira é baixo. Mas se fosse um servidor de produção real, eu jamais faria isso sem review humano rigoroso em cada passo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Meu home server antigo era uma bagunça. Um Intel NUC com Ubuntu Server que eu fui remendando ao longo de dois anos. Containers com caminhos hardcoded, volumes montados em locais aleatórios (<code>/home/akitaonrails/docker/</code>, <code>/home/akitaonrails/sonarr/</code>, <code>/mnt/terachad/</code>), docker-compose files espalhados sem padrão nenhum. Funcionava, mas se eu perdesse o disco, levaria dias pra reconstruir tudo de memória.</p>
<p>Com o <a href="/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/">novo Minisforum MS-S1 Max</a> que comprei, decidi fazer a migração direito. E decidi usar Claude Code desde o início pra acelerar o processo. É um servidor caseiro, só eu uso, o risco de fazer besteira é baixo. Mas se fosse um servidor de produção real, eu jamais faria isso sem review humano rigoroso em cada passo.</p>
<p>O que segue é o relato da migração e o guia pra quem quiser replicar. Se um dia eu precisar reconstruir do zero, esse post é a documentação.</p>
<h2>Escolha do sistema operacional<span class="hx:absolute hx:-mt-20" id="escolha-do-sistema-operacional"></span>
    <a href="#escolha-do-sistema-operacional" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Por que não Ubuntu Server de novo<span class="hx:absolute hx:-mt-20" id="por-que-não-ubuntu-server-de-novo"></span>
    <a href="#por-que-n%c3%a3o-ubuntu-server-de-novo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu usei Ubuntu Server no NUC por praticidade. Mas <code>do-release-upgrade</code> é uma roleta russa. Toda vez que o Ubuntu lança versão nova, a atualização é um risco real de quebrar coisas. Pacotes mudam, configs são sobrescritas, dependências conflitam. Pra um servidor que precisa estar sempre rodando, isso é inaceitável.</p>
<h3>Por que não Arch Linux<span class="hx:absolute hx:-mt-20" id="por-que-não-arch-linux"></span>
    <a href="#por-que-n%c3%a3o-arch-linux" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu uso Arch no desktop e gosto. Mas Arch é uma rolling release sem nenhuma garantia de estabilidade. Pra um desktop onde eu posso parar e resolver problemas, ótimo. Pra um servidor headless que roda 49 containers Docker e precisa funcionar depois de cada reboot, não.</p>
<h3>Fedora CoreOS vs openSUSE MicroOS<span class="hx:absolute hx:-mt-20" id="fedora-coreos-vs-opensuse-microos"></span>
    <a href="#fedora-coreos-vs-opensuse-microos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As duas opções modernas pra servidor de containers são Fedora CoreOS e openSUSE MicroOS. As duas são sistemas imutáveis: o root filesystem é read-only, atualizações são atômicas (ou aplicam inteiro ou não aplicam nada), e rollback é instantâneo.</p>
<p>A diferença: Fedora CoreOS usa Ignition (configuração declarativa antes do primeiro boot) e é projetada pra ser provisionada automaticamente. MicroOS usa <code>transactional-update</code> e permite uso interativo normal. Pra um home server onde eu quero SSH e mexer manualmente quando precisar, MicroOS se encaixa melhor.</p>
<h3>O que torna MicroOS diferente<span class="hx:absolute hx:-mt-20" id="o-que-torna-microos-diferente"></span>
    <a href="#o-que-torna-microos-diferente" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O conceito de sistema imutável muda como você opera o servidor:</p>
<p>Toda instalação de pacote ou edição de <code>/etc</code> passa por <code>transactional-update</code>, que cria um snapshot btrfs novo, aplica a mudança nesse snapshot, e no reboot seguinte o sistema boota no snapshot atualizado. Se a mudança quebrar alguma coisa, você faz <code>transactional-update rollback</code> e volta pro snapshot anterior em segundos.</p>
<p>Atualizações são automáticas e diárias. O <code>transactional-update.timer</code> baixa patches, cria snapshot, e o <code>rebootmgr</code> reinicia numa janela configurada (no meu caso, entre 4h e 5h30 da manhã). Se a atualização quebra o boot, o GRUB automaticamente volta pro snapshot anterior.</p>
<p>SELinux vem enforcing por padrão. Isso causou 90% dos problemas durante a migração, mas é a configuração certa pra segurança.</p>
<h2>Setup inicial<span class="hx:absolute hx:-mt-20" id="setup-inicial"></span>
    <a href="#setup-inicial" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><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><ul>
<li>AMD Ryzen AI Max+ 395, 128GB LPDDR5X</li>
<li>96GB alocados como VRAM via BIOS (UMA Frame Buffer Size)</li>
<li>NVMe de 2TB (sistema + Docker)</li>
<li>Rede cabeada 2.5Gbps</li>
<li>Synology DS1821+ NAS em 192.168.0.21 (NFS)</li>
</ul>
<h3>Primeiros passos<span class="hx:absolute hx:-mt-20" id="primeiros-passos"></span>
    <a href="#primeiros-passos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Instalação do MicroOS é padrão. Depois:</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"># Criar usuário com UID que bate com o NAS (pra NFS funcionar sem problemas de permissão)</span>
</span></span><span class="line"><span class="cl">useradd -u <span class="m">1026</span> -m akitaonrails
</span></span><span class="line"><span class="cl">passwd akitaonrails
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Configurar sudo (dentro de transactional-update shell)</span>
</span></span><span class="line"><span class="cl">sudo transactional-update shell
</span></span><span class="line"><span class="cl"><span class="c1"># dentro: adicionar akitaonrails ao sudoers</span>
</span></span><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span><span class="line"><span class="cl">sudo reboot</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>NFS do Synology<span class="hx:absolute hx:-mt-20" id="nfs-do-synology"></span>
    <a href="#nfs-do-synology" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O NAS Synology exporta <code>/volume1/TERACHAD</code> via NFS. O ponto de montagem no MicroOS é <code>/var/mnt/terachad</code> (não <code>/mnt/</code>, que fica no root imutável).</p>
<p>No <code>/etc/fstab</code> (aplicado via transactional-update):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>192.168.0.21:/volume1/TERACHAD /var/mnt/terachad nfs4 nfsvers=4.1,rsize=262144,wsize=262144,hard,_netdev 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>Detalhes que importam: <code>nfsvers=4.1</code> porque 4.2 não funcionou com o Synology. <code>rsize=262144,wsize=262144</code> (256KB buffers) foi a maior melhoria de performance NFS. <code>hard</code> em vez de <code>nofail</code> pra que o mount retente indefinidamente se o NAS desconectar temporariamente.</p>
<h3>GPU / ROCm<span class="hx:absolute hx:-mt-20" id="gpu--rocm"></span>
    <a href="#gpu--rocm" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse passo deu trabalho. O Radeon 8060S do AI Max+ 395 é gfx1151, que o ROCm não suporta oficialmente. Foram necessários três passos, e os três são obrigatórios:</p>
<ol>
<li>BIOS: setar UMA Frame Buffer Size pra 96GB</li>
<li>Kernel: adicionar <code>amdttm.pages_limit=25165824 amdttm.page_pool_size=25165824</code> em <code>/etc/kernel/cmdline</code></li>
<li>Docker: usar <code>HSA_OVERRIDE_GFX_VERSION=11.5.1</code> em todo container ROCm</li>
</ol>
<p>Sem o passo 2, o ROCm só vê 15.5GB mesmo com a alocação do BIOS. Os números são 96GB / 4KB (page size) = 25.165.824 pages.</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 transactional-update shell
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;amdttm.pages_limit=25165824 amdttm.page_pool_size=25165824&#34;</span> &gt;&gt; /etc/kernel/cmdline
</span></span><span class="line"><span class="cl"><span class="nb">exit</span>
</span></span><span class="line"><span class="cl">sudo sdbootutil update-all-entries  <span class="c1"># FORA do transactional shell</span>
</span></span><span class="line"><span class="cl">sudo reboot</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>Verificaçã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">cat /sys/class/drm/card1/device/mem_info_vram_total
</span></span><span class="line"><span class="cl"><span class="c1"># 103079215104 (96 * 1024^3)</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>Docker no MicroOS<span class="hx:absolute hx:-mt-20" id="docker-no-microos"></span>
    <a href="#docker-no-microos" 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><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo transactional-update --non-interactive pkg install docker
</span></span><span class="line"><span class="cl">sudo reboot
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now docker
</span></span><span class="line"><span class="cl">sudo usermod -aG docker akitaonrails
</span></span><span class="line"><span class="cl"><span class="c1"># logout e login pra o grupo pegar</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Instalar docker-compose standalone (o pacote openSUSE não inclui)</span>
</span></span><span class="line"><span class="cl">sudo curl -L <span class="s2">&#34;https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -o /usr/local/bin/docker-compose
</span></span><span class="line"><span class="cl">sudo chmod +x /usr/local/bin/docker-compose
</span></span><span class="line"><span class="cl">mkdir -p ~/.docker/cli-plugins
</span></span><span class="line"><span class="cl">ln -s /usr/local/bin/docker-compose ~/.docker/cli-plugins/docker-compose</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>daemon.json<span class="hx:absolute hx:-mt-20" id="daemonjson"></span>
    <a href="#daemonjson" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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;log-level&#34;</span><span class="p">:</span> <span class="s2">&#34;warn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;log-driver&#34;</span><span class="p">:</span> <span class="s2">&#34;local&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;log-opts&#34;</span><span class="p">:</span> <span class="p">{</span><span class="nt">&#34;max-size&#34;</span><span class="p">:</span> <span class="s2">&#34;10m&#34;</span><span class="p">,</span> <span class="nt">&#34;max-file&#34;</span><span class="p">:</span> <span class="s2">&#34;5&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;selinux-enabled&#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;live-restore&#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;userland-proxy&#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;exec-opts&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;native.cgroupdriver=systemd&#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><code>live-restore: true</code> faz containers sobreviverem restart do daemon Docker. <code>userland-proxy: false</code> usa iptables direto em vez de processos proxy (menos overhead). <code>selinux-enabled: true</code> é obrigatório no MicroOS.</p>
<h2>SELinux e Docker: a maior fonte de problemas<span class="hx:absolute hx:-mt-20" id="selinux-e-docker-a-maior-fonte-de-problemas"></span>
    <a href="#selinux-e-docker-a-maior-fonte-de-problemas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Isso merece uma seção inteira porque foi responsável por 90% dos bugs durante a migração.</p>
<p>No MicroOS com SELinux enforcing, todo container que escreve em diretório bind-mounted do host precisa de tratamento especial. Existem duas abordagens: o sufixo <code>:Z</code> nos volumes e a opção <code>security_opt: label:disable</code>.</p>
<h3>NUNCA use <code>:Z</code>. Use <code>security_opt: label:disable</code>.<span class="hx:absolute hx:-mt-20" id="nunca-use-z-use-security_opt-labeldisable"></span>
    <a href="#nunca-use-z-use-security_opt-labeldisable" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <code>:Z</code> diz pro Docker relabeling o diretório do host com o contexto SELinux do container. Parece a coisa certa. Na prática:</p>
<ul>
<li>Bancos SQLite quebram. O relabeling muda o contexto do arquivo e o SQLite pode recusar abrir o WAL journal.</li>
<li>Mounts NFS ignoram <code>:Z</code> silenciosamente. O NFS não suporta xattrs do SELinux. O kernel ignora o flag sem erro, mas o container continua sem permissão.</li>
<li>Mounts <code>:ro,Z</code> tentam relabeling mesmo sendo read-only, o que falha em NFS e pode corromper contexto em paths locais.</li>
</ul>
<p>A solução correta pra todo container nesse 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-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">meuservico</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">label:disable    </span><span class="w"> </span><span class="c"># desliga enforcement SELinux pra esse container</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">./data:/data     </span><span class="w"> </span><span class="c"># SEM :Z</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./config.yml:/etc/config.yml:ro </span><span class="w"> </span><span class="c"># SEM :Z mesmo em :ro</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>label:disable</code> desliga enforcement de labels SELinux apenas pra aquele container, não pro sistema inteiro. Combinado com o isolamento de rede e processos do Docker, é seguro pra home server.</p>
<h2>A migração dos stacks<span class="hx:absolute hx:-mt-20" id="a-migração-dos-stacks"></span>
    <a href="#a-migra%c3%a7%c3%a3o-dos-stacks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todos os stacks Docker foram reorganizados em <code>/var/opt/docker/&lt;stack&gt;/docker-compose.yml</code>. No servidor antigo, estavam espalhados em <code>/home/akitaonrails/docker/</code>, <code>/home/akitaonrails/&lt;servico&gt;/</code>, sem padrão.</p>
<h3>Substituições aplicadas em todos os compose files<span class="hx:absolute hx:-mt-20" id="substituições-aplicadas-em-todos-os-compose-files"></span>
    <a href="#substitui%c3%a7%c3%b5es-aplicadas-em-todos-os-compose-files" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Antes</th>
          <th>Depois</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>/mnt/terachad/</code></td>
          <td><code>/var/mnt/terachad/</code></td>
      </tr>
      <tr>
          <td><code>192.168.0.145</code></td>
          <td><code>192.168.0.90</code></td>
      </tr>
      <tr>
          <td><code>/home/akitaonrails/&lt;servico&gt;/</code></td>
          <td><code>/var/opt/docker/&lt;stack&gt;/&lt;servico&gt;/</code></td>
      </tr>
      <tr>
          <td><code>OLLAMA_BASE_URL=http://192.168.0.14:11434</code></td>
          <td><code>OLLAMA_BASE_URL=http://192.168.0.90:11434</code></td>
      </tr>
  </tbody>
</table>
<h3>Stack de media (Plex, Radarr, Sonarr, etc.)<span class="hx:absolute hx:-mt-20" id="stack-de-media-plex-radarr-sonarr-etc"></span>
    <a href="#stack-de-media-plex-radarr-sonarr-etc" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O stack de media é o mais complexo. Plex precisa de IP próprio na LAN (macvlan) pro streaming direto funcionar. O setup:</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">docker network create -d macvlan <span class="se">\
</span></span></span><span class="line"><span class="cl">  --subnet<span class="o">=</span>192.168.0.0/24 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --gateway<span class="o">=</span>192.168.0.1 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -o <span class="nv">parent</span><span class="o">=</span>enp97s0 <span class="se">\
</span></span></span><span class="line"><span class="cl">  plex_macvlan</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 compose, o Plex precisa estar em duas redes: a macvlan (pro IP 192.168.0.6) e a bridge default (pra outros containers como Seerr conseguirem se comunicar):</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">plex</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">plex_macvlan</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ipv4_address</span><span class="p">:</span><span class="w"> </span><span class="m">192.168.0.6</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;02:42:c0:a8:00:06&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span>{}<span class="w">    </span><span class="c"># obrigatório — sem isso, Seerr não enxerga o Plex</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>Detalhe que quase me quebrou: o Plex guarda paths absolutos no banco de dados. Se o volume interno do container mudou de <code>/media</code> pra <code>/data</code>, o Plex não encontra mais nada. Tem que usar exatamente o mesmo mount target do compose antigo.</p>
<h3>Ollama com ROCm<span class="hx:absolute hx:-mt-20" id="ollama-com-rocm"></span>
    <a href="#ollama-com-rocm" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Stack novo, não existia no servidor anterior:</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">ollama</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">ollama/ollama:rocm</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">ollama</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/dev/kfd:/dev/kfd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/dev/dri:/dev/dri</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">security_opt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">seccomp:unconfined</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">label:disable</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">group_add</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;485&#34;</span><span class="w">   </span><span class="c"># render group GID</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;488&#34;</span><span class="w">   </span><span class="c"># video group GID</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">HSA_OVERRIDE_GFX_VERSION=11.5.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">PYTORCH_ROCM_ARCH=gfx1151</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">OLLAMA_KEEP_ALIVE=30m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">OLLAMA_NUM_PARALLEL=4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">OLLAMA_FLASH_ATTENTION=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">OLLAMA_KV_CACHE_TYPE=q8_0</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">/var/lib/ollama:/root/.ollama</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;11434:11434&#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><code>OLLAMA_FLASH_ATTENTION=1</code> ativa flash attention. <code>OLLAMA_KV_CACHE_TYPE=q8_0</code> usa KV cache em 8-bit, cortando bandwidth necessária por token pela metade. São otimizações de performance gratuitas.</p>
<h3>Monitoramento (Grafana + Prometheus)<span class="hx:absolute hx:-mt-20" id="monitoramento-grafana--prometheus"></span>
    <a href="#monitoramento-grafana--prometheus" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Grafana usa named volume (<code>grafana_data</code>) que NÃO é incluído em backups normais de filesystem. Foi o motivo pelo qual perdi todos os dashboards na primeira tentativa. A solução é backup explícito do named volume:</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 servidor antigo:</span>
</span></span><span class="line"><span class="cl">docker run --rm -v grafana_data:/data:ro -v /tmp:/backup alpine <span class="se">\
</span></span></span><span class="line"><span class="cl">  tar czf /backup/grafana_data.tar.gz -C /data .
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Transferir e restaurar no novo:</span>
</span></span><span class="line"><span class="cl">docker run --rm -v grafana_data:/data -v /tmp:/backup alpine <span class="se">\
</span></span></span><span class="line"><span class="cl">  sh -c <span class="s2">&#34;cd /data &amp;&amp; tar xzf /backup/grafana_data.tar.gz&#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>Mesma coisa pro Portainer (<code>portainer_data</code>). Qualquer volume definido no bloco <code>volumes:</code> do compose sem host path precisa desse tratamento.</p>
<h3>Cloudflare Tunnel<span class="hx:absolute hx:-mt-20" id="cloudflare-tunnel"></span>
    <a href="#cloudflare-tunnel" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu uso <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/"target="_blank" rel="noopener">Cloudflare Tunnel</a> pra acessar todos os serviços fora de casa sem abrir portas no roteador. A migração foi a mais fácil: copiar o arquivo de credenciais JSON do túnel e o <code>config.yml</code>, atualizar os IPs de <code>.145</code> pra <code>.90</code>, e subir o container. O túnel mantém o mesmo ID, não precisa recriar DNS.</p>
<p>Os hostnames ficam em <code>config.yml</code>: portainer, grafana, plex, seerr, qbittorrent, syncthing, radarr, sonarr, bazarr, prowlarr, vault, gitea, kavita, e outros. Tudo acessível via <code>https://&lt;servico&gt;.example.com</code> de qualquer lugar.</p>
<h3>Gitea (registry de imagens)<span class="hx:absolute hx:-mt-20" id="gitea-registry-de-imagens"></span>
    <a href="#gitea-registry-de-imagens" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <a href="https://github.com/go-gitea/gitea"target="_blank" rel="noopener">Gitea</a> funciona como registry Docker privado na porta 3007. Os projetos Frank FBI, Frank Mega, Frank Yomik e Mila têm imagens Docker que são buildadas e pushadas pro Gitea. Pra funcionar, o <code>daemon.json</code> do Docker precisa 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-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;insecure-registries&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;192.168.0.90:3007&#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>O SSH do Gitea deu problema na migração: o app.ini antigo tinha <code>SSH_LISTEN_PORT=22</code>, mas o entrypoint do container também inicia sshd na porta 22. Conflito. Solução: <code>GITEA__server__SSH_LISTEN_PORT=2222</code> como variável de ambiente no compose.</p>
<h3>Todos os 49 containers rodando<span class="hx:absolute hx:-mt-20" id="todos-os-49-containers-rodando"></span>
    <a href="#todos-os-49-containers-rodando" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O servidor migrado roda 49 containers em 15 stacks. O stack de media sozinho tem 10 containers (<a href="https://www.plex.tv/"target="_blank" rel="noopener">Plex</a>, <a href="https://github.com/Radarr/Radarr"target="_blank" rel="noopener">Radarr</a>, <a href="https://github.com/Sonarr/Sonarr"target="_blank" rel="noopener">Sonarr</a>, <a href="https://github.com/morpheus65535/bazarr"target="_blank" rel="noopener">Bazarr</a>, <a href="https://github.com/Prowlarr/Prowlarr"target="_blank" rel="noopener">Prowlarr</a>, <a href="https://github.com/qbittorrent/qBittorrent"target="_blank" rel="noopener">qBittorrent</a>, <a href="https://github.com/sabnzbd/sabnzbd"target="_blank" rel="noopener">SABnzbd</a>, <a href="https://github.com/Jackett/Jackett"target="_blank" rel="noopener">Jackett</a>, <a href="https://github.com/FlareSolverr/FlareSolverr"target="_blank" rel="noopener">FlareSolverr</a>, <a href="https://github.com/seerr/seerr"target="_blank" rel="noopener">Seerr</a>). Os projetos pessoais (<a href="https://github.com/akitaonrails/frank_fbi"target="_blank" rel="noopener">Frank FBI</a>, <a href="/2026/02/21/vibe-code-fiz-um-clone-do-mega-em-rails-em-1-dia-pro-meu-home-server/">Frank Mega</a>, <a href="https://github.com/akitaonrails/FrankYomik"target="_blank" rel="noopener">Frank Yomik</a>, Mila) somam mais 11. Monitoramento com <a href="https://github.com/grafana/grafana"target="_blank" rel="noopener">Grafana</a>, <a href="https://github.com/prometheus/prometheus"target="_blank" rel="noopener">Prometheus</a>, node-exporter e <a href="https://github.com/google/cadvisor"target="_blank" rel="noopener">cAdvisor</a>. Utilitários como <a href="https://github.com/portainer/portainer"target="_blank" rel="noopener">Portainer</a>, <a href="https://github.com/dani-garcia/vaultwarden"target="_blank" rel="noopener">Vaultwarden</a>, <a href="https://github.com/syncthing/syncthing"target="_blank" rel="noopener">Syncthing</a>, <a href="https://github.com/causefx/Organizr"target="_blank" rel="noopener">Organizr</a>, <a href="https://github.com/containrrr/watchtower"target="_blank" rel="noopener">Watchtower</a>. <a href="https://github.com/go-gitea/gitea"target="_blank" rel="noopener">Gitea</a> como registry Docker privado. <a href="https://github.com/immich-app/immich"target="_blank" rel="noopener">Immich</a> como Google Photos self-hosted. <a href="https://github.com/oae/kaizoku"target="_blank" rel="noopener">Kaizoku</a> pra manga com <a href="https://github.com/Kareadita/Kavita"target="_blank" rel="noopener">Kavita</a> como reader. <a href="https://github.com/ollama/ollama"target="_blank" rel="noopener">Ollama</a> com ROCm. E o <a href="https://github.com/bitcoin/bitcoin"target="_blank" rel="noopener">Bitcoin Core</a>/<a href="https://github.com/cculianu/Fulcrum"target="_blank" rel="noopener">Fulcrum</a> indexando a blockchain do NAS.</p>
<h2>Backups: duas camadas<span class="hx:absolute hx:-mt-20" id="backups-duas-camadas"></span>
    <a href="#backups-duas-camadas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Camada 1: snapshots btrfs locais (snapper)<span class="hx:absolute hx:-mt-20" id="camada-1-snapshots-btrfs-locais-snapper"></span>
    <a href="#camada-1-snapshots-btrfs-locais-snapper" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <code>/var</code> vive numa partição btrfs de 3.7TB. O snapper cria snapshots automáticos: 7 diários + 1 semanal. São crash-consistent, não application-consistent (postgres pode ficar levemente inconsistente se tiver write pesado no momento do snapshot).</p>
<p>Pra recuperar um arquivo apagado acidentalmente:</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 snapper -c var list
</span></span><span class="line"><span class="cl">sudo cp /var/.snapshots/5/snapshot/opt/docker/media/radarr/appdata/config/radarr.db <span class="se">\
</span></span></span><span class="line"><span class="cl">        /var/opt/docker/media/radarr/appdata/config/radarr.db</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 rollback completo de um stack:</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 docker compose -p media down
</span></span><span class="line"><span class="cl">sudo snapper -c var undochange 7..0 /var/opt/docker/media
</span></span><span class="line"><span class="cl">sudo docker compose -p media up -d</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>Camada 2: restic pro NAS (off-machine)<span class="hx:absolute hx:-mt-20" id="camada-2-restic-pro-nas-off-machine"></span>
    <a href="#camada-2-restic-pro-nas-off-machine" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <a href="https://github.com/restic/restic"target="_blank" rel="noopener">restic</a> roda toda noite às 3h, faz backup incremental pra <code>/var/mnt/terachad/homelab-backups/</code>. Retenção: 7 diários + 4 semanais. Deduplicação por conteúdo, então Plex config (19GB) e Gitea repos (12GB) transferem apenas deltas.</p>
<p>Antes do restic rodar, um <code>pg_dump</code> exporta os bancos postgres (Immich, Kaizoku). Os dumps vão pra <code>/tmp/homelab-db-dumps/</code> e são incluídos no backup.</p>
<p>O que NÃO é incluído no backup (re-downloadável): blockchain Bitcoin (785GB no NAS), imagens Docker (re-pullable), modelos Ollama (re-downloadable), cache do HuggingFace/EasyOCR, transcoding temporário do Plex.</p>
<p>Diretórios grandes e re-downloadáveis foram convertidos em subvolumes btrfs pra que o snapper os ignore: <code>/var/lib/ollama</code> e <code>/var/opt/docker/bitcoin/fulcrum/fulc2_db</code>.</p>
<h2>Tuning de performance<span class="hx:absolute hx:-mt-20" id="tuning-de-performance"></span>
    <a href="#tuning-de-performance" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>btrfs com compressão zstd<span class="hx:absolute hx:-mt-20" id="btrfs-com-compressão-zstd"></span>
    <a href="#btrfs-com-compress%c3%a3o-zstd" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Adicionei <code>compress=zstd:1</code> no fstab pra partição <code>/var</code>. O zstd nível 1 tem overhead de CPU quase zero em NVMe e comprime bem metadata Docker, JSON configs e logs. Dados incompressíveis (SQLite, postgres) são ignorados automaticamente pelo btrfs.</p>
<h3>zram swap<span class="hx:absolute hx:-mt-20" id="zram-swap"></span>
    <a href="#zram-swap" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com ~30GB de RAM disponível pro sistema (96GB vão pra VRAM), swap comprimido em memória ajuda. O zram cria um dispositivo de swap de ~15GB (ram/2) com compressão zstd, muito mais rápido que swap em disco.</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"># /etc/systemd/zram-generator.conf</span>
</span></span><span class="line"><span class="cl"><span class="k">[zram0]</span>
</span></span><span class="line"><span class="cl"><span class="na">zram-size</span> <span class="o">=</span> <span class="s">ram / 2</span>
</span></span><span class="line"><span class="cl"><span class="na">compression-algorithm</span> <span class="o">=</span> <span class="s">zstd</span>
</span></span><span class="line"><span class="cl"><span class="na">swap-priority</span> <span class="o">=</span> <span class="s">100</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>btrfs nodatacow em diretórios de banco de dados<span class="hx:absolute hx:-mt-20" id="btrfs-nodatacow-em-diretórios-de-banco-de-dados"></span>
    <a href="#btrfs-nodatacow-em-diret%c3%b3rios-de-banco-de-dados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Copy-on-write + escrita aleatória de banco de dados = write amplification. Desabilitei CoW nos diretórios que guardam SQLite e postgres:</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 chattr +C /var/opt/docker/gitea/data/gitea.db
</span></span><span class="line"><span class="cl">sudo chattr +C /var/opt/docker/immich/db/
</span></span><span class="line"><span class="cl">sudo chattr +C /var/opt/docker/media/radarr/appdata/config/</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>CPU em modo performance<span class="hx:absolute hx:-mt-20" id="cpu-em-modo-performance"></span>
    <a href="#cpu-em-modo-performance" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Num servidor headless, não faz sentido economizar energia:</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> performance <span class="p">|</span> sudo tee /sys/devices/system/cpu/cpu*/cpufreq/energy_performance_preference</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>Persistido via systemd service <code>cpu-epp.service</code>.</p>
<h3>Docker shutdown fix<span class="hx:absolute hx:-mt-20" id="docker-shutdown-fix"></span>
    <a href="#docker-shutdown-fix" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Problema que descobri: o Docker vem com <code>KillMode=process</code>, que significa que no shutdown do sistema, o systemd mata só o dockerd e deixa todos os <code>containerd-shim</code> (um por container, ~49 no meu caso) órfãos. O systemd-shutdown precisa caçar eles um a um depois que o journal já parou, causando um hang silencioso de vários minutos.</p>
<p>Fix:</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"># /etc/systemd/system/docker.service.d/shutdown.conf</span>
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">KillMode</span><span class="o">=</span><span class="s">control-group</span>
</span></span><span class="line"><span class="cl"><span class="na">TimeoutStopSec</span><span class="o">=</span><span class="s">30</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>Os problemas que encontramos<span class="hx:absolute hx:-mt-20" id="os-problemas-que-encontramos"></span>
    <a href="#os-problemas-que-encontramos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa é a tabela de problemas reais que tivemos durante a migração. Se você está planejando algo parecido, leia antes de começar:</p>
<table>
  <thead>
      <tr>
          <th>Problema</th>
          <th>Causa</th>
          <th>Solução</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ROCm vê só 15.5GB de VRAM</td>
          <td>Kernel TTM limita pages mesmo com BIOS em 96GB</td>
          <td>Adicionar <code>amdttm.pages_limit=25165824</code> no kernel cmdline</td>
      </tr>
      <tr>
          <td>Todos containers: permission denied em volumes</td>
          <td>SELinux <code>container_t</code> não escreve em paths sem label</td>
          <td><code>security_opt: label:disable</code> em todo serviço</td>
      </tr>
      <tr>
          <td>NFS com <code>:Z</code> falha silenciosamente</td>
          <td>NFS não suporta xattr do SELinux</td>
          <td>Nunca usar <code>:Z</code> em paths NFS</td>
      </tr>
      <tr>
          <td>SQLite quebra com <code>:Z</code></td>
          <td>Relabeling muda contexto, WAL mode falha</td>
          <td>Remover <code>:Z</code>, usar <code>label:disable</code></td>
      </tr>
      <tr>
          <td>Radarr/Sonarr mostraram tela de setup</td>
          <td>Backup em <code>appdata/config/</code> mas compose montava <code>appdata/</code></td>
          <td>Corrigir: <code>appdata/config:/config</code></td>
      </tr>
      <tr>
          <td>Grafana perdeu dashboards</td>
          <td>Named volume não incluído no backup de filesystem</td>
          <td>Backup explícito do named volume</td>
      </tr>
      <tr>
          <td>Plex não encontra mídia</td>
          <td>Path interno mudou de <code>/media</code> pra <code>/data</code></td>
          <td>Restaurar path original no compose</td>
      </tr>
      <tr>
          <td>Seerr não conecta no Plex</td>
          <td>macvlan isolada da bridge network</td>
          <td>Adicionar <code>default: {}</code> nas networks do Plex</td>
      </tr>
      <tr>
          <td>Fulcrum crash: &ldquo;option -b missing&rdquo;</td>
          <td>Env vars não suportadas pela imagem</td>
          <td>Usar flags CLI em <code>command:</code></td>
      </tr>
      <tr>
          <td>bitcoind rejeita RPC</td>
          <td>Bind em <code>::1</code> por padrão</td>
          <td>Adicionar <code>-rpcbind=0.0.0.0 -rpcallowip=172.0.0.0/8</code></td>
      </tr>
      <tr>
          <td>sdbootutil warning no transactional shell</td>
          <td>Deve rodar fora da transação</td>
          <td>Executar <code>sdbootutil update-all-entries</code> no shell normal</td>
      </tr>
      <tr>
          <td>Watchtower permission denied em docker.sock</td>
          <td>SELinux bloqueia acesso ao socket</td>
          <td><code>label:disable</code></td>
      </tr>
      <tr>
          <td>Gitea SSH crash</td>
          <td>Conflito: entrypoint sshd porta 22 + app porta 22</td>
          <td><code>GITEA__server__SSH_LISTEN_PORT=2222</code></td>
      </tr>
      <tr>
          <td>docker-compose não instalado com Docker</td>
          <td>Pacote openSUSE só instala o daemon</td>
          <td>Instalar binário standalone manualmente</td>
      </tr>
  </tbody>
</table>
<h2>O que dizer pro Claude Code antes de começar<span class="hx:absolute hx:-mt-20" id="o-que-dizer-pro-claude-code-antes-de-começar"></span>
    <a href="#o-que-dizer-pro-claude-code-antes-de-come%c3%a7ar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se eu fosse refazer a migração do zero, daria estas instruções pro Claude Code na primeira mensagem. Na ordem de importância:</p>
<p>Diga que SELinux está enforcing e que ele NÃO deve usar <code>:Z</code> em nenhum volume Docker, e sim <code>security_opt: label:disable</code> em todo serviço. Diga que <code>/var/mnt/terachad/</code> é mount NFS e que <code>:Z</code> nunca deve aparecer em paths NFS. Diga pra sempre olhar o compose original antes de reescrever e só mudar IPs, paths e nomes de container, sem inventar novos layouts de volume. Avise que named volumes precisam de backup explícito (Grafana, Portainer). Explique que Plex roda em macvlan e precisa de <code>default: {}</code> nas networks. Informe que a GPU é gfx1151, não suportada oficialmente, e que precisa de UMA 96GB no BIOS + kernel TTM params + <code>HSA_OVERRIDE_GFX_VERSION=11.5.1</code>. E diga que Bitcoin/Fulcrum não processam variáveis de ambiente, tudo vai como argumento no <code>command:</code>.</p>
<p>Essas instruções teriam evitado 80% dos problemas que encontramos.</p>
<h2>Layout final do servidor<span class="hx:absolute hx:-mt-20" id="layout-final-do-servidor"></span>
    <a href="#layout-final-do-servidor" 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>/var/opt/docker/
├── bitcoin/          (bitcoind &#43; fulcrum)
├── cloudflared/      (túnel Cloudflare)
├── frank_fbi/        (análise de fraude por email)
├── frank_mega/       (clone do Mega)
├── frank_yomik/      (tradução de mangás)
├── gitea/            (registry Docker)
├── immich/           (Google Photos self-hosted)
├── kaizoku/          (manga downloader &#43; reader)
├── media/            (Plex &#43; *arr stack)
├── mila/             (bot Discord)
├── monitor/          (Grafana &#43; Prometheus)
├── ollama/           (LLM local com ROCm)
├── rip/              (HandBrake)
└── utils/            (Portainer, Vaultwarden, Syncthing, etc.)

/var/mnt/terachad/    (NFS do Synology)
├── Bitcoin/data/     (blockchain, 785GB)
├── Downloads/        (torrents &#43; nzbget)
├── Videos/           (Radarr movies &#43; Sonarr series)
└── Ollama/models/    (overflow de modelos se disco local encher)</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>Aviso sobre usar IA pra administrar servidor<span class="hx:absolute hx:-mt-20" id="aviso-sobre-usar-ia-pra-administrar-servidor"></span>
    <a href="#aviso-sobre-usar-ia-pra-administrar-servidor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu usei Claude Code pra acelerar a migração. Criou compose files, escreveu scripts de backup, configurou firewall, diagnosticou problemas de SELinux. Funcionou bem pro meu caso: home server, só eu uso, e eu estava revisando cada passo.</p>
<p>Mas tem armadilhas. O Claude não sabe que <code>:Z</code> quebra SQLite a menos que você diga. Ele não sabe que o Fulcrum não aceita env vars a menos que já tenha visto o Dockerfile. Ele vai inventar layouts de volume &ldquo;melhores&rdquo; que quebram o Plex porque o Plex guarda paths absolutos no banco de dados.</p>
<p>Se fosse produção real: não faça isso sem review. Cada compose file que o Claude gerar, leia inteiro antes de aplicar. Cada comando destrutivo (rollback, delete, recreate), confirme manualmente. E tenha backups testados antes de começar. O Claude é bom pra gerar a primeira versão e diagnosticar erros, mas as decisões de arquitetura e as validações de segurança são suas.</p>
<p>Os posts anteriores sobre o home server que podem dar contexto adicional:</p>
<ul>
<li><a href="/2024/04/03/meu-netflix-pessoal-com-docker-compose/">Meu &ldquo;Netflix Pessoal&rdquo; com Docker Compose</a></li>
<li><a href="/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/">Acessando meu Home Server com domínio de verdade</a></li>
<li><a href="/2025/09/10/protegendo-seu-home-server-com-cloudflare-zero-trust/">Protegendo seu Home Server com Cloudflare Zero Trust</a></li>
<li><a href="/2025/09/10/instalando-grafana-no-meu-home-server/">Instalando Grafana no meu Home Server</a></li>
<li><a href="/2025/09/10/omarchy-2-0-bitwarden-self-hosted-vaultwarden/">Vaultwarden self-hosted</a></li>
</ul>
]]></content:encoded><category>homelab</category><category>containers</category><category>devops</category><category>seguranca</category></item><item><title>Vibe Code: Fiz um clone do Mega em Rails em 1 dia pro meu Home Server</title><link>https://akitaonrails.github.io/2026/02/21/vibe-code-fiz-um-clone-do-mega-em-rails-em-1-dia-pro-meu-home-server/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/02/21/vibe-code-fiz-um-clone-do-mega-em-rails-em-1-dia-pro-meu-home-server/</guid><pubDate>Sat, 21 Feb 2026 18:48:25 +0000</pubDate><description>&lt;p&gt;Faz tempo que eu queria ter um serviço de file sharing privado no meu home server. Sabe quando precisa mandar um arquivo grande pra alguém? &amp;ldquo;Ah, usa o Google Drive&amp;rdquo;. Não. &amp;ldquo;WeTransfer?&amp;rdquo; Também não. Eu quero controle total. Quero saber onde meus arquivos estão, por quanto tempo existem, quem baixou, e não quero depender de serviço de terceiro pra algo tão simples.&lt;/p&gt;
&lt;p&gt;Então fiz o que qualquer programador faria: sentei, escrevi um documento de especificação, e com ajuda do Claude Code (Claude Opus 4.6), construí o &lt;a href="https://github.com/akitaonrails/FrankMega"target="_blank" rel="noopener"&gt;&lt;strong&gt;FrankMega&lt;/strong&gt;&lt;/a&gt;, um clone simplificado do Mega, self-hosted, em Rails 8, em um único dia de trabalho. 21 commits, 3 horas de desenvolvimento ativo, 210 testes, zero dependência externa além do SQLite.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Faz tempo que eu queria ter um serviço de file sharing privado no meu home server. Sabe quando precisa mandar um arquivo grande pra alguém? &ldquo;Ah, usa o Google Drive&rdquo;. Não. &ldquo;WeTransfer?&rdquo; Também não. Eu quero controle total. Quero saber onde meus arquivos estão, por quanto tempo existem, quem baixou, e não quero depender de serviço de terceiro pra algo tão simples.</p>
<p>Então fiz o que qualquer programador faria: sentei, escrevi um documento de especificação, e com ajuda do Claude Code (Claude Opus 4.6), construí o <a href="https://github.com/akitaonrails/FrankMega"target="_blank" rel="noopener"><strong>FrankMega</strong></a>, um clone simplificado do Mega, self-hosted, em Rails 8, em um único dia de trabalho. 21 commits, 3 horas de desenvolvimento ativo, 210 testes, zero dependência externa além do SQLite.</p>
<p><img src="https://github.com/akitaonrails/FrankMega/raw/master/docs/upload_screen.png" alt="frankmega"  loading="lazy" /></p>
<p>E antes que apareça o esperto nos comentários: &ldquo;ah, mas Vibe Coding é só prompt engineering, qualquer um faz&rdquo; &ndash; calma. Esse post é justamente pra mostrar que <strong>não é</strong>.</p>
<h2>Da Ideia ao Código: o IDEA.md<span class="hx:absolute hx:-mt-20" id="da-ideia-ao-código-o-ideamd"></span>
    <a href="#da-ideia-ao-c%c3%b3digo-o-ideamd" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todo projeto começa com um documento. O meu foi o <a href="https://github.com/akitaonrails/FrankMega/blob/master/docs/IDEA.md"target="_blank" rel="noopener"><code>docs/IDEA.md</code></a> &ndash; 56 linhas descrevendo o que eu queria:</p>
<ul>
<li>Upload de arquivo único, com link compartilhável</li>
<li>Contador de downloads (padrão 5, configurável)</li>
<li>Expiração automática em até 24 horas</li>
<li>Autenticação completa: senha + 2FA + passkeys</li>
<li>Sem página de registro público &ndash; convites only</li>
<li>Admin cria convites, gerencia usuários, bane abusadores</li>
<li>Rate limiting agressivo, IP banning automático</li>
<li>Deploy via Docker + Cloudflare Tunnel no meu home server</li>
<li>Stack: Rails 8, SQLite, Tailwind CSS, Hotwire/Stimulus</li>
</ul>
<p>Eu deliberadamente incluí no final: <em>&ldquo;Sugira features importantes que possam ser relevantes pra um serviço assim.&rdquo;</em> Porque eu sei que um documento de spec nunca cobre tudo. E é aí que começa a parte interessante.</p>
<h2>O Big Bang: Commit #1<span class="hx:absolute hx:-mt-20" id="o-big-bang-commit-1"></span>
    <a href="#o-big-bang-commit-1" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O primeiro commit (<code>e277226</code>) às 12:17 entregou <strong>207 arquivos, 6.901 linhas de código</strong>. Num único commit. Sim, eu sei que é controverso, mas quando se está construindo do zero com AI, o primeiro commit é necessariamente grande.</p>
<p>O que veio nesse commit inicial:</p>
<ul>
<li>Autenticação completa com <code>has_secure_password</code> (bcrypt), TOTP via <code>rotp</code>, passkeys via <code>webauthn</code></li>
<li>Sistema de convites com códigos únicos e expiração</li>
<li>Admin panel completo (Users, Invitations, Files, MIME Types)</li>
<li>Upload com Active Storage + drag-and-drop via Stimulus</li>
<li>Links de download com hashes de 24 bytes (<code>SecureRandom.urlsafe_base64(24)</code>)</li>
<li>Rate limiting via Rack::Attack em todos os endpoints públicos</li>
<li>IP banning automático com modelo <code>Ban</code> e job assíncrono</li>
<li>Tema dark/light com Tailwind CSS inspirado no Mega.nz</li>
<li>73 testes (Minitest + FactoryBot)</li>
<li>Docker configuration com Puma + Thruster</li>
<li>Solid Queue/Cache/Cable &ndash; zero Redis</li>
</ul>
<p>Entenderam? <strong>Tudo isso no commit #1</strong>. O IDEA.md virou código funcional numa tacada. Mas o ponto é esse: <strong>o primeiro commit não era production-ready</strong>. Nem de longe.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-21_15-32-29.png" alt="tema light"  loading="lazy" /></p>
<h2>A Realidade: 20 Commits de Iteração<span class="hx:absolute hx:-mt-20" id="a-realidade-20-commits-de-iteração"></span>
    <a href="#a-realidade-20-commits-de-itera%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois do Big Bang vieram 20 commits ao longo de 3 horas. E é aqui que mora a verdade sobre desenvolvimento de software que nenhum tutorial de &ldquo;Vibe Coding&rdquo; vai te contar.</p>
<h3>Fase 1: &ldquo;Funciona na minha máquina&rdquo; (12:40 - 12:46)<span class="hx:absolute hx:-mt-20" id="fase-1-funciona-na-minha-máquina-1240---1246"></span>
    <a href="#fase-1-funciona-na-minha-m%c3%a1quina-1240---1246" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quatro commits em 6 minutos. Sabe o que aconteceu? Fui buildar o Docker e <strong>quebrou</strong>. Faltava <code>libssl-dev</code>. Consertei. Daí o CSP (Content Security Policy) do <code>secure_headers</code> conflitava com os inline scripts do importmap do Rails. Consertei. Mudei a porta de 3000 pra 3100 pra não conflitar com outros serviços no meu server.</p>
<p>Olha o commit <code>389ebe8</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># CSP agora é feito pelo Rails built-in, não pelo secure_headers</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Porque secure_headers não suporta nonces pra importmap inline scripts</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">csp</span> <span class="o">=</span> <span class="no">SecureHeaders</span><span class="o">::</span><span class="no">OPT_OUT</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 tipo de incompatibilidade entre gems você só descobre na hora. Nenhum LLM vai te avisar disso de antemão porque é uma combinação específica de versões.</p>
<h3>Fase 2: O Commit de Segurança &ndash; 22 Falhas Corrigidas (13:10)<span class="hx:absolute hx:-mt-20" id="fase-2-o-commit-de-segurança--22-falhas-corrigidas-1310"></span>
    <a href="#fase-2-o-commit-de-seguran%c3%a7a--22-falhas-corrigidas-1310" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esse é o commit mais importante do projeto inteiro: <code>4a854a6</code>. 35 arquivos, 612 linhas, 22 issues de segurança corrigidas. Vou detalhar porque é aqui que se separa código de brinquedo de código de produção.</p>
<p>O que o commit inicial fazia de errado:</p>
<p><strong>CRÍTICO &ndash; Chaves de criptografia com fallback hardcoded:</strong></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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># ANTES (perigoso):</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">active_record</span><span class="o">.</span><span class="n">encryption</span><span class="o">.</span><span class="n">primary_key</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s2">&#34;KEY&#34;</span><span class="p">,</span> <span class="s2">&#34;test-primary-key&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># DEPOIS (fail-fast):</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">Rails</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">production?</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">.</span><span class="n">active_record</span><span class="o">.</span><span class="n">encryption</span><span class="o">.</span><span class="n">primary_key</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="s2">&#34;ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Se o ENV var não existisse em produção, a app rodava feliz com a chave hardcoded. Qualquer pessoa que lesse o código-fonte conseguiria decriptar os segredos OTP de todos os usuários. Agora em produção, se faltar a variável, a app <strong>crasheia no boot</strong>. É o comportamento correto.</p>
<p><strong>CRÍTICO &ndash; Replay Attack no OTP:</strong></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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># ANTES:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">verify_otp</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">totp</span> <span class="o">=</span> <span class="no">ROTP</span><span class="o">::</span><span class="no">TOTP</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">otp_secret</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">totp</span><span class="o">.</span><span class="n">verify</span><span class="p">(</span><span class="n">code</span><span class="p">,</span> <span class="ss">drift_behind</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span> <span class="ss">drift_ahead</span><span class="p">:</span> <span class="mi">30</span><span class="p">)</span><span class="o">.</span><span class="n">present?</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># DEPOIS:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">verify_otp</span><span class="p">(</span><span class="n">code</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">totp</span> <span class="o">=</span> <span class="no">ROTP</span><span class="o">::</span><span class="no">TOTP</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">otp_secret</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">timestamp</span> <span class="o">=</span> <span class="n">totp</span><span class="o">.</span><span class="n">verify</span><span class="p">(</span><span class="n">code</span><span class="p">,</span> <span class="ss">drift_behind</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span> <span class="ss">drift_ahead</span><span class="p">:</span> <span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="kp">false</span> <span class="k">unless</span> <span class="n">timestamp</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Previne replay: rejeita se o timestamp desse código já foi usado</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">last_otp_at</span><span class="o">.</span><span class="n">present?</span> <span class="o">&amp;&amp;</span> <span class="no">Time</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">last_otp_at</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">update_column</span><span class="p">(</span><span class="ss">:last_otp_at</span><span class="p">,</span> <span class="no">Time</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="n">timestamp</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="kp">true</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Todo tutorial de TOTP que você encontra na internet mostra o <code>verify(code).present?</code> e para aí. O problema: um código TOTP é válido por 30 segundos. Se eu interceptar seu código (shoulder surfing, câmera, clipboard), posso usá-lo múltiplas vezes nessa janela. O <code>last_otp_at</code> garante one-time use real.</p>
<p><strong>ALTO &ndash; Race Condition no Download Counter:</strong></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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># ANTES (race condition):</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">increment_download!</span>
</span></span><span class="line"><span class="cl">  <span class="n">increment!</span><span class="p">(</span><span class="ss">:download_count</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># DEPOIS (atômico):</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">increment_download!</span>
</span></span><span class="line"><span class="cl">  <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="nb">id</span><span class="p">:</span> <span class="nb">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="s2">&#34;download_count &lt; max_downloads&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="s2">&#34;expires_at &gt; ?&#34;</span><span class="p">,</span> <span class="no">Time</span><span class="o">.</span><span class="n">current</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">.</span><span class="n">update_all</span><span class="p">(</span><span class="s2">&#34;download_count = download_count + 1&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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 <code>increment!</code> faz: (1) lê o valor, (2) soma 1 em Ruby, (3) escreve de volta. Duas requisições simultâneas podem ler <code>4</code>, ambas escreverem <code>5</code>, e o usuário ganha dois downloads quando só tinha um. O <code>UPDATE ... WHERE</code> atômico no SQL garante que só uma requisição vence. E o <code>== 1</code> retorna sucesso/falha numa única operação, sem lock.</p>
<p>Isso é o clássico bug TOCTOU (Time of Check, Time of Use). Você nunca vai ver isso no desenvolvimento local com um browser. Só aparece em produção com requisições concorrentes.</p>
<p><strong>ALTO &ndash; Open Redirect após login:</strong></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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># DEPOIS:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">safe_redirect_url?</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">uri</span><span class="o">.</span><span class="n">host</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="n">uri</span><span class="o">.</span><span class="n">host</span> <span class="o">==</span> <span class="n">request</span><span class="o">.</span><span class="n">host</span>
</span></span><span class="line"><span class="cl"><span class="k">rescue</span> <span class="no">URI</span><span class="o">::</span><span class="no">InvalidURIError</span>
</span></span><span class="line"><span class="cl">  <span class="kp">false</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Sem isso, um atacante podia enviar um link tipo <code>frankmega.com/session?return_to=https://evil.com</code> e após login o usuário seria redirecionado pro site malicioso.</p>
<p><strong>MÉDIO &ndash; Nonce do CSP previsível:</strong></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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># ANTES:</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">content_security_policy_nonce_generator</span> <span class="o">=</span> <span class="o">-&gt;</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="n">request</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">id</span><span class="o">.</span><span class="n">to_s</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># DEPOIS:</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">content_security_policy_nonce_generator</span> <span class="o">=</span> <span class="o">-&gt;</span><span class="p">(</span><span class="n">_request</span><span class="p">)</span> <span class="p">{</span> <span class="no">SecureRandom</span><span class="o">.</span><span class="n">base64</span><span class="p">(</span><span class="mi">16</span><span class="p">)</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>Session ID não muda entre requests. Se o nonce é previsível, um atacante pode injetar scripts com o nonce correto. Tem que ser aleatório por request.</p>
<p>E mais: IPv6 do Cloudflare adicionado aos trusted proxies, mínimo de 12 caracteres pra senha, MIME detection server-side via Marcel, verificação de file size pelo tempfile (não pelo header do client que pode ser forjado), proteção contra deletar/banir/rebaixar o último admin.</p>
<p>O total de testes pulou de 73 pra 109 com esse único commit.</p>
<h2>Como Rails Facilita Segurança<span class="hx:absolute hx:-mt-20" id="como-rails-facilita-segurança"></span>
    <a href="#como-rails-facilita-seguran%c3%a7a" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Rails torna MUITO mais fácil implementar segurança do que fazer do zero. Olha a comparação:</p>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>Rails</th>
          <th>Do zero</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CSRF</td>
          <td>Automático no <code>ActionController::Base</code></td>
          <td>Implementar tokens por sessão em todos os forms</td>
      </tr>
      <tr>
          <td>Hash de senha</td>
          <td><code>has_secure_password</code> (1 linha)</td>
          <td>Escolher algoritmo, salt, implementar verificação</td>
      </tr>
      <tr>
          <td>Parâmetros filtrados</td>
          <td><code>config.filter_parameters</code></td>
          <td>Hook no sistema de logging</td>
      </tr>
      <tr>
          <td>Strong params</td>
          <td><code>params.require().permit()</code></td>
          <td>Middleware de whitelist manual</td>
      </tr>
      <tr>
          <td>Criptografia de campos</td>
          <td><code>encrypts :otp_secret</code> (1 linha)</td>
          <td>Envelope encryption, key rotation, decrypt transparente</td>
      </tr>
      <tr>
          <td>CSP com nonces</td>
          <td>Config de 1 bloco</td>
          <td>Gerar nonces, injetar nos tags HTML, setar headers</td>
      </tr>
      <tr>
          <td>Rate limiting (Rails 8)</td>
          <td><code>rate_limit to: 10, within: 3.minutes</code></td>
          <td>Storage de contadores, lógica de sliding window</td>
      </tr>
      <tr>
          <td>SQL injection</td>
          <td>Parameterized queries by default</td>
          <td>Parametrizar manualmente cada query</td>
      </tr>
  </tbody>
</table>
<p>O Rails 8 em particular trouxe o <code>rate_limit</code> built-in no controller:</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">SessionsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="n">rate_limit</span> <span class="ss">to</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="ss">within</span><span class="p">:</span> <span class="mi">3</span><span class="o">.</span><span class="n">minutes</span><span class="p">,</span> <span class="ss">only</span><span class="p">:</span> <span class="ss">:create</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="ss">with</span><span class="p">:</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">redirect_to</span> <span class="n">new_session_path</span><span class="p">,</span> <span class="ss">alert</span><span class="p">:</span> <span class="n">t</span><span class="p">(</span><span class="s2">&#34;flash.sessions.create.rate_limit&#34;</span><span class="p">)</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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 a autenticação? Rails 8 agora vem com um <strong>authentication generator</strong> built-in. Não precisa mais do Devise pra coisas básicas. O <code>app/controllers/concerns/authentication.rb</code> do FrankMega é baseado nisso e estende com 2FA, passkeys e invitation-only registration.</p>
<h2>File Sharing Não É Só Download<span class="hx:absolute hx:-mt-20" id="file-sharing-não-é-só-download"></span>
    <a href="#file-sharing-n%c3%a3o-%c3%a9-s%c3%b3-download" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Uma coisa que muita gente não percebe: um serviço de file sharing tem uma superfície de ataque enorme. Não é só &ldquo;salvar arquivo, gerar link&rdquo;. Olha tudo que precisa ser considerado:</p>
<h3>Sanitização de Filename<span class="hx:absolute hx:-mt-20" id="sanitização-de-filename"></span>
    <a href="#sanitiza%c3%a7%c3%a3o-de-filename" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sanitize_filename</span><span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">content_type</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="nb">name</span><span class="o">.</span><span class="n">to_s</span><span class="p">)</span>                          <span class="c1"># Strip path traversal</span>
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span> <span class="o">=</span> <span class="n">sanitized</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&#34;UTF-8&#34;</span><span class="p">,</span> <span class="ss">invalid</span><span class="p">:</span> <span class="ss">:replace</span><span class="p">,</span>      <span class="c1"># Handle invalid UTF-8</span>
</span></span><span class="line"><span class="cl">                                <span class="k">undef</span><span class="p">:</span> <span class="ss">:replace</span><span class="p">,</span> <span class="ss">replace</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">sanitized</span> <span class="o">=</span> <span class="n">sanitized</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/[\x00-\x1f\x7f\/\\:*?&#34;&lt;&gt;|]/</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span> <span class="c1"># Control chars + unsafe chars</span>
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span> <span class="o">=</span> <span class="n">sanitized</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\A\.+/</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>                        <span class="c1"># Leading dots (hidden files)</span>
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span> <span class="o">=</span> <span class="n">sanitized</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/\s+/</span><span class="p">,</span> <span class="s2">&#34; &#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span>                  <span class="c1"># Collapse whitespace</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Windows reserved device names</span>
</span></span><span class="line"><span class="cl">  <span class="n">base_without_ext</span> <span class="o">=</span> <span class="n">sanitized</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="sr">/\.[^.]*\z/</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="k">if</span> <span class="n">base_without_ext</span><span class="o">.</span><span class="n">match?</span><span class="p">(</span><span class="sr">/\A(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])\z/i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sanitized</span> <span class="o">=</span> <span class="s2">&#34;_</span><span class="si">#{</span><span class="n">sanitized</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span> <span class="o">=</span> <span class="n">strip_extension_junk</span><span class="p">(</span><span class="n">sanitized</span><span class="p">,</span> <span class="n">content_type</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span> <span class="o">=</span> <span class="n">truncate_filename</span><span class="p">(</span><span class="n">sanitized</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">sanitized</span><span class="o">.</span><span class="n">presence</span> <span class="o">||</span> <span class="s2">&#34;unnamed_file&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Cada linha existe por um motivo:</p>
<ul>
<li><code>File.basename</code> impede path traversal (<code>../../etc/passwd</code> vira <code>passwd</code>)</li>
<li>Strip de control chars impede null byte injection e terminal escape injection</li>
<li>Nomes reservados do Windows (CON, NUL, AUX, LPT1-9) causam problemas se o storage for acessado de um Windows</li>
<li>Truncamento pra 255 bytes (não caracteres!) preservando a extensão &ndash; um emoji de 4 bytes conta como 4 pro limite do ext4/NTFS</li>
<li><code>strip_extension_junk</code> trata nomes que vêm de URLs com parâmetros grudados: <code>photo.jpg_1280x720+quality=80</code> vira <code>photo.jpg</code></li>
</ul>
<p>E isso é server-side. Tem validação client-side também no Stimulus controller pra rejeitar antes do upload:</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-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">isInvalidFilename</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="nx">name</span><span class="p">]).</span><span class="nx">size</span> <span class="o">&gt;</span> <span class="mi">255</span><span class="p">)</span> <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="sr">/[\x00-\x1f\x7f/:*?&#34;&lt;&gt;|\\]/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">name</span><span class="p">))</span> <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="kc">false</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>
<h3>MIME Type Detection Server-Side<span class="hx:absolute hx:-mt-20" id="mime-type-detection-server-side"></span>
    <a href="#mime-type-detection-server-side" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="vi">@shared_file</span><span class="o">.</span><span class="n">content_type</span> <span class="o">=</span> <span class="no">Marcel</span><span class="o">::</span><span class="no">MimeType</span><span class="o">.</span><span class="n">for</span><span class="p">(</span><span class="n">uploaded</span><span class="o">.</span><span class="n">tempfile</span><span class="p">,</span> <span class="nb">name</span><span class="p">:</span> <span class="n">uploaded</span><span class="o">.</span><span class="n">original_filename</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="vi">@shared_file</span><span class="o">.</span><span class="n">file_size</span> <span class="o">=</span> <span class="n">uploaded</span><span class="o">.</span><span class="n">tempfile</span><span class="o">.</span><span class="n">size</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>Não confia no <code>Content-Type</code> que o browser envia. O Marcel inspeciona os magic bytes do arquivo pra determinar o tipo real. E o file size vem do tempfile no disco, não do header <code>Content-Length</code> que pode ser forjado.</p>
<h3>Quotas e Disk Usage<span class="hx:absolute hx:-mt-20" id="quotas-e-disk-usage"></span>
    <a href="#quotas-e-disk-usage" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-21_16-00-52.png" alt="quota"  loading="lazy" /></p>
<p>Cada usuário tem uma quota de disco (5 GB default, admin pode customizar):</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">can_upload?</span><span class="p">(</span><span class="n">file_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">grace</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">x</span><span class="o">.</span><span class="n">security</span><span class="o">.</span><span class="n">disk_quota_grace_bytes</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="n">storage_used</span> <span class="o">+</span> <span class="n">file_size</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="p">(</span><span class="n">disk_quota</span> <span class="o">+</span> <span class="n">grace</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Sem quota, um único usuário malicioso enche o disco do servidor. Com o grace buffer de 10%, um upload que passa um pouco do limite é aceito pra não frustrar o usuário no edge case.</p>
<h3>Cleanup Automático<span class="hx:absolute hx:-mt-20" id="cleanup-automático"></span>
    <a href="#cleanup-autom%c3%a1tico" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">CleanupExpiredFilesJob</span> <span class="o">&lt;</span> <span class="no">ApplicationJob</span>
</span></span><span class="line"><span class="cl">  <span class="n">queue_as</span> <span class="ss">:default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">perform</span>
</span></span><span class="line"><span class="cl">    <span class="no">SharedFile</span><span class="o">.</span><span class="n">inactive</span><span class="o">.</span><span class="n">find_each</span> <span class="k">do</span> <span class="o">|</span><span class="n">shared_file</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">shared_file</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="n">purge</span> <span class="k">if</span> <span class="n">shared_file</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="n">attached?</span>
</span></span><span class="line"><span class="cl">      <span class="n">shared_file</span><span class="o">.</span><span class="n">destroy</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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 a cada 15 minutos via Solid Queue. Se ninguém baixou em 24 horas, sumiu. Se bateu o limite de downloads, sumiu. Sem isso, disco do servidor enche e o serviço morre.</p>
<h3>IP Banning Automático<span class="hx:absolute hx:-mt-20" id="ip-banning-automático"></span>
    <a href="#ip-banning-autom%c3%a1tico" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">InvalidHashAccessJob</span> <span class="o">&lt;</span> <span class="no">ApplicationJob</span>
</span></span><span class="line"><span class="cl">  <span class="n">queue_as</span> <span class="ss">:default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">ip_address</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">security</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">x</span><span class="o">.</span><span class="n">security</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">unless</span> <span class="n">security</span><span class="o">.</span><span class="n">enable_banning</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">cache_key</span> <span class="o">=</span> <span class="s2">&#34;invalid_hash:</span><span class="si">#{</span><span class="n">ip_address</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">count</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">increment</span><span class="p">(</span><span class="n">cache_key</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="ss">expires_in</span><span class="p">:</span> <span class="mi">1</span><span class="o">.</span><span class="n">hour</span><span class="p">)</span> <span class="o">||</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">count</span> <span class="o">&gt;=</span> <span class="n">security</span><span class="o">.</span><span class="n">max_invalid_hash_attempts</span>
</span></span><span class="line"><span class="cl">      <span class="no">Ban</span><span class="o">.</span><span class="n">ban!</span><span class="p">(</span><span class="n">ip_address</span><span class="p">,</span> <span class="ss">reason</span><span class="p">:</span> <span class="s2">&#34;Repeated invalid download hash access&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">               <span class="ss">duration</span><span class="p">:</span> <span class="n">security</span><span class="o">.</span><span class="n">ban_duration</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">cache_key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Tentou 3 hashes inválidos? Banido por 1 hora. Isso impede enumeração de hashes. Com 24 bytes de entropia (192 bits, 2^192 possibilidades), brute force é computacionalmente inviável, mas a proteção extra não custa nada.</p>
<h3>Rate Limiting em Camadas<span class="hx:absolute hx:-mt-20" id="rate-limiting-em-camadas"></span>
    <a href="#rate-limiting-em-camadas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Rack::Attack opera na camada de middleware (antes do Rails processar):</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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Login: 5 tentativas por minuto por IP</span>
</span></span><span class="line"><span class="cl"><span class="no">Rack</span><span class="o">::</span><span class="no">Attack</span><span class="o">.</span><span class="n">throttle</span><span class="p">(</span><span class="s2">&#34;logins/ip&#34;</span><span class="p">,</span> <span class="ss">limit</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="ss">period</span><span class="p">:</span> <span class="mi">1</span><span class="o">.</span><span class="n">minute</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">req</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">req</span><span class="o">.</span><span class="n">ip</span> <span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">path</span> <span class="o">==</span> <span class="s2">&#34;/session&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">req</span><span class="o">.</span><span class="n">post?</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Login: 5 tentativas por minuto por email</span>
</span></span><span class="line"><span class="cl"><span class="no">Rack</span><span class="o">::</span><span class="no">Attack</span><span class="o">.</span><span class="n">throttle</span><span class="p">(</span><span class="s2">&#34;logins/email&#34;</span><span class="p">,</span> <span class="ss">limit</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="ss">period</span><span class="p">:</span> <span class="mi">1</span><span class="o">.</span><span class="n">minute</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">req</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">path</span> <span class="o">==</span> <span class="s2">&#34;/session&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">req</span><span class="o">.</span><span class="n">post?</span>
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="o">.</span><span class="n">params</span><span class="o">.</span><span class="n">dig</span><span class="p">(</span><span class="s2">&#34;email_address&#34;</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">to_s</span><span class="o">&amp;.</span><span class="n">downcase</span><span class="o">&amp;.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Downloads: 60 views e 30 downloads por minuto</span>
</span></span><span class="line"><span class="cl"><span class="no">Rack</span><span class="o">::</span><span class="no">Attack</span><span class="o">.</span><span class="n">throttle</span><span class="p">(</span><span class="s2">&#34;downloads_get/ip&#34;</span><span class="p">,</span> <span class="ss">limit</span><span class="p">:</span> <span class="mi">60</span><span class="p">,</span> <span class="ss">period</span><span class="p">:</span> <span class="mi">1</span><span class="o">.</span><span class="n">minute</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">req</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">req</span><span class="o">.</span><span class="n">ip</span> <span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="s2">&#34;/d/&#34;</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">req</span><span class="o">.</span><span class="n">get?</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Geral: 300 requests por 5 minutos (exceto assets)</span>
</span></span><span class="line"><span class="cl"><span class="no">Rack</span><span class="o">::</span><span class="no">Attack</span><span class="o">.</span><span class="n">throttle</span><span class="p">(</span><span class="s2">&#34;requests/ip&#34;</span><span class="p">,</span> <span class="ss">limit</span><span class="p">:</span> <span class="mi">300</span><span class="p">,</span> <span class="ss">period</span><span class="p">:</span> <span class="mi">5</span><span class="o">.</span><span class="n">minutes</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">req</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">req</span><span class="o">.</span><span class="n">ip</span> <span class="k">unless</span> <span class="n">req</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span><span class="s2">&#34;/assets&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>Sete throttles diferentes pra cenários específicos. Mais o <code>rate_limit</code> do Rails 8 nos controllers. IPs banidos nem chegam ao Rails &ndash; são rejeitados na blocklist do middleware com status 403.</p>
<p>E os limites são configuráveis: em produção é 1x, em desenvolvimento é 10x pra não se trancar durante testes. Tudo centralizado no <code>config/initializers/security.rb</code>.</p>
<h2>A Saga do Download: 5 Commits em 21 Minutos<span class="hx:absolute hx:-mt-20" id="a-saga-do-download-5-commits-em-21-minutos"></span>
    <a href="#a-saga-do-download-5-commits-em-21-minutos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-21_15-31-25.png" alt="download"  loading="lazy" /></p>
<p>Essa parte é divertida porque mostra debugging real. Entre 14:44 e 15:05, eu fiz <strong>5 commits</strong> tentando fazer o download funcionar com Turbo Drive:</p>
<ol>
<li><code>cc3c23d</code> &ndash; Adicionei <code>data-turbo=false</code> no botão de download. <strong>Não funcionou.</strong></li>
<li><code>5224ed2</code> &ndash; Movi <code>data-turbo=false</code> pro <code>&lt;form&gt;</code>. <strong>Não funcionou.</strong></li>
<li><code>98b0d4f</code> &ndash; Abandonei o <code>redirect_to rails_blob_path</code> e usei <code>send_file</code> direto. <strong>Parcialmente funcionou.</strong></li>
<li><code>1fdf5f9</code> &ndash; Troquei de POST pra GET link. Funcionou mas&hellip; bots podem consumir downloads com GET.</li>
<li><code>4c74b27</code> &ndash; Voltei pra POST. Adicionei cleanup de URL artifacts nos filenames.</li>
</ol>
<p>O root cause: o Turbo Drive intercepta navegação agressivamente, e download de arquivo via <code>redirect_to</code> (que gera uma cadeia de 302 redirects do Active Storage) confunde o Turbo. A solução final: <code>send_file</code> direto do disco com <code>disposition: &quot;attachment&quot;</code> via POST com <code>data: { turbo: false }</code> no form.</p>
<p>Esse tipo de problema um LLM não resolve sozinho. Precisa testar no browser real, ver o que acontece, tentar, falhar, ajustar. É desenvolvimento iterativo puro.</p>
<h2>O Gap entre a Ideia e o Produto Final<span class="hx:absolute hx:-mt-20" id="o-gap-entre-a-ideia-e-o-produto-final"></span>
    <a href="#o-gap-entre-a-ideia-e-o-produto-final" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olha o que o IDEA.md não mencionava e que emergiu durante o desenvolvimento:</p>
<ol>
<li>Per-user disk quotas &ndash; sem isso um usuário enche o disco do server</li>
<li>I18n completo (EN + PT-BR) &ndash; 66 arquivos, ~250 strings extraídas</li>
<li>Terms of Service com aceite obrigatório</li>
<li>User self-deletion &ndash; requisito de privacidade</li>
<li>Blocked downloads de usuários banidos &ndash; seus links retornam 410 Gone</li>
<li>Client-side upload validation &ndash; verifica size, quota e filename antes do upload</li>
<li>Inline file previews &ndash; imagens, vídeo e áudio no download page sem consumir downloads</li>
<li>Styled error pages &ndash; em vez de plain text &ldquo;Not Found&rdquo;, páginas branded com i18n</li>
</ol>
<p>O IDEA.md dizia &ldquo;use activeadmin ou administrate&rdquo;. Na prática construí um admin panel custom com Tailwind porque o <code>administrate</code> tinha problemas de compatibilidade com Rails 8.1. O IDEA.md falava em &ldquo;progress bar se possível&rdquo;. Na prática usei drag-and-drop com preview de arquivo via Stimulus, que é melhor que progress bar.</p>
<p>Ninguém consegue prever todas as features no dia 1. Você descobre requisitos conforme constrói. E cada feature nova traz edge cases que precisam ser tratados.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-21_15-32-50.png" alt="pt"  loading="lazy" /></p>
<h2>Os Números<span class="hx:absolute hx:-mt-20" id="os-números"></span>
    <a href="#os-n%c3%bameros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ao final do projeto:</p>
<ul>
<li>21 commits em uma sessão de ~3 horas</li>
<li>210 testes (Minitest + FactoryBot), 513 assertions, zero failures</li>
<li>~3.100 linhas de código de aplicação (models, controllers, views, JS, CSS, configs)</li>
<li>~1.965 linhas de testes (incluindo testes de segurança dedicados)</li>
<li>9 models, 20 controllers, 36 views, 9 Stimulus controllers</li>
<li>24 medidas de segurança distintas em 7 camadas</li>
<li>CI: SimpleCov, RuboCop (zero offenses), Brakeman (zero warnings), bundler-audit (zero vulnerabilities)</li>
<li>Zero dependências externas além do SQLite. Sem Redis, sem PostgreSQL, sem serviço de fila externo</li>
</ul>
<h2>O Invitation-Only Server<span class="hx:absolute hx:-mt-20" id="o-invitation-only-server"></span>
    <a href="#o-invitation-only-server" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-21_15-31-54.png" alt="invite"  loading="lazy" /></p>
<p>O FrankMega não tem cadastro público. O fluxo é:</p>
<ol>
<li>Na primeira execução, sem nenhum usuário, aparece a tela de setup pra criar o admin</li>
<li>A rota <code>/setup</code> só existe enquanto <code>User.count.zero?</code> &ndash; depois some completamente:</li>
</ol>
<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-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">constraints</span><span class="p">(</span><span class="o">-&gt;</span><span class="p">(</span><span class="n">request</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span><span class="o">.</span><span class="n">count</span><span class="o">.</span><span class="n">zero?</span> <span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">get</span> <span class="s2">&#34;setup&#34;</span><span class="p">,</span> <span class="ss">to</span><span class="p">:</span> <span class="s2">&#34;setup#new&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">post</span> <span class="s2">&#34;setup&#34;</span><span class="p">,</span> <span class="ss">to</span><span class="p">:</span> <span class="s2">&#34;setup#create&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</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>
<ol start="3">
<li>O admin cria convites no painel admin (com expiração)</li>
<li>Cada convite gera um código único de 16 bytes</li>
<li>Cada código só pode ser usado uma vez (<code>with_lock</code> + transaction pra impedir race condition)</li>
<li>Após cadastrar, o convite é marcado como &ldquo;used&rdquo;</li>
</ol>
<p>Isso é perfeito pra um serviço pessoal/familiar. Controle total de quem acessa.</p>
<h2>Deploy: Docker + Cloudflare Tunnel<span class="hx:absolute hx:-mt-20" id="deploy-docker--cloudflare-tunnel"></span>
    <a href="#deploy-docker--cloudflare-tunnel" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/frankmd/2026/02/screenshot-2026-02-21_15-33-40.png" alt="share"  loading="lazy" /></p>
<p>Pra fechar, o tutorial de deploy, caso queira rodar no seu próprio Home Server Docker. Não é complicado mas tem detalhes que importam.</p>
<h3>O Dockerfile<span class="hx:absolute hx:-mt-20" id="o-dockerfile"></span>
    <a href="#o-dockerfile" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Multi-stage build em 3 estágios:</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-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># Stage 1: Base (runtime)</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">ruby:3.4.8-slim</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">base</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apt-get install -y curl libjemalloc2 libvips sqlite3<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># jemalloc pra melhor gerenciamento de memória</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"># Stage 2: Build (throwaway)</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">base</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">build</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apt-get install -y build-essential git libssl-dev<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Bundle install + asset precompilation com dummy env vars</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"># Stage 3: Final</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Copia gems e app, roda como user não-root (rails:rails, UID 1000)</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;./bin/thrust&#34;</span><span class="p">,</span> <span class="s2">&#34;./bin/rails&#34;</span><span class="p">,</span> <span class="s2">&#34;server&#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>O <code>bin/thrust</code> é o Thruster (do Basecamp) &ndash; um proxy HTTP na frente do Puma que faz: gzip/brotli compression, asset caching, X-Sendfile acceleration. Escuta na porta 80, proxeia pro Puma na 3000.</p>
<p>O entrypoint roda <code>db:prepare</code> e <code>db:seed</code> automaticamente no boot:</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 -e
</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="si">${</span><span class="p">@: -2:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;./bin/rails&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="p">@: -1:</span><span class="nv">1</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;server&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  ./bin/rails db:prepare
</span></span><span class="line"><span class="cl">  ./bin/rails db:seed
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="nb">exec</span> <span class="s2">&#34;</span><span class="si">${</span><span class="p">@</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>
<h3>docker-compose.yml<span class="hx:absolute hx:-mt-20" id="docker-composeyml"></span>
    <a href="#docker-composeyml" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Dois serviços:</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">web</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">akitaonrails/frankmega: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;3100: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">/home/seuuser/frankmega/uploads:/rails/storage/uploads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/seuuser/frankmega/db:/rails/storage</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">SECRET_KEY_BASE</span><span class="p">:</span><span class="w"> </span><span class="l">${SECRET_KEY_BASE}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">HOST</span><span class="p">:</span><span class="w"> </span><span class="l">${HOST}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># ... mais ~15 variáveis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">tunnel</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">cloudflare/cloudflared:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">tunnel run</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">TUNNEL_TOKEN=${TUNNEL_TOKEN}</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">web</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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 sidecar <code>cloudflare/cloudflared</code> cria um túnel outbound pra edge do Cloudflare. Não precisa abrir portas no firewall do home server. O Cloudflare faz SSL termination, DDoS protection, e roteia o tráfego pro container.</p>
<h3>Passo a Passo<span class="hx:absolute hx:-mt-20" id="passo-a-passo"></span>
    <a href="#passo-a-passo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>1. Crie o Tunnel no Cloudflare:</strong></p>
<ul>
<li>Cloudflare Zero Trust Dashboard &gt; Networks &gt; Tunnels &gt; Create</li>
<li>Tipo: Cloudflared</li>
<li>Copie o <code>TUNNEL_TOKEN</code></li>
<li>Configure o hostname: <code>frankmega.seudominio.com</code> apontando pra <code>http://web:80</code></li>
</ul>
<p><strong>2. Gere os segredos:</strong></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"># SECRET_KEY_BASE</span>
</span></span><span class="line"><span class="cl">openssl rand -hex <span class="m">64</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Chaves de criptografia (3 valores separados)</span>
</span></span><span class="line"><span class="cl">openssl rand -hex <span class="m">32</span>  <span class="c1"># PRIMARY_KEY</span>
</span></span><span class="line"><span class="cl">openssl rand -hex <span class="m">32</span>  <span class="c1"># DETERMINISTIC_KEY</span>
</span></span><span class="line"><span class="cl">openssl rand -hex <span class="m">32</span>  <span class="c1"># KEY_DERIVATION_SALT</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>3. Configure o <code>.env</code>:</strong></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-env" data-lang="env"><span class="line"><span class="cl"><span class="nv">SECRET_KEY_BASE</span><span class="o">=</span>&lt;gerado acima&gt;
</span></span><span class="line"><span class="cl"><span class="nv">RAILS_MASTER_KEY</span><span class="o">=</span>&lt;<span class="k">do</span> config/master.key&gt;
</span></span><span class="line"><span class="cl"><span class="nv">HOST</span><span class="o">=</span>frankmega.seudominio.com
</span></span><span class="line"><span class="cl"><span class="nv">WEBAUTHN_ORIGIN</span><span class="o">=</span>https://frankmega.seudominio.com
</span></span><span class="line"><span class="cl"><span class="nv">WEBAUTHN_RP_ID</span><span class="o">=</span>frankmega.seudominio.com
</span></span><span class="line"><span class="cl"><span class="nv">ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY</span><span class="o">=</span>&lt;gerado&gt;
</span></span><span class="line"><span class="cl"><span class="nv">ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY</span><span class="o">=</span>&lt;gerado&gt;
</span></span><span class="line"><span class="cl"><span class="nv">ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT</span><span class="o">=</span>&lt;gerado&gt;
</span></span><span class="line"><span class="cl"><span class="nv">TUNNEL_TOKEN</span><span class="o">=</span>&lt;<span class="k">do</span> Cloudflare&gt;
</span></span><span class="line"><span class="cl"><span class="nv">FORCE_SSL</span><span class="o">=</span><span class="nb">true</span>
</span></span><span class="line"><span class="cl"><span class="nv">APP_LOCALE</span><span class="o">=</span>pt-BR</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>4. Suba:</strong></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">docker compose pull
</span></span><span class="line"><span class="cl">docker compose up -d</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>5. Acesse</strong> <code>https://frankmega.seudominio.com</code>, crie a conta admin, comece a usar.</p>
<h3>Cuidados Importantes<span class="hx:absolute hx:-mt-20" id="cuidados-importantes"></span>
    <a href="#cuidados-importantes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><code>FORCE_SSL=false</code> pra testar localmente em <code>http://localhost:3100</code> sem Cloudflare na frente, senão entra em redirect loop</li>
<li>WebAuthn origin precisa bater exatamente, incluindo o <code>https://</code>. Se errar, passkeys silenciosamente não funcionam</li>
<li>IPs do Cloudflare são hardcoded no initializer (porque o gem <code>cloudflare-rails</code> não é compatível com Rails 8.1). Se o Cloudflare mudar os ranges, precisa rebuildar a imagem</li>
<li>Chaves de criptografia são permanentes. Se trocar depois que usuários configuraram 2FA, os segredos OTP ficam ilegíveis e eles perdem acesso</li>
<li>Cloudflare free tem limite de 100 MB de upload. Pro plano grátis, arquivos acima disso não passam pelo tunnel. A app permite até 1 GB mas o Cloudflare é o gargalo</li>
<li>O container roda como UID 1000. Se usar bind mounts ao invés de named volumes, o diretório precisa ser owned pelo UID 1000</li>
<li>São 4 bancos SQLite separados em produção (app, cache, queue, cable), todos no volume <code>db_data</code>. Backup é copiar o volume inteiro</li>
</ul>
<h2>O Verdadeiro Ponto Sobre &ldquo;Vibe Coding&rdquo;<span class="hx:absolute hx:-mt-20" id="o-verdadeiro-ponto-sobre-vibe-coding"></span>
    <a href="#o-verdadeiro-ponto-sobre-vibe-coding" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Claude Code é absurdamente produtivo. Eu não teria feito tudo isso em 3 horas sem ele. Lembra de antigamente que você dava murro em ponta de faca por 1 semana só pra fazer um login funcionar? Pois é.</p>
<p>O commit de I18n sozinho (66 arquivos, ~250 strings extraídas pra dois idiomas) levaria um dia inteiro manualmente.</p>
<p>Mas o ponto que ninguém quer ouvir: <strong>o LLM não tomou as decisões de segurança</strong>. Eu pedi pra ele fazer um audit e ele encontrou os problemas, mas eu precisei saber pedir. E precisei saber quais perguntas fazer. <em>&ldquo;Revise a segurança&rdquo;</em> é uma instrução vaga. <em>&ldquo;Verifique se tem race condition no increment do download counter&rdquo;</em> é uma instrução que só quem sabe o que é TOCTOU consegue dar.</p>
<p>Aquelas 56 linhas do IDEA.md carregam décadas de experiência em desenvolvimento web. Eu sabia pedir rate limiting porque já vi serviços derrubados por bots. Sabia pedir IP banning porque já lidei com abuse. Sabia pedir download counter atômico porque entendo concorrência.</p>
<p>Se um iniciante escrevesse o mesmo IDEA.md, provavelmente não teria metade dessas preocupações. E o LLM não iria sugerir espontaneamente. O resultado seria um serviço funcional, bonito, e completamente inseguro.</p>
<p>O valor do programador experiente no Vibe Coding não é escrever código. É saber o que pedir e revisar o que foi gerado. A experiência é o filtro entre &ldquo;funciona&rdquo; e &ldquo;funciona em produção&rdquo;.</p>
<p>Os 210 testes e 24 medidas de segurança não vieram de prompt engineering. Vieram de saber o que testar e por quê.</p>
<p>O código é open source sob AGPL-3.0: <a href="https://github.com/akitaonrails/frankmega"target="_blank" rel="noopener">github.com/akitaonrails/frank_mega</a>. Deploy no seu server, fucem o código, aprendam com os commits. E se acharem mais falhas de segurança, me avisem.</p>
<p>Lembrem-se:</p>
<blockquote>
  <p>A IA é o espelho da sua própria competência.</p>

</blockquote>
]]></content:encoded><category>vibe-coding</category><category>rails</category><category>homelab</category><category>seguranca</category></item><item><title>Omarchy 2.0 - Bitwarden Self-Hosted / VaultWarden</title><link>https://akitaonrails.github.io/2025/09/10/omarchy-2-0-bitwarden-self-hosted-vaultwarden/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/10/omarchy-2-0-bitwarden-self-hosted-vaultwarden/</guid><pubDate>Wed, 10 Sep 2025 20:00:00 -0300</pubDate><description>&lt;p&gt;Continuando a &lt;a href="https://akitaonrails.github.io/tags/omarchy/"&gt;série de Omarchy&lt;/a&gt; e fazendo cross-over com a série de &lt;a href="https://akitaonrails.github.io/tags/homeserver"&gt;home server&lt;/a&gt;, vamos falar sobre Gerenciador de Senhas e 2FA. Quem me acompanha que eu falo frequentemente sobre segurança digital pessoal, a importância de prestar atenção em links pra não cair em golpes, phishing, nem instalar malware sem querer.&lt;/p&gt;
&lt;p&gt;Mais importante: ter uma senha forte, aleatória, exclusiva e diferente para &lt;strong&gt;CADA&lt;/strong&gt; site, app ou serviço que usa. Nunca, jamais, reusar uma senha. Por que? Porque todo dia um desses sites é invadido e as senhas são vazadas. Se você procurar por você mesmo no site &lt;a href="https://haveibeenpwned.com/"target="_blank" rel="noopener"&gt;Have I Been Pwned&lt;/a&gt; certamente vai achar contas suas que já vazaram. E se você compartilha essa mesma senha com 10 outros sites, alguém já deve ter entrado na sua conta a essa altura.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Continuando a <a href="/tags/omarchy/">série de Omarchy</a> e fazendo cross-over com a série de <a href="/tags/homeserver">home server</a>, vamos falar sobre Gerenciador de Senhas e 2FA. Quem me acompanha que eu falo frequentemente sobre segurança digital pessoal, a importância de prestar atenção em links pra não cair em golpes, phishing, nem instalar malware sem querer.</p>
<p>Mais importante: ter uma senha forte, aleatória, exclusiva e diferente para <strong>CADA</strong> site, app ou serviço que usa. Nunca, jamais, reusar uma senha. Por que? Porque todo dia um desses sites é invadido e as senhas são vazadas. Se você procurar por você mesmo no site <a href="https://haveibeenpwned.com/"target="_blank" rel="noopener">Have I Been Pwned</a> certamente vai achar contas suas que já vazaram. E se você compartilha essa mesma senha com 10 outros sites, alguém já deve ter entrado na sua conta a essa altura.</p>
<p>Assista meu video sobre segurança:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/s7ldn31OEFc"
    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>Ter preguiça de usar um gerenciador de senhas é só uma desculpa idiota de gente preguiçosa. E um programador sequer tem direito de usar essa desculpa.</p>
<h3>Omarchy 1Password<span class="hx:absolute hx:-mt-20" id="omarchy-1password"></span>
    <a href="#omarchy-1password" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quem acompanha o DHH desde os primórdios de quando Macs eram a melhor plataforma pra desenvolvedores, lembra que ele sempre foi fã do 1Password, um dos que funcionava melhor em iPhones.</p>
<p>Eu migrei de volta pra Android e Linux muitos anos atrás, em 2015. Eu usava <strong>1Password</strong>, mas em Android o app era tenebroso de feio de ruim de usar. Por isso desisti dele e fiquei migrando entre vários outros apps. Seja Google Authenticator, seja Lastpass (que não recomendo).</p>
<p>Sei que tem diversos outros open source que as pessoas preferem, mas eu acabei caindo no Bitwarden, que é <strong>BONITO</strong> tanto em Android como a extension pra navegadores. Além disso ele tem componentes abertos e permite <strong>self-hosting</strong>.</p>
<p>Mais do que isso, ele não dificulta que você exporte todos os dados da sua conta e importe em outro lugar. Diferente de apps como o Twilio Authy, que te bloqueia de tudo e por isso deixei de usar.</p>
<p>Bitwarden tem gerador seguro de senhas aleatórias, tem suporte a <strong>2FA TOTP</strong>, dá pra armazenar anotações (tipo mini textos) seguras e muito mais. Os apps integram com biometria do celular, então é bem conveniente de usar.</p>
<p>Ainda mantenho meu Bitwarden dentro do <a href="https://www.samsung.com/global/sustainability/popup/popup_doc/AYUqoB6qDDMAIx_C/"target="_blank" rel="noopener">Secure Vault</a> da Samsung, o <strong>Samsung Knox Vault</strong>. Então são 2 níveis de segurança. E quando preciso andar em algum lugar suspeito, não levo meu celular principal, só um burner-phone sem nada de importante logado.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910193713_bitwarden-generator.png" alt="Bitwarden Generator"  loading="lazy" /></p>
<p>Olhem como é bonito. Eu gosto dele e uso já faz alguns anos. Minhas senhas variam entre 21 a 42 caracteres aleatórios, um diferente pra cada site. Eu atualizei todas as senhas de tudo mais de uma vez nos últimos anos, e todas as contas tem 2FA/TOTP cadastrado no Bitwarden também, assim consegui sair do Authy.</p>
<h3>Instalando VaultWarden Self-Hosted<span class="hx:absolute hx:-mt-20" id="instalando-vaultwarden-self-hosted"></span>
    <a href="#instalando-vaultwarden-self-hosted" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mais importante, chegou a hora de hostear meu próprio servidor de Bitwarden, o <a href="https://github.com/dani-garcia/vaultwarden"target="_blank" rel="noopener"><strong>vaultwarden</strong></a> que é uma alternativa open source, código aberto no GitHub, então você mesmo pode fuçar pra aprender ou até contribuir com ele. Novamente, subo tudo com Docker Compose no meu Home Server, então só precisei adicionar este trecho no meu arquivo:</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="w">  </span><span class="nt">vaultwarden</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">vaultwarden/server:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">vaultwarden</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">DOMAIN</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://bitwarden.fabioakita.dev&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SIGNUPS_ALLOWED</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#34;</span><span class="w">          </span><span class="c"># set to true only to create first user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">WEBSOCKET_ENABLED</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ADMIN_TOKEN</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;iA...Q9&#34;</span><span class="w">  </span><span class="c"># for /admin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># SMTP (recommended)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;smtp.gmail.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_PORT</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;587&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_SECURITY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;starttls&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;boss@akitaonrails.com&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xxx&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">SMTP_FROM</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;boss@akitaonrails.com&#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">/home/akitaonrails/vaultwarden/data:/data</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;9999:80&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;wget&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-q&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-O&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;http://localhost/health&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">5</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>Importante:</p>
<ul>
<li>Crie um <code>ADMIN_TOKEN</code> longo e aleatório. Use o próprio Bitwarden pra isso, mantenha secreto.</li>
<li>Mude <code>SIGNUPS_ALLOWED</code> pra <code>true</code> somente uma vez, entre no site local e crie sua nova conta. Depois mude no arquivo pra <code>false</code>, reinicie o container. Dessa forma ninguém mais poderá criar nenhuma nova conta.</li>
<li>É recomendado configurar um GMAIL SMTP pra mandar emails de notificações. Pra isso precisa criar uma senha de aplicativo no Google.</li>
</ul>
<p>Pra criar uma senha exclusivamente pro vaultwarden, acesse <a href="https://myaccount.google.com/apppasswords"target="_blank" rel="noopener">Google App Passwords</a> e gere uma nova senha secreta exclusiva:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910194453_screenshot-2025-09-10_16-34-48.png" alt="App Password"  loading="lazy" /></p>
<p>Ele só vai mostrar essa senha uma única vez. Copie e cole direto no yaml do docker compose. Configurando e-mail bonitinho, toda vez que alguém fizer login, ele manda um e-mail notificando. Isso é bom pra ter certeza que não tem ninguém suspeito mexendo no seu servidor:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910201642_screenshot-2025-09-10_20-16-25.png" alt="notification"  loading="lazy" /></p>
<p>Agora só preciso subir o container:</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">docker compose -f utils-docker-compose.yml up -d</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>Roteando túnel do Cloudflared<span class="hx:absolute hx:-mt-20" id="roteando-túnel-do-cloudflared"></span>
    <a href="#roteando-t%c3%banel-do-cloudflared" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como já mostrei no <a href="https://akitaonrails.com/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/"target="_blank" rel="noopener">post de Cloudflared</a> agora quero poder acessar remotamente, via túnel seguro, usando uma URL pública como <code>https://bitwarden.fabioakita.dev</code>. Pra fazer isso temos que editar aquele arquivo <code>/etc/cloudflared/config.yml</code> e adicionar no final dele 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">bitwarden.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:9999</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">bitwarden.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">http2Origin</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http_status:404</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 temos que reiniciar o serviço e também adicionar a rota manualmente:</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 cloudflared tunnel route dns homelab bitwarden.fabioakita.dev
</span></span><span class="line"><span class="cl">sudo systemctl restart cloudflared</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 pronto! Talvez demore até 5 minutos pros DNS sincronizarem e tudo mais, mas daí é só entrar no seu navegador e vai ver isto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910194927_screenshot-2025-09-10_19-49-14.png" alt="VaultWarden Login"  loading="lazy" /></p>
<p>Se aquele <code>SIGNUPS_ALLOWED</code> estiver <code>true</code> também vai ter um link pra &ldquo;Criar nova conta&rdquo;. No meu caso eu já fiz isso e desliquei, então só tem login mesmo.</p>
<h3>Importando seus Dados<span class="hx:absolute hx:-mt-20" id="importando-seus-dados"></span>
    <a href="#importando-seus-dados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Antes de continuar, vamos abrir a extension do Bitwarden - que continua logado na versão cloud em bitwarden.com e exportar todos os meus dados:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910195209_bitwarden-export.png" alt="Export Vault"  loading="lazy" /></p>
<p>Basta se autenticar, daí na barra inferior de ícones vai ter &ldquo;Settings&rdquo;, depois &ldquo;Vault Options&rdquo; e, finalmente &ldquo;Export vault&rdquo;. Deixe o padrão no formato JSON mesmo (vai ser um <strong>JSON encriptado</strong>) e faça download desse arquivo pra algum lugar seguro.</p>
<p>Agora podemos logar de volta na nossa versão self-hosted em <code>https://bitwarden.fabioakita.dev</code> e seguir pelo mesmo menu, mas escolher &ldquo;Import&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910195410_screenshot-2025-09-10_19-53-56.png" alt="Import"  loading="lazy" /></p>
<p>E pronto! Agora minha versão self-hosted está <strong>idêntica</strong> à versão cloud que vinha usando até agora.</p>
<h3>Logando no novo servidor self-hosted<span class="hx:absolute hx:-mt-20" id="logando-no-novo-servidor-self-hosted"></span>
    <a href="#logando-no-novo-servidor-self-hosted" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora basta deslogar tanto do App mobile quanto da extension do navegador. Daí na tela de login vai ter a seguinte opção:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910195632_bitwarden-login.png" alt="Login self-hosted"  loading="lazy" /></p>
<p>Na extension web a janela não deixa ver essa opção, use o scroll do mouse e scrole até o fim da janela e vai ter essa opção pequena. Escolha &ldquo;self-hosted&rdquo; e vai abrir uma janela pra digitar a URL <code>https://bitwarden.fabioakita.dev</code> e pronto!</p>
<p>A partir daqui o uso é exatamente o mesmo de antes. A interface é a mesma, as opções são as mesmas. Configure <strong>biometria</strong>, PIN, timeout pra lock e tudo mais pra deixar do jeito que você gosta.</p>
<p>Faça a mesma coisa no app mobile e pronto!</p>
<p>Nessa nova conta que criamos no servidor self-hosted, não esqueça de habilitar 2FA também, que é a opção &ldquo;Authenticator App&rdquo; em &ldquo;Settings/Security&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910200009_screenshot-2025-09-10_19-59-55.png" alt="2FA"  loading="lazy" /></p>
<p>Pra todas as outras contas, podemos registrar o 2FA dentro do Bitwarden. Mas pra conta master do próprio Bitwarden/Vaultwarden, precisamos usar um segundo app só pra isso.</p>
<p>Não sei se é o melhor, mas eu tenho gostado de usar o <a href="https://getaegis.app/"target="_blank" rel="noopener">Aegis Authenticator</a> no meu Android:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910203243_hero.7C782sEM.png" alt="Aegis"  loading="lazy" /></p>
<p>É open source, o código está no <a href="https://github.com/beemdevelopment/aegis"target="_blank" rel="noopener">GitHub</a>, então parece minimamente confiável. Também deixo ele dentro do Samsung Knox Vault. Só preciso dele quando preciso re-logar no Bitwarden/Vaultwarden. Pra logar nos Google, Steam ou qualquer outra conta, o 2FA de cada uma está dentro do Vaultwarden mesmo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910200449_2fa.png" alt="2FA"  loading="lazy" /></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>Agora só faltou mesmo me logar com minha conta antiga em <strong>bitwarden.com</strong>, que é a versão &ldquo;oficial&rdquo; no cloud e deletar minha conta, pra garantir que ninguém mais tenha acesso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910200615_screenshot-2025-09-10_20-06-04.png" alt="Deletar"  loading="lazy" /></p>
<p>E pronto, <strong>PERMANENTEMENTE APAGADO</strong>! Não existe mais minhas contas num servidor qualquer que eu não controlo, mesmo que fosse encriptado.</p>
<p>Estou bem contente com esta solução. Agora meu Vaultwarden está num container, os dados do home server tem backup automático pro meu NAS, tudo é encriptado e só tem acesso via túnel seguro da Cloudflare. O risco é bem baixo e ninguém tem chance de ter acesso aos meus dados, porque eles não estão mais no &ldquo;cloud&rdquo;.</p>
<p>Meus dados são somente meus.</p>
]]></content:encoded><category>omarchy</category><category>homelab</category><category>containers</category><category>seguranca</category></item><item><title>Instalando Grafana no meu Home Server</title><link>https://akitaonrails.github.io/2025/09/10/instalando-grafana-no-meu-home-server/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/10/instalando-grafana-no-meu-home-server/</guid><pubDate>Wed, 10 Sep 2025 16:00:00 -0300</pubDate><description>&lt;p&gt;Este post é pra continuar complementando minha &lt;a href="https://akitaonrails.github.io/tags/homeserver"&gt;série sobre meu Home Server&lt;/a&gt;, de novo, pra servir de anotação pra mim mesmo no futuro. Como já expliquei, meu home server é um mini-PC Intel NUC que eu só acesso com SSH. Então eu queria uma forma de monitorar o uso dos recursos do sistema sem ter que manualmente logar via SSH e abrir BTOP ou NTOP.&lt;/p&gt;
&lt;p&gt;Fiquei com preguiça de instalar Grafana na época, mas resolvi fazer isso agora, e foi bem mais fácil do que eu esperava, por isso resolvi compartilhar como fiz.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este post é pra continuar complementando minha <a href="/tags/homeserver">série sobre meu Home Server</a>, de novo, pra servir de anotação pra mim mesmo no futuro. Como já expliquei, meu home server é um mini-PC Intel NUC que eu só acesso com SSH. Então eu queria uma forma de monitorar o uso dos recursos do sistema sem ter que manualmente logar via SSH e abrir BTOP ou NTOP.</p>
<p>Fiquei com preguiça de instalar Grafana na época, mas resolvi fazer isso agora, e foi bem mais fácil do que eu esperava, por isso resolvi compartilhar como fiz.</p>
<p>A idéia é subir <a href="https://github.com/google/cadvisor"target="_blank" rel="noopener">cAdvisor</a> que faz monitoramento de recursos de containers e <a href="https://github.com/prometheus/prometheus"target="_blank" rel="noopener">Prometheus</a> que faz scraping de dados de recursos do sistema e consolidar os dados dos dois no <a href="https://grafana.com/"target="_blank" rel="noopener">Grafana</a> que é uma aplicação web que permite montar dashboards gráficos pra monitorar seus sistemas. Se fizer direito, no final vai ter uma tela assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910155108_screenshot-2025-09-10_15-50-49.png" alt="Grafana Dashboard"  loading="lazy" /></p>
<h3>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></h3><p>Pra subir no meu home server é muito simples, eu faço tudo com Docker Compose, entã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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># monitor-docker-compose.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">monitoring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">bridge</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">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">prometheus_data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">grafana_data</span><span class="p">:</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">node-exporter</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">quay.io/prometheus/node-exporter:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- --<span class="l">path.rootfs=/host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">pid</span><span class="p">:</span><span class="w"> </span><span class="l">host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring]</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">/:/host:ro,rslave</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;no-new-privileges:true&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">read_only</span><span class="p">:</span><span class="w"> </span><span class="kc">true</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">cadvisor</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">gcr.io/cadvisor/cadvisor:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring]</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 class="p">[</span><span class="s2">&#34;8080:8080&#34;</span><span class="nt">]   # optional</span><span class="p">:</span><span class="w"> </span><span class="l">expose UI locally</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">/:/rootfs:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run:/var/run:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/sys:/sys:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/lib/docker/:/var/lib/docker:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/disk/:/dev/disk:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;no-new-privileges:true&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">read_only</span><span class="p">:</span><span class="w"> </span><span class="kc">true</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">prometheus</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">prom/prometheus:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring]</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">prometheus_data:/prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./prometheus.yml:/etc/prometheus/prometheus.yml:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- --<span class="l">config.file=/etc/prometheus/prometheus.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- --<span class="l">storage.tsdb.path=/prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- --<span class="l">storage.tsdb.retention.time=30d</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- --<span class="l">web.enable-lifecycle</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 class="p">[</span><span class="s2">&#34;9090:9090&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;no-new-privileges:true&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">read_only</span><span class="p">:</span><span class="w"> </span><span class="kc">true</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">grafana</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">grafana/grafana:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">monitoring]</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 class="p">[</span><span class="l">prometheus]</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 class="p">[</span><span class="s2">&#34;3001:3000&#34;</span><span class="p">]</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">GF_SECURITY_ADMIN_USER</span><span class="p">:</span><span class="w"> </span><span class="l">admin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GF_SECURITY_ADMIN_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">change-me</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">GF_USERS_ALLOW_SIGN_UP</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;false&#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">grafana_data:/var/lib/grafana</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./provisioning/datasources:/etc/grafana/provisioning/datasources:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">security_opt</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;no-new-privileges:true&#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>Tem que prestar atenção nos mapeamentos de diretório local. Se colocar <code>/etc/grafana/prometheus.yml</code> por exemplo, tem que mudar esta linha:</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="nn">...</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">prometheus_data:/prometheus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/grafana/prometheus.yml:/etc/prometheus/prometheus.yml:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">...</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>Falando nele, eis o <code>prometheus.yml</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">global</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scrape_interval</span><span class="p">:</span><span class="w"> </span><span class="l">15s</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">scrape_configs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;prometheus&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">static_configs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>{<span class="w"> </span><span class="nt">targets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;prometheus:9090&#39;</span><span class="p">]</span><span class="w"> </span>}<span class="p">]</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">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;node&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">static_configs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>{<span class="w"> </span><span class="nt">targets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;node-exporter:9100&#39;</span><span class="p">]</span><span class="w"> </span>}<span class="p">]</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">job_name</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;cadvisor&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">static_configs</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>{<span class="w"> </span><span class="nt">targets</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;cadvisor:8080&#39;</span><span class="p">]</span><span class="w"> </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>Finalmente <code>/etc/grafana/provisioning/datasources/datasource.yaml</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: 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>Cuidado também com mapeamento de portas, o recomendado é fazer 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">ports</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;127.0.0.1:8080:8080&#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>Isso porque cAdvisor e Prometheus só precisam estar acessíveis pro Grafana, dentro do home server, mas não tem porque dar bind em <code>0.0.0.0</code> e estar acessível pra fora do servidor. Aí é mais uma escolha. Como eu estou numa LAN caseira, e não tem túneis apontando pra esses serviços, pra mim tanto faz.</p>
<p>Se por acaso, como eu, também já tiver algum serviço pendurado na porta 8080, o cAdvisor vai falhar. Nesse caso remapeie as portas e reconfigure o Prometheus, ou só tire a linha de porta. Não precisa ter o cAdvisor exposto pra fora.</p>
<p>Outro detalhe é que por padrão Grafana sobe na porta 3000 mas eu mudei pra 3001 porque já tinha outro serviço nessa porta. Então, quando subir, eu posso acessar via <code>http://192.168.0.200:3001</code>.</p>
<p>Como expliquei no <a href="https://akitaonrails.com/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/"target="_blank" rel="noopener">artigo de Cloudflared</a> eu roteio esse endereço pro Cloudflare pra ter um domínio bonito como <code>https://grafana.fabioakita.dev</code> pra acessar tanto localmente quanto remotamente, se quiser. Depois leiam esses outros artigos pra entender como.</p>
<p>E pronto, agora é só subir os containers:</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">docker compose -f monitor-docker-compose.yml up -d</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 acabou, super simples, eu deveria ter feito isso antes.</p>
<p>Como podem ver arquivo de Docker Compose, quando entrar no site pela primeira vez, o login vai ser com <code>admin</code> senha <code>change-me</code>, obviamente, a primeira coisa a fazer é trocar a senha:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910160042_screenshot-2025-09-10_16-00-31.png" alt="Admin Password"  loading="lazy" /></p>
<h3>Dashboards<span class="hx:absolute hx:-mt-20" id="dashboards"></span>
    <a href="#dashboards" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora praticamente acabou. Podemos checar se o Prometheus está funcionando e integrado. Basta ir no menu &ldquo;Connections&rdquo; e &ldquo;Data Sources&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910160151_screenshot-2025-09-10_16-01-40.png" alt="Data Sources"  loading="lazy" /></p>
<p>Grafana consegue consolidar dados de dezenas de fontes diferentes, Prometheus é só um deles. Em uma infra maior e mais complicada, essa lista estaria enorme.</p>
<p>Pra montar Dashboards, dá pra ir do zero e montar manualmente widget a widget, mas é mais fácil importar dashboards que já existem. Pra isso é só clicar na lupa lá no topo, na caixa de procura e escolher a opção &ldquo;Import dashboards&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910160509_screenshot-2025-09-10_16-04-59.png" alt="import dashboards menu"  loading="lazy" /></p>
<p>Nessa página dá pra ou importar um arquivo JSON, ou copiar e colocar o JSON ou pesquisar pelo ID que já existe cadastrado no site grafana.com:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910160603_screenshot-2025-09-10_16-05-46.png" alt="import by ID"  loading="lazy" /></p>
<p>De cara, eu aprendi que existem 3 IDs que são úteis pra mim:</p>
<ul>
<li><strong>1860 - Node Exporter Full</strong> - que é a foto de tela que está no começo deste artigo e tem várias métricas do sistema como CPU, memória, disco, rede, etc. É o mais útil pra mim.</li>
<li><strong>13484 - Docker/cAdvisor Exporter</strong> - que são as métricas de CPU, memória, mas dividido por cada container rodando no servidor.</li>
<li><strong>3662 - Prometheus 2.0 overview</strong> - que é um resumo geral de saúde</li>
</ul>
<p>Se gostar desses ou de outros dashboard, depois é só favoritar com um &ldquo;pin&rdquo; e pronto.</p>
<p>Por exemplo, este é o dashboard do cAdvisor:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20250910160927_screenshot-2025-09-10_16-09-13.png" alt="cAdvisor dashboard"  loading="lazy" /></p>
<p>Eu ainda não explorei outros dashboards. Se tiver recomendações de IDs pra dar import, não deixe de mandar nos comentários abaixo!</p>
<p>Com isso eu tenho uma visão rápida e bonita do uso de recursos do meu home server e consigo ver rapidamente se tem algo de estranho acontecendo. Não parece que esses serviços consumam muitos recursos então posso deixar rodando junto com os demais containers.</p>
]]></content:encoded><category>homelab</category><category>containers</category><category>devops</category></item><item><title>Protegendo seu Home Server com Cloudflare Zero Trust</title><link>https://akitaonrails.github.io/2025/09/10/protegendo-seu-home-server-com-cloudflare-zero-trust/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/10/protegendo-seu-home-server-com-cloudflare-zero-trust/</guid><pubDate>Wed, 10 Sep 2025 02:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.com/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; explico como eu registrei um domínio de verdade na Cloudflare e criei túneis seguros encriptados que me permitem acessar até de fora de casa, sem precisar expor portas no roteador ou qualquer coisa assim.&lt;/p&gt;
&lt;p&gt;Desta forma posso acessar &lt;code&gt;https://portainer.fabioakita.dev&lt;/code&gt; (domínio falso de exemplo) do meu celular, por 5G, e vai acessar o container Docker do portainer, que está na minha LAN em &lt;code&gt;http://192.168.0.200:9000&lt;/code&gt; e só era acessível em casa. Tudo via um túnel seguro.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="https://akitaonrails.com/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/"target="_blank" rel="noopener">post anterior</a> explico como eu registrei um domínio de verdade na Cloudflare e criei túneis seguros encriptados que me permitem acessar até de fora de casa, sem precisar expor portas no roteador ou qualquer coisa assim.</p>
<p>Desta forma posso acessar <code>https://portainer.fabioakita.dev</code> (domínio falso de exemplo) do meu celular, por 5G, e vai acessar o container Docker do portainer, que está na minha LAN em <code>http://192.168.0.200:9000</code> e só era acessível em casa. Tudo via um túnel seguro.</p>
<p>Notem que estou o tempo todo falando que estou usando domínio e IPs falsos de exemplo nestes posts, porque não quero expor o domínio de verdade.</p>
<p>Só de estar atrás de um túnel seguro da Cloudflare, significa que estou embaixo de várias camadas de segurança como proteção contra ataques DDoS, e vários outros tipos de ataques conhecidos. E embora eu tome o cuidado de configurar cada serviço com uma senha forte e aleatória, diferente pra cada serviço, o risco não é zero que alguém possa encontrar meus serviços e tente quebrar a senha, ou use alguma vulnerabilidade zero-day em um dos diversos serviços, como Prowlarr ou Sonarr da vida.</p>
<p>Então eu fico à mercê desses bugs ainda não encontrados que podem surgir do nada, em particular na tela de login de cada um deles, ou endpoints de APIs.</p>
<p>Pra evitar esse risco, existe outro produto da Cloudflare que vale a pena configurar: o <a href="https://www.cloudflare.com/zero-trust/products/access/"target="_blank" rel="noopener"><strong>Zero Trust</strong></a>. A idéia é simples: <strong>adicionar um novo login por cima de tudo</strong>, da própria Cloudflare, pra proteger cada um dos serviços. Dessa forma, alguém precisa quebrar a própria Cloudflare antes de conseguir tentar quebrar um dos meus serviços.</p>
<h3>1. Contratar o serviço<span class="hx:absolute hx:-mt-20" id="1-contratar-o-serviço"></span>
    <a href="#1-contratar-o-servi%c3%a7o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Essa é a parte fácil. Pra poucos serviços e poucos usuários (somente eu), existe um plano <strong>gratuito</strong>, só contratar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_00-35-57.png" alt="Free Plan"  loading="lazy" /></p>
<h3>2. Criar Credenciais OAuth no Google Cloud<span class="hx:absolute hx:-mt-20" id="2-criar-credenciais-oauth-no-google-cloud"></span>
    <a href="#2-criar-credenciais-oauth-no-google-cloud" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como eu pago Google Workspace, decidi usar login integrado com OAuth do Google, mas pode ser qualquer outro, você escolhe o que preferir. Pra isso preciso acessar meu <a href="https://console.cloud.google.com"target="_blank" rel="noopener">console do Google Cloud</a> e entrar na seção <strong>API &amp; Services</strong>. Aqui criamos credenciais pra <strong>OAuth client ID</strong> e configuramos assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_00-58-02.png" alt="Oauth client config"  loading="lazy" /></p>
<p>O nome tanto faz, é o que vai aparecer na tela de login, o importante são os campos de <strong>Javascript Origins</strong> que tem que ser neste formato:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>https://&lt;your-team-name&gt;.cloudflareaccess.com</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 o campo de <strong>redirect URIs</strong> que é pra onde a tela de login do Google vai redirecionar, e tem que devolver pro nosso Cloudflare:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>https://&lt;your-team-name&gt;.cloudflareaccess.com/cdn-cgi/access/callback</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>Feito isso, vai te dar a opção de baixar um arquivo <strong>JSON</strong> com os metadados e segredo. O importante é que no final ele vai te dar um <strong>OAuth Client ID</strong>, que é uma URL no formato <code>xxxxxx.apps.googleusercontent.com</code> e o mais importante, um <strong>Client Secret</strong>, que é bom guardar num BitWarden ou outro meio seguro.</p>
<h3>4. Criando Login Method no Zero Trust<span class="hx:absolute hx:-mt-20" id="4-criando-login-method-no-zero-trust"></span>
    <a href="#4-criando-login-method-no-zero-trust" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora voltamos pro console do Zero Trust na Cloudflare e clicamos em &ldquo;Settings&rdquo; no fim do menu, onde precisamos entrar em &ldquo;Authentication&rdquo; e criar um novo <strong>Login Method</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-04-35.png" alt="Login Method"  loading="lazy" /></p>
<p>Depois de clicar em &ldquo;Add new&rdquo; é só preencher com os dados que trouxemos do Google Cloud:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-07-17.png" alt="New Google Login Method"  loading="lazy" /></p>
<p>Novamente, coloque o nome que quiser e cole o OAuth Client ID como &ldquo;App ID&rdquo; e o Client Secret, e pronto, com isso temos Google OAuth integrado como método de login no nosso Zero Trust.</p>
<h3>5. Criando Policies<span class="hx:absolute hx:-mt-20" id="5-criando-policies"></span>
    <a href="#5-criando-policies" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora vem uma parte que eu não sei se fiz totalmente correto. Se não fiz, expliquem nos comentários abaixo!</p>
<p>Entendi que precisamos criar, pelo menos, duas <strong>Policies</strong> uma pra Service Auth e outra pro Email que quero permitir vindo do Google.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-09-06.png" alt="Policies"  loading="lazy" /></p>
<p>Pra continuar, só clicar em &ldquo;Add a Policy&rdquo; e editar a primeira policy:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-10-42.png" alt="Service Token"  loading="lazy" /></p>
<p>O nome, pode ser o qualquer coisa, o importante é que em &ldquo;Action&rdquo; escolha <strong>&ldquo;Service Auth&rdquo;</strong> e em &ldquo;Add Rules&rdquo; escolha <strong>Any Access Service Token</strong> (esta é a parte que eu acho que está errada, mas não achei outra opção melhor - não entendi bem como funciona Service Token).</p>
<p>Agora precisamos criar a segunda policy assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-20-47.png" alt="Email Policy"  loading="lazy" /></p>
<p>Desta vez o importante é term &ldquo;Action&rdquo; como &ldquo;Allow&rdquo; e adicionar a Rule &ldquo;Emails&rdquo; e escolher o email que você quer autenticar. No meu caso, somente eu.</p>
<h3>6. Criando nova Applications<span class="hx:absolute hx:-mt-20" id="6-criando-nova-applications"></span>
    <a href="#6-criando-nova-applications" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora podemos criar uma nova application no Zero Trust:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-22-51.png" alt="Applications"  loading="lazy" /></p>
<p>Clique em &ldquo;Add an Application&rdquo; e escolha type sendo <strong>&ldquo;Self-Hosted&rdquo;</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-26-20.png" alt="Application Basic Information"  loading="lazy" /></p>
<p>Entendi que cada application suporta no máximo 5 hostnames. Eu tenho bem mais que isso então vou ter que criar mais de uma application. A configuração em todas é a mesma. Em &ldquo;Basic Information&rdquo; só cadastrei os subdomínios de cada container que quero proteger.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-28-37.png" alt="Policies"  loading="lazy" /></p>
<p>Indo pra aba <strong>&ldquo;Policies&rdquo;</strong> basta clicar em &ldquo;Select existing policies&rdquo; e adicionar as duas policies que acabamos de criar. Só isso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-29-43.png" alt="Login Method"  loading="lazy" /></p>
<p>Finalmente, em <strong>&ldquo;Login methods&rdquo;</strong> só escolher a do Google que acabamos de configurar também. E sim, estas são as telas de edição de uma application que eu já tinha criado. A tela de nova application tem tudo isso numa tela só, mas vocês entenderam.</p>
<p>E eu entendi que é só isso. Teoricamente já está tudo funcionando!</p>
<h3>7. Testando o novo Login<span class="hx:absolute hx:-mt-20" id="7-testando-o-novo-login"></span>
    <a href="#7-testando-o-novo-login" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se nada der errado, basta acessar um dos hostnames que cadastramos na application do Zero Trust, por exemplo <code>https://immich.fabioakita.dev</code> e temos que receber esta tela primeiro:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-33-41.png" alt="Zero Trust Login"  loading="lazy" /></p>
<p>Se isso aparecer, sucesso! O Zero Trust impediu de acessar minha aplicação sem antes eu me logar nele.</p>
<p>Depois pode configurar o texto pra ficar como quiser, mas como é pra mim mesmo, tanto faz o texto de &ldquo;bem vindo&rdquo;, na verdade, <strong>ninguém é bem vindo aqui, somente EU!</strong></p>
<p>Clicando em &ldquo;Google&rdquo; vai abrir o Pop-up que todo mundo conhece de login do Google e só depois disso que eu consigo entrar no login da minha aplicação por baixo. E isso vale pra todos os hostnames que adicionei nas applications do Zero Trust.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/userinter.png" alt="Google Sign In"  loading="lazy" /></p>
<p>Uma vez que fizer login no Google com o usuário correto, a sessão vai ficar válida por 24 horas (que é o que configuramos nas policies e application). Nesse período não precisa mais fazer login no Zero Trust em nenhuma das outras aplicações do meu home server. A sessão é a mesma pra todas as applications.</p>
<h3>8. Deslogando Sessões<span class="hx:absolute hx:-mt-20" id="8-deslogando-sessões"></span>
    <a href="#8-deslogando-sess%c3%b5es" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Depois que fizer o primeiro login com seu usuário, dá pra controlar essa sessão indo no menu &ldquo;My team&rdquo; e &ldquo;Users&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-38-38.png" alt="Users"  loading="lazy" /></p>
<p>Clicando no usuário, temos várias informações e opções, em particular veja <strong>&ldquo;Revoke Sessions&rdquo;</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-40-31.png" alt="Revoke"  loading="lazy" /></p>
<p>Desta forma podemos testar o login sem precisar esperar 24 horas até o token de autenticação expirar com o tempo. É uma forma de forçar o logout dos seus usuários. No plano gratuito do Zero Trust, entendi que dá pra ter até 10 usuários na sua organização.</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>Eu nunca tinha usado Zero Trust, então pode ser que tenha cometido algum erro no procedimento, então leiam a documentação também. Em princípio, parece que está funcionando. Já criei mais applications e cadastrei o resto dos meus hostnames,</p>
<p>Só de usar CloudflareD pra túneis encriptados, todo tráfego que chega no meu home server passa por uma série de proteções da Cloudflare que incluem: DDoS protection, URL normalization, Origin Rules, IP Address Rules, WAF (Web Application Firewall), Bots Mitigation, Rate Limiting, Cache, Compression e mais.</p>
<p>Essas camadas protegem contra os principais tipos de ataques conhecidos e eles estão sempre adicionando novas regras a cada nova vulnerabilidade que fica conhecida. Mesmo que você, como desenvolvedor, não saiba como se proteger de tudo na sua aplicação, existe a chance da Cloudflare te proteger antes. Por isso recomendo deixar seus domínios sempre sob controle da Cloudflare.</p>
<p>Em cima disso tudo, ainda adicionamos <strong>Zero Trust</strong>, que garante que só depois de um login com minha conta do Google é que ele vai começar a permitir acesso pra baixo. E no final disso ainda tem a última barreira que são os logins de cada aplicação independente rodando no meu home server.</p>
<p>Além disso, no dashboard da Cloudflare tem monitoramento do tráfego, analytics e muito mais. Eu configurei só o básico, mas se quiser ir mais a fundo, tem várias policies e regras extras que podem ser adicionadas.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-10_01-53-58.png" alt="Analytics"  loading="lazy" /></p>
<p>Pra alguém conseguir atravessar tudo isso e conseguir invadir meu home server e minha LAN, realmente precisa ser alguém muito dedicado e muito inteligente - não acredito que exista alguém assim perdendo tanto tempo pra acessar um servidor de videos 🤣 mas podem tentar.</p>
]]></content:encoded><category>homelab</category><category>seguranca</category><category>cloud</category></item><item><title>Acessando meu Home Server com Domínio de Verdade</title><link>https://akitaonrails.github.io/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/09/09/acessando-meu-home-server-com-dominio-de-verdade/</guid><pubDate>Tue, 09 Sep 2025 17:00:00 -0300</pubDate><description>&lt;p&gt;Agora uma dica avançada que complementa meu artigo sobre meu &lt;a href="https://akitaonrails.com/2024/04/03/meu-netflix-pessoal-com-docker-compose/"target="_blank" rel="noopener"&gt;Home Server&lt;/a&gt;. Em resumo, além do meu desktop PC, que agora roda Omarchy, eu tenho um mini-PC (um simples Intel NUC, não precisa ser muito potente), exclusivamente pra rodar containers Docker.&lt;/p&gt;
&lt;p&gt;Eu tenho um bom Synology DS1821+ que também tem suporte pra rodar Docker, mas eu prefiro deixar o NAS somente pra funções de NAS e não arriscar comprometer meus dados com outras coisas rodando nele. Não é um &amp;ldquo;problema&amp;rdquo; ter um servidor só que faz tudo, mas já que eu posso, prefiro separar as coisas.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Agora uma dica avançada que complementa meu artigo sobre meu <a href="https://akitaonrails.com/2024/04/03/meu-netflix-pessoal-com-docker-compose/"target="_blank" rel="noopener">Home Server</a>. Em resumo, além do meu desktop PC, que agora roda Omarchy, eu tenho um mini-PC (um simples Intel NUC, não precisa ser muito potente), exclusivamente pra rodar containers Docker.</p>
<p>Eu tenho um bom Synology DS1821+ que também tem suporte pra rodar Docker, mas eu prefiro deixar o NAS somente pra funções de NAS e não arriscar comprometer meus dados com outras coisas rodando nele. Não é um &ldquo;problema&rdquo; ter um servidor só que faz tudo, mas já que eu posso, prefiro separar as coisas.</p>
<p>Esse Intel NUC roda um Ubuntu Server LTS headless (sem desktop manager), que eu uso só acessando via SSH (como expliquei no <a href="https://akitaonrails.com/2025/09/09/omarchy-2-0-entendendo-ssh-e-yubikeys/"target="_blank" rel="noopener">post anterior sobre SSH</a>). Nele rodo coisas como Portainer, Plex, Sonarr, Bazarr, qBitTorrent e muito mais.</p>
<p>Da forma como está eu só adicionei uma entrada no <code>/etc/hosts</code> do meu desktop assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>192.168.0.100 synology.lan
192.168.0.200 plex.lan</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>A partir de agora todos os meus domínios e endereços IPs vão ser falsos, obviamente.</strong></p>
<p>Enfim, dessa forma eu posso abrir meu navegador e entrar em <code>http://plex.lan:32400</code> pra acessar meu Plex, por exemplo. Mas isso tem três desvantagens:</p>
<ol>
<li>Só no meu desktop consigo usar esses hostnames, do meu notebook precisaria editar o arquivo hosts também. Além disso preciso saber as portas pra cada container.</li>
<li>Não tenho SSL pra nada, então toda autenticação vai em texto aberto. Não é um problema muito grande porque o tráfego é somente interno na minha LAN local. Não tenho acessado de fora de casa.</li>
<li>Se por acaso eu quiser acessar de fora, nada disso funciona.</li>
</ol>
<p>Existem dezenas de formas de resolver esse problema, soluções que envolvem WireGuard, ou soluções comerciais como TailScale ou coisas assim. Mas tem uma solução razoavelmente simples que resolve todos os problemas de uma só vez: <strong>Cloudflare Tunnels</strong>.</p>
<p>A idéia é criar túneis seguros via Cloudflare, pra ter acesso aos meus serviços na minha LAN interna <strong>SEM</strong> precisar expor portas pra fora do meu roteador ou coisas assim. Minha rede permanece isolada e inalterada.</p>
<h3>1. Registrando Domínio<span class="hx:absolute hx:-mt-20" id="1-registrando-domínio"></span>
    <a href="#1-registrando-dom%c3%adnio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A primeira recomendação é registrar um domínio de verdade. Fazendo isso é possível ter certificados SSL de verdade que a própria Cloudflare gerencia e renova automaticamente.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_16-52-14.png" alt="Cloudflare Domain"  loading="lazy" /></p>
<p>Eu tenho alguns domínios registrados e não vou revelar qual uso pro meu Home Server. Vou usar esse vazio <strong>fabioakita.dev</strong> como exemplo só pra este post.</p>
<p>Domínios são baratos, faixa de USD 12 por ano, dependendo do nome que escolher. Escolha um curto e fácil de digitar.</p>
<h3>2. Instalando Cloudflared<span class="hx:absolute hx:-mt-20" id="2-instalando-cloudflared"></span>
    <a href="#2-instalando-cloudflared" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com o domínio criado, agora precisamos instalar o serviço <a href="https://pkg.cloudflare.com/index.html"target="_blank" rel="noopener"><strong>cloudflared</strong></a> no Home Server. Ele é quem vai se conectar aos servidores da Cloudflare a abrir o túnel pra dentro do meu servidor local. Clique no link pra instruções de como instalar, mas num Ubuntu Server 22.04 é 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"># Add cloudflare gpg key</span>
</span></span><span class="line"><span class="cl">sudo mkdir -p --mode<span class="o">=</span><span class="m">0755</span> /usr/share/keyrings
</span></span><span class="line"><span class="cl">curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg <span class="p">|</span> sudo tee /usr/share/keyrings/cloudflare-main.gpg &gt;/dev/null
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Add this repo to your apt repositories</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s1">&#39;deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared jammy main&#39;</span> <span class="p">|</span> sudo tee /etc/apt/sources.list.d/cloudflared.list
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># install cloudflared</span>
</span></span><span class="line"><span class="cl">sudo apt-get update <span class="o">&amp;&amp;</span> sudo apt-get install cloudflared</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 precisamos fazer login:</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"># login</span>
</span></span><span class="line"><span class="cl">sudo cloudflared tunnel login
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># adicione tunel homelab</span>
</span></span><span class="line"><span class="cl">sudo cloudflared tunnel create homelab
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># crie diretório pra configurações</span>
</span></span><span class="line"><span class="cl">sudo mkdir -p /etc/cloudflared</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 as mesmas credenciais que usa pra logar no website da Cloudflare. Se tudo der certo, no final ele vai criar um arquivo em <code>/root/.cloudflared/xxxxx-yyyy-zzzz...json</code>, são suas credenciais, assim como uma chave privada SSH, não jogue num pendrive ou num drive de cloud da vida. Guarde muito bem. Vamos mover:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mv /root/.cloudflared/*.json /etc/cloudflared
sudo chmod 600 /etc/cloudflared/*.json</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, precisamos criar um arquivo de configurações <code>/etc/cloudflared/config.yml</code> com todos os hostnames que queremos pra cada um dos containers, apontando pras portas corretas. Vou dar um trecho de exemplo da minha config, modificado com IPs falsos e o domínio falso:</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="c"># /etc/cloudflared/config.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tunnel</span><span class="p">:</span><span class="w"> </span><span class="l">homelab</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">credentials-file</span><span class="p">:</span><span class="w"> </span><span class="l">/etc/cloudflared/xxxx-yyyy-....json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ingress</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">plex.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.254:32400</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">plex.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">immich.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:2283</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">immich.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">overseer.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:5055</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">overseer.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">syncthing.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:8384</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">qbittorrent.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">radarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:7878</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">radarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">sonarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:8989</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">sonarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">bazarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:6767</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">bazarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">prowlarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:9696</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">prowlarr.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">portainer.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:9000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">portainer.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">hostname</span><span class="p">:</span><span class="w"> </span><span class="l">grafana.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http://192.168.0.200:3001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">originRequest</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">httpHostHeader</span><span class="p">:</span><span class="w"> </span><span class="l">grafana.fabioakita.dev</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">http_status:404</span><span class="w"> </span><span class="c"># this has to be the LAST ONE</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>Como podem ver, é um arquivo extremamente simples. Acho que qualquer um consegue entender o que isso faz. Modifique pras suas próprias necessidades.</p>
<p>Depois de salvar o arquivo e voltar pra linha de comando, precisa manualmente rodar este comando pra cada hostname:</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 cloudflared tunnel route dns homelab plex.fabioakita.dev</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 isso é muito trabalhoso, com esse tanto de hostname, achei mais fácil criar um script <code>/etc/cloudflared/add.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"><span class="c1"># run only once</span>
</span></span><span class="line"><span class="cl"><span class="nv">TUN</span><span class="o">=</span>homelab
</span></span><span class="line"><span class="cl"><span class="nv">CONF</span><span class="o">=</span>/etc/cloudflared/config.yml
</span></span><span class="line"><span class="cl">grep -E <span class="s1">&#39;^[[:space:]]*-[[:space:]]*hostname:&#39;</span> <span class="s2">&#34;</span><span class="nv">$CONF</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="p">|</span> awk <span class="s1">&#39;{print $3}&#39;</span> <span class="p">|</span> tr -d <span class="s1">&#39;&#34;&#39;</span> <span class="p">|</span> tr -d <span class="s2">&#34;&#39;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> -r h<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;adding </span><span class="nv">$h</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    sudo cloudflared tunnel route dns <span class="s2">&#34;</span><span class="nv">$TUN</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$h</span><span class="s2">&#34;</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>Agora é só rodar: <code>sudo sh /etc/cloudflared/add.sh</code>, <strong>só uma vez!</strong> Então garanta que seu arquivo de config está correto antes!</p>
<p>Finalmente, podemos configurar o serviço pra reiniciar sozinho em todo boot:</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 cloudflared --config /etc/cloudflared/config.yml service install
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now cloudflared</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>3. Acessando seus serviços via túnel<span class="hx:absolute hx:-mt-20" id="3-acessando-seus-serviços-via-túnel"></span>
    <a href="#3-acessando-seus-servi%c3%a7os-via-t%c3%banel" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se nada deu errado, já acabou! Já pode ir direto pro seu navegador e testar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_17-06-40.png" alt="Immich"  loading="lazy" /></p>
<p>Olha só que beleza. Note na barra de URL que digitei <code>https://immich.fabioakita.dev</code> e acessou normalmente. Aliás, se não conhecia o <a href="https://immich.app/"target="_blank" rel="noopener">Immich</a> funciona idêntico a um Google Photos, só que sob seu total controle! Tem app pra smartphone e sincroniza bonitinho.</p>
<p>Mais importante, note que está com <strong>HTTPS</strong> que foi provido pelo Cloudflare pro meu domínio inteiro. Quando fizer login, vai ser via <strong>túnel encriptado</strong>. E mais do que isso: mesmo se eu estiver via 5G, fora de casa, no meu celular, vou poder acessar minhas fotos usando esse endereço!</p>
<p>Do meu celular vai abrir um túnel seguro pra Cloudflare, e aquele serviço <code>cloudflared</code> no meu home server está também com um túnel seguro aberto pra Cloudflare, daí ele faz uma ponte entre os dois túneis e pronto! Tenho acesso remoto sem abrir meu servidor direto pra internet! O melhor dos dois mundos!</p>
<p>Não se esqueça de configurar todos os seus serviços com senhas fortes (eu gero elas via Bitwarden, mas qualquer um server 1Password, o que for):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/screenshot-2025-09-09_17-10-15.png" alt="Radarr General Auth Settings"  loading="lazy" /></p>
<h3>4. Bridge pra vlans<span class="hx:absolute hx:-mt-20" id="4-bridge-pra-vlans"></span>
    <a href="#4-bridge-pra-vlans" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu não lembro exatamente o motivo, mas só meu container de Plex deu mais trabalho. Isso porque ele usa uma VLAN diferente das demais. Novamente, não lembro porque fiz isso na época, mas está 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">plex</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c">#image: plexinc/pms-docker:latest</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">linuxserver/plex:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1026</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1026</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VA_DRIVER=IHD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PLEX_HW_TRANS_MAX=16</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PLEX_MEDIA_SERVER_OPTS=--network-host</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">/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/transcode:/transcode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/Videos:/media</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/dri:/dev/dri</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/bus/usb:/dev/bus/usb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="l">curl --connect-timeout 15 --silent --show-error --fail http://localhost:32400/identity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">1m00s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">15s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">start_period</span><span class="p">:</span><span class="w"> </span><span class="l">1m00s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">mem_limit</span><span class="p">:</span><span class="w"> </span><span class="l">4g</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cpus</span><span class="p">:</span><span class="w"> </span><span class="m">5.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">plex_macvlan</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ipv4_address</span><span class="p">:</span><span class="w"> </span><span class="m">192.168.0.254</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">mac_address</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;xx:xx:xx:xx:xx:xx&#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">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">plex_macvlan</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">plex_macvlan</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 causa disso o <code>cloudflared</code> não consegue rotear pra esse container. A forma de consertar isso é criando uma ponte dessa vlan pra minha ethernet.</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 ip link add macvlan0 link enp3s0 <span class="nb">type</span> macvlan mode bridge
</span></span><span class="line"><span class="cl">sudo ip addr add 192.168.0.200/24 dev macvlan0   <span class="c1"># free IP in your LAN</span>
</span></span><span class="line"><span class="cl">sudo ip link <span class="nb">set</span> macvlan0 up</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>Obviamente, trocar <code>enp3s0</code> pelo nome correto da sua ethernet. Cheque com <code>ip addr</code> ou <code>ip link</code> por exemplo. Só de rodar os comandos acima, se eu for no navegador e digitar <code>https://plex.fabioakita.dev</code> já deve conseguir acessar. Pra tornar isso permanente, precisamos do 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"># /etc/systemd/network/80-macvlan0.netdev</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">NetDev</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Name</span><span class="p">=</span><span class="nx">macvlan0</span>
</span></span><span class="line"><span class="cl"><span class="nx">Kind</span><span class="p">=</span><span class="nx">macvlan</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">MACVLAN</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Mode</span><span class="p">=</span><span class="nx">bridge</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>
<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"># /etc/systemd/network/80-macvlan0.network</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Match</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Name</span><span class="p">=</span><span class="nx">macvlan0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">Network</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">Address</span><span class="p">=</span><span class="mf">192.168</span><span class="p">.</span><span class="mf">0.200</span><span class="err">/</span><span class="mi">24</span>
</span></span><span class="line"><span class="cl"><span class="nx">Gateway</span><span class="p">=</span><span class="mf">192.168</span><span class="p">.</span><span class="mf">0.1</span>
</span></span><span class="line"><span class="cl"><span class="nx">DNS</span><span class="p">=</span><span class="mf">192.168</span><span class="p">.</span><span class="mf">0.1</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 agora, só reiniciar os serviços:</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 restart systemd-networkd
</span></span><span class="line"><span class="cl">ip addr show macvlan0</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 &ldquo;correto&rdquo; seria meu Plex estar configurado com <code>network_mode: host</code>mas, de novo, não lembro porque não estava e não estava a fim de desconfigurar tudo pra descobrir. Caso alguém tenha configurações similares, é assim que bypassa.</p>
<p>Falando em Plex, eu já tinha ouvido falar que tem que tomar cuidado com streaming via cloudflared, não sei se tem limites ou custos extras. Achei <a href="https://mythofechelon.co.uk/blog/2024/1/7/how-to-set-up-free-secure-high-quality-remote-access-for-plex"target="_blank" rel="noopener">este artigo</a> que fala mais sobre isso mas não parei pra ver a fundo. A única coisa que fiz extra, no dashboard da Cloudflare, foi adicionar uma regra pra não fazer <strong>CACHING</strong> do subdomínio <code>plex</code>, pra evitar custos adicionais.</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>Como podem ver, é extremamente fácil ter um home server, isolar tudo com Docker e depois expor seus containers via túnel seguro pra acessar remotamente. Nada disso significa segurança instantânea. Garanta que seus containers sempre estejam atualizados usando serviços como <a href="https://github.com/containrrr/watchtower"target="_blank" rel="noopener">Watchtower</a>, garanta que cada serviço tenha senhas fortes aleatórias e únicas - não use a mesma senha pra tudo. Garanta que seu firewall esteja bem configurado e ativo, mesmo numa LAN.</p>
<p>Muitos routers de Wi-Fi modernos permitem criar uma VLAN separada pra Wi-Fi pra visitas. Assim eles podem usar seu Wi-Fi mas não enxergam seus computadores, que vão estar logicamente isoladas em outra VLAN. Cheque a documentação do seu roteador. Eu uso uma <strong>TP-Link BE1900 Tri-Band Wi-Fi 7</strong>, mas pra maioria das pessoas ela é cara demais e excessiva demais. Pesquise bem. E não adianta ficar me perguntando de outros modelos: eu não testei todos pra saber as diferenças.</p>
<p>Se você viu nomes como Radarr, Sonarr e não sabe o que isso significa, leia o <a href="https://wiki.servarr.com/"target="_blank" rel="noopener">Wiki do Servarr</a> que consolida todas as documentações desses serviços num único lugar. É a infraestrutura que permite você ter sua própria &ldquo;Netflix Particular&rdquo;.</p>
<p>Também não perguntem porque eu uso Plex e não Jellyfin. É escolha pessoal, escolha o que é melhor pra você. Eu gosto de Plex, já usei Jellyfin, e não pretendo mudar.</p>
<p>Eu odeio a idéia de pagar assinaturas pra tudo e não ter propriedade de nada. Acho dinheiro jogado fora. Eu prefiro <strong>TER</strong> as minhas próprias coisas sob <strong>MEU CONTROLE</strong>. É mais caro, é mais trabalhoso. Conveniência demais significa que você não tem controle de nada e sempre depende dos outros.</p>
<p>Eu odeio depender dos outros.</p>
]]></content:encoded><category>homelab</category><category>redes</category><category>containers</category></item><item><title>Acessando seu NAS usando iSCSI em vez de SMB</title><link>https://akitaonrails.github.io/2025/04/24/acessando-seu-nas-usando-iscsi-em-vez-de-smb/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/24/acessando-seu-nas-usando-iscsi-em-vez-de-smb/</guid><pubDate>Thu, 24 Apr 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;Se você tem um NAS ou qualquer tipo de servidor de arquivos na rede, provavelmente o padrão é criar uma pasta compartilhada e depois montar no seu sistema operacional, seja Windows, Mac ou Linux usando o velho, e cansado, protocolo SMB (Samba).&lt;/p&gt;
&lt;p&gt;Em Linux, isso é um enorme pé no saco, porque o protocolo SMB (feito pra Windows), não trás direito conceitos como ownership (chown) ou permissões (chmod). Ele não entende coisas como permissão de execução (tudo é executável). Pra coisas simples como diretório de Downloads, Videos, Fotos, meio que não importa.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Se você tem um NAS ou qualquer tipo de servidor de arquivos na rede, provavelmente o padrão é criar uma pasta compartilhada e depois montar no seu sistema operacional, seja Windows, Mac ou Linux usando o velho, e cansado, protocolo SMB (Samba).</p>
<p>Em Linux, isso é um enorme pé no saco, porque o protocolo SMB (feito pra Windows), não trás direito conceitos como ownership (chown) ou permissões (chmod). Ele não entende coisas como permissão de execução (tudo é executável). Pra coisas simples como diretório de Downloads, Videos, Fotos, meio que não importa.</p>
<p>Mas digamos que queira editar código fonte em projetos com Git. Vai ser um pesadelo, porque o SMB sobrescreve as permissões, independente do que está por baixo, e toda vez vai conflitar com o Git, que vai achar que arquivos foram modificados (mudar permissões é uma mudança que ele guarda), e vai ficar mandando você fazer commit disso, e isso vai sujar todo seu repositório (nunca use projetos Git em SMB na rede).</p>
<p>Em vez disso, eu estou em Linux, meu NAS é Linux. O certo é usar um protocolo de Linux. Esse protocolo é NFS e eu mostrei como configurei isso no <a href="https://www.akitaonrails.com/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux"target="_blank" rel="noopener">outro post</a> da semana passada.</p>
<p>Enfim, estou usando NFS e com muito menos dor de cabeça de permissões em arquivos. Mas aí tive outro problema: se viu meus últimos posts, viu que fiquei experimentando MUITO com Docker. Gerando imagens novas como se não houvesse amanhã. Rapidamente, meu NVME local de 2 TB encheu e começou a reclamar de falta de espaço. Então pensei, <em>&ldquo;Ah, é só mover o diretório de armazenamento do Docker, o /var/lib/docker, pra um diretório montado com NFS no meu NAS&rdquo;</em></p>
<p>Pra fazer isso é muito fácil:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /mnt/nfs/docker
sudo chown root:docker /mnt/nfs/docker
sudo chmod 771       /mnt/nfs/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>Isso é pra criar um novo diretório dentro do meu NAS e aplicar as permissões corretas.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl stop docker
sudo mv /var/lib/docker /mnt/nfs/docker
# sudo rm -Rf /var/lib/docker (isso vai liberar tudo pra mim)
sudo ln -s /mnt/nfs/docker /var/lib/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>Isso vai liberar todo o espaço ocupado. Não preciso me preocupar com as imagens porque eu posso recriar tudo dos meus Dockerfiles. E não preciso me preocupar com volumes porque não tinha nada importante que não seja montado por fora, em diretórios de verdade. Regra: nunca guarde coisas importantes em volumes de Docker. É uma má prática!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># editar /etc/docker/daemon.json
{
  &#34;data-root&#34;: &#34;/mnt/nfs/docker&#34;
}

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>Pronto, isso muda a configuração de <code>/var/lib/docker</code> pro novo <code>/mnt/nfs/docker</code> e depois de reiniciar o serviço, ele vai passar a gravar tudo lá. Então acabou o post né?</p>
<p>É até aqui que a maioria vai. Mas se você trabalhar 2 minutos nessa configuração, vai sentir algo MUITO errado.</p>
<p>Eu tentei fazer build de uma nova imagem. E estava demorando ABSURDO. Acontece que NFS é sim, mais rápido que SMB, mas ambos foram feitos pra servidores de arquivos, e não pra sistemas operacionais. Faz muita diferença.</p>
<p>Esses protocolos são <strong>FILE-BASED</strong>, toda operação é baseada em arquivos. E isso é extremamente ineficiente.</p>
<p>Se assistiu meus videos sobre sistemas de arquivos, já deveria saber que eles são <strong>BLOCK-LEVEL</strong>. No nível do sistema de arquivos, se trabalha com blocos de bytes, de tamanho fixo, organizadas em alguma variação de árvores B-TREE (depende do sistema de arquivos). As operações são em nível de bloco. Arquivos são abstrações pra coleções de blocos.</p>
<p>Quando se faz um <code>chmod -R</code> que é mudança de permissão recursiva, dentro do build do Dockerfile, por exemplo, mesmo uma árvore vazia vai causar recursão em cada entrada lá dentro, seja arquivos escondidos, ACLs, etc. E ele precisa pesquisar todas as permissões de tudo lá dentro. Isso custa tempo de rede (porque NFS/SMB são protocolos de rede).</p>
<p>Isso causa latência. Cada permissão de cada arquivo é uma chamada remota individual, uma RPC call. Mesmo na rede mais rápida, como a minha de 10Gbps, mesmo que cada RPC seja de menos de 0.1 ms, rapidamente vai dar gargalo. E é isso mesmo que começou a acontecer.</p>
<p>Eu usei <code>sudo nfsiostat 1 10</code> pra checar. Tava dando isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>write: ops/s=2417  kB/s≈295 MiB/s  avg exe=106 ms  avg queue=102 ms</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>Média de 106 ms pra cada RPC. Isso é uma eternidade. Tempo médio na fila de espera de 102 ms. Mesmo que que eu faça só 100 chamadas RPC, vai custar mais de 10 segundos só de latência. E num build de sistema operacional, instalando pacotes com milhares de arquivos, isso vai custar literamente uma eternidade. Não é viável usar Docker build em NFS/SMB.</p>
<h3>iSCSI<span class="hx:absolute hx:-mt-20" id="iscsi"></span>
    <a href="#iscsi" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviamente eu não sou o primeiro a esbarrar nesse problema e fazer a besteira de usar um protocolo file-based pra serviços de alto volume de alterações de arquivos como cache de Docker.</p>
<p>O certo é criar um drive virtual iSCSI no meu NAS. Se nunca ouviu falar, SCSI é um protocolo avançado pra drives. Em PCs baratos dos anos 90 e 2000 se usava IDE/ATA, que é um protocolo mais simples e mais barato. Mas os primeiros Mac e workstations UNIX dos anos 80 e 90 sempre usaram SCSI, que era muito superior.</p>
<p>Ele era <strong>assíncrono</strong>, com controle de fila. Dava pra mandar dezenas de comandos de uma só vez e o protocolo era inteligente de reordenar esses comandos pra ter mais eficiência (por exemplo, ignorar leitura de um arquivo se antes veio um comando de deletar). IDE era bloqueante, só um comando por vez. Um único BUS de SCSI conseguia ter até 16 dispositivos. Parallel ATA era 2 por cabo. Ridículo. SCSI tinha sistemas avançados de recuperação de erros e relatórios. Tinha recursos &ldquo;enterprise&rdquo; pra hot-swap, gerenciamento de energia, comportamentos de timeout, recuperação, clustering, etc. E além de tudo era agnóstico ao meio de transporte, podia ser fibra ótica, SAS ou até mesmo iSCSI que é SCSI sobre Ethernet, que é o que vamos usar.</p>
<p>IDE/ATA não tinha nada disso. Era o protocolo de discos de PC pobre. Workstation de verdade, SUN, IRIX, Silicon Graphics, Macs, usavam SCSI.</p>
<p>Mais importante iSCSI é um protocolo de blocos, e não de arquivos. Enfim, isso varia de gerenciador de NAS, mas no caso do Synology DSM, que é o que eu uso, ele tem um aplicativo chamado SAN Manager, que permite criar um drive iSCSI:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fraxoryhuszmp4cmr0ldc8h7hiip" alt="iSCSI create"  loading="lazy" /></p>
<p>Só seguir o wizard, dar nome, escolher o tamanho, habilitar CHAP se precisar de segurança extra (eu não preciso, estou numa rede local controlada). Isso vai criar o drive iSCSI (pense como se fosse um drive USB remoto) e um LUN (Logical Unit Number). Um LUN é simplesmente um de muitos potenciais &ldquo;discos virtuais&rdquo; que um target pode apresentar. LUN é como um slot numerado no array de storage de um target. Target é um servidor de discos. Num SAN você pode ter vários discos virtuais e a forma de organizar isso é com LUNs. No nosso caso não interessa muito saber detalhes de LUN.</p>
<p>Demora um pouco, mas ao terminar é só voltar pro meu Manjaro Linux e fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S open-iscsi
sudo systemctl enable --now iscsi </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 sobe o serviço pra iniciar automaticamente em todo boot. Agora pra descobrir meu drive na rede:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code></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>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m discovery -t sendtargets -p 192.168.0.xx
192.168.0.xx:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[2804:1b3:....:fe18:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[fe80::9209:....:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...</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>Todo drive virtual tem um <strong>IQN</strong> que é tipo uma &ldquo;URL&rdquo; pro drive. Agora fazemos login:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node --login
Login to [iface: default, target: iqn.2000-01.com.synology:TERACHAD.docker.7af6e9116c1, portal: 192.168.0.21,3260] successful.</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>Como não habilitei CHAP nem pede senha nem nada (em empresa o certo é estar habilitado, lógico). E pronto. Ao fazer isso eu ouço no GNOME o barulhinho igual de quando você conecta um pen-drive no PC e ele já automaticamente aparece como um disco. Se fizer <code>lsblk</code>, vai aparecer como um drive normal <code>/dev/sdX</code> da vida. No aplicativo Disks do GNOME (ou no Disk Management do Windows) vai realmente aparecer como se fosse qualquer outro hard-drive ou SSD da vida. Ele não se comporta como uma pasta compartilhada e sim como um drive de verdade. Incluve precisa formatar, igual um pen drive novo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/nvgrl3bov311vl4h251dcj1wvchz" alt="Disks"  loading="lazy" /></p>
<p>A formatação em si, demora bastante, porque no processo ele precisa checar e mapear bloco a bloco. Como é pela rede, é mais lento do que num drive de verdade, claro. Mas é só uma vez. Mesmo na minha rede de 10Gbps ele vai formatar numa velocidade de uns 120 MB/s, num drive de 2TB, isso vai dar na faixa de 2 horas pra um fast format. Eu esqueci que dava pra formatar com &ldquo;lazy&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkfs.ext4 -v -E lazy_itable_init=1,lazy_journal_init=1 /dev/sdX</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>Fica pra próxima, agora é esperar 2 horas. Pra garantir que monte automaticamente no boot, precisa checar o <code>node.startup</code> dele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node -o show | grep -E &#39;Target:|node.startup&#39;

node.startup = manual
node.startup = manual
node.startup = manual</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>Está como &ldquo;manual&rdquo;, pra trocar pra &ldquo;automatic&rdquo; precisa fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node \
  -T iqn.2000-01.com.synology:TERACHAD.docker.7af6... \
  -o update -n node.startup -v automatic</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 o daemon iSCSI de pé e depois do login, além de <code>/dev/sda</code> se fizer <code>ls /dev/disk/by-path</code> vamos encontrar algo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6exxxxx-lun-1 -&gt; ../../sdX</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>Como estou usando o serviço AUTOFS e ele &ldquo;rouba&rdquo; meu diretório <code>/mnt</code>, vou montar o drive iSCSI via fstab mesmo em <code>/media/docker</code> adicionando esta linha no final de <code>/etc/fstab</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
/dev/disk/by-path/ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6e9xxxx-lun-1  /media/docker  ext4      _netdev,nofail,x-systemd.automount,x-systemd.requires=iscsid.service,noatime,nodiratime,commit=60  0 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>Depois é só recarregar e montar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl daemon-reload
sudo mount -a</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 é isso. A partir de agora eu tenho um &ldquo;drive virtual&rdquo; no meu NAS remoto. Agora, vamos mudar o Docker pra começar a usar ele. Posso reconfigurar meu Docker pra fazer cache de imagens e volumes e tudo mais direto nesse drive. Só editar o arquivo <code>/etc/docker/daemon.json</code> com o mount point:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;data-root&#34;: &#34;/media/docker/docker&#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>Não esquecer de criar o diretório no novo drive com as permissões certas (e ele aceita permissões, porque é um drive formatado com ext4 como qualquer outro):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /media/docker/docker
sudo chown root:docker /media/docker/docker
sudo chmod 771 /media/docker/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>Por fim, é só reiniciar o serviço do Docker, apagar o <code>/usr/lib/docker</code> pra liberar espaço no meu NVME principal e pronto. Deve ser MUITO mais rápido que NFS, não tão rápido quanto um disco local, claro. Mesmo com 10Gbps, rede introduz latência, não tem jeito. Mas o mais pesado é mesmo o processo de build, que tem muita escrita. Pra carregar uma imagem pronta, é pra ser super rápido. Mas pelo menos usando um protocolo de bloco em vez de protocolo de arquivos, é pra diminuir a latência numa ordem de pelo menos 10x ou mais.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xh40fu275xx9q6os00m6j5gyr9w9" alt="docker build"  loading="lazy" /></p>
<p>E é mais rápido mesmo? <strong>SIM</strong>. Nesta foto de tela do docker build rodando, com NFS os tempos à direita estavam sendo de CENTENAS DE SEGUNDOS pra vários comandos. Agora está de novo abaixo de 1 segundo como deveria. Pra todos os efeitos e propósitos, não estou sentindo diferença se fosse um HD externo USB 3.2 Gen 2.</p>
<p>A diferença MASSIVA que um drive virtual BLOCK-LEVEL faz, comparado com um protocolo NFS/SMB FILE-LEVEL é brutal, o protocolo é um overhead gigante. Aliás, é mais ou menos assim que funciona quando você vai na AWS e contrata storage EBS (Elastic BLOCK Store), por isso se chama &ldquo;BLOCK&rdquo;, porque é um protocolo de BLOCO como iSCSI e não NFS. Espero que tenha aprendido como faz diferença entender como protocolos e sistemas de arquivos funcionam. Se não assistiu meus vídeos, tem <a href="https://www.youtube.com/watch?v=lxjBgxmDZAI&amp;list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt&amp;pp=gAQB"target="_blank" rel="noopener">esta playlist</a> que explica BLOCOS em detalhes.</p>
<p>Mas sim, iSCSI tem desvantagens: sendo um drive, não pode, ou não deveria, ser compartilhado (pense dois PCs compartilhando o mesmo pen drive, ia dar muita m&amp;rda). Block devices foram feitos pra serem usados por um único dispositivo. Justamente, pra compartilhar, é que existem protocolos como SMB ou NFS: pra gerenciar acessos concorrentes ao mesmo arquivo na rede.</p>
<p>Outra desvantagem: o drive virtual é um grande BLOB de bits. Sem &ldquo;montar&rdquo; não tem como ver os arquivos. No servidor NAS, ele não mostra os arquivos. É como um arquivo .vhd de Virtual Box. Precisa &ldquo;ligar&rdquo; e &ldquo;montar&rdquo; no &ldquo;PC&rdquo;. Por isso que ainda vou usar NFS pras coisas de sempre, como Videos ou Downloads. E o drive virtual é exclusivamente pro cache de Docker, neste caso. Aí funciona bem. Nenhum outro PC acessa. Se eu tivesse um segundo Linux que precisasse de cache de Docker, precisaria criar uma segunda LUN só pra ele. Lembre-se, é um drive. Virtual ou não, ele se comporta como um drive de verdade pro seu sistema.</p>
]]></content:encoded><category>homelab</category><category>armazenamento-e-backup</category><category>performance</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>Meu "Netflix Pessoal" com Docker Compose</title><link>https://akitaonrails.github.io/2024/04/03/meu-netflix-pessoal-com-docker-compose/</link><guid isPermaLink="true">https://akitaonrails.github.io/2024/04/03/meu-netflix-pessoal-com-docker-compose/</guid><pubDate>Wed, 03 Apr 2024 13:30:00 -0300</pubDate><description>&lt;p&gt;Quem acompanhava meu canal no &lt;a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener"&gt;YouTube&lt;/a&gt; ou meu &lt;a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener"&gt;Instagram&lt;/a&gt; já acompanhou a saga com meu &lt;a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/285491936_1978927048966317_5629598792539832539_n.jpg?stp=c0.181.965.965a_dst-jpg_e35_s150x150&amp;amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;amp;_nc_cat=109&amp;amp;_nc_ohc=9AzXyFhsnbcAX_Rf1Ei&amp;amp;edm=AGW0Xe4BAAAA&amp;amp;ccb=7-5&amp;amp;oh=00_AfBLLcqehVZYeDCRMQJ_Ont8CcybMrpmtHtoW4UkpV1GJA&amp;amp;oe=660E928F&amp;amp;_nc_sid=94fea1"target="_blank" rel="noopener"&gt;NAS&lt;/a&gt; (meu servidor pessoal), meu &lt;a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/332393994_6261434103896443_128632134976019123_n.webp?stp=c170.267.761.761a_dst-jpg_e35_s150x150&amp;amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;amp;_nc_cat=107&amp;amp;_nc_ohc=GxgcgjHRywEAX-oyW9H&amp;amp;edm=AGW0Xe4BAAAA&amp;amp;ccb=7-5&amp;amp;oh=00_AfAsh8cPFO7E4NzEwOw7MeNrMD4UBzFcL3qaeHT7qYLDtA&amp;amp;oe=660E9C85&amp;amp;_nc_sid=94fea1"target="_blank" rel="noopener"&gt;Synology DS1821+&lt;/a&gt; com quase 80 TB de espaço.&lt;/p&gt;
&lt;p&gt;Todos os videos do meu canal, incluindo os arquivos originais, estão lá. Só isso dá terabytes. Todo minha biblioteca do Steam está lá, são uns 4 terabytes. Todos os meus jogos antigos, retro-games, também, incluindo ISOs de Xbox 360 e PS3. São mais 4 terabytes. Toda minha coleção de discos Ultra HD (BluRay 4K) &lt;a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/412635176_915622043464387_8034096537912791825_n.heic?stp=c513.618.363.363a_dst-jpg_e35_s150x150&amp;amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;amp;_nc_cat=107&amp;amp;_nc_ohc=OOeWxOEhySoAX8yISjX&amp;amp;edm=AGW0Xe4BAAAA&amp;amp;ccb=7-5&amp;amp;oh=00_AfAaINFVuRSfg-isKRLy-fmtXvSTVz-d7Axf1d2jQL6ZvQ&amp;amp;oe=660EA292&amp;amp;_nc_sid=94fea1"target="_blank" rel="noopener"&gt;eu ripei&lt;/a&gt; (fiz backup), são mais alguns terabytes. Neste instante já estou usando mais de 50 terabytes.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Quem acompanhava meu canal no <a href="https://www.youtube.com/channel/UCib793mnUOhWymCh2VJKplQ"target="_blank" rel="noopener">YouTube</a> ou meu <a href="https://www.instagram.com/akitaonrails/"target="_blank" rel="noopener">Instagram</a> já acompanhou a saga com meu <a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/285491936_1978927048966317_5629598792539832539_n.jpg?stp=c0.181.965.965a_dst-jpg_e35_s150x150&amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;_nc_cat=109&amp;_nc_ohc=9AzXyFhsnbcAX_Rf1Ei&amp;edm=AGW0Xe4BAAAA&amp;ccb=7-5&amp;oh=00_AfBLLcqehVZYeDCRMQJ_Ont8CcybMrpmtHtoW4UkpV1GJA&amp;oe=660E928F&amp;_nc_sid=94fea1"target="_blank" rel="noopener">NAS</a> (meu servidor pessoal), meu <a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/332393994_6261434103896443_128632134976019123_n.webp?stp=c170.267.761.761a_dst-jpg_e35_s150x150&amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;_nc_cat=107&amp;_nc_ohc=GxgcgjHRywEAX-oyW9H&amp;edm=AGW0Xe4BAAAA&amp;ccb=7-5&amp;oh=00_AfAsh8cPFO7E4NzEwOw7MeNrMD4UBzFcL3qaeHT7qYLDtA&amp;oe=660E9C85&amp;_nc_sid=94fea1"target="_blank" rel="noopener">Synology DS1821+</a> com quase 80 TB de espaço.</p>
<p>Todos os videos do meu canal, incluindo os arquivos originais, estão lá. Só isso dá terabytes. Todo minha biblioteca do Steam está lá, são uns 4 terabytes. Todos os meus jogos antigos, retro-games, também, incluindo ISOs de Xbox 360 e PS3. São mais 4 terabytes. Toda minha coleção de discos Ultra HD (BluRay 4K) <a href="https://instagram.fcgh13-1.fna.fbcdn.net/v/t51.29350-15/412635176_915622043464387_8034096537912791825_n.heic?stp=c513.618.363.363a_dst-jpg_e35_s150x150&amp;_nc_ht=instagram.fcgh13-1.fna.fbcdn.net&amp;_nc_cat=107&amp;_nc_ohc=OOeWxOEhySoAX8yISjX&amp;edm=AGW0Xe4BAAAA&amp;ccb=7-5&amp;oh=00_AfAaINFVuRSfg-isKRLy-fmtXvSTVz-d7Axf1d2jQL6ZvQ&amp;oe=660EA292&amp;_nc_sid=94fea1"target="_blank" rel="noopener">eu ripei</a> (fiz backup), são mais alguns terabytes. Neste instante já estou usando mais de 50 terabytes.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/29mxcuiblyunqnnbtc10sdnqjl3f" alt="Meu Plex com UHDs"  loading="lazy" /></p>
<p>Antes que venham dar palpite, sim, isso não é pra qualquer um. Estamos falando de 8 HDs de 10.9 TB, mais upgrade de 2 HDs de 20 TB, mais outros 2 HDs de 20 TB de backup pra caso um deles dê pane, mais 2 NVMEs de 1 TB cada só pra ser cache, mais placa de rede de 10 Gbps. Além disso eu mantenho várias cópias online de parte desse conteúdo em Google Drive ou Dropbox, e um backup inteiro na Amazon Glacier, pra rara ocasião se minha casa pegar fogo, por exemplo. Estou preparado pra qualquer catástrofe, sempre.</p>
<p>A Synology é uma das melhores marcas de NAS caseiros. Sim, dá pra fazer bem mais sofisticado também, basta montar um servidor de verdade, com CPUs como AMD EPYC ThreadRipper, usando sistemas como <a href="https://www.truenas.com/"target="_blank" rel="noopener">TrueNAS</a>. Mas aí é demais pra mim, já não me sinto confortável dando manutenção. Como eu disse, os limites cada um tem que saber os seus.</p>
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="pt" dir="ltr">1o HD de 20TB terminou de se integrar ao RAID! Levou 2 dias inteiros!! Colocando o 2o HD ... de 4 😅 <a href="https://t.co/Rv6dqBQr05">pic.twitter.com/Rv6dqBQr05</a></p>— Akitando.com (@AkitaOnRails) <a href="https://twitter.com/AkitaOnRails/status/1772995683915649435?ref_src=twsrc%5Etfw">March 27, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><em>&ldquo;Por que não deixar tudo online??&rdquo;</em> Pra maioria das pessoas é o caminho mais fácil mesmo. Como eu disse, meu caso é particular. Você NÃO QUER ter mais de 50 TERAbytes online. Minha rede cabeada local é de 10 Gbps, minha internet de fibra é míseros 0.5 Gbps (500 Mbps). Trafegar video de alta qualidae pela internet é horrivelmente lento. Eu quero tudo em tempo real, e pra isso só sendo rede cabeada local.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hejrhguxmbprma7c02hsp4mpr2rq" alt="Synology Dashboard"  loading="lazy" /></p>
<p>Isso dito, eu montei um segundo mini-computador, usando um Intel NUC, conectado na mesma rede cabeada com o NAS. É Core i7, 32GB de RAM, ethernet 2.5Gbps, com o único intuito de ser um servidor Ubuntu vazio (poderia ser qualquer Linux, só escolhi o mais fácil mesmo) que serve pra subir containers Docker.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/byblhfmjthild3vjq4kyif974vt2" alt="Intel Nuc Core i7"  loading="lazy" /></p>
<p>Mesmo esse PC-zinho é overkill, muito mais do que é necessário. Muitos vão comentar que um Raspberry-Pi 5 seria suficiente, e não estão errados. A melhor solução é a que você tem orçamento pra comprar e se sente confortável em dar manutenção. Não tente copiar exatamente o setup de ninguém, estude o que melhor encaixa pra você. E não percam tempo dando pitaco no setup dos outros, façam o seu.</p>
<p>De qualquer forma, resolvi compartilhar com vocês todos os arquivos de docker-compose que estou usando pra subir os containers docker nesse servidor. Eis o <a href="https://github.com/akitaonrails/plex_home_server_docker"target="_blank" rel="noopener">repositório no GitHub</a>.</p>
<p><em>AVISO:</em> não esqueçam que precisa editar os diretórios nos scripts. Ele está configurado pra acessar o mount point do meu NAS em rede, que é <code>/mnt/terachad</code>. Arrume pra apontar pros lugares certos na sua máquina.</p>
<p><em>AVISO 2:</em> existem muitos detalhes que não estou cobrindo neste artigo. Este <a href="https://www.reddit.com/r/pirataria/comments/18ch7bt/guia_do_streaming_dom%C3%A9stico_automatizado_sonarr/"target="_blank" rel="noopener">fórum do Reddit</a> tem mais detalhes e mais discussões. Dependendo se estiver com dúvidas ou problemas específicos, talvez esteja respondido lá.</p>
<p>Pra você não se perder no meio de tantos serviços, aqui está o desenho completo do que a gente vai montar, do pedido até assistir na TV, incluindo as camadas de proteção que impedem malware e download errado de entrar na sua biblioteca:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260905220142_home-server-arquitetura-pt.png" alt="Arquitetura completa do home server pra Plex, do pedido até a reprodução"  loading="lazy" /></p>
<p>A ideia é essa: você pede num lugar (Seerr), o sistema encontra (Prowlarr e os indexers), baixa com proteção (qBittorrent e SABnzbd, mais as camadas de segurança), organiza no NAS (com legendas do Bazarr) e você assiste em qualquer tela (Plex e Navidrome). Deixa eu começar explicando um a um dos principais.</p>
<h2>Portainer e Utilitários<span class="hx:absolute hx:-mt-20" id="portainer-e-utilitários"></span>
    <a href="#portainer-e-utilit%c3%a1rios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4g7nani6heneielnqirwt8xba9iq" alt="Portainer Dashboard"  loading="lazy" /></p>
<p>Este é o trecho (veja completo no <a href="https://github.com/akitaonrails/plex_home_server_docker/blob/master/kaizoku-docker-compose.yml"target="_blank" rel="noopener">repositório</a>):</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">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">portainer_data</span><span class="p">:</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">portainer</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">portainer/portainer-ce:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">portainer_data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock</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="s1">&#39;9000:9000&#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><a href="https://www.portainer.io/"target="_blank" rel="noopener">Portainer</a> é um gerenciador visual dos recursos gerenciados pelo serviço de Docker na sua máquina. Objetivo é mais pra eu visualmente conseguir &ldquo;bater o olho&rdquo; e ver se tem algum container com problema ou pra testar algum ajustezinho rápido em alguma configuração antes de editar no arquivo de docker compose.</p>
<p>No arquivo <a href="https://github.com/akitaonrails/plex_home_server_docker/blob/master/utils-docker-compose.yml"target="_blank" rel="noopener">utils-docker-compose.yml</a> tem outros serviços como Organizr que eu instalei mas ainda não parei pra configurar nem ver se realmente quero manter. Tem o Librespeed, que funciona como aqueles testes de velocidade de internet, mas pra rede local. Tem o <a href="https://github.com/containrrr/watchtower"target="_blank" rel="noopener">Watchtower</a>, que monitora atualização das imagens de todos os containers Docker, faz o download e automaticamente manda reiniciar com versões novas, pra manter todos os containers sempre atualizados.</p>
<p>E pra acessar os serviços locais fora de casa, tem opções fáceis de VPN como o <a href="https://tailscale.com/"target="_blank" rel="noopener">Tailscale</a> ou o <a href="https://www.zerotier.com/"target="_blank" rel="noopener">ZeroTier</a>, com pouca configuração. Sim, sim, antes que alguém vá pros comentários, dá pra montar OpenVPN do zero, mas vai por mim, essas opções são infinitamente mais &ldquo;plug and play&rdquo; e possivelmente mais seguras, porque todo mundo instala, mas poucos têm a disciplina de dar manutenção e atualizar o que precisa o tempo todo. Hoje em dia eu acabei migrando pra um túnel da Cloudflare pra expor meus serviços, mas pra começar, um Tailscale da vida resolve super bem.</p>
<h2>MakeMKV e HandBrake<span class="hx:absolute hx:-mt-20" id="makemkv-e-handbrake"></span>
    <a href="#makemkv-e-handbrake" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra montar sua biblioteca pessoal de filmes, de forma legítima, o correto é você ser proprietário de discos de BluRay ou UHD e &ldquo;ripar&rdquo; eles. Se for esse o caso, recomendo comprar modelos de leitor de discos como o <a href="https://www.amazon.com/Pioneer-BDR-XS07S-Silver-Revision-Blu-ray/dp/B081R74KVW"target="_blank" rel="noopener">Pioneer BDR-XS07S</a> Tem que ficar MUITO esperto porque não é qualquer leitor que funciona.</p>
<p>Veja no <a href="https://forum.makemkv.com/forum/viewforum.php?f=16"target="_blank" rel="noopener">fórum do MakeMKV</a> quais os modelos mais recomendados. Tem modelos que tiveram atualização de firmware que impedem deles serem usados pra ripar BluRay. Precisa achar modelos que ou não foram atualizados, ou que não é difícil fazer downgrade de firmware. O ideal é já comprar modelos que todo mundo sabe que funciona, e pra isso tem que fuçar fóruns como esse.</p>
<p><a href="https://www.makemkv.com/"target="_blank" rel="noopener">MakeMKV</a>, como o próprio nome diz, &ldquo;Faz arquivos de video MKV&rdquo;, que é o bom e velho formato Matroska de envelope de video, um dos mais versáteis (porque suporta múltiplos streams não só de áudio, mas de legenda também).</p>
<p><em>Crash course:</em> uma coisa é o codec que faz o encodings dos streams de video e streams de audio. Por exemplo, H.265 ou AV1, são codecs de video. MP3 ou AAC são codecs de áudio. Agora precisamos empacotar esses dois streams (ou mais, caso tenha múltiplas dublagens, por exemplo), num &ldquo;envelope&rdquo;, um &ldquo;container&rdquo;, um &ldquo;formato de arquivo&rdquo;, como arquivos &ldquo;.mp4&rdquo; ou &ldquo;.mov&rdquo;. Matroska é um formato de envelope de arquivo, que é bem flexível, permite múltiplos streams de tudo, incluindo de legenda, por isso é muito usado pra compartilhar em torrent.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/0swbtawzxepqgd4r88cfnbg97ne9" alt="MakeMKV"  loading="lazy" /></p>
<p>Com esse container, você consegue acessar via navegador a interface gráfica do aplicativo (é como se fosse um cliente remoto de Windows, como VNC). Basta ter o leitor de Blu-Ray conectado via USB no seu computador e deve achar. Ele vai usar a biblioteca <a href="https://forum.makemkv.com/forum/viewtopic.php?t=18856"target="_blank" rel="noopener">libredrive</a> pra descriptografar os streams de dados do Blu-Ray, e por isso a importância das versões de firmware serem compatíveis (já terem sido quebrados).</p>
<p>Muitos não sabem que a gente chama tudo de &ldquo;Blu-Ray&rdquo; mas na realidade Blu-Ray é pra filmes encodados em versão Full HD, que é 1080p. As versões em 4K (2160p) vem em UHD, que é <strong>Ultra HD</strong>. A caixa de Blu-Ray costuma ser azul, as de UHD são pretas, você quer as UHD sempre que possível, claro. E eu tenho uma coleção enorme:</p>
<blockquote class="twitter-tweet" data-media-max-width="560"><p lang="pt" dir="ltr">"onde eu tô gastando tanto espaço??"<br><br>Boa parte é fazendo Rip (backup) de todos os meus UHD (BluRay 4K). E isso porque estou re-encodando em bitrate menor depois de ripar kkkk <a href="https://t.co/T1yPa6EIcV">pic.twitter.com/T1yPa6EIcV</a></p>— Akitando.com (@AkitaOnRails) <a href="https://twitter.com/AkitaOnRails/status/1772698430063731135?ref_src=twsrc%5Etfw">March 26, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>A vantagem de um app como MakeMKV é conseguir descriptografar o stream de video e copiar bit a bit exatamente como está no UHD, a melhor qualidade possível. É o que se chama de REMUX (remuxing), que é extrair o stream do container e colocar em outro container sem re-encodar. Já deve ter visto em alguns arquivos de Torrent, é a melhor qualidade. Porém significa que vai gastar o mesmo espaço que no disco, uma faixa de 50 a 80 GIGAbytes, por filme. Cada uma dúzia de filmes vai consumir quase 1 terabyte. Dependendo de quantos filmes você quer ter, seu espaço livre vai acabar muito rápido.</p>
<p>Pra minimizar isso, podemos recodificar ou &ldquo;re-encodar&rdquo;. É como quando pegamos uma foto em RAW e re-encodamos pra um formato de menor qualidade, como PNG ou mesmo JPEG. Se souber onde procurar, vai achar os artefatos de compressão, mas no geral, compensa. Por exemplo, no meu caso, encodar pra H.265 10-bit, QP 15 Constant Quality.</p>
<p>A melhor opção é ter uma placa de video NVIDIA, com suporte a encoding de video via hardware NVENC e usar o melhor software pra encoding de video: <a href="https://handbrake.fr/"target="_blank" rel="noopener">Handbrake</a>. No meu caso, eu escolho o perfil pré-configurado de <strong>Matroska H.265 MKV 4K60fps</strong> e mudo pra usar o encoder NVENC da minha placa RTX 4090, que isso acelera o encoding em múltiplas vezes.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vcjscocbej7d64mo6gnvu97rcoiz" alt="HandBrake"  loading="lazy" /></p>
<p>No docker compose, assim como MakeMKV, posso subir esse app pra ter acesso via navegador web, 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">handbrake</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">jlesage/handbrake</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;5801:5800&#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">AUTOMATED_CONVERSION=0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">HANDBRAKE_GUI=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DARK_MODE=1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">TZ=America/Sao_Paulo</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="s2">&#34;/home/akitaonrails/handbrake:/config:rw&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/home/akitaonrails:/storage:ro&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/mnt/terachad/Videos/BluRay:/watch:rw&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/mnt/terachad/Videos/BluRayOptimized:/output:rw&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/dri:/dev/dri</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>Seu PC tem no mínimo uma placa de video Intel com suporte a encoding via hardware QSV ou melhor, da NVIDIA, com suporte a NVENC. Em ambos os casos, fazemos o container ter acesso a isso mapeando o dispositivo <code>/dev/dri</code>. Sem isso, o encoding vai ser via CPU, e vai demorar ordens de grandeza mais.</p>
<p>Lembrando que encoding via software, é mais lento mas vai ter qualidade superior a Intel QSV que, apesar de rápido, sempre me dá qualidade de video inferior. NVENC é o melhor dos dois mundos: excelente qualidade e muito rápido.</p>
<p><strong>AVISO:</strong> no meu caso, quero a melhor qualidade possível, por isso faço backup de UHD, por isso mesmo torrent eu baixo BR-DISC que é backup de UHD, e isso é absurdamente pesado como expliquei. Daí eu manualmente refaço o encoding pro perfil Matroska H.265 como também expliquei.</p>
<p>Muitos não querem ter esse trabalho, pra isso não deixe de ler o <a href="https://www.reddit.com/r/pirataria/comments/18ch7bt/guia_do_streaming_dom%C3%A9stico_automatizado_sonarr/"target="_blank" rel="noopener">fórum do Reddit</a> que explica como configurar os próximos serviços pra não baixar arquivos grandes assim, eu não vou mostrar aqui, porque não é meu caso.</p>
<h2>QBitTorrent<span class="hx:absolute hx:-mt-20" id="qbittorrent"></span>
    <a href="#qbittorrent" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Por mais Blu-Ray e UHDs que você tenha ou consiga comprar, muita coisa nem existe em disco físico. Nesse caso, sua única opção é BitTorrent. E sim, é <strong>ilegal piratear</strong>, eu <strong>não estou incentivando pirataria</strong> bla bla bla &hellip;</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ar374pd43442x45030482kqhzq17" alt="QBitTorrent"  loading="lazy" /></p>
<p>Cada país tem uma lei, vá pesquisar. Vou assumir que todo mundo sabe com o que está fazendo e não é problema meu. Não adianta comentar sobre isso, não vou alongar isso. A Web já está abarrotada de material a respeito, vá no Google e procure você mesmo.</p>
<p>Primeira coisa que queremos é instalar <a href="https://www.qbittorrent.org/"target="_blank" rel="noopener">QBitTorrent</a>, provavelmente o melhor e mais completo aplicativo pra baixar torrent, e também tem interface web e funciona via Docker:</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="w">  </span><span class="nt">qbittorrent</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">linuxserver/qbittorrent:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">WEBUI_PORT=8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</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">/home/akitaonrails/qbittorrent/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</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="s1">&#39;8080:8080&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;62609:62609&#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>É importante lembrar de gerar uma porta aleatória nas configurações do QBitTorrent, como mostrado na imagem abaixo. Qualquer porta entre 1025 e 65535 deve funcionar, mas é possível que sua rede bloqueie alguma sequência de portas nesse intervalo, então tem que testar. Pesquise fóruns na web, cuidado que tem muita dica online que já está desatualizado.</p>
<p>Precisa entender o mínimo de redes, entender se está atrás de um NAT, como mapear portas externas, se tem firewall ativo bloqueando portas, etc.</p>
<p>Se o torrent não estiver baixando, &ldquo;pode ser&rdquo; isso. Coloco entre aspas porque é só uma possibilidade. Então clique em &ldquo;random&rdquo; pra escolher outra porta, edite a configuração de docker compose pra mapear a porta nova e reinicie o container.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vb3jnft1drvqsjjreo2bycp1y0e5" alt="QBitTorrent Port Listen"  loading="lazy" /></p>
<p>Por alguma razão o gerenciamento de salvamento vem tudo como Manual, é importante mudar pra automático:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jqk5vdpxthahdwi2q5y1edgyxxa6" alt="QBitTorrent Saving Management"  loading="lazy" /></p>
<p>Outro detalhe é pedir pro QBitTorrent re-anunciar os downloads para os trackers quando a porta mudar. Pra isso habilite essa opção &ldquo;Reannounce to all trackers when IP or port changed&rdquo;, senão os downloads podem não reiniciar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yoo525n36cc08u6mijoxzwpgg79e" alt="QBitTorrent Reannounce"  loading="lazy" /></p>
<p>Agora o ajuste mais importante desta seção inteira, e que eu aprendi na marra: <strong>bloquear executáveis no momento do download</strong>. Um belo dia achei nove torrents &ldquo;completos&rdquo; na biblioteca cujo único conteúdo era um executável de Windows de quase 1 GB, sem video nenhum. E o pior, o nome do release era limpinho, coisa tipo <code>Reacher S04E07 1080p WEB H264-CAKES</code> ou <code>Rick and Morty S09E10 ... EDITH.scr</code>. Você olha o nome e jura que é o episódio. O <code>.exe</code> ou <code>.scr</code> estava escondido no arquivo lá dentro.</p>
<p>Filtro por nome não pega isso, porque o nome está limpo. A defesa que realmente funciona é bloquear a extensão na hora de gravar. No QBitTorrent, vá em Options, Downloads, e habilite &ldquo;Excluded file names&rdquo; com esta lista:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>*.exe
*.scr
*.bat
*.cmd
*.msi
*.lnk
*.com
*.vbs
*.pif</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, o arquivo malicioso simplesmente nunca é escrito no disco. O torrent &ldquo;completa&rdquo; sem nada de útil, o Sonarr ou Radarr percebem que a importação falhou, jogam aquele release na blocklist e saem procurando outro sozinhos. O malware nem toca no disco, e você nem repassa a porcaria seedeando pra frente. Essa é a primeira camada de defesa, e a mais eficaz.</p>
<p>Finalmente, como pesquiso coisas pra baixar? Posso manualmente ir no Google ou DuckDuckGo ou direto em sites como os do PirateBay da vida e buscar arquivos &ldquo;.torrent&rdquo; ou links magnéticos. Pra saber o que é isso, assista meu vídeo sobre <a href="https://www.youtube.com/watch?v=iAA8NrfQtHo"target="_blank" rel="noopener">Criptografia na Prática</a> onde explico tudo sobre Torrent também.</p>
<p>O QBitTorrent tem suporte a plugins de pesquisa. Ele já vem com alguns habilitados. No canto inferior direito, tem o botão de &ldquo;Search plugins&rdquo;. Queremos adicionar o plugin que engloba tudo: o serviço <a href="https://github.com/qbittorrent/search-plugins/wiki/How-to-configure-Jackett-plugin"target="_blank" rel="noopener">Jackett</a>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dvndok2h1lgavk0igv24dazi2pwp" alt="Jackett plugin"  loading="lazy" /></p>
<p>Veja que ele quer um host com nome &ldquo;jackett&rdquo; habilitado na porta 9117, pra isso temos este trecho no 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="w">  </span><span class="nt">jackett</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">ghcr.io/linuxserver/jackett:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</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">/home/akitaonrails/jackett/downloads:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/jackett/config:/config</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="s1">&#39;9117:9117&#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>Ele vai subir um serviço onde podemos acessar a interface web assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p2f8xj5cemh9g48f8sayg52os2ts" alt="Jackett Web UI"  loading="lazy" /></p>
<p>É um serviço que consolida dezenas de sites de pesquisa de torrents num lugar só, consegue testar se estão online e, mais importante, fornece uma chave de API no canto superior direito, que devemos copiar e editar na <a href="https://github.com/qbittorrent/search-plugins/wiki/How-to-configure-Jackett-plugin"target="_blank" rel="noopener">configuração do plugin</a> do QBitTorrent. Com isso é possível fazer pesquisar diretamente, recebendo respostas assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/q8tbrlvwuoj2ilehs05kbfj40cs4" alt="QBitTorrent Search Engine"  loading="lazy" /></p>
<p>Veja na coluna da direita que lista de onde puxou esses resultados: está vindo do serviço Jackett.</p>
<p>Um aviso importante, porque isso mudou com o tempo: o Jackett aqui serve pra ISSO, a busca manual que você mesmo faz no QBitTorrent. Não caia na tentação de plugar o Jackett direto no Sonarr e no Radarr. A parte automática vai ser gerenciada pelo Prowlarr, que eu explico mais pra frente, e é ele que alimenta os indexers dos serviços. Eu mantenho o Jackett rodando só pra busca manual e, no máximo, como ponte pra algum tracker que o Prowlarr não suporta nativamente, tipo o RuTracker, que o Prowlarr consegue consumir através do Jackett.</p>
<h2>Radarr (Filmes)<span class="hx:absolute hx:-mt-20" id="radarr-filmes"></span>
    <a href="#radarr-filmes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Procurar manualmente funciona, mas existem formas melhores: deixar um serviço fazer a procura e gerenciar o download pra você, e pra baixar filmes, podemos usar o serviço <a href="https://radarr.video/"target="_blank" rel="noopener">Radarr</a>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/d9fz9nc4dc8mgcaowpo5xul6w71e" alt="Radarr Web UI"  loading="lazy" /></p>
<p>Pra isso, temos este trecho no 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="w">  </span><span class="nt">radarr</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">ghcr.io/linuxserver/radarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">qbittorrent</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</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">/home/akitaonrails/radarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/radarr/movies:/movies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/radarr/anime:/anime</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="s1">&#39;7878:7878&#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>Veja que mapeei diretórios diferentes pra filmes ocidentais e filmes anime, é uma preferência minha, você pode organizar como quiser. Como avisei antes, não copie e cole igual estou mostrando aqui: ajuste os diretórios pra sua máquina.</p>
<p>O principal é que uma vez iniciado, podemos navegar pela interface web, pra opção &ldquo;Settings&rdquo;. Ali vai ter &ldquo;Download Clients&quot;e podemos registrar o serviço web de QBitTorrent:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/o3bkso3dmxppqk7qkbxc5miwt3ul" alt="Radarr QBitTorrent"  loading="lazy" /></p>
<p>Eu não mostrei, mas nas configurações do QBitTorrent vai ter um lugar pra cadastrar essa senha de admin pra acessar a API dele via web. Não esqueça de cadastrar lá e usar a mesma senha aqui. Assim o Radarr pode controlar o download de filmes diretamente com o QBitTorrent, sem você precisar se meter.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hx4xnogjlt7i4hjkpkrniqgboaui" alt="QBitTorrent Web UI"  loading="lazy" /></p>
<p>Tem um ajuste no Radarr que eu recomendo fazer logo de cara, porque ele vem com um buraco bobo: por padrão, TODA definição de qualidade vem com tamanho mínimo zero. Isso deixa passar arquivo falso minúsculo se passando por 4K. Eu já fui vítima: um tal <code>Superman (2025) WEBDL-2160p.iso</code> de 598 MB entrou como se fosse o filme e ficou meses aparecendo no Plex. Por dentro da ISO tinha um <code>.exe</code> de 597 MB disfarçado.</p>
<p>A correção é ir em Settings, Quality, e botar um piso de tamanho em cada definição de qualidade. Uso mais ou menos assim, em MB por minuto de video:</p>
<ul>
<li>720p (HDTV/WEBDL/WEBRip/Bluray): 3</li>
<li>1080p (HDTV/WEBDL/WEBRip): 5, Bluray 1080p: 8, Remux 1080p: 25</li>
<li>2160p (HDTV/WEBDL/WEBRip): 10, Bluray 2160p: 15, Remux 2160p: 50</li>
</ul>
<p>É baixo o suficiente pra aceitar um bom encode x265 enxuto, e alto o suficiente pra recusar um &ldquo;4K&rdquo; de menos de 1 GB, que só pode ser cilada. Um detalhe chato: o Radarr tem um bug em que salvar tudo de uma vez não persiste o tamanho mínimo, então tem que salvar qualidade por qualidade, uma de cada vez.</p>
<h2>Sonarr (Séries de TV)<span class="hx:absolute hx:-mt-20" id="sonarr-séries-de-tv"></span>
    <a href="#sonarr-s%c3%a9ries-de-tv" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Mesma coisa que o Radarr, mas pra séries de TV, temos o serviço <a href="https://sonarr.tv/"target="_blank" rel="noopener">Sonarr</a>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ky9rxjko4m5mfordc84fn6so7xlx" alt="Sonarr Web UI"  loading="lazy" /></p>
<p>Esse serviço é sensacional. Ele acha suas séries, cruzando com bancos de dados online com detalhes como temporadas, nomes de cada episódio e vários outros metadados. Daí ele não só consegue baixar episódios antigos, como mantém uma agenda pra ir baixando séries que ainda estão passando. Toda semana aparece episódio novo sozinho:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xf3cj62rrzl30q34qnf1f629fmm3" alt="Sonarr Série"  loading="lazy" /></p>
<p>E mesma coisa do Radarr: em Settings e Download Clients, posso registrar meu QBitTorrent, daí o Sonarr se vira pra ir baixando os episódios:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qxxuy0wa90se9x8alm9pi7ycmzt0" alt="Sonarr QBitTorrent"  loading="lazy" /></p>
<p>E pra subir o serviço, no docker compose temos este trecho:</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="w">  </span><span class="nt">sonarr</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">ghcr.io/linuxserver/sonarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">qbittorrent</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</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">/home/akitaonrails/sonarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/sonarr:/tv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</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="s1">&#39;8989:8989&#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>Mesma coisa que o Radarr, eu mapeei pastas diferentes pra anime e não-anime, que é questão de preferência da sua organização.</p>
<p>Tanto no Sonarr quanto no Radarr, tem uma configuração que virou obrigatória pra mim depois que comecei a levar cilada: um Release Profile que bloqueia nomes de release conhecidos por serem armadilha. Em Settings, Profiles, eu deixo uma lista de &ldquo;Must Not Contain&rdquo; com termos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>BROADCAST
FULL HD
.scr
.exe
SODAPOP
REACHERSON</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>Cada um desses tem história. <code>BROADCAST</code>, <code>SODAPOP</code> e <code>REACHERSON</code> são grupos que empacotam RAR dividido com uma pasta <code>Sample/</code> que trava a importação (peguei quase 60 GB de lixo de um <code>Welcome to Derry</code> falso de uma vez). <code>FULL HD</code> é a assinatura de nome dos que carregam <code>.exe</code>. E <code>.scr</code>/<code>.exe</code> no nome é literal mesmo. Um aviso pra quem for copiar: o Radarr, por baixo, usa o mesmo campo <code>ignored</code> que o Sonarr, mesmo a interface chamando de &ldquo;Must Not Contain&rdquo;, então configure a mesma lista nos dois.</p>
<p>Ainda sobra uma classe que nenhum filtro pega sozinho: os fakes de pré-estreia. Aparece um <code>Reacher S04E07</code> com zero seeds pra um episódio que só vai ao ar semana que vem. O Sonarr não recusa release com data anterior à estreia, então esses eu limpo na mão mesmo. A boa notícia é que, com o bloqueio de executável do QBitTorrent lá de cima, mesmo quando um desses baixa, ele vem vazio e é descartado automaticamente.</p>
<h2>Downloads que travam<span class="hx:absolute hx:-mt-20" id="downloads-que-travam"></span>
    <a href="#downloads-que-travam" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um problema clássico do dia a dia: o torrent fica eternamente preso em &ldquo;downloading metadata&rdquo; ou &ldquo;stalled&rdquo;, com zero peers, e o Sonarr nunca desiste dele sozinho. Já tive episódio parado cinco dias atrás de um magnet morto enquanto o release bom passava batido, esperando a vez que nunca chegava.</p>
<p>A solução que montei é um script simples rodando uma vez por dia (um cron ou um systemd timer, tanto faz). Ele consulta a fila do Sonarr, do Radarr e do Lidarr pela API, acha os itens presos em &ldquo;downloading metadata&rdquo; ou &ldquo;stalled with no connections&rdquo; há mais de 72 horas, e manda remover com um comando só:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>DELETE /api/v3/queue/{id}?removeFromClient=true&amp;blocklist=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>Isso apaga o download morto, joga o release na blocklist pra não pegar de novo, e a aplicação sai atrás de outra fonte automaticamente. O pulo do gato é o cuidado pra ele NÃO mexer em download que só está lento (menos de 72h) nem em item esperando importação manual, senão você apaga coisa boa por engano. Na primeira vez que rodei, ele limpou dezenove downloads zumbis de uma vez só.</p>
<h2>Prowlarr (indexador)<span class="hx:absolute hx:-mt-20" id="prowlarr-indexador"></span>
    <a href="#prowlarr-indexador" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra tanto o Radarr e Sonarr saberem de onde puxar os torrents de séries e filmes, eles precisam pesquisar em indexadores ou trackers públicos (ou privados, se você tiver bons contatos - eu não tenho, nem pergunte). Pra isso temos <a href="https://prowlarr.com/"target="_blank" rel="noopener">Prowlarr</a>. Esse é o serviço mais importante de todos, porque se esse não funcionar, nada vai baixar direito.</p>
<p>Levei um tempo pra chegar numa configuração boa, mas hoje tenho uma regra simples que resolve: o Prowlarr é o único lugar que gerencia indexer. Sonarr, Radarr e Lidarr NÃO devem ter indexer cadastrado direto, só os que o Prowlarr sincroniza. Todo indexer que eu tinha cadastrado na mão dentro do Radarr era fonte de dor de cabeça, inclusive um &ldquo;Lime Torrents&rdquo; que foi exatamente quem me serviu aquele malware da ISO do Superman. Apaguei todos e deixei só o Prowlarr alimentando os três.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a1xmx4qhm0i554d0vwsij051ivnz" alt="Prowlarr"  loading="lazy" /></p>
<p>Veja que lista dezenas de indexadores, ou trackers. É parecido com o Jackett. A diferença é que o Jackett é pra pesquisas manuais que você mesmo vai fazer, direto no QBitTorrent. Já os indexadores do Prowlarr vão ser configurados direto no Sonarr e Radarr. Aqui começa a confusão, vamos entender:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5fu7qbgxgr4qf5kzzjyma1zevr9u" alt="Sonarr Indexes"  loading="lazy" /></p>
<p>Tanto no Sonarr quanto no Radarr, além de Download Clients, onde podemos configurar o QBitTorrent, também vai ter essa opção de &ldquo;Indexers&rdquo;, onde podemos configurar em quais trackers procurar pela mídia.</p>
<p>Pense num tracker como um mini-Google público, é onde as pessoas registram seus torrents quando querem compartilhar. Torrents não são mágicos e nem aparecem do nada. Alguém tem que criar um registro em algum lugar. Mas não existe só &ldquo;um&rdquo; lugar: é uma rede distribuída e qualquer participante pode subir um tracker se quiser. É como se você pudesse subir seu próprio Google miniatura, só pra registrar arquivos de torrent.</p>
<p>É um saco ficar gerenciando isso. Alguns trackers pequenos resolvem sair do ar, novos aparecem, configurações são modificadas, e precisamos ficar gerenciando manualmente isso, tanto no Sonarr quanto no Radarr. Pra isso serve o Prowlarr: pra gerenciar os trackers pra você. Vamos entender como.</p>
<p>Nas configurações do Prowlarr, começamos configurando onde encontrar nossos serviços de Sonarr e Radarr:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/t5opcjgeutzt3l4omy6wkk4gbw98" alt="Prowlarr Apps"  loading="lazy" /></p>
<p>Por exemplo, esta é a configuração do meu Sonarr. Notem que o hostname é &ldquo;sonarr&rdquo; e isso funciona porque no meu docker compose eu subo todos os serviços na mesma VLAN, e por padrão o docker registra como hostname o nome do serviço configurado no arquivo YAML. Se não entende Docker, não deixe de assistir meus videos sobre <a href="https://www.youtube.com/watch?v=85k8se4Zo70"target="_blank" rel="noopener">containers</a>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/d95y3u96q4tw9q9hs5u5q3eq26zf" alt="Prowlarr Sonarr"  loading="lazy" /></p>
<p>Também precisamos configurar acesso ao QBitTorrent:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/179tso31cnwvyxan9ltvgibeu8sm" alt="Prowlarr QBitTorrent"  loading="lazy" /></p>
<p>Esse outro <strong>SABnzbd</strong> usa outro protocolo de downloads mais antigo, baseado em newsreader de USENET. Sim, aquela Usenet mesmo. Quem sabe, sabe. Eu deixo o torrent como principal, mas mantenho o SABnzbd configurado como segunda fonte, porque muito conteúdo só existe nesses grupos mais antigos. Se for usar, aplique a mesma proteção de extensão do QBitTorrent: no arquivo <code>sabnzbd.ini</code>, na seção <code>[misc]</code>, coloque <code>unwanted_extensions = exe, scr, bat, cmd, msi, lnk, com, vbs, pif</code> e <code>action_on_unwanted_extensions = 2</code>, que manda abortar o download inteiro se aparecer um desses. Assim o Sonarr e o Radarr veem a falha e procuram outra fonte.</p>
<p>Finalmente, muitos trackers hoje implementam algum tipo de proteção contra bots usando captcha da Cloudflare, sabe aquele troço que fica &ldquo;você é um humano?&rdquo;. Pra passar por isso, precisamos do serviço Flaresolvrr, que podemos subir no docker compose 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-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">flaresolverr</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">ghcr.io/flaresolverr/flaresolverr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">/home/akitaonrails/flaresolverr/config:/config</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="s1">&#39;8191:8191&#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>No Prowlarr ele entra como um &ldquo;indexer proxy&rdquo;. Você cadastra o FlareSolverr apontando pra <code>http://flaresolverr:8191/</code>, cria uma tag pra ele, e marca com essa tag só os indexers que ficam atrás do Cloudflare. Aí esses passam a resolver o captcha por baixo dos panos, e os outros nem precisam passar por ele:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7949bp73gbb49994tynwuil9249h" alt="Prowlarr FlareSolvrr"  loading="lazy" /></p>
<p>Ainda não acabamos. Na tela principal, é sair habilitando os indexers que fazem sentido pra você. Não habilite tudo: fica pesado e, pior, alguns indexers servem mais cilada do que conteúdo bom. Veja as tags de cada um. Tem indexer que é só de pornô, tem indexer só de conteúdo russo ou chinês, esses eu pulo.</p>
<p>Os que me servem bem hoje são o <strong>EZTV</strong> e o <strong>1337x</strong> (os dois atrás do Cloudflare, então dependem do FlareSolverr), o <strong>YTS</strong> pra filme, e o <strong>AnimeTosho</strong> mais o <strong>Nyaa</strong> pra anime. No total sobra algo entre 18 e 21 indexers ativos por aplicação. E, mais importante do que habilitar os bons, é desabilitar os ruins: eu desliguei o <strong>LimeTorrents</strong> e o <strong>TorrentDownload</strong>, porque, somados, geraram mais de duzentos downloads e quase nenhuma importação aproveitável, além de serem fonte recorrente de malware e de fake. Menos indexer bom vale mais que um monte de indexer ruim.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cnl4yefcsvas84m6eslb3in7jmq9" alt="Add Indexer"  loading="lazy" /></p>
<p>No final, é bom clicar esse &ldquo;Test All Indexers&rdquo; pra checar quais ainda estão ativos ou não. E por fim, podemos clicar em &ldquo;Sync App Indexers&rdquo;, que vai cadastrar todos os indexers que sabemos que estão funcionando, nos registros do Sonarr e Radarr. Entenderam? O Prowlarr vai configurar os dois com os indexers mais atualizados, e remover os que não estão mais ativos. Não precisamos configurar cada um deles manualmente.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/if86fzdhieaqt5fxpbozfrh0x13k" alt="Sync App"  loading="lazy" /></p>
<p>É importante entender isso, porque senão você vai ver o Sonarr e o Radarr não conseguindo baixar nada e não vai saber porque: mas é porque os indexers estão errados ou obsoletos ou insuficientes. Gaste bastante tempo lendo a documentação do <a href="https://prowlarr.com/"target="_blank" rel="noopener">Prowlarr</a>.</p>
<h2>Plex (Netflix Particular)<span class="hx:absolute hx:-mt-20" id="plex-netflix-particular"></span>
    <a href="#plex-netflix-particular" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra assistir tudo que baixar, tem várias opções. Se é só pra você, basta ir baixando tudo num HD externo ou algo assim, e assistir diretamente do seu PC ou notebook. Dependendo da Smart TV ou console de videogame, também dá pra conectar seu HD ou pendrive via USB e assistir direto.</p>
<p>Se for um pouquinho mais sofisticado, e tiver um PC ou Raspberry Pi sobrando, dá pra instalar Sonarr, Radarr, e compartilhar o diretório de downloads pela rede, usando protocolos como CIFS, que é pra pasta compartilhada de Windows. Novamente, muitas Smart TVs e consoles suportam montar pastas compartilhadas pela rede.</p>
<p>Mas se quiser algo mais amigável e sofisticado, dá pra ter literalmente seu &ldquo;Netflix Particular&rdquo;, com uma interface gráfica parecida com qualquer plataforma de streaming, onde mostra thumbnails, descrições, detalhes do filme ou série, tocador de video e tudo mais e até sugestões do que assistir, sugestões de filme baseado no que está assistindo, informações de atores e tudo mais.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uvfh2i3ppr5mgrt1xl1r6kjgtvdb" alt="Plex"  loading="lazy" /></p>
<p>Existem vários projetos que fazem isso. Um deles, que a comunidade curte mais por ser 100% aberto, é o <a href="https://jellyfin.org/"target="_blank" rel="noopener">Jellyfin</a> (derivado do antigo Emby). Recomendo que testem, tem tudo que a maioria das pessoas precisa. Basta mapear as pastas de downloas do Sonarr, Radarr e ele vai indexar tudo e organizar pra você. Daí, de qualquer PC, é só acessar a interface web e tudo só funciona.</p>
<p>Mas eu pessoalmente prefiro o <a href="https://www.plex.tv/pt-br/"target="_blank" rel="noopener">Plex</a>, que é mais antigo, é mais fechado (por isso alguns não gostam), mas por oferecer <a href="https://support.plex.tv/articles/202526943-plex-free-vs-paid/"target="_blank" rel="noopener">planos pagos</a> com funcionalidades exclusivas, o nível de polimento é nitidamente melhor. A interface é bem mais bonita, e parece mais &ldquo;profissional&rdquo;. Jellyfin, por melhor que seja, ainda tem cara que foi um programador backend que fez, com muita má vontade em UX e design.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/gh6kum745hoedx34km4rggpfubjd" alt="Plex details"  loading="lazy" /></p>
<p>Além disso, pra assistir numa Smart TV ou Android TV da vida, precisa ter app no Google Play Store. Jellyfin não tem app pra tudo, algumas TV, não lembro se LG ou Samsung, nem tem o app disponível. Então precisa fazer side-load. Não é amigável. Plex, por outro lado, está disponível em bem mais lugares, então costuma ser mais &ldquo;plug and play&rdquo;, parecido com baixar app de Netflix ou Amazon Prime.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zvipl6wce0rruosctykjnou6ayu0" alt="Plex Skip Intro"  loading="lazy" /></p>
<p>Plex também tem funcionalidades que &ldquo;just works&rdquo;, coisas triviais que se espera num tocador, como botão de &ldquo;pular abertura&rdquo; ou &ldquo;resumir a partir de onde parou&rdquo; ou &ldquo;pular pro próximo episódio&rdquo; ou &ldquo;pesquisar e trocar legenda&rdquo;. É isso que interfaces como essas vão te oferecer, daí a usabilidade é realmente muito parecida com assistir Netflix. Não tem isso se usar um tocador genérico de PC como um VLC da vida.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mev5extpv8lgv7k1lhsq0bcm8i46" alt="Plex Subtitles"  loading="lazy" /></p>
<p>Pra subir é fácil, eis o trecho do meu 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="w">  </span><span class="nt">plex</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">plexinc/pms-docker:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">VA_DRIVER=IHD</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PLEX_HW_TRANS_MAX=16</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">/home/akitaonrails/plex/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/plex/data:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/plex/transcode:/transcode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos:/media</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/dri:/dev/dri</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/dev/bus/usb:/dev/bus/usb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</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>Existe um pequeno detalhe que muitos não entendem: Plex faz <em>transcoding</em> dos videos. Ou seja, ele vai re-encodar o video antes de mandar pro app ou navegador que vai tocar, isso é pesado, é conversão de stream de video. Se seu servidor não for forte o suficiente, seu video vai ficar engasgando pra tocar.</p>
<p>Na realidade, é mais complicado do que isso. Existe um protocolo chamado &ldquo;Direct Play&rdquo; ou &ldquo;Direct Stream&rdquo;. Tem um <a href="https://support.plex.tv/articles/200250387-streaming-media-direct-play-and-direct-stream/"target="_blank" rel="noopener">artigo que explica isso</a> no site do Plex. Se o PC, SmartTV, console, etc, tiverem suporte ao codec do video em questão, teoricamente o Plex só vai passar os bits direto, sem tentar converter nem nada. Entenda: nem todo dispositivo tem capacidade pra tocar qualquer codec de video.</p>
<p>A maioria do conteúdo hoje em dia é encodado em H.264 ou H.265 ou até AV1, que é mais novo e mais eficiente. Problema, digamos que você queira tocar num PC bem antigo ou num PS4 da vida. Eles vão ter suporte a H.264, talvez H.265, se tiver sorte, mas certamente não a AV1. Daí seria super lento tentar tocar usando só a CPU. O certo é ter instruções de hardware pra tocar AV1, especialmente num CPU antigo.</p>
<p>Então o Plex pergunta pro dispositivo: <em>&ldquo;você suporta AV1?&rdquo;</em> O tocador vai dizer <em>&ldquo;nope, sou fraco.&rdquo;</em> Então o Plex vai fazer o transcoding de AV1 pra H.264, por exemplo. Tudo isso é transparente pra você.</p>
<p>Mas aí você vai notar que na hora de tocar o video fica engasgando, pausa toda hora. Pode ser um problema de banda da sua rede ou, pode ser problema do seu servidor de Plex ser muito fraco ou, de não estar configurado pra transcoding via hardware, e estar indo na força bruta usando só a CPU.</p>
<p>Assista <a href="https://www.youtube.com/watch?v=j4a1SgUWh1c"target="_blank" rel="noopener">meu video sobre compressão</a>, onde explico sobre codecs, mas em resumo, o correto é que seja lá qual PCzinho estiver usando pro seu Plex, que ele tenha o mínimo de capacidade de encoding de video em hardware. A maioria costuma ter pelo menos capacidade de H.264, como as instruções <strong>Intel QSV</strong>, veja se sua CPU tem esse suporte. Só não vai ter se for velho demais, tipo coisa de 10 anos pra trás. Se for um servidor forte, com GPU NVidia, vai ter coisas como <strong>NVENC</strong>, que dá suporte a H.265. Se for CPU Intel moderna, vai ter suporte a <strong>AV1</strong>.</p>
<p>Em todo caso, em Linux, essas instruções devem ficar expostas no dispositivo <code>/dev/dri</code>, e por isso precisamos mapear no container docker como fiz acima. Cheque se existe esse dispositivo.</p>
<p>Se estiver tudo certo, no Plex vai ter essa opção de configurar &ldquo;Transcoder&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/q6y2swsu9u0uhzggskx3bimnxcou" alt="Plex Transcoder"  loading="lazy" /></p>
<p>Queremos nos certificar que a qualidade está em &ldquo;Make my CPU hurt&rdquo; e que a opção de &ldquo;Use hardware acceleration when available&rdquo; está hablitado também, daí ele vai usar o <code>/dev/dri</code> pra acelerar o transcoding. E isso faz uma diferença BRUTAL na velocidade. Se estiver com playback sendo engasgado, muito provavelmente é o transcoding.</p>
<p>Quando o dispositivo, seu smartphone ou sua TV, tem suporte ao exato codec usado no arquivo de video que queremos assistir, teoricamente, o Plex consegue passar o stream direto, sem transcodar, que é o mundo ideal, porque aí não processamento nenhum. Isso é Direct Play. Mas pra isso os arquivos de video precisam estar num determinado codec, numa determinada configuração. Mas mesmo num codec suportado, se sua TV for fraca e não suportar 4K, e pedir pro Plex em Full HD (1080p), daí o Plex vai transcodar de H.264 4K pra H.264 1080p, por exemplo. Por isso falo que é mais complicado do que parece.</p>
<p><strong>DETALHE IMPORTANTE:</strong> Recentemente fiquei confuso ao tocar alguns dos meus videos. Eu tenho o melhor hardware, tanto pro servidor de Plex, com aceleração via hardware, quanto Android TV pra tocar que é meu NVIDIA Shield Pro, quando rede cabeada, etc. Mas alguns videos ficavam engasgando. E no final o culpado não era hardware, nem configuração, nem nada disso, era a maldita legenda:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/z8wlwg5nze02ubi9m8dbkxlqqnin" alt="Plex problema de Legenda PGS"  loading="lazy" /></p>
<p>Quem já precisou baixar legendas, já deve ter visto o formato mais comum, que é o <strong>.SRT</strong> (SubRip). Alguns que se aventuraram mais, já devem ter visto outros formatos, como SSA (SubStation Alpha), TTML (Timed Text Markup Language), VTT (Web Video Text Track), VOBSUB, etc.</p>
<p>Sim, existe MUITO mais que SRT. Em particular, se você fizer como eu e ripar UHDs e extrair as trilhas de legenda do próprio disco, aplicativos como o MakeMKV podem baixar em formato VOBSUB (que é ok) ou o maldito <strong>PGS</strong> (Presentation Graphic Stream Subtitle Format).</p>
<p>A maioria das trilhas de legenda foram feitas pra serem enviadas ao tocador em paralelo, e separado, da trilha de video. Assim como a trilha de áudio vai separado. O tocador recebe trilha de video, trilha de áudio, e toca os dois sincronizados. Mesma coisa com trilha de legenda, no caso de arquivos SRT ou SSA ou TTML da vida.</p>
<p>Mas PGS é diferente: ele tem capacidades gráficas! Estou chutando, mas se já viu legenda coloridas, animadas, que se mexem ou se posicionam em locais absolutos na tela (como pra ficar exatamente em cima de outdoor no video, por exemplo), daí precisa haver um passo antes de rasterizar e renderizar essa legenda gráfica <strong>EM CIMA</strong> do stream de video. Isso existe um passo mais pesado de encoding, ou seja, vai ser <strong>PESADO</strong> de tocar em tempo real.</p>
<p>A solução: use o Plex pra pesquisar uma nova legenda, em formato SRT, e ignore a legenda PGS. Só de fazer isso, parou de engasgar na hora de tocar.</p>
<h2>Seerr (o antigo Overseerr)<span class="hx:absolute hx:-mt-20" id="seerr-o-antigo-overseerr"></span>
    <a href="#seerr-o-antigo-overseerr" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Só com o Sonarr e Radarr, mais os indexers do Prowlarr, já é suficiente pra procurar e baixar tudo. Mas tem um outro app que facilita muito a parte de descobrir coisa nova pra baixar. Ele se chama <a href="https://seerr.dev/"target="_blank" rel="noopener">Seerr</a>, e é a evolução do que antes era o Overseerr. O projeto do Overseerr se juntou com o Jellyseerr, o fork que atendia Jellyfin, e os dois viraram um só: o Seerr, que agora serve Plex, Jellyfin e Emby no mesmo lugar. Se você ainda ouve falar em &ldquo;Overseerr&rdquo; por aí, é isso aqui.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/scl2gpzjir3g0hga7p1ip50cs54w" alt="Seerr"  loading="lazy" /></p>
<p>Ele vai em bancos de dados online gratuitos (tipo os IMDB) da vida e fica de olho em tudo que sai de novo, tudo que está em &ldquo;trending&rdquo;. Se eu quero achar alguma coisa nova, é uma boa. Mesmo coisas que já sei o que quero, basta pesquisar no Seerr e clicar em &ldquo;Request&rdquo;:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4rcrqhxpooq14nip3zj3m7epo7p3" alt="Seerr Request"  loading="lazy" /></p>
<p>Eu configuro a integração com o Sonarr e o Radarr nas configurações, assim ele sabe pra quem mandar a requisição. Funciona tudo integrado. Só preciso usar a interface do Seerr pra pedir as coisas e posso esquecer do Sonarr e do Radarr rodando em background.</p>
<h2>Bazarr (legendas)<span class="hx:absolute hx:-mt-20" id="bazarr-legendas"></span>
    <a href="#bazarr-legendas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu não preciso, mas minha namorada às vezes prefere assistir com legendas em português. E é um saco isso porque nem sempre tem, ou o que tem é de baixa qualidade. Aliás, deixa eu já avisar que tanto legenda oficial quanto não-oficial, eu tô cansado de ver tradução errada. Minha diversão é ficar achando os erros na legenda. Se você depende de legenda, saiba que muitos significados importantes estão errados mesmo. Sempre prefira original.</p>
<p>Antigamente tinha que ir manualmente em sites como <a href="https://www.opensubtitles.org/en/search"target="_blank" rel="noopener">OpenSubtitles.org</a> da vida, baixar o arquivo &ldquo;.SRT&rdquo;, renomear igual o nome do arquivo de video e ficar testando manualmente pra ver se tá sincronizado ou não.</p>
<p>Existem dois jeitos de resolver isso. Um é pelo próprio Plex, que tem a opção de pesquisar legendas em diversas línguas e ficar testando direto da interface do tocador. Facilita bastante porque pula o passo de ter que ficar no PC, manualmente baixando arquivos. Como expliquei na seção anterior, no meu caso, que a legenda dos backups de UHD foram extraídos no formato PGS, eu preciso buscar no formato SRT pra não engasgar na hora de tocar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ok05g5tdrevvaywqfqmjwy0we3d6" alt="Plex Legendas"  loading="lazy" /></p>
<p>Outra forma é tentar baixar as legendas automaticamente antes. Isso não é perfeito porque existem diversas versões de legendas, algumas foram feitas pra DVD, outras pra BluRay, alguns até pra versões mais populares de &ldquo;CAM&rdquo; que são torrents de videos gravados direto no cinema com um smartphone, que é a versão mais porcaria de todas.</p>
<p>É muito importante renomear os arquivos de video no formato certo, com os metadados bem organizados, como explicado no <a href="https://www.reddit.com/r/pirataria/comments/18ch7bt/guia_do_streaming_dom%C3%A9stico_automatizado_sonarr/"target="_blank" rel="noopener">fórum de Reddit</a> que mencionei no começo do artigo. Isso ajuda o Bazarr a casar a legenda certa com o arquivo certo. E já avisando: Português do Brasil é uma das línguas que menos tem legenda. É mais fácil achar em Francês, Italiano ou Russo do que em Português.</p>
<p>Pra facilitar isso temos o serviço <a href="https://www.bazarr.media/"target="_blank" rel="noopener">Bazarr</a>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mjziydblv9rh4yowhuodgxycjzf6" alt="Bazarr Web UI"  loading="lazy" /></p>
<p>Na configuração do Bazarr, vamos apontar pros nossos serviços locais de Sonarr e Radarr, assim ele fica sabendo toda vez que aparece episódio novo de série ou algum filme novo, e já agenda pra tentar procurar a legenda:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rhiefnojwsullwi1m9swhxds6ocj" alt="Bazarr integração"  loading="lazy" /></p>
<p>O principal é configurar os Providers, que são os sites de onde vêm as legendas. É aqui que mora a diferença entre &ldquo;não acha nada&rdquo; e &ldquo;acha quase tudo&rdquo;. Os cinco que me dão o melhor resultado são: <strong>opensubtitlescom</strong>, <strong>tvsubtitles</strong>, <strong>yifysubtitles</strong> (só filme), <strong>animetosho</strong> (pra anime, nem precisa de conta) e <strong>gestdown</strong> (um proxy do Addic7ed pra inglês, também sem conta). Uma pegadinha que me custou horas: o provider OpenSubtitles.com pede o seu <strong>nome de usuário, não o email</strong>. Se você botar o email, ele dá erro de &ldquo;você não pode consumir este serviço&rdquo;, e ainda te deixa de castigo por 12 horas mesmo depois de corrigir.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8lzq8umwyjgg40abb1tlvtvt8nxu" alt="Bazarr Providers"  loading="lazy" /></p>
<p>Pra subir o serviço, no docker compose, temos este trecho:</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="w">  </span><span class="nt">bazarr</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">ghcr.io/linuxserver/bazarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</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">/mnt/terachad/Videos/radarr:/movies</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Videos/sonarr:/tv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/home/akitaonrails/bazarr/appdata/config:/config</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="s1">&#39;6767:6767&#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>Aqui precisamos mapear as pastas do radarr e sonarr pra ele saber onde gravar as legendas. Um detalhe que trava muita gente: mapeie exatamente as mesmas subpastas que o Radarr e o Sonarr usam (no meu caso, <code>/movies</code> e <code>/anime</code>), senão o Bazarr fica reclamando que &ldquo;não acha o arquivo de video pra analisar&rdquo; na biblioteca inteira.</p>
<p>O último passo, e o que fez o Bazarr finalmente virar útil pra mim, é o perfil de idioma. Criei um perfil &ldquo;Português do Brasil com inglês de reserva&rdquo;: ele lista <code>pb</code> primeiro e <code>en</code> depois, com o corte (cutoff) no <code>pb</code>. Na prática, o Bazarr procura as duas, baixa a que aparecer primeiro, e considera o item resolvido assim que existe uma legenda em português. Se o pt-BR nunca aparecer, o inglês fica de backup.</p>
<p>E o pulo do gato, que me custou caro: <strong>todo item precisa ter um perfil de idioma atribuído</strong>. O Bazarr ignora, calado, qualquer filme ou série sem perfil. Quando fui conferir, tinha 140 séries e 201 filmes sem perfil nenhum, sendo ignorados em silêncio desde que entraram na biblioteca. Marcar o perfil como padrão só cobre o que você adiciona dali pra frente; pra biblioteca que já existe, tem que atribuir o perfil em massa, de uma vez, pra tudo que já está lá.</p>
<p>Tem ainda um problema irmão desse, só que do lado do áudio. De vez em quando o Sonarr ou o Radarr baixam um release cujo único áudio é uma dublagem estrangeira: um italiano, um francês, um russo. E nenhuma legenda salva áudio errado. Pra isso eu uso um Custom Format nos dois, o &ldquo;foreign-language-only&rdquo;, que reconhece marcadores de dublagem estrangeira (ITALIAN, FRENCH, RUS, GERMAN e por aí vai) e dá uma pontuação de -500, o que faz o release ser recusado já na busca. Um detalhe que aprendi na marra: um release russo veio com o título todo em cirílico, sem nenhum &ldquo;RUS&rdquo; em letra latina, e passou batido. Então o filtro também precisa pegar qualquer caractere cirílico.</p>
<p>Depois que arrumei os providers, passei a biblioteca inteira pra esse perfil e liguei esse filtro de áudio, o Bazarr deixou de ser o serviço mais inútil da minha stack e virou um que resolve a maioria dos casos sozinho. Quando ainda falta, aí sim eu caço na mão pelo Plex, mas hoje é exceção. Só lembrando o que já falei: legenda em português do Brasil é das mais escassas que existem, então mesmo com tudo certo, às vezes não tem mesmo.</p>
<h2>Lidarr (Música)<span class="hx:absolute hx:-mt-20" id="lidarr-música"></span>
    <a href="#lidarr-m%c3%basica" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu não sou muito de música. Tenho uns MP3 que baixei décadas atrás e, no dia a dia, toco a playlist de Top 50 do Spotify e tá bom. Mas a mesma lógica do Sonarr e do Radarr vale pra música, então acabei montando também: o serviço se chama <a href="https://lidarr.audio/"target="_blank" rel="noopener">Lidarr</a>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260905215028_lidarr-library.png" alt="Lidarr Web UI"  loading="lazy" /></p>
<p>Ele cataloga seus artistas, cruza com bancos de dados de discografia e vai atrás dos álbuns que faltam, marcando cada artista como &ldquo;Monitored&rdquo; pra buscar lançamentos novos sozinho, do mesmo jeito que o Sonarr faz com episódio de série. No print acima dá pra ver minha biblioteca com tudo em &ldquo;Lossless&rdquo;, que é o perfil de qualidade que eu configurei.</p>
<p>No docker compose é o mesmo padrão dos outros:</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="w">  </span><span class="nt">lidarr</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">ghcr.io/linuxserver/lidarr:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</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">qbittorrent</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">TZ=America/Sao_Paulo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PUID=1000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGID=1000</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">/home/akitaonrails/lidarr/appdata/config:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Music:/music</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/mnt/terachad/Downloads/torrents:/downloads</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="s1">&#39;8686:8686&#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>A configuração é idêntica em espírito à do Radarr e Sonarr. Em Settings e Download Clients eu registro o mesmo QBitTorrent, mas usando uma categoria própria chamada <code>lidarr</code>, pra ele não misturar os downloads de música com os de filme e série. A pasta raiz da biblioteca aponta pra <code>/music</code>, e o perfil de qualidade dá pra deixar aberto (o &ldquo;Any&rdquo;, que pega o que aparecer) ou travar em &ldquo;Lossless&rdquo; quando você só quer FLAC, que é o meu caso.</p>
<p>E o melhor: os indexers eu não configuro na mão. O Prowlarr, que eu expliquei lá em cima, também cadastra o Lidarr como aplicação e sincroniza sozinho os indexers que têm categoria de música. É o mesmo &ldquo;Sync App Indexers&rdquo; que ele faz pro Sonarr e Radarr, agora pra música também.</p>
<p>Pra ouvir, o próprio Plex já toca música: basta criar uma biblioteca do tipo Music apontando pra essa pasta, e no celular o app Plexamp é ótimo só pra isso. E, do mesmo jeito que tenho o Kavita como &ldquo;Plex pra mangá&rdquo;, pra música ainda dá pra usar o <a href="https://www.navidrome.org/"target="_blank" rel="noopener">Navidrome</a>, um servidor de streaming open source tipo um Spotify particular, que indexa a mesma pasta <code>/music</code> (só leitura) e tem vários apps de celular compatíveis. O Lidarr baixa e organiza, o Navidrome ou o Plex servem.</p>
<h2>Outros Serviços<span class="hx:absolute hx:-mt-20" id="outros-serviços"></span>
    <a href="#outros-servi%c3%a7os" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No <a href="https://github.com/akitaonrails/plex_home_server_docker/tree/master"target="_blank" rel="noopener">meu repositório</a> tem outros arquivos de Docker Compose pra minha infra local. Não vou explicar em detalhes, mas só a saber:</p>
<p>Pra bloquear banner, ad, rastreador e até site de malware na rede toda, eu uso DNS filtrado. Durante muito tempo rodei um <a href="https://pi-hole.net/"target="_blank" rel="noopener">Pi-Hole</a> local pra isso, que é um servidor de DNS caseiro com bloqueio embutido. Funciona muito bem, mas dá manutenção. Hoje eu troquei por um serviço hospedado, o <a href="https://nextdns.io/"target="_blank" rel="noopener">NextDNS</a>, que faz o mesmo bloqueio de ads, rastreadores e malware, só que sem eu precisar manter servidor nenhum, e já vem com DNS criptografado de fábrica.</p>
<p>E por que DNS criptografado importa? Query de DNS, por padrão, é um protocolo de texto aberto. Seu provedor sabe exatamente tudo que você navega. Eu prefiro não dar esse dado de graça pra eles. Com o NextDNS as consultas saem encriptadas e ainda passam pela lista de bloqueio que eu escolho. Não substitui um anti-virus, mas dificulta bastante que terceiros montem uma impressão digital dos meus hábitos de navegação, e dá bem menos trabalho que o Pi-Hole que eu mantinha antes.</p>
<p>Agora, no arquivo <a href="https://github.com/akitaonrails/plex_home_server_docker/blob/master/kaizoku-docker-compose.yml"target="_blank" rel="noopener">kaizoku-docker-compose.yml</a> tem outro serviço tipo Sonarr pra TV, Radarr pra filmes, que é o <a href="https://github.com/oae/kaizoku"target="_blank" rel="noopener">Kaizoku</a> pra mangá. Se você lê mangá online, já deve ter visto muitos sites sendo fechados por causa de copyright, direitos autorais e tudo mais. Eventualmente, os mangás online que você leu, vão desaparecer. Com o Kaizoku podemos preservar eles localmente também.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/iucprcsmn9emkzzurs7z2fstcsjm" alt="Kaizoku"  loading="lazy" /></p>
<p>O Kaizoku é só pra baixar. Pra ler, precisamos de um leitor, da mesma forma que temos Plex pra video, temos <a href="https://www.kavitareader.com/"target="_blank" rel="noopener">Kavita Reader</a> pra ler os mangás baixados. Assim como Plex, basta na primeira configuração apontar pra pasta onde o Kaizoku baixa tudo, daí o Kavita vai se virar pra organizar a biblioteca, baixar capas e outros metadados. É literalmente o Plex pra Mangá.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bxuz95wo3luc308lz1dos9bf2kcr" alt="Kavita"  loading="lazy" /></p>
<p>Ainda não explorei muito esse serviço. Ou ainda tem coisas pra refinar nele, ou eu que não sei configurar direito, mas nem tudo baixa capas direito. Vejam como não está bonito. Se tiverem dicas sobre Kavita e Kaizoku, não deixem de compartilhar abaixo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bcrh2ifucs86fupzk8gqa38yx5iq" alt="Kavita Reader"  loading="lazy" /></p>
<p>Pra ler em si, é bem parecido com um Kindle Web ou outros sites online de mangá. Dá pra ler página a página, ou &ldquo;scrollar&rdquo; o capítulo inteiro, ele vai lembrar onde você parou e coisas básicas assim. Pode ser um bom projeto pra contribuir, se tem vontade de treinar com código aberto.</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>Fico muito contente com o estado atual dos serviços de compartilhamento de arquivos. Não percam muito tempo pensando demais. Só instalem e se divirtam. O código de tudo é aberto, se você é programador, vá lá fuçar o código e aprender algo novo, e quem sabe, até contribuir.</p>
<p>Eu não sou adepto de teorias apocalípticas, teorias da conspiração, nem nada assim. Eu só considero que, o que eu não tenho controle físico, eu <strong>não tenho</strong>. Se o que &ldquo;é meu&rdquo; está no controle de um terceiro, então &ldquo;não é meu&rdquo;. Esse é o princípio. Eu quero tudo que é meu sob meu único e exclusivo controle e pronto. Não importa se é menos conveniente, não importa se é mais caro. É uma questão de princípios: meu é meu e ponto final.</p>
<p>Além disso, nas plataformas de streaming, não existe tudo. Muita coisa de décadas passadas simplesmente foram apagadas da memória. Muitos nem lembram mais, especialmente quem não viu na época. Eu tenho nostalgia por programas de TV, filmes, que não existe online. Muitos sequer existem em mídia física. Tem filmes que depois de VHS nem chegaram a sair em DVD. Tem DVD que nunca saiu como Blu-Ray. Tem coisa em disco físico que nunca vai sair em streaming. Tem coisa em streaming que vai ser apagado em breve, e muitos nem vão notar.</p>
<p>O que é meu, é meu. Podem apagar o quanto quiserem. O que é meu, ninguém apaga. Se você tem apreço pelo que é seu, comece a explorar estas opções. Não precisa ser um setup gigante como o meu. Qualquer HD externo sobrando já dá pra começar. Comece pequeno, vá aumentando aos poucos. Eu coleciono essas coisas faz décadas! Não comecei hoje.</p>
]]></content:encoded><category>homelab</category><category>containers</category><category>audio-e-video</category></item><item><title>[Akitando] #126 - Criando uma Rede Segura | Introdução a Redes Parte 6 - VPN e NAS</title><link>https://akitaonrails.github.io/2022/08/29/akitando-126-criando-uma-rede-segura-introducao-a-redes-parte-6-vpn-e-nas/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/08/29/akitando-126-criando-uma-rede-segura-introducao-a-redes-parte-6-vpn-e-nas/</guid><pubDate>Mon, 29 Aug 2022 10:01:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/EOmzo5d0F9Y"
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;Hoje vamos fechar a minissérie de Introdução a Redes. Meu objetivo todo foi poder falar sobre minha nova solução de armazenamento e edição de videos com NAS e na sequência, como criei uma rede segura pra conseguir acessar coisas como meu Media Server, mesmo estando do meu celular numa rede insegura fora de casa, que é a mesma solução que empresas tem usado pra conseguir dar infraestrutura pra funcionários remotos. Hoje vamos entender como VPNs funcionam.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/EOmzo5d0F9Y"
    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>Hoje vamos fechar a minissérie de Introdução a Redes. Meu objetivo todo foi poder falar sobre minha nova solução de armazenamento e edição de videos com NAS e na sequência, como criei uma rede segura pra conseguir acessar coisas como meu Media Server, mesmo estando do meu celular numa rede insegura fora de casa, que é a mesma solução que empresas tem usado pra conseguir dar infraestrutura pra funcionários remotos. Hoje vamos entender como VPNs funcionam.</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</li>
<li>00:44 - Cap 1. Meu Primeiro NAS de backup</li>
<li>04:38 - Cap 2. Meu Setup Novo de Gravação</li>
<li>07:24 - Cap 3. O Novo NAS</li>
<li>14:17 - Cap 4. DNS Privado com Pi-Hole</li>
<li>18:31 - Cap 5. Meu “Netflix” Particular</li>
<li>21:56 - Cap 6. Recapitulando Redes e dispositivos virtuais</li>
<li>27:49 - Cap 7. Redes Seguras com Zero Tier</li>
<li>36:57 - Cap 8. Como funciona uma VPN?</li>
<li>43:51 - Conclusão</li>
<li>45:59 - 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>WHAT HAPPENED TO GEEKBRIEF TV? (<a href="https://www.thegeekpub.com/4173/what-happened-to-geekbrief-tv/"target="_blank" rel="noopener">https://www.thegeekpub.com/4173/what-happened-to-geekbrief-tv/</a>)</li>
<li>Pi-Hole cloudflared (DoH) (<a href="https://docs.pi-hole.net/guides/dns/cloudflared/"target="_blank" rel="noopener">https://docs.pi-hole.net/guides/dns/cloudflared/</a>)</li>
<li>Media Server Setup Pt. 3b: Prowlarr/Sonarr/Radarr (<a href="https://morrismotel.com/servarr-pt3b-prowlarr-sonarr-radarr/"target="_blank" rel="noopener">https://morrismotel.com/servarr-pt3b-prowlarr-sonarr-radarr/</a>)</li>
<li>Jellyfin (<a href="https://jellyfin.org/"target="_blank" rel="noopener">https://jellyfin.org/</a>)</li>
<li>Plex Media Server (<a href="https://www.plex.tv/media-server-downloads/"target="_blank" rel="noopener">https://www.plex.tv/media-server-downloads/</a>)</li>
<li>Getting Started with Software-Defined Networking and Creating a VPN with ZeroTier One (<a href="https://www.digitalocean.com/community/tutorials/getting-started-software-defined-networking-creating-vpn-zerotier-one"target="_blank" rel="noopener">https://www.digitalocean.com/community/tutorials/getting-started-software-defined-networking-creating-vpn-zerotier-one</a>)</li>
<li>Docker Networking Overview (<a href="https://docs.docker.com/network/"target="_blank" rel="noopener">https://docs.docker.com/network/</a>)</li>
<li>Hamachi VPN (<a href="https://www.vpn.net/"target="_blank" rel="noopener">https://www.vpn.net/</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>Finalmente, hoje vou voltar a falar do meu novo NAS e na segunda metade dar mais um tópico sobre redes que me interessa, como acessar meu NAS fora de casa, sem comprometer a segurança da minha rede de casa. Na verdade, eu acho que o objetivo todo dessa mini-série de redes que ando fazendo foi justamente pra chegar no episódio de hoje pra falar do meu NAS novo. De volta aos meus videos sobre armazenamento, lá explico tudo sobre como HDs funcionam e como eu posso configurar vários HDs num único volume pra ter redundância e mais segurança. Se não assistiram, recomendo ver depois. Entender arquivos e entender redes é o mínimo do mínimo que um programador precisa saber.</p>
<p>(&hellip;)</p>
<p>Foi lá por 2009 que eu comprei meu primeiro NAS caseiro. Naquela época eu usava Mac e nos primórdios de podcast e webcast eu assistia uma reviewer de tech chamada Cali Lewis, que fez propaganda de uma nova marca muito da hora chamada Drobo. Desde aquela época eles faziam um lance mais inteligente do que RAID normal. Um RAID clássico precisa de HDs de mesma capacidade pra montar o array. Significa que se eu colocar um HD diferente maior, o espaço que sobra ele desperdiça. Mas o Drobo usava um sistema proprietário que conseguia usar o máximo de discos de tamanho diferente. Foi o que me chamou a atenção.</p>
<p>Por quase 10 anos eu usei esse Drobo, primeiro acho que foram uns 4 HDs de 512 GB ou 1 TB cada. Com o tempo eu ia trocando pra HDs maiores um de cada vez. Mas acho que nunca usei nada maior que uns 4 TB por HD. E durante essa quase uma década eu já tive problema de HD realmente falhar e graças ao Drobo eu conseguia só trocar esse HD falhando e ele conseguia se reconstruir sem perder nada. Porém a interface dele acho que era USB 2, com seus meros 480 megabits, o que não dá muito mais que 60 megabytes por segundo.</p>
<p>Mas como era mais pra backup, e não pra acessar o tempo todo, eu deixava fazendo backup a noite, aí não importava tanto se era muito lento. Eu tive uma pane no Drobo inteiro uma vez que ele não ligava mais. Comprei outro Drobo, a versão 5N, que já era USB 3. Só tirei os HDs do primeiro que quebrou, coloquei no novo na mesma ordem, e tudo voltou a funcionar. E com a vantagem que com USB 3 já era até 10 vezes mais rápido que o USB 2. Chegava perto dos 100 megabytes por segundo. Que continua sendo 2 a 3 vezes mais lento que um bom HD mecânico, e pelo menos 4 ou 5 vezes mais lento que um SSD SATA.</p>
<p>Durante a pandemia resolvi experimentar com um novo NAS e peguei um Synology DS420j, de tamanho similar ao Drobo. Depois de mais de 10 anos, o Drobo meio que parou no tempo. O software dele é super limitado e não oferece nada além do básico. Diversas outras marcas como QNap, Synology e mesmo opções open source como TrueNAS pra você montar seu próprio NAS num gabinete de PC evoluíram e deixaram o Drobo no chinelo. Resolvi ver o que os concorrentes estavam fazendo. E o Synology eu achei bacana porque é basicamente um Linux, com Linux Raid rodando mdadm por baixo e suportando filesystems open source como ext4 ou btrfs.</p>
<p>Ele não é tão sofisticado quanto um TrueNAS, que tem ZFS e consegue facilmente suportar armazenamento em nível de data center, com arrays de dezenas de HDs e petabytes de dados. Mas TrueNAS não é pra amadores. Não basta só seguir um tutorial na internet e fazer funcionar da primeira vez. Se você nunca precisou recuperar um TrueNAS com problemas, seus dados vão estar inseguros quando você mais precisar. Não adianta nada um software sofisticado que você vai cometer erros de amador e potencialmente corromper seus dados no meio do caminho. E não adianta só fazer funcionar da primeira vez. O teste de fogo é quando alguma coisa der pane e você realmente precisa recuperar dados. Se nunca passou por isso, considere que vai fazer errado da primeira vez.</p>
<p>Como eu não quero virar administrador de sistemas nem trabalhar em data center, eu queria uma solução que usasse componentes open source que eu confio como Linux Raid, mas com suporte comercial decente e softwares maduros que me ajude a não cometer erros de amador. E a Synology oferece tudo isso. Sim, é mais caro que montar um do zero com TrueNAS, mas é mais confiável, e pra isso eu pago mais caro. Valor é relativo. Não adianta nada ser barato pra montar mas na hora de recuperar dados, você cometer um erro e perder tudo. Aí saiu super caro. Todo amador comete esse erro de só considerar o valor inicial das coisas.</p>
<p>O DS420j foi bacana, mas ele tinha o mesmo problema do Drobo, interfaces lentas. Ele tinha ou USB 3 ou conexão de ethernet 1 gigabit. Como eu disse no primeiro episódio desta série de redes, 1 gigabit é ok. Pra maioria dos casos é velocidade suficiente. Mas por causa do canal, mais e mais eu estava lidando com arquivos de centenas de gigabytes. Eu gravo meus videos em 4K com codec de edição DNxHR, isso dá uma média de quase 100 gigabytes por hora de gravacão. Alguns episódios eu preciso ficar umas 2 horas ou mais gravando, então toda vez da 200 gigas ou mais. E não, se deu vontade de comentar &ldquo;ah, porque você não grava de jeito X ou jeito Y&rdquo;, obrigado pelo palpite, mas se eu gravo nesta configuração é porque eu já sei que é a melhor. E isso nem é considerado muito. Estúdios profissionais que gravam em 8K estão acostumados a lidar com terabytes por projeto.</p>
<p>Agora pensa, transferir 200 gigas a uma velocidade de uns 120 megabytes por segundo significa quase meia hora pra transferir tudo pro Synology. 120 megabytes por segundo é quase saturar a conexão ethernet de 1 gigabit. É o máximo que meu Synology conseguia ir. E no Drobo era mais lento ainda. E isso só pra fazer backup. Eu precisava ter terabytes de SSD no meu PC pra editar isso. Pra editar video 4K desse tamanho, HD mecânico é muito lento. E assistindo videos de outros youtubers como Linus Tech Tips, MKBHD e outros eu sabia que todos editam videos direto dos NAS deles, usando redes acima de 2.5 gigabits.</p>
<p>Em 2021 eu atualizei meu equipamento de gravação. Antes minha câmera Sony A7III gravava em SD cards. Mas ele tem aquela maldita limitação de parar de gravar automaticamente depois de 30 minutos. Toda câmera fotográfica faz isso por causa de mais uma maldita lei da Europa. Europeu é campeão de criar leis imbecis. De qualquer forma SD cards não são confiáveis e um dia tive problema de corromper meus arquivos e perdi um episódio. Cansado dessas limitações eu comprei um Atomos Ninja V. Agora ligo a saída HDMI da minha câmera nesse Atomos, que por sua vez tem um SSD SATA e ele grava direto do HDMI em arquivo no SSD, sem limitações. Isso resolveu o problema de video. De novo, se tava indo comentar &ldquo;porque não grava direto no PC&rdquo;, novamente, pra mim não era uma boa solução. Obrigado pelo palpite.</p>
<p>Daí um belo dia meu gravador de áudio, que era uma Tascan velha, comecou a dar pau e mastigar os arquivos de áudio. Cansei dele e comprei um gravador novo, um Zoom H5. Agora ele grava tanto em SD card, mas eu ligo a saída de áudio na entrada do Atomos, então o Atomos grava tanto o video da câmera quanto o áudio do gravador tudo junto e eu ainda tenho uma segunda cópia no gravador, via SD Card. Daí resolvi o audio também.</p>
<p>Agora eu queria resolver a edição e backup dos videos originais. E sim, eu guardo e sempre vou guardar os originais. Novamente, não precisa ir nos comentários dizer pra apagar os originais pra economizar espaço. Um profissional nunca joga fora originais. E pra isso eu queria transferir os arquivos do SSD do Atomos direto pro meu NAS em rede 10 gigabit. 10 gigabit seria um pico de mais de 1 gigabyte por segundo. E esse meu Synology DS420j que é um modelo caseiro, não oferece upgrade de hardware, tipo colocar placa de 10 gigabits. Então resolvi que era hora de investir num NAS profissional, o modelo DS1821+. A série que termina com  &ldquo;j&rdquo; acho que é de entrada, é bom pra quem tá começando. A série 21 plus é profissional. Tem modelos ainda maiores, mas pra mim esse seria suficiente.</p>
<p>Pra começar, esse modelo tem 8 baias de HDs. Então decidi ir all in e comprar nada menos que 6 HDs Seagate IronWolf de 12 Terabytes. IronWolf são HDs feitos pra NAS, eles tem mais durabilidade, aguentam mais vibrações, tem cache maior. É como um HD caseiro, tipo modelo Barracuda, mas muito mais resistente, e obviamente mais caros. Liguei os 6 no novo Synology, e montei um volume com 5 deles em modo SHR que é Synology Hybrid RAID. Em vez de um RAID normal que exige HDs de tamanho igual, ele consegue aproveitar o espaço de HDs diferentes melhor de um jeito proprietário dele. Lembra o que eu falei que o Drobo fazia? Hoje em dia outros fabricantes já superaram. No meu caso, isso virou um volume de 42 terabytes, e o 6o HD fica desligado como hot spare. Se um dos 5 HDs falhar, o NAS automaticamente desliga o que falhou, liga o hot spare no lugar e começa a reconstruir o volume sem perder nada.</p>
<p>E como é pra ir all in, resolvi fazer todos os upgrades que esse modelo permite. Coloquei 2 NVMEs de 1 terabyte cada dedicados só pra ser cache. Assim arquivos mais acessados ficam no NVME, em vez de ter que ir nos HDs mecânicos. E nem é muito que precisava porque apesar de serem HDs mecânicos e muita gente ficar me perguntando se um RAID feito com SSDs não seria mais rápido. A resposta deveria ser óbvia: não, o custo simplesmente não compensa. Mas na cabeça de vocês, se imagina que porque estou lidando com HDs mecânicos, vai ser mais lento que um SSD. E é aí que estão errados. Em IOPS talvez, mas em velocidade não. Cada HD desses é capaz sozinho de mais de 200 megabytes por segundo.</p>
<p>Lembra da explicação de dispositivos de bloco e como arquivos são divididos em blocos e como esses blocos são dividos nos vários HDs de um RAID? Cada HD no RAID contribui com mais ou menos metade da velocidade pra leitura e escrita. Então cada um contribui com pelo menos 100 megabytes por segundo pra velocidade total, indo pra mais de 500 megabytes por segundo. Pra dar perspectiva isso é mais de 4000 gigabits. E eu estou arredondando por baixo, em benchmarks eu estou conseguindo ir até 6000 ou 7000 gigabits. Sua rede cabeada de casa é só 1 gigabit, mas na minha que é 10 gigabits, consigo suportar isso.</p>
<p>Pra conseguir essas velocidades na rede eu comprei a placa de rede 10 gigabits e comprei cabos de rede cat 6a com conectores blindados. Não adianta nada placa de rede boa com cabos que não aguentam a velocidade. Se eu falei que consigo ir na faixa acima de 7000 gigabits por segundo, às vezes 8000, significa que estou usando a maior parte do que minha rede consegue. E pra tudo falar nessa velocidade, também comprei uma placa de 10 gigabits pro meu PC e um pequeno switch TP-Link que suporta 10 gigabits em todas as portas.</p>
<p>Aliás, cuidado com switches com marketing duvidoso. Eu vi caixas de switches baratos dizendo que suportam 5 gigabits, e tem 5 portas, aí você pensa que cada porta suporta 5 gigabits, mas não, em letras miúdas eles falam que o aparelho suporta até o máximo de 5 gigabits ou seja, cada porta é só 1 gigabit. No meu caso são 5 portas de 10 gigabits cada, então o aparelho todo suporta 50 gigabits. E isso é importante, porque lembra todo o processamento de checagem de erros, roteamento e tudo mais que um switch precisa fazer? Tudo isso exige CPU que consiga processar o tráfego de 50 gigabits por segundo. Ele fica bem quente quando tá ligado trabalhando.</p>
<p>Finalmente, o último upgrade desse all in foi trocar os 4 gigabytes de RAM que o DS1821 vem e colocar 16 giga de RAM ECC, que tem correção de erros, pra evitar single event upset de raios cósmicos, por exemplo, como expliquei no segundo video da série, de correção de erros. Minha memória ECC provavelmente tá rodando Hamming Code, pra recuperar qualquer bit flip acidental. Tudo isso pra garantir que nenhum dado nessa caixa seja corrompida de jeito nenhum.</p>
<p>E agora sim, eu consigo gravar os videos crus depois da gravação direto pro NAS em velocidade máxima e consigo usar o DaVinci Resolve pra editar videos direto do NAS, sem precisar ter nada em SSD local. Vou te falar que a qualidade de vida é incrível, meu fluxo de trabalho fica bem mais fácil. Isso tudo já é equipamento que um pequeno estúdio de gravação teria, pra ter 2 ou 3 editores de video trabalhando direto do NAS. Pra estúdio maior, eles teriam racks de HDs, cada uma com mais de uma porta de 10 gigabits, talvez numa rede de 100 gigabits por segundo. Nos videos do Linus ele tem o projeto petabyte, onde montam rack com TrueNAS e dezenas de HDs ou SSDs com ZFS que suportaria uma dúzia de editores de video tudo trabalhando por rede 2.5 gigabit, lidando com videos 8K de terabytes de tamanho. O que eu montei é a versão junior disso.</p>
<p>Dá pra ir muito longe. Mas isso foi só pra solucionar meu fluxo de trabalho de video que, honestamente nem é tão pesado assim. Eu sou o único editor e faço tudo sozinho. Portanto sim, esse investimento foi bem mais do que eu precisaria. Só pra ter uma idéia, o NAS, os HDs, os upgrades de SSD, rede e RAM, deram um total de bem mais que 30 mil reais. Se fosse lá fora teria custo fácil mais de 3 mil dólares. Em dólar nem parece tão caro. Um computador moderno como um Mac Studio custa mais que isso. Mas falando em reais, sim, é um terço de um carro popular. Qualidade é proporcional ao preço.</p>
<p>Mas, eu não ia usar só pra videos. A vantagem desse NAS é que ele vem com um bom processador, um AMD Ryzen quad-core de 2.2 Ghz cada. Não chega nem aos pés dos 16 core que meu Ryzen 9 5850 do meu PC tem, mas pra um NAS tá sobrando. Um NAS é nada mais, nada menos, que um PC com Linux. Na teoria você não deveria, mas pode rodar qualquer outro software dentro dele.</p>
<p>Não deveria porque a função de um NAS é ser dedicado a lidar com arquivos. Num ambiente onde outras pessoas estivessem trabalhando e puxando arquivos o tempo todo pra editar, a CPU ia ficar trabalhando só pra entregar os blocos o mais rápido possível. Fora que você não quer que, por acidente, um aplicativo com bug acabe corrompendo seus dados. Então nunca rode nada diferente num NAS de trabalho. Mas como é meu NAS caseiro onde eu sou a única pessoa com acesso, quis aproveitar o processamento sobrando pra mais coisas.</p>
<p>O software da Synology já suporta Docker nativamente, com interface gráfica facinha de usar e acesso a terminal via ssh se eu quiser rodar manualmente. Então comecei a fuçar coisas pra rodar nele. Em outro episódio vou finalmente explicar mais detalhes sobre Docker e containers, mas por hoje entenda Docker como uma forma fácil, controlada e isolada de instalar software no meu NAS sem grandes riscos.</p>
<p>Vamos recapitular o básico de redes. Toda vez que eu navego em sites, normalmente não me preocupo porque todos oferecem HTTPS, então tudo que trafega entre meu navegador e o site é criptografado. Mas isso é depois que já sei o endereço IP do site. Pra saber o endereço IP baseado no domínio como google.com ou apple.com, alguém precisa me dizer que apple.com é o IP x.y.z. E quem sabe disso é um DNS, lembram?</p>
<p>No terceiro episódio da série eu disse que meu PC ganha endereço IP privado atrás de um NAT graças ao serviço de DHCP do meu roteador, que funciona como um síndico do prédio. Eu ligo meu PC e ele pergunta pro síndico, &ldquo;ow, que endereço ip tem sobrando pra mim&rdquo;, e o DHCP responde &ldquo;tem esse 192.168.1.200, beleza?&rdquo; e aí o PC assume esse endereço. Junto com o endereço vem outras configurações, como o endereço de DNS, que eu disse pra mudar pra ser 1.1.1.1 da CloudFlare ou 8.8.8.8 do Google e evitar usar o DNS que o provedor já pré-configurou pra você caso se preocupe com privacidade, porque DNS de provedor não oferece DoH, que é DNS over HTTPS.</p>
<p>O navegador abre conexão encriptada via TLS com o site depois que sabe o endereço ip do site, mas a requisição pro DNS, perguntando &ldquo;qual ip que é apple.com&rdquo; vai em texto puro, aberto, sem criptografar. E se você deixou o DNS do provedor, ele sabe que sites você navegou, que horas, a partir de qual lugar. Não chega a ser nenhuma informação tão crucial assim, mas pra quem se importa com privacidade, é mais uma informação sobre você que está vazando. Tem gente que diz &ldquo;foda-se&rdquo; e tudo bem, não precisa comentar isso, seu palpite é irrelevante. Estou falando com quem se importa.</p>
<p>Não só isso. A gente usa plugins de adblock pra bloquear propagandas em sites, mas eles não conseguem pegar tudo sempre. Toda ajuda a mais é bem vinda. Por tudo isso resolvi que o primeiro container que queria instalar no NAS seria do aplicativo Pi-Hole. Ele se chama assim porque foi originalmente feito pra instalar num raspberry-pi, que é como eu usava antes. Mas ele é levinho e mover pro NAS significa um equipamento a menos que preciso dar manutenção.</p>
<p>O Pi-Hole é um servidor de DNS e que oferece uma aplicação web pra dar um monitoramento gráfico pra gente. O que eu faço é configurar pra usar o DNS da CloudFlare 1.1.1.1 e ele faz cache de tudo. E ele também se sincroniza com alguns bancos de dados mantidos pela comunidade de domínios reconhecidos como sites de propaganda ou mesmo de malwares. Daí se algum site onde navego pede alguma coisa desses domínios, o Pi-Hole rejeita e não me deixa navegar pra lá.</p>
<p>Daí o que eu faço é colocar o endereço IP do NAS como DNS no DHCP do meu roteador. Daí todos os equipamentos de casa, meu PC, minhas TVs, meus consoles de video game, meu celular, até minhas lâmpadas inteligentes, recebem a mesma configuração e passam a usar o Pi-Hole como servidor de DNS. A outra vantagem é que ele consegue fazer requisições de DoH ou DNS over HTTPS. Lembram que eu falei que toda requisição de pergunta pra DNS vai em texto aberto? Com DNS over HTTPS ele primeiro abre uma conexão segura via HTTPS e manda a pergunta criptografada. Agora tudo é criptografado, dificultando alguém saber meu padrão de navegação na web.</p>
<p>Pra isso precisei de um segundo container de Docker, oferecido pela própria Cloudflare, que é o serviço cloudflared. Lembrando que &ldquo;d&rdquo; no final é de daemon. Não vou entrar em detalhes porque a documentação diz tudo que precisa saber mas o container em si já vem pré-configurado apontando pros servidores de DNS que suportam HTTPS da Cloudflare. Daí eu aponto meu Pi-Hole pra esse daemon e pronto, meu navegador pergunta pro Pi-Hole, que abre uma conexão segura com a Cloudflare e resolve o domínio via HTTPS e nesse percurso, ninguém no meio do caminho, incluindo meu provedor, conseguem saber em quais sites estou navegando. Se gostaram da solução, vou deixar links pro que falei na descrição do video abaixo.</p>
<p>O próximo container eu falei um bocado nas stories do meu Instagram então quem acompanhou sabe. Faz décadas que venho baixando animes, série e filmes. Tem coisas que eu assistia nos anos 80 e 90 e que não existem mais em nenhuma plataforma de streaming de hoje, em particular animes antigos. Tem outros que existem no Japão, mas nunca vieram pro Ocidente. Enfim, tem milhares de conteúdos que se você só assiste Netflix, nunca ficou sabendo que existe. Aliás, a seleção do Netflix é bem ruim e incompleta. Sinto pena de quem só conhece o que tem lá.</p>
<p>Sim, tecnicamente é pirataria. Não estou incentivando mas eu também acho que quem quer piratear vai piratear independente. Avisar que não pode nunca fez nenhum efeito. É que nem foto de câncer em pulmão em caixa de cigarro. Isso nunca funcionou, como ex-fumante eu sempre ignorei completamente aquilo. Cada um é adulto e lide com as consequências. Quer fumar, fume. Só não incomode os outros. Quer piratear, pelo menos pirateia direito.</p>
<p>Que atire a primeira pedra quem nunca baixou nada via bittorrent. Em outro episódio falo sobre ele, mas por hoje, todo mundo pelo menos sabe que é o melhor jeito de piratear. Eu sou da época do LimeWire, eDonkey, Kazaa, Napster e outros, mas bittorrent é animal. E existem softwares open source como Jellyfin, Radarr, Sonarr e Prowlarr que fazem o seguinte.</p>
<p>Jellyfin é um projeto em cima de outro conhecido, o Emby, é basicamente um software que organiza sua mídia, videos, música e tudo mais numa interface parecida com da Netflix, com tocador e tudo mais. Vale a pena pesquisar. Ele vasculha suas pastas de mídia e faz índice, baixa capas dos filmes, notas do Rottentomato, informações de elenco num IMDB e organiza bonitinho pra você.</p>
<p>Mas você ainda tem que ficar baixando as coisas manualmente, renomeando, colocando nas pastas certas. E se desse pra ser tudo automático? É pra isso que servem o Radarr, Sonarr e Prowlarr. Você pode assinar o RSS de bittorrent de séries que ainda estão saindo e o povo de fansub ainda tá legendando, e quando ficam disponíveis, baixa sozinho pra você, renomeia, baixa capa e tudo mais e quando você liga a TV, já está organizado no seu Jellyfin. Dependendo da série que tá acompanhando, se for muito famosa, no mesmo dia que saiu na Amazon Prime, algumas horas depois já aparece no seu Jellyfin. É literalmente como se você tivesse seu próprio Netflix particular, só que melhor.</p>
<p>Hoje não vou mostrar como configura tudo isso mesmo porque eu sou das antigas e uso mais pra baixar coisas que não se encontra fácil. Séries novas eu prefiro assistir direto dum Crunchyroll da vida. Eu assino todos os serviços de qualquer forma. Mesmo filmes que gosto muito compro em Blu-Ray, então quando baixo, normalmente já tinha comprado, só baixo porque é mais fácil pra assistir. E em vez de Jellyfin, prefiro usar o Plex. Ele é meio chatinho com planos pagos e tudo mais, mas é o que tem a interface mais bem feita, melhores apps pra celular e TV, e realmente funciona igual uma Netflix. Dêem uma olhada. Eu tenho séries de anime, ele organiza as temporadas, episódios que já assisti, posso começar a assistir no PC e continuar na TV, perfeito.</p>
<p>O único problema é que ele só funciona dentro de casa, na minha rede local. O Plex é o concorrente do Jellyfin, Emby, Kodi e outros, tem planos pagos se eu quiser acessar de fora, mas digamos que prefira continuar usando só os recursos gratuitos, como faço pra poder assistir meus animes quando estiver viajando, na casa dos meus pais ou em qualquer lugar fora de casa?</p>
<p>E agora vem a última coisa que queria explicar de redes pra vocês. Por causa da pandemia, muita gente ganhou a opção de trabalhar remotamente de casa e aqui o problema é oposto. Como você faz pra poder ter acesso a servidores da empresa por exemplo? É aqui que entra a solução de uma VPN, Virtual Private Network ou redes virtuais privadas.</p>
<p>VPN existem diversos tipos e de novo,não sou especialista em redes então quero compartilhar com vocês só o básico. Até aqui a gente sempre assume que redes funcionam com dispositivos de hardware como placas de rede, que fisicamente tem um buraco pra espetar um cabo ethernet com conector RJ-45. Ou uma placa Wifi que você conecta num ponto de acesso. Mas em ambos os casos se trata de dispositivos físicos de hardware.</p>
<p>Pro sistema operacional falar com esses e outros dispositivos eles precisam de um pedaço de software chamado driver. Esses drivers normalmente são carregados juntos com a kernel do seu sistema operacional, seja Linux, Windows, Mac, Android, iOS, não importa. Se tem um hardware, a kernel precisa de um driver. Num Windows você enxerga os dispositivos no gerenciador de dispositivos e pode ver detalhes, tentar atualizar os drivers por lá e tudo mais. Em Linux pode usar comandos como <code>lspci</code> pra listar os dispositivos ligados no bus ou barramento de PCI. Enfim, o importante é a kernel conseguir enxergar todos eles.</p>
<p>Mas tecnicamente pro sistema operacional, meio que não importa se o hardware existe de verdade, contanto que exista um driver que diz que existe. Presta atenção. Um driver pode mentir pro sistema. É o que acontece quando se instala uma máquina virtual com um VirtualBox da vida. Dentro dela manda instalar o Windows e ela vai instalar drivers que dizem pra esse Windows que tem uma placa gráfica, embora não tenha hardware disso. O driver está emulando um hardware e desenhando na janela do VirtualBox. O Windows virtualizado acha que é uma placa de verdade. Ele não tem como saber a diferença, ele tá dentro da Matrix. E isso acontece com todos os dispositivos. Dentro da máquina virtual os drivers dizem que tudo existe, mas na prática os drivers conversam com o VirtualBox do lado de fora e assim possibilita compartilhar meu teclado de verdade, mouse, som e tudo mais.</p>
<p>E se fosse possível eu ter placas de redes virtuais? E aqui entra os conceitos de TUN e TAP. Lembra a idéia que pacotes de rede são divididos em camadas? No modelo OSI temos camada 1 que é física, camada 2 que é data link, que é o que ethernet se preocupa, tem a camada 3 que é network que é onde a parte IP de TCP/IP se preocupa. Na prática TUN é um dispositivo de rede virtual que atua na camada 3 de rede e TAP cria um dispositivo de rede virtual que atua na camada 2. Isso é uma tecnicalidade que pra gente não importa muito, mas só pra saber. Nos diversos componentes de uma infraestrutura de rede, cada um lida com pacotes em camadas diferentes. A gente que é programador acaba só lidando com camada de aplicação, mas coisas como placas de rede ou switches atual da camada 3 pra baixo, não se importando com as camadas superiores.</p>
<p>Numa máquina virtual, se instala um driver de rede, pra simular que existe rede, e o Windows virtualizado fala com uma placa que não existe, um TAP, que por baixo cria um bridge, ou ponte, entre essa placa virtual e a placa real do PC que é host. Graças a essa placa virtual, dentro da máquina virtual ele ganha um IP tipo 172 ponto alguma coisa. E tudo que é roteado por essa placa, chega na placa real e é roteado pra internet. Na prática é como se existisse uma nova sub-rede privada dentro da rede interna.</p>
<p>Eu uso VMWare na minha máquina, e se abrir as configurações avançadas de rede, olha só o que eu acho, várias placas de rede. Quase todas virtuais. Essa Ethernet aqui é a minha placa de 10 gigabits de verdade que falei antes. Essa outra é a de 2.5 gigabit que vem embutida na placa mãe. Só essas são as de verdade. Mas essas outras aqui são placas virtuais pra coisas como minhas máquinas virtuais. Mesmo num Docker você pode subir container em dois modos de rede, host ou bridge. Se for host, vai subir como se fosse qualquer outro programa no seu PC, usando as mesmas configurações de rede local. Mas se escolher bridge, o container vai falar com uma placa de rede virtual TAP, que vai criar uma sub-rede separada, e rotear os pacotes por essa ponte até meu PC host.</p>
<p>A vantagem de usar bridge é o seguinte. Digamos que eu suba dois containers de web, que internamente vai carregar um programa servidor web que quer se conectar na porta 80. Ambos estão em modo host. O primeiro vai carregar e se pendurar na porta 80 do meu PC. Mas o segundo container vai falhar, porque vai tentar se conectar na porta 80, mas já tá ocupado, então o sistema operacional recusa e rejeita o pedido do container. Agora, se rodar ambos em modo bridge, eles ganham uma rede vazia com outro range de endereços IP estilo 172 alguma coisa que do lado de fora ninguém enxerga. É como sua rede 192.168 local que do lado de fora ninguém enxerga por causa do NAT.</p>
<p>Cada container, nessa rede 172 privada, consegue se ligar na porta 80 interna do container, porque é como se estivessem numa máquina vazia com uma placa de rede só pra eles. Mas do lado de fora podemos mapear a porta 80 de dentro do container com a porta 8080 de fora do meu PC, e o segundo container sube na porta 80 interna e podemos mapear pra porta 8081 do mesmo PC. Ambos os programas acreditam que estão sozinhos, conseguem ligar em portas 80 virtuais, mas do lado de fora estamos remapeando as portas virtuais pra outras portas livres de verdade. Máquinas virtuais e containers são formas de eu mentir pros programas acharem que estão rodando numa máquina sozinhos. E do lado de fora eu controlo o que quero expor pra elas. De novo, containers de Docker estão na Matrix e não sabem disso.</p>
<p>Não são só tecnologias de virtualização e containers que usufruem de placas de rede virtuais. E agora entra os TUN que significa tunel. A parte que eu falei em outros episódios que não queria explicar é roteamento e agora vou mostrar só o mínimo pra vocês entenderem onde quero chegar. Eu mostrei já no episódio 3 desse mini-série como pacotes conseguem sair da minha rede pelo roteador do provedor e ir pra internet. E mostrei rapidamente uma tabela de rotas. Todo sistema operacional com TCP/IP mantém uma tabela de rotas.</p>
<p>Ela que diz pra mandar todo tráfego pro roteador, que costuma ter o endereço IP mais baixo da sub-rede interna como 192.168.1.1 ou 10.0.0.1 ou algo assim. No meu caso é esse TP-Link que deixo do lado do modem. Ele recebe os pacotes e também tem uma tabela de rotas como essa só que no caso dele a saída padrão é mandar pro modem, e o modem por sua vez passa pra frente pra outros servidores na internet. Essa é a tarefa de um default gateway, a porta padrão de saída.</p>
<p>Lembra no episódio anterior onde mostrei o exemplo onde estou numa rede de empresa rígida que restringe uso da internet e fecha quase todas as portas e nem deixa eu navegar em todos os sites? E como usando um servidor da Digital Ocean eu furei um buraco no firewall e abri um túnel seguro encriptado usando SSH? Uma VPN é um programa diferente de SSH mas em conceito funciona parecido, furando buraco em firewall ou no meu caso no CGNAT da Vivo, se conectando num servidor de fora e abrindo um túnel.</p>
<p>Mas em vez de eu abrir o túnel manualmente com o comando SSH uma VPN costuma instalar uma placa de rede virtual TUN. No caso de Linux cria um dispositivo como <code>/dev/tun</code>. Esse dispositivo ganha um endereço IP inválido de internet como 172 alguma coisa. E o software de VPN faz mais uma coisa, cria entradas nessa tabela de roteamento que falei.</p>
<p>Dá pra configurar de varias formas. Por exemplo, posso adicionar na tabela que toda vez que do meu PC de casa eu tentar acessar um IP que só existe na rede da empresa, os pacotes são direcionados não pra minha placa de rede de verdade, mas sim pra esse dispositivo virtual TUN que na realidade é um tunel criptografado ligado no servidor de VPN da empresa.</p>
<p>Vamos dar um exemplo. Como já falei existem dezenas de formas diferentes de VPN. Existe OpenVPN, existe Microtik, soluções baseadas em Wireguard como Tail Scale, mas eu escolhi usar a Zero Tier, que achei simples, fácil de usar, e resolve o meu problema de expor meu Plex de anime fora de casa. Pra começar crio uma conta lá e uma nova rede. Essa rede tem um código identificador que, obviamente estou escondendo uma parte. Ela começa com a84.</p>
<p>Agora vou no meu NAS e instalo o docker da ZeroTier, que é um programa cliente que vai se conectar no servidor deles. Seja no meu NAS, seja num PC com Linux, primeiro preciso carregar o driver pra criar o tal dispositivo virtual de TUN. Eu me conecto no NAS via SSH e, como root, crio o seguinte script:</p>
<p><code>echo -e '#!/bin/sh -e \ninsmod /lib/modules/tun.ko' &gt; /usr/local/etc/rc.d/tun.sh chmod a+x /usr/local/etc/rc.d/tun.sh /usr/local/etc/rc.d/tun.sh ls /dev/net/tun /dev/net/tun</code></p>
<p>Estou só escrevendo um arquivo com o comando insmod e o caminho pro módullo de tun pra kernel carregar. Na sequência estou dando permissão pra esse script executar. Eu falei nos videos de Ubuntu pra estudar chmod e permissões lembram? Daí rodo da primeira vez manualmente, no próximo boot já vai carregar sozinho. No final checo se o dispositivo virtual apareceu com o comando ls pra listar e de fato, tá lá. Agora tenho o equivalente a uma placa de rede virtual, de mentira.</p>
<p>Ainda do terminal, agora baixo e rodo o container da zerotier que é um programa cliente que vai se conectar no servidor deles, furando um buraco em firewall e CGNAT.  Relembrando, o conceito de furar é porque do lado de fora, meu PC é inacessível por conta de firewall ou NAT do provedor. O servidor da ZeroTire não tem como abrir uma conexão comigo. Mas eu consigo me conectar com o servidor deles. Pra entrar está bloqueado, mas pra sair está liberado. Agora que eu abri a conexão, o servidor da ZeroTier consegue falar de volta comigo por essa mesma conexão. Isso que significa &ldquo;furar&rdquo;.</p>
<p><code>docker run -d           \ --name zt             \ --restart=always      \ --device=/dev/net/tun \ --net=host            \ --cap-add=NET_ADMIN   \ --cap-add=SYS_ADMIN   \ -v /var/lib/zerotier-one:/var/lib/zerotier-one zerotier/zerotier-synology:latest</code></p>
<p>Nessa configuração eu faço esse container enxergar o dispositivo TUN que acabamos de criar, configuro rede pra ser host, ou seja, sem ser ponte, configuro um volume externo pra configuração que é onde o container vai gravar coisas que ele precisa e que não podem desaparecer se o container bootar. E é isso. Quando terminar de subir vai se conectar e vai ganhar um número identificador da zero tier.</p>
<p>Ainda pelo terminal, posso mandar comandos pra esse container e o que preciso fazer é mandar se conectar naquela rede da ZeroTier que criei que começa com a84 lembra? É um numerozão. Se você se registrar no site e criar uma nova rede, obviamente o número vai ser diferente. Então vamos conectar esse cliente que instalei via Docker com minha rede privada no servidor da ZeroTire.</p>
<p><code>docker exec -it zt zerotier-cli join e5cd7a9e1cae134f</code></p>
<p>Pronto. Do lado do NAS é só isso. Agora vou no meu celular e no meu PC e baixo o aplicativo da ZeroTier e instalo nos dois. É o equivalente ao docker que instalei no NAS, um aplicativo cliente da ZeroTire. No meu celular abre esse aplicativo feioso aqui. Mesma coisa, eu clico em Add Network e cadastro a mesma rede que começa com a84 e já posso conectar nela. No meu PC depois que instalei e rodei, aparece aqui embaixo na barra de tarefas. Mesma coisa, me junto na mesma rede. Note que em cada dispositivo diferente ele tem um outro número identificador. No meu celular lá embaixo tem esse outro número que eu escondi que começa com <code>eeb</code>, é o identificador do meu celular. E no app do PC mesma coisa, ele tem esse &ldquo;My Address&rdquo; que começa com <code>3d7</code>.</p>
<p>Pronto, todo mundo configurado e conectado na mesma rede da minha conta na Zero Tire. Último passo, volto pro site, abro a configuração da rede <code>a84</code> e lá embaixo vai ter uma lista de dispositivos que se conectaram. E olha só, meu PC que começa com <code>3d7</code> apareceu, meu NAS que começa com <code>d46</code> aparece e meu celular que começa com <code>eeb</code> apareceu. Agora só preciso autorizar todos com esses checks do lado e pronto. Tá todo mundo conectado na mesma rede privada. Nessa nova sub-rede todo mundo tem endereços 172.22 ponto alguma coisa. Por exemplo, meu NAS ganhou o endereço 172.22.61.42, o mesmo vale pra todos os dispositivos, cada um com um endereço IP. Esses endereços não são válidos na internet, só dentro dessa rede privada.</p>
<p>Agora vamos entrar no meu celular com o ZeroTier desligado. Se eu abro o app do Plex, que não pago a assinatura, olha que tristeza. Vazio. Vamos fechar, abrir o ZeroTier e conectar na rede dele. Agora abro o app do Plex de novo e voilá!! Olha aqui minhas bibliotecas, e todos os meus animes bonitinho aparecendo! Estou via 4G, fora do Wifi de casa. Estou conseguindo falar com o docker de Plex no meu NAS porque estamos conectados na mesma sub-rede privada da ZeroTier via VPN.</p>
<p>Mesma coisa no meu notebook. Se estivesse fora de casa, em vez de usar o endereço IP da minha rede que é 192.168 ponto alguma coisa, ligo o ZeroTier e no navegador digito aquele endereço 172.22 porta 32400 e olha só, estou navegando no Plex de dentro do NAS, mesmo estando fora da minha rede. Mas como isso é possível? E aí vem o truque que a ZeroTier e outros softwares de VPN fazem. Vamos abrir o terminal do Windows.</p>
<p>Digitando <code>route print -4</code> pra mostrar rotas de ipv4, tenho rotas pra todo endereço 192.168 interno da minha rede. É com essa tabela que ele sabe pra onde mandar pacotes. Mas depois que habilito o ZeroTier, podemos rodar o comando de route de novo e vemos que adiciona novas entradas nessa tabela. Olha só, ele passa a reconhecer endereços 172 ponto alguma coisa e o default gateway é esse dispositivo 172.22.121.226. Vamos de volta na janela de configurações avançadas de rede do Windows, onde lista dispositivos de rede e olha aqui o dispositivo virtual TUN da ZeroTier. É como se fosse uma placa de rede normal, mas ela não existe, é o driver mentindo pro Windows, ela é virtual.</p>
<p>Botão direito e propriedades. Vamos ver que configurações de IPV4 ele tem e olha o endereço, 172.22.121.226, que é o default gateway que registrou na tabela de roteamento. Por isso que toda vez que tento navegar, por exemplo, pro meu Plex, com o endereço 172.22.61.142, o sistema pega os pacotes, manda pro dispositivo virtual, que por sua vez é só um software cliente que tá conectado no servidor da ZeroTier. Ele faz forward desses pacotes, ou seja, redireciona pra fora, pra ZeroTier. E no mesmo servidor está conectado o cliente de docker que instalei no NAS, que recebe os pacotes por essa conexão.</p>
<p>O que eu tinha mostrado de abrir túnel SSH no episódio anterior é o modelo que se chama de client to site ou acesso remoto. Mas nesse caso eu tenho dois clientes atrás de uma rede privada conversando como se estivessem na mesma rede, ambos com clientes conectados num servidor de VPN, é o modelo site to site ou gateway to gateway. Depois estudem esses termos. Mas na prática, essa solução da ZeroTier cria um túnel criptografado entre meu celular ou meu notebook com o NAS usando um serviço no meio que permite burlar as restrições de cada uma das redes, seja firewalls, seja CGNATs.</p>
<p>Isso que mostrei da ZeroTier é uma solução boa quando você precisa acessar um servidor específico na sua casa ou na empresa, dentro de uma rede privada virtual. Mas isso deve soar um pouco diferente do que você já deve ter ouvido falar de VPN. Se eu tentar navegar na Web, esse tráfego não vai pra ZeroTier. Só endereços 172.22 que vão ser enviados pelo túnel, todo o resto é roteado normalmente pela sua rede local, sem mudar nada.</p>
<p>O que você conhece de VPN é o contrário, que todo tráfego Web passa pelo túnel da VPN e sai pelos servidores deles. Muita gente que vê propaganda de VPN pensa neles como uma forma de burlar o filtro de região de sites como Netflix ou Amazon Prime pra conseguir assistir séries que não passam no Brasil. Ou o uso mais correto que é conseguir navegar com segurança mesmo quando estiver em redes não-confiáveis como o Wifi do Starbucks.</p>
<p>Falando sobre redes públicas, obviamente, nunca, jamais, digite sua senha de qualquer coisa enquanto estiver conectado em rede da faculdade, do aeroporto, do starbucks ou o que for. Lembra no terceiro episódio da minissérie quando falei sobre o problema de hubs, modo promíscuo e tudo mais? Você não sabe quem está escutando seus pacotes numa rede aberta. Sempre que estiver numa rede pública, use uma VPN pra acessar coisas como seu banco. Assim você garante que vai estar tudo criptografado e mesmo se tiver alguém fuçando seus pacotes de rede, não vai conseguir ler nada. Se usa um celular android da Samsung, eles já tem embutido um serviço de VPN barato que ativa automaticamente quando se conecta em Wifi público. Recomendo testar se puder, mas qualquer um serve. NordVPN, ExpressVPN, qualquer um.</p>
<p>O que acontece quando você usa um cliente de VPN? Por acaso eu uso o NordVPN. Ele oferece opção de se conectar em servidores que eles mantém pelo mundo todo. Então vou me conectar num dos Estados Unidos. E pronto, conectado. Agora navego na web normalmente e parece que nada mudou. Vamos praquele site que mostra qual é meu ip, what is my ip. E olha só, fala que o endereço que ele enxerga de mim é um endereço nos Estados Unidos. Justamente porque todos os pacotes da minha máquina estão sendo tunelados pra um servidor da NordVPN nos Estados Unidos e de lá indo pros sites que quero navegar. Do ponto de vista dos sites, é um computador nos Estados Unidos que tá conectado.</p>
<p>Foi isso que fizemos com SSH no episódio anterior, lembra? Local Forwarding? É a mesma coisa, só que mais fácil de usar em vez de ter que digitar aquele comando grande no terminal. Mas o que de fato tá acontecendo? Pra isso vamos abrir a tabela de rotas de novo na linha de comando com <code>route print</code>. Veja a primeira linha e a última linha. Estou simplificando mas ele basicamente diz pra qualquer endereço IP que quero acessar de 0 até 255, pra mandar pro default gateway 192.168.1.1 que é meu roteador como mostrei no terceiro episódio. Mas agora vamos ver quando estou conectado na NordVPN.</p>
<p>Olha o que apareceu. Logo no começo e lá no final da tabela de rotas, agora tem novas entradas apontando pra interface 10.5.0.2. E quem diabos é isso? Ainda na linha de comando digitamos <code>ipconfig /all</code> no Windows pra ver todos os dispositivos de rede e olha só. Tem um dispositivo NordLynx Tunnel. Um dispositivo virtual de rede. O Windows acha que é uma placa de rede, mas não tem hardware por baixo. Ele cria um túnel e roteia os pacotes pela minha placa de rede de verdade, só que criptografado e sempre pro servidor que eu conectei na rede da Nord.</p>
<p>Como que o sistema operacional decide se deve mandar pacotes pra rota anterior do meu roteador 192.168 ou mandar pra rota nova da VPN em 10.5? É por causa desse número de métrica aqui no fim, que é um número de prioridade. Ele vai mandar pro menor número. E de curiosidade, esse “on-link” significa que são endereços que podem ser resolvidos localmente, sem precisar rotear pra fora. Claro, são dispositivos virtuais locais, estão na mesma máquina. Tem um tanto de outras rotas aqui mas é porque meu PC tem máquinas virtuais instaladas, e elas também tem placas de rede virtuais que precisam ser roteadas. Na sua máquina, vai ser uma tabela diferente, mas o princípio é o mesmo, a primeira coisa que vai ter é a rota pro seu roteador. É assim que os pacotes saem da sua máquina e sabem pra onde ir.</p>
<p>Toda vez que você aperta o botão pra conectar na VPN, o que acontece por baixo dos panos é que ele adiciona essas novas entradas na tabela de roteamento. Quando desconecta, apaga essas linhas, aí seus pacotes voltam a trafegar pelo seu roteador como sempre. É isso que um cliente de VPN faz, cria um dispositivo virtual e reconfigura sua tabela de rotas pra desviar os pacotes por esse dispositivo virtual.</p>
<p>Mas voltando pra conversa da ZeroTier e meu servidor de anime. Sem uma solução de VPN, todos esses dispositivos não conseguiriam se conectar através da internet, só seriam acessíveis na mesma rede local, no caso, dentro da minha casa. Mas agora meu NAS fica sempre conectado na rede da ZeroTier, esperando pacotes virem de lá até a placa de rede virtual TUN que criamos, e a partir daí, pro resto do sistema e aplicações, é como se fosse uma conexão local na mesma rede. E é assim que muitas empresas conseguiram ter funcionários trabalhando de casa, em redes restritas atrás de CGNAT, abrindo conexão em servidores que estão nas empresas, de forma segura, porque todas as soluções de VPN são criptografadas.</p>
<p>Por muito tempo se usou protocolos como PPTP que é Point to Point Tunneling Protocol, pra criar túneis ponto a ponto seguros, mas se não estou muito errado ninguém mais usa PPTP, já é obsoleto e acho que considerado inseguro. Se esbarrar num tutorial com PPTP pode pular porque deve ser velho. Muita gente ainda usa soluções derivadas de OpenVPN, que é um software complexo, escrito em cima de outro software complexo e controverso que é o OpenSSL. Mas no mundo Linux pelo menos, acho que povo tá migrando pro Wireguard, com código mais moderno e mais leve, que agora vem direto na kernel do Linux. Eu mesmo não sei muitos detalhes, mas se o assunto de redes e segurança te atrai, definitivamente estude Wireguard.</p>
<p>Como disse antes, a ZeroTier usa alguma solução proprietária que parece que é criptograficamente um pouco menos seguro que Wireguard, mas não significa que seja inseguro. Se quiser um serviço equivalente que use Wireguard por baixo, tem a Tail Scale, que funciona mais ou menos do mesmo jeito que mostrei. E no caso de soluções pra empresas, acho que muitos usam os produtos da Microtik. Só lembrando que nada disso sai de graça, e não digo no custo de assinatura dos serviços. Pacotes tendo que passar por outras máquinas intermediárias no meio do caminho, em túnel criptografado, adiciona tempo de processamento e tempo de rota, por isso seus pacotes vão chegar no destino levando mais tempo e por isso conexões atrás de VPN tem velocidade menor e latência maior. O que varia nos diversos serviços por aí é a oferta de servidores próximos de você que podem ajudar a diminuir essa latência. Pra navegar na web não faz diferença mas pra coisas como gamers, latência é importante.</p>
<p>Enfim, no frigir dos ovos consegui fazer o que queria. Configurei um NAS super rápido, o suficiente pra conseguir editar video pesado direto dele em rede de 10 gigabits local e consigo acessar coisas do meu NAS como meu Plex, mesmo estando fora de casa, usando uma VPN da ZeroTier. Tudo isso foi super simples de configurar porque já estou mais que familiarizado com VPNs, túneis e tudo mais. Do momento que decidi usar o ZeroTier, já estava com tudo configurado em todos os dispositivos em uns 15 minutos.</p>
<p>Mesmo você não sendo alguém de infraestrutura, é importante saber esses conceitos porque em pouquíssimo tempo dá pra criar soluções pra problemas simples como o meu, que era expor meu NAS na internet de forma segura. Eu falo como é impressionante o quanto se aprende quando brinca de pirataria. Mesmo gamers já tiveram que lidar com esse tipo de tecnologia. Quem aqui nunca arrancou os cabelos tentando fazer o Hamachi funcionar? Pra quem não sabe Hamachi é que nem o NordVPN, um serviço de VPN da LogMeIn. Por alguma razão gamers tendem a usar Hamachi.</p>
<p>Eu poderia ter criado tudo na mão, um servidor particular numa Amazon ou Digital Ocean, um túnel local forwarding com SSH ou mesmo uma VPN com Wireguard, tudo configurado via linha de comando no terminal, na mão. E é uma boa opção pra quem quer aprender. Mas eu queria algo plug and play. Como exercício deixo de lição de casa pra vocês que tem um PC em casa e um notebook, criar uma VPN usando Wireguard ou SSH, sem usar um software mais fácil como ZeroTier ou NordVPN. Acho que só o tanto de documentação pra ler e tentativa e erro que vão ter que fazer vai valer mais que muito curso de redes que tem por aí.</p>
<p>E com isso finalmente consegui concluir a mini-série de redes. Em seis episódios mostrei só o básico do básico do que considero introdução ao assunto de redes e internet. Ainda tem bem mais coisas e muito mais detalhes, como VLANs. Mas vou deixar pra falar sobre essas coisas mais pra frente. Por agora cansei de falar de redes. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais!</p>
]]></content:encoded><category>homelab</category><category>redes</category><category>armazenamento-e-backup</category><category>akitando</category></item><item><title>[Akitando] #99 - Quebrei 3 HDs: Entendendo Armazenamento</title><link>https://akitaonrails.github.io/2021/06/09/akitando-99-quebrei-3-hds-entendendo-armazenamento/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/06/09/akitando-99-quebrei-3-hds-entendendo-armazenamento/</guid><pubDate>Wed, 09 Jun 2021 13:21:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/lxjBgxmDZAI"
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;Nessa nova mini-série quero explicar os diversos conceitos sobre armazenamento e neste primeiro episódio vou explorar um pouco sobre RAID e mostrar como antigamente a gente mapeava dados em disco usando o sistema de cilindros-cabeças-setores e como as coisas evoluiram hoje.&lt;/p&gt;
&lt;h2&gt;Conteúdo:&lt;span class="hx:absolute hx:-mt-20" id="conteúdo"&gt;&lt;/span&gt;
&lt;a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;02:17 - Drobo - o que é RAID?&lt;/li&gt;
&lt;li&gt;04:11 - RAID-5&lt;/li&gt;
&lt;li&gt;05:56 - Synology&lt;/li&gt;
&lt;li&gt;06:52 - Resolvendo um problema catastrófico de HDs&lt;/li&gt;
&lt;li&gt;09:23 - Você ainda não entende HDs/RAID-0&lt;/li&gt;
&lt;li&gt;11:21 - o começo: instaladores de Linux&lt;/li&gt;
&lt;li&gt;13:54 - CHS&lt;/li&gt;
&lt;li&gt;17:28 - Blocos&lt;/li&gt;
&lt;li&gt;21:18 - Velocidades&lt;/li&gt;
&lt;li&gt;24:09 - fdisk&lt;/li&gt;
&lt;li&gt;25:35 - LBA&lt;/li&gt;
&lt;li&gt;26:54 - TBC&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal, Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/lxjBgxmDZAI"
    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>Nessa nova mini-série quero explicar os diversos conceitos sobre armazenamento e neste primeiro episódio vou explorar um pouco sobre RAID e mostrar como antigamente a gente mapeava dados em disco usando o sistema de cilindros-cabeças-setores e como as coisas evoluiram hoje.</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</li>
<li>02:17 - Drobo - o que é RAID?</li>
<li>04:11 - RAID-5</li>
<li>05:56 - Synology</li>
<li>06:52 - Resolvendo um problema catastrófico de HDs</li>
<li>09:23 - Você ainda não entende HDs/RAID-0</li>
<li>11:21 - o começo: instaladores de Linux</li>
<li>13:54 - CHS</li>
<li>17:28 - Blocos</li>
<li>21:18 - Velocidades</li>
<li>24:09 - fdisk</li>
<li>25:35 - LBA</li>
<li>26:54 - TBC</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>Se você assistiu o episódio sobre meus Backups que fiz lá no começo do canal sabe que costumo ser um pouco obsessivo sobre armazenamento. Eu cheguei a perder algumas coisas que tinha em disquetes e cartuchos de zip drive, então meus primeiros projetinhos, coisas da faculdade, eu perdi. Desde então tento manter múltiplas cópias das coisas mais importantes.</p>
<p>Eu tenho backupsdo meu Outlook do ano 2000 por exemplo, todos os meus emails de mais de 20 anos atrás. Mesmo os atuais que ficam no Gmail eu baixo offline com o Thunderbird pra ter uma cópia local. Nunca confie em serviços cloud, especialmente se forem gratuitos. Você sempre vai receber exatamente o que paga. Um belo dia sua conta tá suspensa, por erro ou porque você foi imprudente e não habilitou autenticação de 2 passos nem manteve senhas fortes. Vai acontecer, é só questão de quando.</p>
<p>Mesmo usando coisas como Dropbox, Google Drive e Microsoft OneDrive - sim, eu uso os três - tenho uma cópia nos HDs do meu PC, outra cópia no meu laptop, e outra nos meus NAS. A grande vantagem que eu vejo hoje em dia comparado com os anos 90 ou anos 2000 é que armazenamento barateou muito.</p>
<p>No primeiro dia de 1995, ano que entrei na faculdade, o custo médio por 1 gigabyte era nada menos que 850 dólares, em dólares dos anos 90! Só 1 giga! 5 anos depois, no último dia de 1999 o mesmo 1 gigabyte agora custava em média 15 dólares. E em 2009 custava só 7 centavos. Hoje em dia 1 terabyte é menos de 100 dólares.</p>
<p>Hoje eu quero discutir um pouco sobre armazenamento. Na nossa saga pela máquina de Turing e a linguição de bytes, eu expliquei como bytes mais ou menos são organizados na memória, estruturas de árvores, stack, heap e tudo mais. Mas memória RAM é volátil. Toda vez que você desliga o computador essa memória foi pro saco.</p>
<p>Pra ter algo persistente agora precisamos entender como armazenar nossa fita de bytes numa mídia sólida, como os atuais drives de SSD NVME. Muita gente pensa que a única coisa importante sobre drives é que a gente particiona e tem uma estrutura de diretórios e arquivos, e só lê arquivos e grava arquivos e acabou a história. Só que é muito mais que isso. Então vamos entender.</p>
<p>(&hellip;)</p>
<p>Por muitos anos, desde 2008, eu mantinha meu backup num dispositivo chamado Drobo. Quando saiu parecia excepcional, hoje em dia é bem ultrapassado, mas eu ainda tenho um Drobo 5N que é um NAS. NAS é sigla pra Network Attached Storage ou armazenamento anexado na rede que é um nome mais bonito pra um PC na rede com vários HDs.</p>
<p>Esse Drobo 5N tem 5 baias pra HDs. E eu tenho 5 HDs mecânicos de 4 terabytes cada. Isso dá um total de 4 vezes 5 que é 20 terabytes, mas eu só posso usar pouco menos de 15. Se você nunca ouviu falar em redundância pode achar isso estranho, pra onde foi os outros 5 teras??</p>
<p>O Drobo e outros NAS costumam usar alguma variação do conceito de RAID que é sigla pra Redundant Array of Inexpensive Disks ou um arranjo redundante de discos baratos. O problema é o seguinte, HDs tradicionais mecânicos tem o problema de serem mecânicos, terem partes móveis. O HD tem discos um em cima do outro formando um cilindro e cada um tem cabeças de leitura, um pra cada lado do disco, ligadas num motor pra movimentar essas cabeças a milímetro de distância dos discos.</p>
<p>Eu lembro que nos anos 90 a gente tinha que dar um comando no MS-DOS chamado <code>park</code> que é estacionar. Era um comando pra forçar o estacionamento das cabeças. Pense em cabeças como a agulha que desliza sobre um disco de vinil de música. Apesar das cabeças num HD não tocarem fisicamente os discos e sim usar magnetismo pra ler os bits, se você esbarrasse no computador podia arriscar dessa cabeça tocar no disco, e aí podia estragar. Por isso era boa prática mandar estacionar antes de desligar o computador. Hoje em dia eles são mais inteligente e já fazem isso sozinhos.</p>
<p>O ponto é que existem muitas peças móveis, e tudo que tem peças móveis eventualmente vai falhar. Seja por problema de fabricação, seja só por tempo de uso. Tudo deteriora. E quando isso acontecer você pode perder acesso aos seus dados.</p>
<p>Existem variações na implementação de estratégias de RAID, mas no modelo clássico RAID-5 tem esse número porque se espera ter pelo menos 5 HDs. 4 vão estar ligados e um 5o fica offline de reserva, desligado mesmo e por isso chamamos de Cold Spare. Apesar de ser 4 HDs ligados ao mesmo tempo, você enxerga como se fosse um único drive. Um único volume pra ser mais exato.</p>
<p>Os dados, mais corretamente os blocos, são divididos entre os 4 drives e cada HD mantém uma parte da cópia do outro drive. Sabemos que HDs mecânicos eventualmente vão morrer. Mas vamos apostar que em 4 drives a chance de todos morrerem ao mesmo tempo é baixa. Normalmente só um  vai morrer primeiro. Se isso acontecer os outros 3 drives vão ter cópias dos demais e seu volume vai sobreviver intacto, sem perder nenhum dado.</p>
<p>Isso te dá tempo pra pegar aquele 5o HD de reserva, o cold spare pra substituir pelo que falhou. Daí o controlador do RAID vai reconstruir o novo HD com os dados redundantes dos outros três. Por isso que apesar de eu ter um total de 20 tera nos meus 4 drives, ele só me deixa usar 15. Porque os outros 5 tera são gastos com redundância. São os dados de paridade. E eu já tive esse problema de HDs morrerem várias vezes e toda vez fui salvo por esse sistema.</p>
<p>Em data centers isso é crucial. Imagina que um data center como os do Google ou Amazon da vida tem milhares de HDs. Todos eles vão falhar uma hora. Não de uma vez, mas aos poucos. Com essa quantidade massiva, falha alguns por dia, especialmente porque ficam ligados e funcionando dia e noite sem nunca desligar. HDs usados em data centers são diferentes do que a gente usa nos nossos PCs caseiros. HDs como os Seagate Iron Wolf Pro, feitos pra serem mais robustos e resistentes. HD doméstico quebra mais fácil, por isso é mais barato.</p>
<p>No meu caso, eu precisava de mais de 15 teras. Eu tenho duas opções: uma seria trocar os HDs de 4 teras por outros maiores de 6 ou 8 teras. Em vez disso eu queria testar os produtos de NAS da Synology e por isso comprei um modelo de entrada que é o DS420j, e já digo que achei razoável.</p>
<p>Sem me alongar muito, nada de errado com esse modelo, ele faz exatamente o que diz que faria e acho que se for só pra backup tá bem mais que suficiente. Mas não dá pra usar ele no dia a dia com arquivos grande, porque ele é limitado pela velocidade dos HDs mecânicos e não tem opção pra aumentar a RAM e nem colocar um SSD NVME como cache. Uma versão como o DS920+ já tem opção pra esses upgrades e isso aumentaria bastante a velocidade: mais cache. E mesmo assim, se eu quisesse editar video direto dele precisaria ser um outro modelo que também tenha saída de rede 10Gigabit em vez do tradicional 1 gigabit. Lembrem-se disso quando estiverem escolhendo um.</p>
<p>Esses dias eu tive um problema. Uns dois anos atrás eu pedi pro meu sócio trazer HDs pra mim quando ele foi pros Estados Unidos. O problema de HDs, como eu falei, é que  são frágeis. Se você jogar os HDs na na mala e não proteger muito bem, com todo o balanço da viagem, mala sendo jogada dentro do avião, é quase certeza que vai estragar.</p>
<p>Dito e feito, eles nunca se comportaram como deveriam. Eu usei eles no Drobo, mas aí o Drobo reclamou que os HDs tavam falhando. Resolvi comprar mais 4 HDs novos no Mercado Livre e fui trocando. Daí esses HDs massacrados ficaram sobrando. Quando comprei o Synology eu pensei: ok, eu sei que eles não tão 100% mas vamos tentar de novo. Se falhar não vai ser todos de uma vez.</p>
<p>Funcionou por alguns meses, esses dias o Synology desligou tudo sozinho. Tentei religar e entrar no painel de administração e batata, 3 dos HDs tavam marcados com defeito. Três de uma vez só é foda. Mas na prática os próprios HDs é que sinalizam pro servidor que tão começando a falhar e que é arriscado continuar, não quer dizer que quebraram totalmente. Ainda bem que hoje em dia os HDs são mais inteligentes, eles tem a funcionalidade de SMART que é sigla pra Self-Monitoring, Analysis and Reporting Technology ou seja, Auto-monitoramento, análise e tecnologia de relatório. O HD sabe que vai morrer a qualquer momento.</p>
<p>Enfim, lá vou eu encomendar mais HDs no Mercado Livre. Eles acabaram de chegar, dessa vez comprei Western Digital RED que são os equivalentes em robustez aos Seagate Iron Wolf. Qualquer uma das duas linhas eu recomendo. O problema é que 3 HDs de uma vez começaram a notificar problema. Num dia normal onde no máximo só um falha, bastaria trocar só ele e os outros três iriam ser usados pra reconstruir o quarto.</p>
<p>Como os três falharam, o próprio Synology se auto-desligava inteiro por garantia. Então seria arriscado tentar o processo de reconstrução dele mesmo. Eu queria uma forma mais direta de fazer um clone de um HD direto pro outro. Por isso procurei um sistema de clonagem e achei um genérico que parece que funciona, da marca Mymax e você acha no Mercado Livre.</p>
<p>É um dispositivo que não precisa estar ligado no computador pra funcionar. Você coloca o HD original no slot A, o novo HD no slot B, só garante que o novo HD é do mesmo tamanho ou maior que o original, aperta um botão e ele sozinho vai fazer um clone, bloco a bloco. E isso é importante. Esse dispositivo não tem a mínima idéia de que sistema de arquivos o HD tem, se é NTFS de Windows ou ext4 de Linux. Ele não precisa carregar um filesystem e copiar arquivo a arquivo, isso seria o arriscado. Em vez disso ele vai fazer um clone de verdade, copiando bloco a bloco de um HD pra outro. Isso é modo raw ou crú.</p>
<p>Daí veio a idéia desse episódio. Muita gente, incluindo programadores não tem a mínima idéia de como HDs funcionam. No máximo sabem que um SSD é mais rápido que um HD e só. Quando eu falei clonar um HD, muita gente deve imaginar tipo selecionar vários arquivos e copiar pro outro como faria num Windows Explorer.</p>
<p>Tem gente que tem até computadores ou HDs externos com mais de um drive configurado em RAID-0, formando um único volume, ou seja, estão em stripe. Mau eles sabem que se um dos HDs falhar você acabou de perder todos os dados dos dois ao mesmo tempo.</p>
<p>Se você não sabe o que é Stripe, é uma versão de RAID que faz um único volume em cima de dois ou mais HDs. Daí no seu Windows você enxerga só um drive C:, por exemplo. A vantagem é que a velocidade vai ser maior, porque você distribui o trabalho entre os dois HDs, em paralelo. No melhor caso você vai ter o dobro de performance. Alguns blocos vão pra um HD e outros blocos vão pro outro HD.</p>
<p>Mas você pode pensar, ué, se metade dos arquivos foi pra um HD e outra metade foi pro outro, se um dos HD falhar eu vou perder só metade dos meus arquivos, mas a outra metade tá salva, não tá? Não! Eu disse que vai distribuir blocos e não arquivos.</p>
<p>O você talvez não saiba é que arquivos são divididos em blocos, normalmente de tamanho fixo, digamos de 512 bytes. Então um arquivo de 1 megabyte pode ocupar pelo menos 2 blocos. Um arquivo de 10 mega vai ocupar 20 blocos e assim por diante.</p>
<p>Agora, o sistema RAID-0 de stripe vai DIVIDIR esses blocos entre os dois HDs. Por isso é mais rápido ler depois, porque ele pode ler partes do mesmo arquivo vindo de HDs diferentes. E por isso que se você perder um dos HDs, vai perder o arquivo todo, porque tá picotado. Se você perder só 1 bloco, seu arquivo inteiro foi corrompido. Você trocou confiabilidade por performance. E isso é válido, por exemplo, se você tá configurando um servidor de cache, onde se os dados forem corrompidos, consegue reconstruir porque tem os originais em outro lugar.</p>
<p>As aplicações tentam facilitar as escolhas pra você, por isso às vezes você não sabe o que está acontecendo. Por exemplo, toda vez que reinstala um Windows, ou qualquer distro Linux moderna, só precisa ir dando next, next, next e o instalador é inteligente o suficiente pra fazer as escolhas mais sensíveis e você nem precisa entender o que ele fez. Uma dessas escolhas é como particionar seu HD, como gravar uma partição de boot, qual sistema de arquivos escolher e formatar. Em poucos minutos você parte de um HD virgem pro sistema operacional instalado e funcionando.</p>
<p>Nem sempre foi assim. Talvez tirando os Macs, que mesmo nos anos 90 tinha 100% de controle do hardware e do software, então ele sabia exatamente que HDs estavam instalados e como formatar da melhor forma. A vantagem da idéia de comoditizar PCs da IBM é que você podia comprar os componentes separadamente, um chip compatível com x86 só que da Cirrus, uma placa mãe talvez da Soyo, uma placa de som da Sound Blaster, um modem da US Robotics, e um HD ou Winchester como a gente chamava naquela época da Seagate.</p>
<p>Agora você quer instalar MS-DOS ou um UNIX ou uma das primeiras versões de Linux no começo dos anos 90. Boa sorte. Falando especificamente de Linux, primeiro você ia encomendar uma distro comercial como um RedHat. A versão 4.2 foi minha segunda distro. Daí coloca o disquete de boot pra inicializar o sistema, detectar o hardware que a BIOS já achou, carregar os drivers mais básicos pra cada componente. No final ele monta um ramdisk, que é o equivalente a um volume de HD só que na memória.</p>
<p>Agora você troca o disquete e coloca o de root ou suplemento como a RedHat costumava chamar. Ele vai montar esse disquete como o diretório /usr no ramdisk. Agora ele tem as ferramentas pra continuar. O próximo passo é detectar seu HD, se é IDE ou SCSI, carregar o driver e te dar a opção de particionar os volumes usando o temido <code>fdisk</code>.</p>
<p>Se você já teve o azar de cair num sistema que só te dá fdisk, e você nunca usou na vida, vai ser difícil. Só no fim dos anos 90 a RedHat adicionou a ferramenta Disk Druid que já ajuda muito, e hoje em dia você tem um GParted ou equivalente que vai particionar seu HD automaticamente, indicar onde você tá errando, e você nem precisa saber muitos detalhes.</p>
<p>Mas fdisk não, fdisk ele te pede pra criar a partição nativa que vai montar como o root ou barra, a partição de swap e pra fazer isso você precisa digitar o número do primeiro e do último CILINDRO. Daí ele te mostra informações como setores ou sectors e cabeças ou heads. Esse é o sistema CHS, cylinder-sector-head que são as coordenadas que definem a geometria tridimensional do seu HD.</p>
<p>Se você nunca pensou nisso, vou resumir. Se você abrir um HD mecânico, vai encontrar vários discos organizados um em cima do outro. Em cima de cada disco vai ter um braço com uma cabeça na ponta, que é quem lê e escreve de fato no disco. Cada cabeça identifica um dos discos. Quantas cabeças tem depende do HD.</p>
<p>Agora, como se organiza o disco? Na época do LP de música, o disco de vinil era organizado numa trilha espiral. Você coloca a cabeça no começo da espiral, mais pra borda do disco e enquanto gira, a cabeça vai deslizando dentro da trilha, lendo o engruvinhado que representa as ondas, que ele traduz como som. E o final da trilha é perto do meio do disco.</p>
<p>HDs não são espiral, em vez disso é um conjunto de anéis um dentro do outro. É isso que chamamos de cilindro. A distância de cada cilindro varia de acordo com a evolução da tecnologia magnética da cabeça. Se os cilindros estiverem muito perto e o motor não tiver precisão suficiente, poderia acabar pulando cilindros. Então o número de cilindros é limitado pela resolução da cabeça e o limite onde o magnetismo causa interferência.</p>
<p>Em vez de ser um engruvinhado físico dentro da trilha como num disco de vinil, no caso de HD as trilhas são magnéticas. A cabeça serve pra magnetizar ou desmagnetizar um bit de cada vez. Faz de conta que se tem uma carga então é 1, se tira a carga é zero. Se as trilhas, ou cilindros ficarem pertos demais, a carga de um bit poderia afetar o estado do outro bit. Aliás, um jeito fácil de estragar um HD é colocar um ímã forte perto. Isso é verdade até hoje.</p>
<p>Um HD moderno tem mais de 16 mil cilindros por lado do disco. A estrutura de dados que identifica cada cilindro tem 16-bits então é possível ter até 65 mil quinhentos e trinta e seis cilindros. Eu acho que ainda não estamos perto de atingir esse limite. À medida que a precisão do motor e das cabeças foi aumentando a gente pulou de menos de 4 mil cilindros nos anos 90 até mais de 16 mil hoje. Por isso dá pra ter HDs com 18 terabytes ou mais.</p>
<p>Um disco pode ser escrito dos dois lados, assim como você pode virar um vinil e do outro lado tem mais música. Como num HD não tem como &ldquo;virar&rdquo; o disco, ele vai ter duas cabeças por disco. Numa estrutura padrão de 8-bits significa que podemos identificar até 256 cabeças, numeradas de 0 a 255, isso dá um máximo de 128 discos. Na prática, duvido que existe um HD que chega perto de 128 discos.</p>
<p>Continuando, esses cilindros são organizados em setores. Dependendo do sistema operacional, sistema de arquivos, os setores podem variar de tamanho. Um dos tamanhos mais comuns era 512 bytes. Hoje em dia acho que é 4 kilobytes. Estou especulando, mas eu acho que essa divisão existe pra ajudar a fragmentar menos os dados. Fora que não seria eficiente movimentar o motor pra ler um bit de cada vez. Ele vai ler blocos de bits de cada vez. Na realidade acho que mais de um setor.</p>
<p>Pra ler os bits no disco, o motor precisa mover a cabeça de uma borda a outra do disco, passando pelos cilindros. Digamos que você tenha um arquivo de 100 bytes. Agora imagine que você usou um sistema de arquivos bem estúpido que vai gravando cada byte em lugares aleatórios no disco. O pobre motor vai precisar trabalhar bastante pra achar cada byte. É um motor mecânico, se cada byte estiver num cilindro, isso vai fazer o motor trabalhar bastante e tudo vai ficar lento.</p>
<p>Agora, se os mesmos 100 bytes estiverem organizados sequencialmente, um byte atrás do outro, o motor só precisa ter o trabalho de chegar no primeiro byte do cilindro e pronto, girar o disco é mais fácil que mover a cabeça. Então é só deixar o disco girar e quando eu achar o primeiro byte vou lendo sequencialmente um atrás do outro. Isso minimiza o trabalho do HD.</p>
<p>Digamos que você tenha um arquivo de 21 mil e trezentos bytes, ou mais ou menos 21 ponto oito megabytes. Se dividir em setores de 512 bytes e arredondar, porque não dá pra ter números quebrados, seriam 42 setores. Mas isso daria um total de 21 mil quinhentos e quatro bytes, isso é duzentos e quatro bytes maior que nossos 21 mil e trezentos. E agora?</p>
<p>Na prática o HD vai reservar quarenta e dois setores. Ele vai usar quarenta e um setores inteiros e o último vai ocupar só trezentos e oito e deixar livre duzentos e quatro bytes. E esses duzentos e quatro bytes tão perdidos, não dá pra usar eles pra outro arquivo. Como os arquivos do seu sistema operacional e os que você produz nunca vão ser exatamente múltiplos de quinhentos e doze ou seja lá qual for o tamanho do setor, sempre o último setor vai desperdiçar alguns bytes de espaço inusável. E isso é normal.</p>
<p>É um dos motivos de porque o total ocupado no seu HD não é a soma do tamanho em bytes de todos os arquivos. Na prática vai ser um pouco mais, porque precisa ser múltiplo do tamanho do setor. Eu sempre falo que computação é um trade off. Pra ter zero desperdício de espaço, era só fazer cada setor ser 1 bit. Mas você já deve ter entendido que além de fragmentar bastante, isso ia fazer o motor do nosso HD suar sangue pra ler depois.</p>
<p>Com isso você entende porque a velocidade num HD nunca é constante. Depende do tamanho do arquivo, da fragmentação do drive. O problema é que nunca temos como saber se o drive vai ter mais arquivos muito pequenos ou poucos arquivos muito grandes. Por isso também costuma ser ordens de grandeza mais rápido ler um único arquivo gigante, tipo um zip grande ou um video ou um arquivo de ISO de 500 gigabytes em vez de quinhentos mil arquivos de 1 megabyte cada. O tamanho total é o mesmo, mas o número de operações do HD vai ser muito maior e é isso que mais afeta a velocidade.</p>
<p>O arquivo grandão tende a ter seus setores próximos. Mesmo com alguma fragmentação, você dá a ordem pro HD e ele só tem que se preocupar com uma sequência. Mas no caso de quinhentos mil arquivos, você vai ter que dar quinhentos mil comandos de leitura e pra cada arquivo o motor vai ficar se movendo pra achar onde no disco tá cada um. Isso que chamamos de overhead, o trabalho extra de ter que receber milhares de comandos em vez de só um.</p>
<p>Conseguem imaginar esse trampo? Não é à toa que um HD vai se deteriorando com o tempo. Daí como a gente não sabe se vai ter mais arquivos pequenos ou mais arquivos grandões, também precisamos achar um tamanho de setor que balanceia desperdício de espaço no último setor de um arquivo e não pode ser tão pequeno que vai fazer o HD ser mais lento tendo que caçar muitos setores por arquivo. Se eu quiser ter pouca ou nenhuma fragmentação, basta fazer setores gigantes, mas aí cabe poucos arquivos. Se eu quiser ter milhões de pequenos arquivos, desperdiçando pouco espaço, basta fazer o setor ser o menor possível, só que aí a velocidade vai cair drasticamente. Trade off.</p>
<p>Na maioria dos sistemas de arquivos o tamanho do setor não é menor que 512 bytes e em alguns casos pode ser mais próximo de 4 kilobytes. Isso desperdiça um pouco de espaço, mas o preço veio caindo então hoje temos espaço sobrando, daí dá pra desperdiçar espaço pra ter mais velocidade. Se estiver no Windows e quiser saber, basta abrir um command prompt com permissão de administrador e rodar <code>fsutil fsinfo ntfsinfo c:</code> ou outro drive. No meu caso por exemplo meu C: tem setores de 4 kilobytes e meu I: tem 512 bytes.</p>
<p>Voltando à nossa instalação de Linux RedHat do meio dos anos 90, cá estamos frente à frente com essa tela de fdisk pedindo pra gente adivinhar o tamanho das partições baseado em cilindros. Antigamente o tamanho máximo de um HD era pouco menos de 8 gigabytes. Eu falei que cada gigabyte custava uns 15 dólares? Então isso era um HD de uns 120 dólares. Em 2021 isso é o preço de um HD Barracuda de 6 Terabytes. Quase mil vezes maior.</p>
<p>Esse HD Barracuda de cinco mil e quatrocentos rotações por minuto, diz a propaganda que consegue uma velocidade máxima de 6 gigabits por segundo. Como é em bits, precisa dividir por 8 pra ter em bytes, que é uns 700 megabytes por segundo. Mas isso é velocidade teórica de pico. Na prática num HD desses você vai ter sorte se conseguir mais que uns 300 megabytes por segundo, em média.</p>
<p>Pra piorar nossa conta, HDs modernos mais caros tem módulos de memória RAM. Ou no caso de Intel Optane, tem um mini SSD de cache. A Barracuda tem duzentos e cinquenta e seis mega de RAM. Isso é pelo menos dezesseis vezes mais RAM do que eu tinha no meu PC em 1995, e é só pra cache. A vantagem de ter RAM no HD que ele pode manter dados mais acessados na memória daí não precisa movimentar o motor pra pegar nos discos, por exemplo. É uma das formas do HD mecânico ficar mais rápido.</p>
<p>Mais do que isso, quando você pede pra gravar seu arquivo na realidade você não pede pro HD. Você pede pro seu sistema operacional como seu Windows e o Windows que vai fazer o pedido pro HD. O HD tem RAM, mas sua máquina também tem RAM e o Windows vai primeiro gravar em RAM, esperar acumular mais operações de escrita, reordenar pra facilitar o trabalho e vai dar &ldquo;ok&rdquo; e devolver o controle pro seu editor de textos. Provavelmente ele nem mandou pro HD ainda. Quando mandar o HD pode usar a RAM dela e dizer pro Windows que tá tudo ok também.</p>
<p>Isso parece inteligente não parece? RAM é ordens de grandeza mais rápido que seu HD. Eu disse que a velocidade do HD mecânico vai ser na faixa de 300 megabytes por segundo, na média. Um pente de memória DDR4 de 3600 megahertz tem picos de velocidade de 28 GIGAbytes. Isso é quase cento e cinquenta vezes mais rápido. É isso que significa diferença de ordens de grandeza.</p>
<p>O problema desses níveis de cache no meio do caminho é que antigamente, quando você recebia OK que o arquivo foi gravado, com grande probabilidade ele realmente foi gravado. Se daqui 1 segundo a energia da sua casa cair e desligar seu computador, pelo menos aquele último arquivo com certeza gravou no seu disco.</p>
<p>Hoje, se a força cair, você não tem certeza que o arquivo foi gravado. Provavelmente foi, porque é rápido, mas não se sabe. Num desktop, computador doméstico, tudo bem, porque raramente vai dar coincidência de você estar no meio da operação de escrita e a energia cair. Mas num servidor, tipo do Dropbox, com milhões de pessoas subindo arquivos sem parar, as chances de perder dados se a energia cair é alta. Por isso data centers tem tantos sistemas de redundância. O sistema de RAID que eu falei é só uma das peças do Lego deles.</p>
<p>Enfim, acabei dando uma tangente. O ponto é que todas essas peças móveis, dos HDs mecânicos até seu programa, tem muitas camadas no meio do caminho, muitos caches, e uma pane em alguma dessas camadas vai fazer você perder arquivos. Felizmente em 2021 as chances de você perder ou corromper arquivos é menor do que na minha época de MS-DOS e Winchesters de meros 20 megabytes que custavam uma fortuna.</p>
<p>E eu ainda não terminei o fdisk do Linux que tava instalando. Se prestar atenção, mesmo o fdisk de 1997 já tem uma ajuda. Nas primeiras versões você precisava saber tudo o que eu falei até agora sobre a geometria física dos HDs. Se eu tinha um HD de 8 giga, ia querer reservar uma partição de swap de uns 128 megabytes, que era o máximo.</p>
<p>Olha só essa tela do fdisk. Eu dou <code>p</code> pra dar <code>print</code> nas partições. Não tem nada. Daí dou <code>n</code> pra <code>new</code> ou nova partição. Começo no cilindro 1. Agora eu precisaria fazer esse cálculo que ele mostra acima. Unidade é cilindros vezes 16 mil sessenta e cinco setores, onde cada setor tem quinhentos e doze bytes, então cada cilindro pode ter uns 8 megabytes.</p>
<p>Em cima ele fala que meu disco pode ter até mil e vinte e quatro cilindros, cada cilindro com sessenta e três setores, indo de zero até duzentos e cinquenta e cinco heads ou cabeças. Claro que meu HD vai ter menos que isso. 128 megabytes é do cilindro 1 até dezessete, o que vai dar cento e trinta e seis blocos. O fdisk assume cada bloco tendo uns mil e vinte e quatro bytes. Pra chegar no número de cilindros é só dividir os cento e vinte e oito que eu quero por mil e vinte e quatro.</p>
<p>Pra ter o número de blocos por cilindro é só dividir a unidade, os quase 8 gigas por mil e vinte e autro. Isso dá o número de blocos por cilindro que são uns oito mil. Agora é multiplicar isso pelo número de cilindros pra cada partição. Cento e vinte e oito mega dividido por 8 vai dar dezesseis e uns quebrados, por isso arredonda pra 17.</p>
<p>Achou chato? Pois é, isso era chato. Agradeça que os aplicativos de particionamento hoje fazem todas essas contas pra você. Na realidade, a partir de 2002 com o padrão ATA-6 finalmente mudamos pro sistema de endereçamento linear de 48-bits chamado LBA ou Logical Block Addressing. E com isso esse sistema CHS de cilindro, cabeça, setor foi declarado obsoleto.</p>
<p>LBA é um mapeamento de endereços lineares em cima do sistema de CHS que o HD entende fisicamente por baixo. 2 elevado a 48 dá um máximo de duzentos e cinquenta e seis terabytes. Parece bastante pra uso doméstico, mas pra servidores não é tanto assim não. Por isso depois foi de 48-bits pra 64-bits.</p>
<p>Assim você endereça o disco do endereço zero até duzentos e cinquenta e seis teras. E por baixo ele vai converter em cilindros, cabeças e setores como antes. Você vai notar que muito na programação é fazer &ldquo;de-para&rdquo; que é conversão de coisas complicadas em coisas mais simples.</p>
<p>Tabelas de-para é que nem um dicionário, você traduz as coordenadas tridimensionais do disco por exemplo, cilindro, cabeças e setores num número. E pro sistema operacional, pra você como programador, é como se fosse um arrayzão gigante de 0 até aquele máximo. À medida que as coisas evoluem a gente pode desperdiçar um pouco de memória e processamento em ficar fazendo essas conversões.</p>
<p>Desta vez vou quebrar o assunto em múltiplos episódios. Eu fui escrevendo e no final deu quase 80 páginas de coisas que se eu tentar explicar tudo de uma só vez vai ficar complicado demais. Acho que por hoje já foi o suficiente, mas não se preocupem que eu já escrevi a continuação então o próximo episódio vai chegar mais rápido dessa vez. Se ficaram com dúvidas mandem nos comentários abaixo, não deixem de assinar o canal e clicar no sininho pra não perder a continuação, e compartilhem o video pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>armazenamento-e-backup</category><category>homelab</category><category>hardware</category><category>akitando</category></item></channel></rss>