Pular para o conteúdo
Retrato de Henrique KasprzakHenrique Kasprzak
Todos os projetos

2026 — hoje

Manutenção Lavoura

Um diário de manutenção que cabe em um binário

Papel
Autor único
Feito com
Go, SQLite, htmx, PWA, Caddy, systemd, GitHub Actions

Para quem é

Duas pessoas. Um agrônomo e um gerente de fazenda, que entre os dois cuidam de um trator, uma colheitadeira, uma plantadeira, uma semeadora, um pulverizador e um caminhão, e que precisavam saber o que foi feito, quando, e com qual leitura no horímetro ou no hodômetro.

Esse é o público inteiro, e o desenho leva isso a sério. Não há tela de cadastro; as duas contas são criadas pela linha de comando. Não há sistema de papéis; há duas pessoas que confiam uma na outra.

Uma visita, vários serviços

Uma visita de manutenção raramente faz uma coisa só. Troca-se o óleo, um filtro junto, e um bico enquanto a máquina já está aberta. Então um registro lista quantos serviços forem necessários, cada um com preço, quantidade e um “fazer de novo depois de N horas ou quilômetros” — e a tela de gastos soma o que cada máquina custou num período, ou detalha uma máquina por serviço.

O que torna os totais confiáveis é pequeno: os serviços são agrupados por um nome que ignora maiúsculas, acentos e espaçamento, então Troca de Óleo e troca de oleo viram uma linha, e não duas. O campo de descrição sugere os nomes já em uso, que é o que impede o resto de se desalinhar desde o início.

Offline exatamente onde importa

Só uma página funciona offline: o formulário de novo registro. Ele fica em cache no service worker, e um registro salvo sem sinal espera numa fila no aparelho e sobe sozinho quando a conexão volta. Navegar e editar são só online, por decisão.

Essa divisão é o ponto. O momento que precisa de offline é estar ao lado de uma máquina no meio da lavoura; todo o resto acontece numa mesa. Fazer a aplicação inteira funcionar offline compraria muita complexidade de sincronização para um caso que não existe.

Fotos nunca seguram um registro

Um registro pode carregar fotos — a nota fiscal do que foi comprado, ou o serviço em si. Cada uma é redimensionada e recodificada no celular antes de ser guardada ou enviada, espera na própria fila e sobe logo depois do seu registro. O registro vai primeiro; as fotos vão atrás. Um registro nunca fica esperando pelas fotos.

Os bytes ficam no mesmo arquivo SQLite que todo o resto, então o único backup cobre as fotos também.

Apps que se atualizam sozinhos

Um PWA instalado tem uma falha clássica: a versão em cache sobrevive ao deploy, e dois celulares passam a rodar, em silêncio, duas aplicações diferentes. A resposta de sempre é um número de versão do cache que alguém precisa lembrar de incrementar.

Aqui o cache do service worker recebe o nome de um hash dos arquivos que ele guarda — os assets estáticos e os templates que renderizam o formulário offline. Um build que mude qualquer um deles muda o script do worker; o navegador vê um worker novo, instala, reabastece o cache e recarrega a página aberta. Um formulário com texto digitado segura esse reload até que a página seja deixada, para que nada escrito pela metade se perca. Não há versão para incrementar à mão, e uma mudança só em Go deixa o cache em paz.

Um binário, um arquivo

A implantação é, de propósito, a menor coisa que ainda está certa:

PeçaEscolha
RuntimeUm binário Go estático, CGO_ENABLED=0, com um driver SQLite em Go puro — nada para instalar no servidor
DadosUm arquivo SQLite, fotos incluídas
Processosystemd
TLS e ingressoCaddy no host, fora do compose de qualquer projeto, para que o redeploy de um projeto nunca derrube a entrada de outro
MigraçõesRodam na inicialização, cada uma dentro de uma transação; uma falha deixa o banco como estava e o serviço se recusa a subir em vez de servir metade de um schema
ReleasesUm workflow no GitHub Actions que reexecuta os testes, compila para o servidor, tira um snapshot do banco e então instala e reinicia por SSH

Ele precisa de uma origem própria, e não de um caminho dentro de um site existente, e o README diz por quê: o escopo do service worker é /, o cookie de sessão é por host, e um service worker simplesmente não registra sobre HTTP puro — sem TLS, o formulário offline, a fila e a autoatualização estão todos mortos.

Mais um detalhe do runbook fácil de deixar passar: no iOS, o app precisa ser adicionado à tela de início. Uma aba comum do Safari tem o armazenamento apagado depois de cerca de uma semana parada, o que descartaria em silêncio um registro ainda esperando na fila.

Honestidade sobre o escopo

Vinte e três commits em cinco semanas, e o irmão menor do Xanadu Fleet. Reaproveita as mesmas ideias — uma fila de saída, uma fila separada de fotos, registros que vão primeiro — com um décimo da maquinaria, porque duas pessoas anotando trocas de óleo não precisam de uma cadeia de hashes. Saber qual projeto precisa do quê é a maior parte do trabalho.