<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="pt-BR"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://0.0.0.0:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://0.0.0.0:4000/" rel="alternate" type="text/html" hreflang="pt-BR" /><updated>2026-06-17T02:00:17+00:00</updated><id>http://0.0.0.0:4000/feed.xml</id><title type="html">Vitor blog</title><subtitle>This website is about anything I find interesting—mostly a  place for me to document things so I can remember them.  Maybe others will find some useful info as I solve my  own problems. I&apos;m interested in DevOps, SRE, coding,  keyboards, and electronics.</subtitle><entry><title type="html">Proxmox + Terraform — do template à VM (minhas notas)</title><link href="http://0.0.0.0:4000/jekyll/update/2026/06/01/proxmox-terraform.html" rel="alternate" type="text/html" title="Proxmox + Terraform — do template à VM (minhas notas)" /><published>2026-06-01T13:00:00+00:00</published><updated>2026-06-01T13:00:00+00:00</updated><id>http://0.0.0.0:4000/jekyll/update/2026/06/01/proxmox-terraform</id><content type="html" xml:base="http://0.0.0.0:4000/jekyll/update/2026/06/01/proxmox-terraform.html"><![CDATA[<blockquote>
  <p>Isso aqui é o que eu fiz aprendendo a usar Terraform com Proxmox, na pasta
<code class="language-plaintext highlighter-rouge">default_proxmox/</code>. Anotei pra conseguir refazer depois sem ter que garimpar
tudo de novo. O caminho é: <strong>preparar a imagem cloud-init, criar
usuário/token pro Terraform, montar o template base e deixar o Terraform
clonar e provisionar.</strong></p>
</blockquote>

<p>Meu setup (pra referência, ajusta pro teu):</p>

<ul>
  <li>Node: <code class="language-plaintext highlighter-rouge">pve2</code></li>
  <li>Storage: <code class="language-plaintext highlighter-rouge">local-btrfs</code></li>
  <li>Bridge de rede: <code class="language-plaintext highlighter-rouge">vmbr0</code></li>
  <li>Template base: VM id <code class="language-plaintext highlighter-rouge">9009</code></li>
  <li>Provider: <a href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs"><code class="language-plaintext highlighter-rouge">bpg/proxmox</code></a></li>
</ul>

<hr />

<h2 id="1-baixar-a-cloud-image-certa">1. Baixar a cloud image certa</h2>

<p>Imagem <strong>cloud</strong> do Ubuntu (a <code class="language-plaintext highlighter-rouge">cloudimg</code>, não a ISO de instalação — essa já vem
pronta pra cloud-init).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget <span class="nt">-P</span> /var/lib/vz/template/iso/ <span class="se">\</span>
  https://cloud-images.ubuntu.com/daily/server/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img
</code></pre></div></div>

<h3 id="injetar-o-qemu-guest-agent-na-imagem">Injetar o qemu-guest-agent na imagem</h3>

<p>Sem o guest agent o Proxmox <strong>não enxerga o status real da VM</strong> (IP, estado,
etc). Dá pra instalar manualmente depois, mas é muito mais limpo já costurar na
própria imagem antes de virar template, usando <code class="language-plaintext highlighter-rouge">virt-customize</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>virt-customize <span class="nt">-a</span> /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img <span class="se">\</span>
    <span class="nt">--install</span> qemu-guest-agent <span class="se">\</span>
    <span class="nt">--run-command</span> <span class="s1">'systemctl enable qemu-guest-agent'</span>
</code></pre></div></div>

<blockquote>
  <p>Depois, no Terraform, eu ligo <code class="language-plaintext highlighter-rouge">agent { enabled = true }</code> — é o lado de cá
dessa mesma história.</p>
</blockquote>

<hr />

<h2 id="2-criar-o-usuário--token-que-o-terraform-vai-usar">2. Criar o usuário + token que o Terraform vai usar</h2>

<p>A ideia: o Terraform <strong>não</strong> loga como root. Crio um role com as permissões
necessárias, jogo num grupo, aplico o role nos recursos, e o usuário entra no
grupo. No fim gero um token pra ele.</p>

<h3 id="21-criar-o-role-com-as-acls">2.1 Criar o role com as ACLs</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pveum role add TerraformUser2 <span class="nt">-privs</span> <span class="s2">"Datastore.Allocate </span><span class="se">\</span><span class="s2">
  Datastore.AllocateSpace Datastore.AllocateTemplate </span><span class="se">\</span><span class="s2">
  Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify </span><span class="se">\</span><span class="s2">
  SDN.Use VM.Allocate VM.Audit VM.Clone VM.Config.CDROM </span><span class="se">\</span><span class="s2">
  VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.HWType </span><span class="se">\</span><span class="s2">
  VM.Config.Memory VM.Config.Network VM.Config.Options VM.Migrate </span><span class="se">\</span><span class="s2">
  VM.Console VM.PowerMgmt User.Modify"</span>
</code></pre></div></div>

<p>Isso cria um <strong>role</strong> (um pacote de permissões). Ele ainda não está ligado a
ninguém — é só a definição.</p>

<h3 id="22-criar-o-grupo">2.2 Criar o grupo</h3>

<p>Coloco o role no grupo pra ficar gerenciável. Se um dia quiser ser granular, dá
pra aplicar ACL direto no usuário também — mas grupo é mais limpo.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pveum group add terraform-users
pveum group list      <span class="c"># confere que o terraform-users apareceu</span>
</code></pre></div></div>

<h3 id="23-aplicar-o-role-nos-recursos-as-acls-de-verdade">2.3 Aplicar o role nos recursos (as ACLs de verdade)</h3>

<p>Aqui que a mágica acontece: associo o <strong>role</strong> ao <strong>grupo</strong> em cada caminho de
recurso. É isso que dá ao usuário (que vai entrar no grupo) acesso àquele
recurso.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pveum acl modify /storage   <span class="nt">-group</span> terraform-users <span class="nt">-role</span> TerraformUser2
pveum acl modify /vms       <span class="nt">-group</span> terraform-users <span class="nt">-role</span> TerraformUser2
pveum acl modify /sdn/zones <span class="nt">-group</span> terraform-users <span class="nt">-role</span> TerraformUser2
</code></pre></div></div>

<h3 id="24-criar-o-usuário-e-gerar-o-token">2.4 Criar o usuário e gerar o token</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pveum useradd terradorm2@pve <span class="nt">-groups</span> terraform-users
pveum user list                                   <span class="c"># confere o terradorm2@pve com realm pve</span>
pveum user token add terradorm2@pve token <span class="nt">-privsep</span> 0
</code></pre></div></div>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">-privsep 0</code> = o token herda <strong>todas</strong> as permissões do usuário (sem separação
de privilégio). O comando cospe o <strong>secret do token uma única vez</strong> — copia na
hora, depois não dá mais pra ver. A saída vem assim:</p>

  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>full-tokenid   terradorm2@pve!token
value          b412d05a-2661-446e-8f7e-9e31c72cd3ca   &lt;- o secret, salva AGORA
</code></pre></div>  </div>
</blockquote>

<p>O formato que o Terraform espera é <code class="language-plaintext highlighter-rouge">USER@REALM!TOKENID=UUID</code>, ou seja:
<code class="language-plaintext highlighter-rouge">terradorm2@pve!token=b412d05a-2661-446e-8f7e-9e31c72cd3ca</code>.</p>

<blockquote>
  <p>Pra conferir o que ficou aplicado: <code class="language-plaintext highlighter-rouge">pveum acl list</code> mostra cada <code class="language-plaintext highlighter-rouge">path</code> com seu
<code class="language-plaintext highlighter-rouge">roleid</code>, o <code class="language-plaintext highlighter-rouge">type</code> (user/group) e o <code class="language-plaintext highlighter-rouge">ugid</code> (quem recebeu).</p>
</blockquote>

<hr />

<h2 id="3-montar-o-template-base">3. Montar o template base</h2>

<p>Esse é o template que TODAS as VMs vão clonar. No meu caso, uma única base
Ubuntu 24.04 cloud-init.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cria o esqueleto da VM</span>
qm create 9009 <span class="nt">--name</span> <span class="s2">"ubuntu-24.04-cloud-init-template"</span> <span class="se">\</span>
  <span class="nt">--memory</span> 2048 <span class="nt">--cores</span> 2 <span class="nt">--net0</span> virtio,bridge<span class="o">=</span>vmbr1

<span class="c"># importa o disco da cloud image pro storage</span>
qm importdisk 9009 /var/lib/vz/template/iso/ubuntu-24.04-server-cloudimg-amd64.img local-btrfs
</code></pre></div></div>

<p>Depois do import o disco entra como <strong><code class="language-plaintext highlighter-rouge">unused0</code></strong> (não atachado). Confere com:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm config 9009
<span class="c"># unused0: local-btrfs:9009/vm-9009-disk-0.raw</span>
</code></pre></div></div>

<p>Atacho o disco num controller SCSI virtio:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm <span class="nb">set </span>9009 <span class="nt">--scsihw</span> virtio-scsi-pci <span class="se">\</span>
  <span class="nt">--scsi0</span> local-btrfs:9009/vm-9009-disk-0.raw
</code></pre></div></div>

<blockquote>
  <p>Resize <strong>só depois</strong> de atachar — a cloud image vem pequena (~2-3G), então
dou um tamanho de verdade:</p>
</blockquote>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm resize 9009 scsi0 40G
</code></pre></div></div>

<p>Configuro boot, drive de cloud-init, rede e o usuário/senha inicial da imagem:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm <span class="nb">set </span>9009 <span class="nt">--boot</span> c <span class="nt">--bootdisk</span> scsi0
qm <span class="nb">set </span>9009 <span class="nt">--ide2</span> local-btrfs:cloudinit          <span class="c"># drive de cloud-init</span>
qm <span class="nb">set </span>9009 <span class="nt">--ipconfig0</span> <span class="nv">ip</span><span class="o">=</span>dhcp
qm <span class="nb">set </span>9009 <span class="nt">--ciuser</span> ubuntu <span class="nt">--cipassword</span> <span class="s1">'sua-senha-aqui'</span>   <span class="c"># user/pass cloud-init</span>
</code></pre></div></div>

<p>Converto em template (a partir daqui não dá mais pra dar boot nele direto, só
clonar):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm template 9009
qm list   <span class="c"># confere que aparece como template</span>
</code></pre></div></div>

<p>No <code class="language-plaintext highlighter-rouge">qm list</code> os meus templates/VMs ficam assim (o <code class="language-plaintext highlighter-rouge">9009 ubuntu-template</code> é a
base que o Terraform usa):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VMID  NAME              STATUS   MEM(MB)  BOOTDISK(GB)
201   new-ubuntu-vm     stopped  2048     13.50
9009  ubuntu-template   stopped  2048     20.00
</code></pre></div></div>

<p><strong>A partir daqui já dá pra ir pro Terraform.</strong> Mas dá pra testar o clone na mão
antes, só pra ver que a base presta:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm clone 9009 201 <span class="nt">--name</span> <span class="s2">"new-ubuntu-vm"</span>
qm resize 201 scsi0 +10G
qm start 201
</code></pre></div></div>

<hr />

<h2 id="4-o-lado-terraform-default_proxmox">4. O lado Terraform (<code class="language-plaintext highlighter-rouge">default_proxmox/</code>)</h2>

<p>Estrutura: o Terraform lê <strong>todos os <code class="language-plaintext highlighter-rouge">.tf</code></strong> da pasta e junta tudo. Eu separei
em arquivos por responsabilidade.</p>

<h3 id="providertf--quem-é-o-backend"><code class="language-plaintext highlighter-rouge">provider.tf</code> — quem é o backend</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">terraform</span> <span class="p">{</span>
  <span class="nx">required_version</span> <span class="p">=</span> <span class="s2">"&gt;=1.5"</span>
  <span class="nx">required_providers</span> <span class="p">{</span>
    <span class="nx">proxmox</span> <span class="p">=</span> <span class="p">{</span>
      <span class="nx">source</span>  <span class="p">=</span> <span class="s2">"bpg/proxmox"</span>
      <span class="nx">version</span> <span class="p">=</span> <span class="s2">"&gt;=0.66.0"</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="nx">provider</span> <span class="s2">"proxmox"</span> <span class="p">{</span>
  <span class="nx">endpoint</span>  <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">proxmox_api_url</span>
  <span class="nx">api_token</span> <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">proxmox_api_token</span>
  <span class="nx">insecure</span>  <span class="p">=</span> <span class="kc">true</span>        <span class="c1"># cert self-signed do Proxmox</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="variablestf--o-que-é-secreto-fica-como-variável"><code class="language-plaintext highlighter-rouge">variables.tf</code> — o que é secreto fica como variável</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">variable</span> <span class="s2">"proxmox_api_url"</span>   <span class="p">{</span> <span class="nx">type</span> <span class="p">=</span> <span class="nx">string</span> <span class="p">}</span>
<span class="nx">variable</span> <span class="s2">"proxmox_api_token"</span> <span class="p">{</span> <span class="nx">type</span> <span class="p">=</span> <span class="nx">string</span><span class="err">,</span> <span class="nx">sensitive</span> <span class="p">=</span> <span class="kc">true</span> <span class="p">}</span>
<span class="nx">variable</span> <span class="s2">"vm_password"</span>       <span class="p">{</span> <span class="nx">type</span> <span class="p">=</span> <span class="nx">string</span><span class="err">,</span> <span class="nx">sensitive</span> <span class="p">=</span> <span class="kc">true</span> <span class="p">}</span>
</code></pre></div></div>

<h3 id="terraformtfvars--os-valores-esse-não-vai-pro-git"><code class="language-plaintext highlighter-rouge">terraform.tfvars</code> — os valores (ESSE NÃO VAI PRO GIT)</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">proxmox_api_token</span> <span class="err">=</span> <span class="s2">"terradorm2@pve!token2=&lt;SECRET-AQUI&gt;"</span>
<span class="nx">proxmox_api_url</span>   <span class="err">=</span> <span class="s2">"https://10.66.66.19:8006/"</span>
<span class="nx">vm_password</span>       <span class="err">=</span> <span class="s2">"&lt;senha&gt;"</span>
</code></pre></div></div>

<blockquote>
  <p>Bota isso no <code class="language-plaintext highlighter-rouge">.gitignore</code>. Token de Proxmox no histórico do git é dor de
cabeça garantida.</p>
</blockquote>

<h3 id="localstf--as-constantes-do-meu-ambiente-num-lugar-só"><code class="language-plaintext highlighter-rouge">locals.tf</code> — as constantes do meu ambiente num lugar só</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">locals</span> <span class="p">{</span>
  <span class="nx">target_node</span>    <span class="p">=</span> <span class="s2">"pve2"</span>
  <span class="nx">storage</span>        <span class="p">=</span> <span class="s2">"local-btrfs"</span>
  <span class="nx">network_bridge</span> <span class="p">=</span> <span class="s2">"vmbr0"</span>
  <span class="nx">template_id</span>    <span class="p">=</span> <span class="mi">9009</span>
  <span class="nx">ssh_user</span>       <span class="p">=</span> <span class="s2">"shabba"</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="vmtf--clona-o-template-e-provisiona-via-cloud-init"><code class="language-plaintext highlighter-rouge">vm.tf</code> — clona o template e provisiona via cloud-init</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">resource</span> <span class="s2">"proxmox_virtual_environment_vm"</span> <span class="s2">"vm"</span> <span class="p">{</span>
  <span class="nx">name</span>      <span class="p">=</span> <span class="s2">"terraform-test"</span>
  <span class="nx">node_name</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">target_node</span>
  <span class="nx">started</span>   <span class="p">=</span> <span class="kc">true</span>
  <span class="nx">on_boot</span>   <span class="p">=</span> <span class="kc">true</span>

  <span class="nx">agent</span> <span class="p">{</span> <span class="nx">enabled</span> <span class="p">=</span> <span class="kc">true</span> <span class="p">}</span>      <span class="c1"># o par do qemu-guest-agent que instalei na imagem</span>

  <span class="nx">clone</span> <span class="p">{</span>
    <span class="nx">vm_id</span>        <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">template_id</span>
    <span class="nx">full</span>         <span class="p">=</span> <span class="kc">true</span>         <span class="c1"># clone completo, não linked</span>
    <span class="nx">datastore_id</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">storage</span>
  <span class="p">}</span>

  <span class="nx">cpu</span>    <span class="p">{</span> <span class="nx">cores</span> <span class="p">=</span> <span class="mi">2</span><span class="err">,</span> <span class="nx">sockets</span> <span class="p">=</span> <span class="mi">1</span><span class="err">,</span> <span class="nx">type</span> <span class="p">=</span> <span class="s2">"host"</span> <span class="p">}</span>
  <span class="nx">memory</span> <span class="p">{</span> <span class="nx">dedicated</span> <span class="p">=</span> <span class="mi">2048</span> <span class="p">}</span>

  <span class="nx">disk</span> <span class="p">{</span>
    <span class="nx">datastore_id</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">storage</span>
    <span class="nx">interface</span>    <span class="p">=</span> <span class="s2">"scsi0"</span>
    <span class="nx">size</span>         <span class="p">=</span> <span class="mi">32</span>
  <span class="p">}</span>

  <span class="nx">network_device</span> <span class="p">{</span>
    <span class="nx">bridge</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">network_bridge</span>
    <span class="nx">model</span>  <span class="p">=</span> <span class="s2">"virtio"</span>
  <span class="p">}</span>

  <span class="c1"># cloud-init: usuário, senha, chave SSH e rede</span>
  <span class="nx">initialization</span> <span class="p">{</span>
    <span class="nx">datastore_id</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">storage</span>
    <span class="nx">user_account</span> <span class="p">{</span>
      <span class="nx">username</span> <span class="p">=</span> <span class="nx">local</span><span class="err">.</span><span class="nx">ssh_user</span>
      <span class="nx">password</span> <span class="p">=</span> <span class="nx">var</span><span class="err">.</span><span class="nx">vm_password</span>
      <span class="nx">keys</span>     <span class="p">=</span> <span class="p">[</span><span class="nx">trimspace</span><span class="err">(</span><span class="nx">file</span><span class="err">(</span><span class="s2">"~/.ssh/id_ed25519.pub"</span><span class="err">))</span><span class="p">]</span>
    <span class="p">}</span>
    <span class="nx">ip_config</span> <span class="p">{</span>
      <span class="nx">ipv4</span> <span class="p">{</span> <span class="nx">address</span> <span class="p">=</span> <span class="s2">"dhcp"</span> <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="outputstf--me-devolve-o-ip-depois-do-apply"><code class="language-plaintext highlighter-rouge">outputs.tf</code> — me devolve o IP depois do apply</h3>

<div class="language-hcl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">output</span> <span class="s2">"vm_ipv4_addresses"</span> <span class="p">{</span> <span class="nx">value</span> <span class="p">=</span> <span class="nx">proxmox_virtual_environment_vm</span><span class="err">.</span><span class="nx">vm</span><span class="err">.</span><span class="nx">ipv4_addresses</span> <span class="p">}</span>
<span class="nx">output</span> <span class="s2">"vm_ipv6_addresses"</span> <span class="p">{</span> <span class="nx">value</span> <span class="p">=</span> <span class="nx">proxmox_virtual_environment_vm</span><span class="err">.</span><span class="nx">vm</span><span class="err">.</span><span class="nx">ipv6_addresses</span> <span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>O IP só aparece porque o guest agent está rodando dentro da VM. Sem ele, esse
output vem vazio — tá tudo conectado.</p>
</blockquote>

<hr />

<h2 id="5-os-comandos-do-dia-a-dia">5. Os comandos do dia a dia</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>terraform init                                   <span class="c"># baixa o provider</span>
terraform plan  <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"terraform.tfvars"</span>     <span class="c"># mostra o que vai fazer</span>
terraform apply <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"terraform.tfvars"</span>     <span class="c"># aplica</span>
terraform apply <span class="nt">-auto-approve</span> <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"..."</span>    <span class="c"># aplica sem perguntar</span>

<span class="c"># salvar o plano e aplicar exatamente ele depois</span>
terraform plan  <span class="nt">-out</span><span class="o">=</span><span class="s2">"proxmox_plan"</span> <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"terraform.tfvars"</span>
terraform apply <span class="s2">"proxmox_plan"</span>

<span class="c"># derrubar tudo</span>
terraform destroy <span class="nt">-auto-approve</span> <span class="nt">-var-file</span><span class="o">=</span><span class="s2">"terraform.tfvars"</span>
</code></pre></div></div>

<blockquote>
  <p>Se o arquivo terminar em <code class="language-plaintext highlighter-rouge">.auto.tfvars</code>, o Terraform carrega sozinho e nem
precisa do <code class="language-plaintext highlighter-rouge">-var-file</code>.</p>
</blockquote>

<hr />

<h2 id="links-que-me-ajudaram">Links que me ajudaram</h2>

<ul>
  <li><a href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/guides/vm-lifecycle">Ciclo de vida da VM (bpg)</a></li>
  <li><a href="https://www.trfore.com/posts/provisioning-proxmox-8-vms-with-terraform-and-bpg/">Provisioning Proxmox 8 com Terraform + bpg</a></li>
  <li><a href="https://medium.com/@DatBoyBlu3/provisioning-proxmox-virtual-machines-with-terraform-d9e9c549f947">Guia no Medium</a></li>
  <li><a href="https://www.youtube.com/watch?v=16qs7LJSyps">Criando plugin (vídeo)</a></li>
</ul>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Isso aqui é o que eu fiz aprendendo a usar Terraform com Proxmox, na pasta default_proxmox/. Anotei pra conseguir refazer depois sem ter que garimpar tudo de novo. O caminho é: preparar a imagem cloud-init, criar usuário/token pro Terraform, montar o template base e deixar o Terraform clonar e provisionar.]]></summary></entry><entry xml:lang="pt"><title type="html">Monitor de Energia UPS</title><link href="http://0.0.0.0:4000/projects/power-ups-front-pt/" rel="alternate" type="text/html" title="Monitor de Energia UPS" /><published>2026-04-27T00:00:00+00:00</published><updated>2026-04-27T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/power-ups-front-pt</id><content type="html" xml:base="http://0.0.0.0:4000/projects/power-ups-front-pt/"><![CDATA[<p>Um dashboard web para monitoramento em tempo real de nobreaks (UPS). Ele lê dados de log gerados pelo <code class="language-plaintext highlighter-rouge">upslog</code>, do Network UPS Tools, e transforma esses registros em cards e gráficos para acompanhar tensão de entrada, carga da bateria, uso atual, autonomia estimada, tensão da bateria e status do equipamento.</p>

<p>O frontend espera um arquivo simples em <code class="language-plaintext highlighter-rouge">data/data.txt</code>, gerado por cron. Cada linha é separada por ponto e vírgula e inclui data, hora e métricas do UPS em uma ordem fixa. A aplicação interpreta esses registros e converte a autonomia de segundos para um formato mais legível em horas e minutos.</p>

<p>O painel mostra valores atuais junto com resumos de tensão mínima, máxima e média. Os gráficos históricos podem ser filtrados por intervalo de datas, o que ajuda a acompanhar a estabilidade elétrica e a saúde do nobreak em um servidor doméstico ou qualquer máquina que precise ficar ligada.</p>

<p>O projeto está organizado como uma aplicação Flask/WSGI pequena, com templates HTML em <code class="language-plaintext highlighter-rouge">power_ups_dashboard</code>, logs locais do UPS em <code class="language-plaintext highlighter-rouge">data/</code> e screenshots de documentação em <code class="language-plaintext highlighter-rouge">docs/assets</code>.</p>

<p><strong>Stack:</strong> Python, Flask, Chart.js, Network UPS Tools (upslog), Docker.</p>

<p><a href="https://github.com/VitorHolandaI/power_ups_front">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Dashboard web em Flask para monitorar nobreaks (UPS) com tensão, carga, uso, autonomia e histórico.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://raw.githubusercontent.com/VitorHolandaI/power_ups_front/main/docs/assets/dashboard.png" /><media:content medium="image" url="https://raw.githubusercontent.com/VitorHolandaI/power_ups_front/main/docs/assets/dashboard.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">UPS Energy Monitor</title><link href="http://0.0.0.0:4000/projects/power-ups-front/" rel="alternate" type="text/html" title="UPS Energy Monitor" /><published>2026-04-27T00:00:00+00:00</published><updated>2026-04-27T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/power-ups-front</id><content type="html" xml:base="http://0.0.0.0:4000/projects/power-ups-front/"><![CDATA[<p>A web dashboard for real-time UPS monitoring. It reads log data produced by <code class="language-plaintext highlighter-rouge">upslog</code> from Network UPS Tools and turns it into cards and charts for input voltage, battery charge, current load, estimated runtime, battery voltage, and UPS status.</p>

<p>The frontend expects a simple <code class="language-plaintext highlighter-rouge">data/data.txt</code> file generated by cron. Each line is separated by semicolons and includes the date, time, and UPS metrics in a fixed order. The application parses those records and converts the runtime value from seconds into a more readable hours/minutes format.</p>

<p>The dashboard shows current values alongside voltage summaries, including minimum, maximum, and average readings. Historical charts can be filtered by date range, making it useful for checking power stability and UPS health on a home server or any machine that needs to stay online.</p>

<p>The project is organized as a small Flask/WSGI application, with HTML templates under <code class="language-plaintext highlighter-rouge">power_ups_dashboard</code>, local UPS logs under <code class="language-plaintext highlighter-rouge">data/</code>, and documentation screenshots under <code class="language-plaintext highlighter-rouge">docs/assets</code>.</p>

<p><strong>Stack:</strong> Python, Flask, Chart.js, Network UPS Tools (upslog), Docker.</p>

<p><a href="https://github.com/VitorHolandaI/power_ups_front">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[A Flask dashboard for monitoring UPS voltage, load, battery charge, runtime, and historical power data.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://raw.githubusercontent.com/VitorHolandaI/power_ups_front/main/docs/assets/dashboard.png" /><media:content medium="image" url="https://raw.githubusercontent.com/VitorHolandaI/power_ups_front/main/docs/assets/dashboard.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt"><title type="html">Mesh System Pager</title><link href="http://0.0.0.0:4000/projects/mesh-system-pager-pt/" rel="alternate" type="text/html" title="Mesh System Pager" /><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/mesh-system-pager-pt</id><content type="html" xml:base="http://0.0.0.0:4000/projects/mesh-system-pager-pt/"><![CDATA[<p>Um daemon simples que lê métricas do sistema em tempo real de uma máquina Linux e as transmite por uma rede mesh Meshtastic LoRa, uma mensagem por categoria, a cada 60 segundos.</p>

<p>Três relatórios são enviados em sequência a cada ciclo: CPU (uso por núcleo, sensores de temperatura, uptime), RAM (usado/total, swap, top 5 processos por memória) e rede (taxas de RX/TX, totais, IPs das interfaces ativas, uso de disco). Cada relatório é empacotado em uma string compacta separada por pipes e truncado para caber no limite de 228 bytes de payload de texto do Meshtastic.</p>

<p>A ferramenta se conecta via serial a um dispositivo Meshtastic local e entra em loop indefinidamente. Handlers de sinal para <code class="language-plaintext highlighter-rouge">SIGINT</code> e <code class="language-plaintext highlighter-rouge">SIGTERM</code> fecham a interface corretamente ao sair.</p>

<p>O caso de uso é monitoramento remoto de uma máquina, como um servidor doméstico, um Raspberry Pi em campo ou qualquer máquina headless. Não precisa de Wi-Fi, celular ou conexão com a internet. Enquanto houver um nó Meshtastic dentro do alcance LoRa, é possível puxar as métricas de qualquer lugar.</p>

<p><strong>Stack:</strong> Python, Meshtastic Python SDK, psutil.</p>

<p><a href="https://github.com/VitorHolandaI/mesh_system_pager">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Uma ferramenta em Python que transmite métricas de CPU, RAM, rede e disco em tempo real por uma rede mesh Meshtastic LoRa.]]></summary></entry><entry xml:lang="en"><title type="html">Mesh System Pager</title><link href="http://0.0.0.0:4000/projects/mesh-system-pager/" rel="alternate" type="text/html" title="Mesh System Pager" /><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/mesh-system-pager</id><content type="html" xml:base="http://0.0.0.0:4000/projects/mesh-system-pager/"><![CDATA[<p>A small daemon that reads live system metrics from a Linux machine and broadcasts them over a Meshtastic LoRa mesh network, one message per category, every 60 seconds.</p>

<p>Three reports are sent in sequence on each cycle: CPU (per-core usage, temperature sensors, uptime), RAM (used/total, swap, top 5 processes by memory), and network (RX/TX rates, totals, active interface IPs, disk usage). Each report is packed into a compact pipe-delimited string and truncated to fit within Meshtastic’s 228-byte text payload limit.</p>

<p>The tool connects via serial to a locally attached Meshtastic device, then loops indefinitely. Signal handlers for <code class="language-plaintext highlighter-rouge">SIGINT</code> and <code class="language-plaintext highlighter-rouge">SIGTERM</code> close the interface cleanly on exit.</p>

<p>The use case is remote monitoring of a machine, such as a home server, a Raspberry Pi in the field, or any headless box. It does not need Wi-Fi, cellular, or an internet connection. As long as a Meshtastic node is within LoRa range, you can pull stats from anywhere.</p>

<p><strong>Stack:</strong> Python, Meshtastic Python SDK, psutil.</p>

<p><a href="https://github.com/VitorHolandaI/mesh_system_pager">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[A Python tool that broadcasts live CPU, RAM, network, and disk stats over a Meshtastic LoRa mesh network.]]></summary></entry><entry xml:lang="pt"><title type="html">Meshtastic File Transfer</title><link href="http://0.0.0.0:4000/projects/meshtastic-file-transfer-pt/" rel="alternate" type="text/html" title="Meshtastic File Transfer" /><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/meshtastic-file-transfer-pt</id><content type="html" xml:base="http://0.0.0.0:4000/projects/meshtastic-file-transfer-pt/"><![CDATA[<p>Um protocolo de transferência de arquivos confiável construído sobre redes mesh Meshtastic LoRa. O Meshtastic por si só só garante entrega best-effort. Este projeto adiciona as primitivas de confiabilidade necessárias para transferir arquivos de verdade: divisão em chunks, confirmação por chunk, retransmissão em caso de timeout e verificação MD5 de ponta a ponta.</p>

<p>O protocolo (chamado de MeshTCP) funciona em três fases. Primeiro, o remetente envia um header FILE com o nome do arquivo, total de chunks e hash MD5. Em seguida, envia cada chunk um por vez, aguardando um ACK antes de prosseguir. Se nenhum ACK chegar dentro do timeout, o chunk é retransmitido até atingir um limite configurável antes de abortar a transferência. Por fim, quando todos os chunks são recebidos, o receptor remonta o arquivo, verifica o MD5 e envia um pacote DONE com o resultado.</p>

<p>Como o LoRa é extremamente lento, o remetente calcula uma estimativa antes de começar e pede confirmação. A conta usada pelo script é simples: o arquivo é dividido em chunks de 200 bytes e cada chunk entra na estimativa como cerca de 5 segundos de rádio. Um arquivo de 1 KB vira 6 chunks e fica perto de 30 segundos. Um arquivo de 5 KB vira 26 chunks e fica perto de 2 minutos e 10 segundos. Um arquivo de 10 KB fica em torno de 50 chunks e pode levar cerca de 4 minutos.</p>

<p>Esse tempo é uma estimativa otimista, porque ele assume que cada chunk recebe ACK sem perda. Na prática, se o receptor não confirmar um chunk em 15 segundos, o sender retransmite. Cada chunk pode ser tentado até 5 vezes antes da transferência ser abortada, então um único trecho ruim de rádio já pode adicionar mais de um minuto. Por isso o README trata arquivos abaixo de 10 KB como o uso mais prático, mostra warning quando a estimativa passa de 5 minutos e marca arquivos acima de 50 KB como arriscados, principalmente por causa de quedas de USB em transferências longas.</p>

<p>A implementação lida com vários cenários reais de falha: chunks duplicados são re-ACKados sem reescrever o dado, o ACK é enviado três vezes para combater perda de pacotes no caminho de volta, e desconexões USB durante a transferência disparam uma reconexão automática com retry. O receptor também filtra os pacotes pelo ID do nó remetente para ignorar tráfego não relacionado na mesh.</p>

<p>Cada pacote é codificado em binário com um prefixo de tipo compacto (FILE, CHK, ACK, NAK, DONE, ABORT) e cabe dentro do limite de 228 bytes de payload do Meshtastic. O protocolo vive em <code class="language-plaintext highlighter-rouge">meshtcp.py</code>, com <code class="language-plaintext highlighter-rouge">sender.py</code> e <code class="language-plaintext highlighter-rouge">receiver.py</code> como os dois lados da transferência.</p>

<p><strong>Stack:</strong> Python, Meshtastic Python SDK, pypubsub.</p>

<p><a href="https://github.com/VitorHolandaI/meshtastic_file_transfer">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Um protocolo de transferência de arquivos confiável estilo TCP construído sobre Meshtastic LoRa, com chunking, ACK/NAK, retransmissão e verificação MD5.]]></summary></entry><entry xml:lang="en"><title type="html">Meshtastic File Transfer</title><link href="http://0.0.0.0:4000/projects/meshtastic-file-transfer/" rel="alternate" type="text/html" title="Meshtastic File Transfer" /><published>2026-04-06T00:00:00+00:00</published><updated>2026-04-06T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/meshtastic-file-transfer</id><content type="html" xml:base="http://0.0.0.0:4000/projects/meshtastic-file-transfer/"><![CDATA[<p>A custom reliable file transfer protocol layered on top of Meshtastic LoRa mesh networks. Meshtastic itself only guarantees best-effort delivery. This project adds the reliability primitives needed to transfer actual files: chunking, per-chunk acknowledgement, retransmission on timeout, and end-to-end MD5 verification.</p>

<p>The protocol (dubbed MeshTCP) works in three phases. First, the sender broadcasts a FILE header containing the filename, total chunk count, and MD5 hash. Then it sends each chunk one at a time, waiting for an ACK before moving to the next. If no ACK arrives within the timeout window, it retransmits until it reaches a configurable maximum before aborting the transfer. Finally, once all chunks are received, the receiver assembles the file, checks the MD5, and sends a DONE packet back with the result.</p>

<p>Because LoRa is extremely slow, the sender calculates an estimate before starting and asks for confirmation. The script uses a simple rule: the file is split into 200-byte chunks, and each chunk is estimated at about 5 seconds of radio time. A 1 KB file becomes 6 chunks and lands near 30 seconds. A 5 KB file becomes 26 chunks and lands near 2 minutes and 10 seconds. A 10 KB file is around 50 chunks and can take about 4 minutes.</p>

<p>That estimate is optimistic because it assumes every chunk gets an ACK without packet loss. In practice, if the receiver does not confirm a chunk within 15 seconds, the sender retransmits it. Each chunk can be retried up to 5 times before the transfer is aborted, so one bad stretch of radio link can add more than a minute. That is why the README treats files under 10 KB as the practical target, warns when the estimate passes 5 minutes, and marks files above 50 KB as risky, mainly because USB disconnects become more likely during long transfers.</p>

<p>The implementation handles several real-world failure modes: duplicate chunks are re-ACKed without writing twice, the ACK is sent three times to fight packet loss on the return path, and USB disconnections mid-transfer trigger an automatic reconnect and retry. The receiver also filters incoming packets by sender node ID to ignore unrelated traffic on the mesh.</p>

<p>Each packet is binary-encoded with a compact type prefix (FILE, CHK, ACK, NAK, DONE, ABORT) and fits within Meshtastic’s 228-byte raw payload limit. The protocol lives in <code class="language-plaintext highlighter-rouge">meshtcp.py</code>, with <code class="language-plaintext highlighter-rouge">sender.py</code> and <code class="language-plaintext highlighter-rouge">receiver.py</code> as the two sides of the transfer.</p>

<p><strong>Stack:</strong> Python, Meshtastic Python SDK, pypubsub.</p>

<p><a href="https://github.com/VitorHolandaI/meshtastic_file_transfer">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[A TCP-like reliable file transfer protocol built on top of Meshtastic LoRa, with chunking, ACK/NAK, retransmission, and MD5 verification.]]></summary></entry><entry xml:lang="pt"><title type="html">Servitor Assistant</title><link href="http://0.0.0.0:4000/projects/servitor-assistant-pt/" rel="alternate" type="text/html" title="Servitor Assistant" /><published>2026-03-18T00:00:00+00:00</published><updated>2026-03-18T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/servitor-assistant-pt</id><content type="html" xml:base="http://0.0.0.0:4000/projects/servitor-assistant-pt/"><![CDATA[<p>Um assistente de voz dividido entre duas máquinas: um PC faz o trabalho pesado, incluindo reconhecimento de fala, inferência do LLM e síntese de voz. Um Raspberry Pi serve como interface física, ouvindo pelo microfone, reproduzindo a resposta nos alto-falantes e pulsando um LED enquanto aguarda.</p>

<p>O assistente tem personalidade de Adeptus Mechanicus do Warhammer 40k. Ele fala como um Magos do Imperium: breve, curioso e levemente perturbador.</p>

<p>Por baixo dos panos, a fala é transcrita offline com Vosk, enviada a um agente LangChain com um modelo local Ollama, e a resposta é sintetizada com Piper TTS. O áudio passa pelo SoX no Pi para adicionar overdrive e reverb que combinam com o efeito de voz. Há também um frontend de chat web em React que faz stream das respostas via SSE e pode opcionalmente acionar o mesmo pipeline de TTS no Pi.</p>

<p><img src="/assets/projects/servitor-assistant-web-chat.png" alt="Interface de chat web do Servitor Assistant" /></p>

<p>O agente tem acesso a ferramentas via servidor MCP: clima de Campina Grande, gerenciador de tarefas completo com suporte a tarefas recorrentes em SQLite, e matemática básica. Um loop de lembretes verifica a cada minuto e envia um alerta de áudio ao Pi quando uma tarefa vence.</p>

<p>Tudo isso está dentro de uma caveira impressa em 3D.</p>

<p><strong>Stack:</strong> Python, FastAPI, LangChain, FastMCP, Ollama (llama3.2), Vosk, Piper TTS, SoX, React + Vite + TypeScript, SQLite, Raspberry Pi, RPi.GPIO.</p>

<p><a href="https://github.com/VitorHolandaI/ServitorAssisstant">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Um assistente de voz distribuído com personalidade de Magos do Warhammer 40k, rodando num PC e num Raspberry Pi.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://raw.githubusercontent.com/VitorHolandaI/ServitorAssisstant/main/skull.jpg" /><media:content medium="image" url="https://raw.githubusercontent.com/VitorHolandaI/ServitorAssisstant/main/skull.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Servitor Assistant</title><link href="http://0.0.0.0:4000/projects/servitor-assistant/" rel="alternate" type="text/html" title="Servitor Assistant" /><published>2026-03-18T00:00:00+00:00</published><updated>2026-03-18T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/servitor-assistant</id><content type="html" xml:base="http://0.0.0.0:4000/projects/servitor-assistant/"><![CDATA[<p>A voice assistant split across two machines: a PC handles the heavy work, including speech recognition, LLM inference, and text-to-speech. A Raspberry Pi acts as the physical interface, listening through a microphone, playing back the response through speakers, and pulsing an LED while it waits.</p>

<p>The assistant has a Warhammer 40k Adeptus Mechanicus personality. It talks like a Magos from the Imperium: brief, curious, and slightly unnerving.</p>

<p>Under the hood, speech is transcribed offline with Vosk, sent to a LangChain agent backed by a local Ollama model, and the response is synthesized with Piper TTS. Audio passes through SoX on the Pi for some overdrive and reverb to sell the voice effect. There’s also a web chat frontend in React that streams responses via SSE and can optionally trigger the same TTS pipeline on the Pi.</p>

<p><img src="/assets/projects/servitor-assistant-web-chat.png" alt="Servitor Assistant web chat interface" /></p>

<p>The agent has access to tools through an MCP server: weather for Campina Grande, a full task manager with recurring task support backed by SQLite, and basic math. A reminder loop polls every minute and sends an audio alert to the Pi when a task comes due.</p>

<p>The whole thing is housed inside a 3D-printed skull.</p>

<p><strong>Stack:</strong> Python, FastAPI, LangChain, FastMCP, Ollama (llama3.2), Vosk, Piper TTS, SoX, React + Vite + TypeScript, SQLite, Raspberry Pi, RPi.GPIO.</p>

<p><a href="https://github.com/VitorHolandaI/ServitorAssisstant">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[A distributed voice assistant with a Warhammer 40k Magos personality, running across a PC and a Raspberry Pi.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://raw.githubusercontent.com/VitorHolandaI/ServitorAssisstant/main/skull.jpg" /><media:content medium="image" url="https://raw.githubusercontent.com/VitorHolandaI/ServitorAssisstant/main/skull.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="pt"><title type="html">RC Rover</title><link href="http://0.0.0.0:4000/projects/rc-rover-pt/" rel="alternate" type="text/html" title="RC Rover" /><published>2026-01-07T00:00:00+00:00</published><updated>2026-01-07T00:00:00+00:00</updated><id>http://0.0.0.0:4000/projects/rc-rover-pt</id><content type="html" xml:base="http://0.0.0.0:4000/projects/rc-rover-pt/"><![CDATA[<p>Um rover terrestre controlado remotamente feito com peças impressas em 3D e movido por um ESP32. A parte interessante está em como ele lida com acesso remoto e vídeo: em vez de um módulo de câmera dedicado, um smartphone é montado no rover e faz dois trabalhos. Ele transmite vídeo ao vivo via app de câmera IP e funciona como gateway de rede por VPN, tornando o rover controlável de qualquer lugar com cobertura móvel, não apenas na Wi-Fi local.</p>

<p>O ESP32 roda um pequeno servidor HTTP que expõe endpoints direcionais (<code class="language-plaintext highlighter-rouge">/forward</code>, <code class="language-plaintext highlighter-rouge">/backward</code>, <code class="language-plaintext highlighter-rouge">/left</code>, <code class="language-plaintext highlighter-rouge">/right</code>). Uma página web otimizada para mobile servida diretamente pelo ESP32 envia requisições contínuas enquanto um botão é pressionado, movendo dois motores DC por uma ponte H. O smartphone roda Nginx como proxy reverso para rotear o tráfego externo ao ESP32.</p>

<p>O chassi é baseado em um design 3D printável do Thingiverse, com baterias separadas para a eletrônica e os motores.</p>

<p><strong>Stack:</strong> C++ / Arduino (ESP32), Nginx, HTML, impressão 3D.</p>

<p><a href="https://github.com/VitorHolandaI/rc-rover">GitHub Repository</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Um rover controlado remotamente com chassi impresso em 3D, ESP32 e vídeo ao vivo transmitido por um smartphone.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://raw.githubusercontent.com/VitorHolandaI/rc-rover/master/roverPhone.jpg" /><media:content medium="image" url="https://raw.githubusercontent.com/VitorHolandaI/rc-rover/master/roverPhone.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>