WiFi no Raspberry Pi

Agora que o Raspberry Pi está com as configurações básicas prontas, (veja o post anterior), o próximo passo é simplificar a conexão dele com a internet.

Quero deixá-lo em um lugar onde não atrapalhe. Quanto menos fios e cabos precisar melhor, portanto WiFi é uma opção melhor do que Ethernet.

Esse Raspberry Pi é bem antigo e não tem WiFi integrado na placa e precisa de um adaptador USB-WiFi.

Mas vamos lá para a configuração. Para quem não sabe, o sistema operacional do Raspberry OS (ou Raspbian) é baseado no Debian Linux e podemos usar muito da documentação deste.

O link abaixo explica como configurar o WiFi no Debian:

https://wiki.debian.org/WiFi/HowToUse#Using_ifupdown

Primeiro precisamos determinar qual é o nome do adaptador WiFi (seja ele integrado na placa ou via USB). Para isso logamos usando SSH e usamos o comando ip a, que lista as interfaces de rede.

$ ip a
> 1: lo: ...
> 2: eth0: ...
> 3: wlan0: ...

A saída do comando tem um tanto a mais de informação do que estou mostrando. Porém podemos identificar "wlan0" como a interface WiFi.

Agora vamos criar um arquivo de configuração para a rede sem fio:

  1. Usamos sudo su para mudar para o usuário root;
  2. Mudamos a máscara de criação de arquivos para 077, que faz com que apenas o dono do arquivo tenha acesso. Isso é importante para evitar os usuários possam ler o arquivo abaixo, que vai conter a senha do WiFi
  3. Criamos o arquivo de configuração usando o comando touch; e
  4. Editamos o arquivo (com o editor nano) adicionando a configuração mostrada em seguida.
$ sudo su
$ uname 077
$ touch /etc/network/interfaces.d/wlan0.conf
$ nano /etc/network/interfaces.d/wlan0.conf

O arquivo de configuração pode ter qualquer nome. Para facilitar coloco o nome da interface que ele configura.

O conteúdo do arquivo de configuração é mostrado abaixo, configurando a interface para ser detectada automaticamente, usar DHCP para obter um endereço de IP e também configura o SSID da rede (nome da rede sem fio) e a sua senha.

# Arquivo: /etc/network/interfaces.d/wlan0.conf mode=0600
allow-hotplug wlan0
iface wlan0 inet dhcp
    wpa-ssid NOME_DA_REDE
    wpa-psk SENHA_DA_REDE

Note que no arquivo de configuração precisamos especificar corretamente o nome da interface de rede duas vezes, "wlan0" no meu caso.

Raspberry Pi modelo B com WiFi

Agora é só reiniciar o Raspberry Pi, desconectar todos os cabos desnecessários e curtir ele através da rede sem fio. Agora, a única coisa que ele precisa é uma tomada.

Revivendo um Raspberry Pi B (modelo antigo)

Tenho um Raspberry Pi antigo, modelo B, e quero fazer ele voltar à vida para rodar testes de projetos multiplataforma.

Gravando o Cartão SD

Entrando no site do Raspberry Pi, encontramos a página de downloads. Optei por escolher a imagem manualmente na página abaixo.

https://www.raspberrypi.com/software/operating-systems/

Como pretendo criar um servidor, não preciso da imagem desktop, podendo utilizar a imagem "lite" (leve), que tem apenas a linha de comando.

Fiz o download da imagem "Raspberry Pi OS Lite (32-bit)", obtendo o arquivo "2022-09-22-raspios-bullseye-armhf-lite.img.xz".

Se você tiver algum modelo mais novo do Raspberry Pi, é possível usar uma imagem "Raspberry Pi OS Lite (64-bit)". Há uma lista dos modelos que suportam o sistema 64-bits.

Para extrair a imagem usamos o comando xz. Isso remove o arquivo compactado .img.xz cria um arquivo descompactado .img.

$ xz -d 2022-09-22-raspios-bullseye-armhf-lite.img.xz

Então, para gravá-la usamos o comando dd, enviando os dados para o cartão SD no caminho /dev/mmcblk0.

$ dd if=2022-09-22-raspios-bullseye-armhf-lite.img of=/dev/mmcblk0

Se tiver dúvidas sobre qual é o seu cartão de memória, o comando ls -lrt /dev/ vai te mostrar uma lista com os dispositivos detectados no sistema, listados do mais velho para o mais novo.

Se quiser também é possível escrever no cartão SD enquanto extrai, sem criar o arquivo descomprimido.

$ xz -dc 2022-09-22-raspios-bullseye-armhf-lite.img.xz | dd of=/dev/mmcblk0

Antes do Primeiro Boot

Habilitando SSH

Para realizar algumas modificações montei a partição de root e de boot do Raspberry Pi.

$ mount /dev/mmcblk0p2 /mnt
$ mount /dev/mmcblk0p1 /mnt/boot

Na pasta /mnt/boot (/boot do Raspberry Pi) criei um arquivo vazio chamado ssh, que faz o servidor SSH ser habilitado no primeiro boot.

touch /mnt/boot/ssh

Obsoleto: Isso evita ter que acessar o mini computador com tela e teclado só para habilitar o SSH. Assim posso ir direto para o acesso remoto.

Infelizmente, agora é preciso configurar usuário e senha no primeiro boot. Então parece ser obrigatório conectar uma tela e teclado. Deve haver algum jeito de evitar isso.

Agora basta desmontar a partição e iniciar o Raspberry Pi com o cartão SD.

$ umount /dev/mmcblk0p1
$ umount /dev/mmcblk0p2

Raspberry Pi modelo B

Primeiro Boot

As primeiras coisas a fazer são:

  • Escolher o usuário
  • Escolher a senha
  • Identifique o IP no roteador (no meu caso é 192.168.0.101)
  • Instalar chave SSH (opcional)
    $ ssh-copy-id [email protected]
    
  • Fazer o login pelo SSH
    $ ssh [email protected]
    
  • Atualizar
    pi$ sudo apt update
    pi$ sudo apt upgrade
    
  • Reduzir a memória da GPU
    pi$ sudo raspi-config
    
    • 4 Performance Options
      • P2 GPU Memory
        • 8 > OK
    • Finish
  • Reiniciar
    pi$ sudo reboot
    

Conversor SATA-USB

Estou com alguns HDs velhos, alguns são externos (com case) e outros são internos (sem case), e quero fazer uma limpa neles. Eu queria algo prático e rápido de ligar os outros HDs no computador.

Os HDs externos já tem um conversor SATA-USB neles. Para os HDs internos, é possível comprar um case, porém não muito prático ficar trocando o HD do case, também não quero comprar um carrinho de mão deles… A boa é que existem conversores SATA-USB como o abaixo, que liga direto na porta SATA do HD.

Simples e prático.

Conversor SATA-USB Montado

Porém nada acontece ao ligar o HD no computador através deste conversor.

Com o comando abaixo tentei verificar os dispositivos conectados recentemente e nada.

$ ls -lrt /dev/

Ao desconectar tudo que percebi que o conversor estava razoavelmente quente.

Abri ele, conectei apenas o conversor no USB do computador e constatei que o CI U2 (o maior CI, no centro da imagem abaixo) está esquentando muito. Ele fica tão quente que não dá pra manter o dedo em cima dele.

CIs:

  • U1 P25Q21H – Puya Semiconductor – Serial Flash Memory
  • U2 VL711S-Q4 – VLI – USB 3.0 to SATA 6Gb/s Bridge Controller

Frente da Placa do Conversor SATA-USB

A pesar do problema citado, a placa parece bem desenhada. Pode-se ver pelo cuidado que tiveram ao criar as trilhas diferenciais, mesmo em uma placa dupla-face.

Porém a soldagem deixa um bocado a desejar:

Na frente da placa (acima) podemos ver pedaços longos de fio desencapados e tocados pelo ferro de solda. Estes podem causar curto-circuitos nos fios. No verso vemos o mesmo.

No verso da placa (abaixo) também podemos ver os dois pinos de suporte do conector SATA (em cima e embaixo) sem solda alguma.

Por último, porém não menos agonizante, apenas um dos três pintos do conector 12 V está bem soldado. O segundo faz conexão, porém a solda não fluiu por todo pino e o pino do meio está sem solda alguma.

Verso da Placa do Conversor SATA-USB

Testando se um aquivo é ASCII

Surgiu a necessidade de verificar se um arquivo tem apenas caracteres ASCII.

O código abaixo resolve esta problema. Para usá-lo é muito simples: basta passar o nome do arquivo que deseja testar.

  • teste_ascii.py README.md
  • teste_ascii.py graph.m
#!/usr/bin/env python3
# Arquivo: teste_ascii.py
import sys

if len(sys.argv) != 2:
    print(f"Uso: {sys.argv[0]} ARQUIVO", file=sys.stderr)
    sys.exit(1)

# Abre o arquivo passado na linha de comando em modo binário
# e lê o conteúdo do arquivo.
fp = open(sys.argv[1], 'rb')
x = fp.read()

# Decodifica o conteúdo do arquivo para ASCII.
# Se houver algum caractere que não é ascii uma exceção será
# lançada.
x.decode('ascii')

Ansible: Criptografando Senhas de Usuários

As vezes queremos gerar uma senha fixa para usuários, a qual vamos reutilizar em várias máquinas.

Um exemplo típico é criar usuário administrador, capaz de realizar login com senha. Dessa forma, caso ocorra algum problema com o servidor SSH ou perca a sua chave privada junto com o seu HD, ainda é possível fazer login direto na máquina, por VNC ou por outros meios de acesso remoto.

Porém essa é a senha de administrador de muitos computadores e não queremos colocá-la diretamente em scripts de configuração, como os do Ansible, onde podem ser lidas diretamente.

Podemos, no entanto, criar uma versão encriptada da senha (a mesma usada no arquivo /etc/shadow) e aí sim colocá-la no script.

O código abaixo cria uma senha segura 24 caracteres utilizando pwgen. A opção -s indica que a senha deve ser completamente aleatória.

$ pwgen -s 24 1
> VONga6jUFoXCMQNhDlHgTHQY

Por fim usamos openssl passwd para encriptar a senha gerada Usamos a opção -6 para criptografar a senha com SHA512.

$ openssl passwd -6 'VONga6jUFoXCMQNhDlHgTHQY'
> $6$80Ll7KOwC/XDQVgZ$NwTjYG8cXcMTr9NI1EbtaX7OEAIBq3ULUSJPmpjtk/vUiyB9duYO3aNqmWmBnS.mjgaN37/mo34R8COzNrFBh0

Dessa forma, a preciosa senha está segura, longe de olhos curiosos, e podemos colocá-la no script de criação de usuários.

# Arquivo:  tasks/default-tasks.yml
---
- name: Criar usuário admin com senha
  ansible.builtin.user:
    name: admin
    comment: Usuário Administrador
    password: "$6$80Ll7KOwC/XDQVgZ$NwTjYG8cXcMTr9NI1EbtaX7OEAIBq3ULUSJPmpjtk/vUiyB9duYO3aNqmWmBnS.mjgaN37/mo34R8COzNrFBh0"

Ansible: Testando Playbooks

Ansible é uma excelente ferramenta para automatização de tarefas.

Cria-se uma lista de computadores e instalando neles um servidor SSH e Python 3, é possível automatizar a instalação de programas, alteração de configurações e, o meu favorito, atualização dos computadores.

No maior estilo do TDD (Test Driven Development), muitas vezes, queremos ter certeza que algo está funcionando antes de prosseguir com a execução do playbook.

Exemplos destes casos são:

  • A nova configuração funciona?
  • O usuário realmente pode (não pode) usar usar sudo?
  • Login do usuário root está mesmo desativado?
  • O firewall liberou/bloqueou a porta HTTP?

Podemos criar testes como esses com certa facilidade e inclusive podemos escolher entre a máquina remota ou local para executar os comandos, o que é essencial para alguns testes.

Abaixo mostro dois exemplos, testando as permissões de um usuário do servidor remoto e testando se o computador local pode fazer login como root.

# Arquivo: tasks/testes.yml
---
- name: TESTE Verificar se o usuário 'admin' pode usar SUDO sem senha
  # Teste no servidor remoto:
  # - su admin -c "xxx": executa o comando xxx como admin;
  # - whoami: imprime o nome do usuário; e
  # - Esperamos que imprima 'admin' (sem o sudo) e em seguida
  #   imprima 'root' (com sudo).
  ansible.builtin.command: |
    su admin -c "whoami; sudo whoami"
  register: result
  changed_when: false
  failed_when: >
    result.stdout_lines[0] != 'admin'
    or result.stdout_lines[1] != 'root'
  timeout: 5

- name: TESTE Negar login do usuário 'root' port SSH
  # Teste no computador local:
  # - SSH, ativando senha e desativando chave pública;
  # - whoami: um comando qualquer para ser executado; e
  # - Esperamos que a tentativa de login seja negada.
  ansible.builtin.command: >
    ssh -oPasswordAuthentication=yes -oPubkeyAuthentication=no
    -p{{ ansible_port }} root@{{ ansible_host }} whoami
  register: result
  changed_when: false
  failed_when: "'Permission denied (publickey)' not in result.stderr"
  timeout: 5
  # Esta parte final definie que o teste é executado no computador local
  # (que executa o Ansible).
  # Desativamos ansible_become para que use o usuário atual, em vez de
  # usar sudo para se tornar root.
  delegate_to: localhost
  vars:
    ansible_become: false

Ansible: Variáveis Dependentes do Sistema Operacional

Ansible é uma excelente ferramenta para automatização de tarefas.

Cria-se uma lista de computadores e instalando neles um servidor SSH e Python 3, é possível automatizar a instalação de programas, alteração de configurações e, o meu favorito, atualização dos computadores.

No entanto, há coisas que são diferentes de um sistema para outro, por exemplo caminhos de arquivos de configuração e nomes de pacotes.

Para evitar criar várias e várias tarefas que fazem a mesma coisa ajustadas para cada sistema (when: ansible_distribution == ...), enchendo o playbook com código repetido, Ansible permite a importação de arquivos de variáveis com o módulo include_vars, onde podemos escolher dinamicamente o arquivo de variáveis correspondente ao sistema.

Então podemos criar um arquivo para cada um dos sistemas em uso e seguir em frente. Porém isso acaba por se tornar um pesadelo de manutenção: cada nova variável precisa ser adicionada em TODOS os arquivos de variáveis e, ao criar um arquivo de variáveis novo, é preciso atualizar todas as variáveis possíveis.

Uma alternativa interessante seria uma criar estrutura hierárquica, onde definimos valores padrão para todos, em seguida substituímos com os valores para a família, então com os valores para a distribuição e quem sabe até com valores para certas versões.

Padrão          default
                 /   \
Família      Debian  FreeBSD
              / \
Distro  Debian   Ubuntu

Além disso, é importante que a inexistência de algum desses arquivos de especialização não seja um empecilho.

Com um pouco de pesquisa e um pouco de tentativa-e-erro, e com "um pouco" quero dizer interminável, cheguei no código abaixo:

# Arquivo: tasks/default-tasks.yml
---
- name: Carrega variáveis padrão, da família e da distro
  ansible.builtin.include_vars: "{{ item }}"
  with_fileglob:
    - vars/default.yml
    - vars/family_{{ ansible_os_family }}.yml
    - vars/distro_{{ ansible_distribution }}.yml

Usamos include_vars, como de costume, porém junto com with_fileglob. Este geralmente é usado para encontrar vários arquivos usando coringas (wildcards) como vars/*.yml. Mas aqui não usamos coringas, damos direto o nome do arquivo. Se o arquivo não é encontrado ele é ignorado.

Abaixo estão exemplos dos arquivos de variáveis padrão e especializado.

# Arquivo: vars/default.yml
---
doas_config_file: /etc/doas.conf
# Arquivo: vars/family_FreeBSD.yml
---
doas_config_file: /usr/local/etc/doas.conf

Como testar pendrive e cartão SD no Linux

Uma situação extremamente chata é comprar um pendrive ou cartão SD falsificado.

Você coloca arquivos nele e, ao ler os dados de volta, estes estão corrompidos.

Comprei 4 pendrives de 16 GB e, para evitar essa situação inconveniente, quero testar se eles realmente possuem esse tamanho ou se são falsificados.

Para esses testes, geralmente crio um arquivo grande com dados aleatórios (de 128 MB até 1 GB) e copio ele várias vezes para dentro do pendrive e em seguida verifico se a checksum de cada um dos arquivos bate com a original.

Como testar pendrive e cartão SD no Linux

Testando 4 pendrives de uma vez com ZFS

Só que como dessa vez eu tenho 4 pendrives para testar, optei por fazer um pouco diferente.

Para este teste resolvi usar o sistema de arquivos ZFS.

O ZFS automaticamente guarda checksums de todos os arquivos armazenados e, ao lê-los valida essas checksums. Além disso, há um comando que realiza a verificação de todos os dados gravados.

Então, testar usando o ZFS me poupa o trabalho de manualmente criar checksums e de manualmente verificá-las. Basta gravar arquivos até encher os pendrives e verificar os dados.

Muito cuidado: é possível corromper o seu disco principal com os comandos utilizados.

Instalando as dependências

Realizei o teste no Ubuntu Linux 22.04.

Acredito que a única dependência sejam os utilitários do ZFS.

sudo apt install zfsutils-linux

Formatando

Primeiro listamos os discos.

$ ls -1rt /dev/sd*
> /dev/sda
> /dev/sdb
> /dev/sdc
> /dev/sdd
> /dev/sde

Para descobrir quais dos discos são os pendrives listamos os discos com o programa parted, filtrando os dados de interesse com o comando grep.

$ sudo parted -l | grep '/dev/sd'
> Disk /dev/sda: 1000GB
> Disk /dev/sdb: 15,7GB
> Disk /dev/sdc: 15,7GB
> Disk /dev/sdd: 15,7GB
> Disk /dev/sde: 15,7GB

Dica: Fique atento a qual é o seu disco principal. No meu caso é /dev/sda.

Vamos usar o ZFS para criar uma ZPOOL (conjunto de discos). Criamos uma ZPOOL chamada "teste" com os 4 pendrives em stripes (listras?).

$ sudo zpool create teste sdb sdc sdd sde

Dica: Não adicione o seu disco principal na ZPOOL.

Os pendrives já estavam formatados com FAT, então o ZFS se negou a criar a ZPOOL, o que causaria a perda de dados. Bastou adicionar -f no comando para forçar a criação, pois não tenho nada gravado neles.

Para melhorar um pouco a performance do teste configuramos as seguintes opções:

$ sudo zpool trim teste --wait
$ sudo zfs set atime=off teste
$ sudo zfs set sync=disabled teste
$ sudo zfs set compression=off teste

Sobre as opções habilitadas

Executar um "trim" no sistema de arquivos avisa o pendrive de todo o espaço livre, permitindo utilizá-lo de forma mais efetiva. Esse passo vai ser repetido no fim do teste. É possível que a operação de trim não seja suportada pelo pendrive ou cartão SD.

Além do trim, a única opção que recomendo usar na prática com ZFS é atime=off. Isso evita atualizar a data de acesso a cada leitura. Não tem porque transformar uma leitura em uma leitura-e-escrita, por isso o uso dessa opção é comum e recomendada.

As outras duas opções é só pra deixar o teste mais rápido e podem ter consequências indesejadas.

A opção sync=disabled, do ponto de vista da aplicação, pode afetar seriamente a consistência dos dados gravados. A maioria das aplicações, precisam que todas as gravações sejam síncronas: "só quero continuar quando os dados estiverem efetivamente gravados no disco." Essa opção mente para estas aplicações, tornando todas as gravações assíncronas.

Na prática, recomendo usar sync=standard, que deixa a aplicação escolher entre síncrono e assíncrono.

Já a opção compression=off serve para não comprimir os dados no disco.

Sim, outra vantagem do ZFS é que ele pode comprimir dados, automaticamente economizando o precioso espaço do disco. Porém, neste teste, compactar seria contra-produtivo porque queremos preencher o disco completamente e então varrê-lo em busca de erros.

Na prática, recomendo usar sync=lz4, que compacta muito bem as sequências de zero, que são muito comuns, e arquivos de texto.

Gravando

Uma coisa muito legal do ZFS é que ele monta tudo automaticamente. Como acabamos de criar uma ZPOOL, é óbvio que vamos utilizá-la e por isso ela já é montada pelo próprio ZFS.

Então basta logar como root (usando sudo), entrar na pasta do ZPOOL e começar a escrever arquivos.

$ sudo -i
$ cd /teste

Os arquivos que vamos escrever são arquivos aleatórios (lidos de /dev/urandom). Criamos uma variável COUNT, que conta o número de arquivos criados e, dentro de um laço while criamos arquivos de 128 MB utilizando o comando dd. Caso o comando dd falhe (ex: por falta de espaço em disco) o comando break pára o laço e aguardamos o sistema operacional sincronizar com o comando sync. Por fim, também já iniciamos o scrub (varredura) nos discos.

# Execute como root
COUNT=0
while :; do
    dd if=/dev/urandom of=file-$COUNT.bin bs=1M count=128 || break
    COUNT="$(($COUNT + 1))"
done
sync
zpool scrub teste

Para ver o andamento da gravação podemos usar os comandos abaixo. Eu prefiro o penúltimo.

$ ls -lh /teste
$ zfs list teste
$ zpool iostat teste 5
$ zpool iostat teste 5 -v

Verificando

Finalizada a gravação, o scrub (varredura) nos discos foi iniciado e o ZFS vai ler todos os dados, de todos os pendrives, e vai validar esses dados com as checksums.

No fim da gravação, logo após o comando sync já iniciamos o scrub, que pode ser iniciado novamente com o comando abaixo.

$ sudo zpool scrub teste

Podemos ver o andamento do scrub no status da ZPOOL.

$ zpool status teste
> ...
>   scan: scrub in progress since ...
>     14.0G scanned at 182M/s, 720M issued at 9.12M/s, 14.0G total
>     0B repaired, 5.01% done, no estimated completion time
> ...

Quando o scrub finalizar vemos o resultado também no status da ZPOOL.

$ zpool status teste
> ...
>   scan: scrub repaired 0B in 00:27:31 with 0 errors on ...
> ...
>     NAME      STATE     READ WRITE CKSUM
>     teste     ONLINE       0     0     0
>       sdb     ONLINE       0     0     0
>       sdc     ONLINE       0     0     0
>       sdd     ONLINE       0     0     0
>       sde     ONLINE       0     0     0
> ...

Ótimas notícias: Nenhum erro!

Limpando e exportando

Agora vamos apagar todos os arquivos e aplicar um "trim" no sistema de arquivos, instruindo ao pendrive que o espaço livre pode ser reutilizado, melhorando a longevidade do pendrive.

$ sudo rm -f /teste/file-*.bin
$ sudo zpool trim teste --wait

Estando tudo finalizado, podemos exportar (ejetar) a ZPOOL para podermos formatar os pendrives e usá-los normalmente.

$ sudo zpool export teste

Conclusão

Em 3 horas os 4 pendrives estavam testados e pude aproveitar boa parte do tempo para escrever este post.

Tamanha a conveniência, acredito que será meu novo método de testes de pendrives e cartões SD.

Copiando a suas chaves SSH do GitHub ou GitLab

As vezes queremos copiar uma chave pública SSH para um computador.

Esse é um jeito fácil de adicionar a sua própria chave ou de adicionar a chave de alguém para te ajudar com algum problema.

Tendo uma conexão com a internet, é possível fazer o download das chaves públicas SSH de qualquer usuário do GitHub ou GitLab.

Com os comandos abaixo é possível fazer o download da chave adicioná-las nas chaves permitidas para acesso SSH. Basta trocar USUARIO pelo nome do usuário desejado.

  1. Configure a máscara de permissões para 077: leitura e escrita apenas para o usuário dono.
    • umask 077
  2. Crie a pasta ~/.ssh, se ela não existir.
    • mkdir -p ~/.ssh
  3. Faça o download das chaves:
    • GitHub: curl https://github.com/USUARIO.keys >>~/.ssh/authorized_keys
    • GitLab: curl https://gitlab.com/USUARIO.keys >>~/.ssh/authorized_keys

Também podemos usar wget em vez de curl, substituindo curl por wget -O-.