Skip to content
אצלי
Go back

easy1090: instalador completo

Mapa web do tar1090 com aeronaves ao vivo sobre a região de Guarulhos

Oitavo artigo da série. Os sete anteriores descrevem, passo a passo, como sair de um dongle na caixa até um mapa de tráfego aéreo rodando em casa. São muitos passos, e vários deles existem apenas porque alguma coisa não funcionou como devia.

Este artigo é sobre transformar isso em código, e sobre o que aprendi ao testar esse código em máquinas de verdade - Validado e em uso num mini servidor com EndevourOS e em um laptop (thinkpad) com Omarchy.

O projeto está no github.com/Esl1h/easy1090, sob licença MIT.

Por que um instalador

Releia a parte 4 e conte quantas coisas deram errado: o pacote do AUR que não compila com o GCC atual, o yay que descarta o patch, a regra udev que funciona para você e não para o serviço, o override do systemd com flags de outra versão. Some a parte 5, com o fork trocado no meio do caminho e o lighttpd do Arch que ignora a configuração inteira em silêncio.

Nenhum desses atritos é interessante. Nenhum ensina algo sobre rádio, ADS-B ou aviação. São impostos que você paga por rodar um stack pensado para Raspberry Pi numa distro que não é Debian.

Um instalador não deixa esses problemas mais fáceis. Ele os resolve uma vez e guarda a solução onde a próxima pessoa encontra.

As decisões antes da primeira linha

Escrevi um documento de planejamento antes de qualquer código, e ele evitou pelo menos duas reescritas.

Idempotente, sem modo update. Rodar de novo é a forma de atualizar. Cada módulo detecta o que já está pronto e pula, define que o arquivo de configuração é a declaração do estado desejado da máquina, e que reexecutar converge para ele.

Bash primeiro, V depois. A tentação era começar em Go ou em V, que eu gosto. Mas o trabalho aqui é 90% orquestrar pacman, yay, makepkg e systemctl, e o argumento decisivo não foi técnico: um instalador que roda sudo na máquina alheia sendo um script legível permite auditoria antes de rodar. Binário compilado quebra esse contrato - talvez num segundo momento e como alternativa, mais como satisfação pessoal do que como uma solução técnica ou com algum tipo de ganho.

O --dry-run imprime os comandos exatos.

Saída do comando easy1090 install --dry-run, com o preflight aprovado, várias linhas SKIP indicando o que já está instalado, e as linhas DRY mostrando os comandos exatos que seriam executados

Esse print, aliás, mostra as duas ideias juntas. As linhas [DRY] são os comandos literais. E a quantidade de [SKIP] é a idempotência funcionando: numa máquina já configurada, quase tudo é reconhecido e pulado, sobrando só o que de fato precisa mudar.

Roda como usuário normal, nunca como root, porque makepkg e yay se recusam a rodar assim. A escalação é pontual, com um sudo -v no início e um keepalive em background, para a senha não ser pedida no meio de uma compilação de 45 minutos.

A camada de gerenciador de pacotes fica isolada, num lib/pkg-arch.sh. Suportar outra distro é escrever um arquivo irmão com a mesma interface, sem tocar nos módulos.

O padrão que só aparece testando de verdade

Aqui começa a parte que interessa. Escrevi o instalador, ele passou no shellcheck, o dry-run parecia perfeito. Aí rodei numa máquina limpa.

A primeira instalação funcionou. A segunda foi onde tudo apareceu.

O serviço que roda com a configuração antiga

Reexecutei o instalador com coordenadas novas. Ele escreveu o /etc/default/readsb corretamente e chamou:

systemctl enable --now readsb

E não aconteceu nada. enable --now não reinicia uma unidade que já está ativa. O daemon seguiu rodando com a configuração anterior, sem erro nenhum. A prova estava no carimbo de tempo: o arquivo escrito às 16:16, o processo no ar desde 15:39, e o journalctl mostrando a latitude antiga enquanto o disco tinha a nova.

A correção foi fazer o run::sudo_write sinalizar se o conteúdo mudou de fato, e os módulos reiniciarem quando mudou.

O detalhe constrangedor: essa é exatamente a armadilha que eu já tinha documentado no instalador do tar1090, dois artigos antes. Documentar um erro alheio não impede você de repeti-lo.

Arquivo existir não significa que o serviço leu

A correção acima não bastou. Na terceira execução, o mod_redirect aparecia como [SKIP] já habilitado, porque o arquivo e o symlink existiam. E a URL continuava devolvendo 404.

O arquivo tinha sido criado às 15:45 por uma execução anterior que não reiniciou o lighttpd, e o daemon estava no ar desde 15:39. Presença de arquivo não prova leitura de arquivo.

A solução foi uma função que compara o ActiveEnterTimestamp da unidade com o mtime do arquivo:

svc::predates_file() {
    started="$(systemctl show "$unit" -p ActiveEnterTimestamp --value)"
    started="$(date -d "$started" +%s)"
    file_mtime="$(stat -c %Y "$file")"
    [[ "$started" -lt "$file_mtime" ]]
}

Se o serviço subiu antes do arquivo ser escrito, ele não pode tê-lo carregado. Reinicia, mesmo no caminho de [SKIP].

A flag que apaga o que acabou de instalar

Testando a instalação completa, com SDR++ e SatDump, reparei num bloco estranho ao final: o yay removendo 24 pacotes. Entre eles airspy, hackrf, bladerf, rtaudio, soapysdr. A linha imediatamente anterior listava esses mesmos pacotes como dependências opcionais do SDR++, todas marcadas como instaladas.

Ele instalou e desinstalou na mesma execução.

A culpa era minha, de uma flag que eu tinha colocado achando que era higiene:

yay -S --removemake ...

O --removemake descarta os pacotes instalados como dependência de build. Acontece que pacotes do AUR frequentemente listam a mesma biblioteca em makedepends e em optdepends: ela é necessária para compilar um plugin e necessária de novo, em runtime, para carregá-lo. O yay só enxerga o lado de build.

O estrago, medido:

for p in /usr/lib/sdrpp/plugins/*.so; do
  ldd "$p" | grep -q "not found" && echo "quebrado: $p"
done

Dez plugins sem suas bibliotecas, entre eles o audio_sink.so. O SDR++ abria normalmente, aparecia instalado no pacman -Q, e estava sem saída de áudio, sem suporte a Airspy, HackRF, BladeRF, LimeSDR e sem o decodificador M17. Nenhuma mensagem de erro em lugar nenhum.

O rtl_test que falha porque está tudo certo

Na reexecução, apareceu um aviso dizendo que o tuner não foi identificado. Investigando, o rtl_test terminava com usb_claim_interface error -6.

O readsb já estava rodando e detinha o dispositivo, então o teste enumerava a placa e não conseguia reivindicá-la. Sistema saudável, mensagem alarmante. O módulo do driver agora reconhece essa saída e pula o teste, dizendo o porquê:

[SKIP ] Dongle em uso pelo readsb (RTL-SDR Blog V4); pulando o rtl_test.

A assinatura comum

Releia os quatro casos acima e note o que eles têm em comum: nenhum deles emite erro.

O serviço fica active e enabled, verde no systemctl, rodando com a configuração errada. O arquivo está lá, o symlink está lá, e o daemon nunca os leu. O programa está instalado, os plugins estão no disco, e metade não carrega. O teste de hardware falha justamente porque o hardware está sendo usado.

Falha ruidosa é fácil: ela te diz onde olhar. Esse tipo de falha exige que você desconfie de um sistema que está afirmando estar bem. E a única forma que encontrei de pegá-las foi rodar de novo, e de novo, numa máquina de verdade.

Foi isso que mudou o meu critério de “pronto”. Um instalador que funciona na primeira execução não está pronto; está apenas não testado.

O segundo padrão: todo mundo presume Raspberry Pi

O outro tema recorrente apareceu nos scripts de terceiros.

O instalador do tar1090 usa adduser, com fallback para useradd, e sobrevive no Arch. O do ADSBExchange usa adduser sem fallback, sob set -e, e morre na linha 10, deixando um diretório vazio, nenhum serviço e nenhuma mensagem útil no site deles. E o do airplanes.live, escrito por outra equipe, comete exatamente o mesmo erro, com um comentário no código dizendo que a segunda forma é “para fedora / centos”, quando as duas formas são adduser.

Some a isso o lighttpd do Arch que não inclui conf-enabled, o mod_redirect que não vem carregado, e o pacote de estatísticas que procura o JSON num diretório que só existe se você usar o cliente de feed deles.

Nenhum desses projetos está errado em presumir Debian: a esmagadora maioria dos receptores ADS-B do mundo é um Raspberry Pi. É uma decisão de mercado razoável que produz código frágil, e o easy1090 existe justamente para absorver essa fragilidade num lugar só.

A regra que segui foi nunca alterar script de terceiro. Trabalho em volta dele: crio o usuário de sistema antes, resolvo as dependências que ele não sabe buscar, e então entrego o controle. O script roda intacto, o que significa que uma atualização do upstream não conflita com um patch meu.

O bug que eu mesmo escrevi

Justiça seja feita, nem todo problema foi de terceiro.

Quando unifiquei os scripts num entrypoint único com subcomandos, movi a lógica para arquivos carregados dentro de uma função. E variável declarada com declare dentro de função é local:

f(){ source /dev/stdin <<< "declare A=1
readonly B=2"; }
f
echo "A=${A:-SUMIU} B=${B:-SUMIU}"
# A=SUMIU B=2

O readonly sobrevive, o declare não. Todas as constantes dos módulos usavam readonly e continuaram funcionando, e as duas variáveis de estado que usavam declare viraram fantasmas.

O comando uninstall passou a falhar com unbound variable na primeira tentativa de uso, e eu não percebi porque não o retestei depois do refactor. Só apareceu quando escrevi o comando update e ele quebrou do mesmo jeito. A correção é declare -g.

A lição não é sobre bash. É que refactor que muda o contexto de execução do código invalida todo teste anterior, e eu só revalidei o que exercitei na hora.

O que a ferramenta é hoje

Uma execução completa, com --full, numa máquina que já tinha tudo instalado:

Saída completa do comando easy1090 install --full, do preflight à validação final, com linhas SKIP para tudo que já estava pronto e a confirmação de que os três serviços estão ativos e 12 aeronaves sendo decodificadas

É o retrato do que eu queria alcançar. Nada foi reinstalado, porque nada precisava. O rtl_test foi pulado com o motivo explicado. Os dois feeds aparecem com aviso, porque compartilhar dados merece ser dito em voz alta toda vez. E a validação final não confia na própria instalação: ela consulta o systemd, mede a idade do JSON e faz uma requisição HTTP real no mapa antes de dizer que está tudo no ar.

São cerca de 2.700 linhas de bash, com um entrypoint único e nove subcomandos:

easy1090 install       instala o stack (idempotente)
easy1090 update        atualiza versões de pacote
easy1090 feed          alimenta ADSBExchange e airplanes.live
easy1090 status        o que está rodando, o que caiu, o que falta
easy1090 start | stop | restart
easy1090 open          abre um componente
easy1090 uninstall     desfaz a instalação

O status e o open nunca pedem sudo, o que é uma propriedade deliberada: consultar o estado do sistema não deveria custar uma senha.

Saída do comando easy1090 status, listando hardware e driver, decodificação com 7 aeronaves, os serviços web e os componentes opcionais, cada um com estado e versão

O status responde à pergunta que eu mais me fazia durante o desenvolvimento: o que está de pé agora? Ele checa presença de pacote, estado de unidade do systemd e, no caso da decodificação, a idade do JSON, porque céu vazio não é defeito e contagem de aeronaves não serve como sinal de saúde.

A interface é bilíngue, português e inglês, com 287 mensagens em catálogos separados. O CI verifica mecanicamente que os dois catálogos definem exatamente as mesmas chaves, porque tradução faltando não quebra nada, só imprime a chave crua na tela de alguém.

O instalador do tar1090 é vendorizado no repositório e verificado por checksum antes de rodar, com a licença GPL dele preservada e documentada. Se o arquivo mudar sem o pin mudar junto, a execução aborta.

E o KNOWN_ISSUES.md tem 20 entradas, cada uma com a causa e o motivo do contorno existir. É o arquivo de que mais me orgulho, porque é o que sobra quando o código for reescrito.

O que ficou de fora

O --full roda limpo de ponta a ponta, como no print acima, mas naquele servidor o SDR++ e o SatDump já estavam instalados desde a parte 5, então ele percorreu o caminho de [SKIP]. O build do SatDump, aquele de 45 minutos, continua sem ter sido exercitado pelo instalador numa máquina onde ele não exista. O caminho de remoção do fork antigo do readsb também não foi exercitado, porque nenhuma das minhas máquinas tem mais aquele pacote, e forçar o cenário exigiria instalá-lo de propósito.

E não existe CI de verdade: nada do que importa aqui pode ser testado sem um dongle conectado. O que dá para automatizar é a parte estática, e é o que o CI faz.

Tudo isso está escrito no repositório, em inglês e sem eufemismo.

O próximo

O próximo artigo reúne essa série inteira em inglês, para quem chega pelo repositório e não lê português. Depois disso, a série volta ao que ela era antes de virar engenharia de software: rádio. Tem um SatDump instalado no servidor esperando um satélite passar.


Share this post on:

Previous Post
ADS-B on Arch Linux: from an RTL-SDR dongle to a live map, and the installer it took
Next Post
ADSBExchange: enviando dados dos aviões para internet