A briga entre Docker e firewalld

Categoria: Linux Publicado: Segunda, 16 Agosto 2021 Escrito por Helio Loureiro

Tirei férias longas demais.   Na verdade foram apenas 3 semanas, mas eu meio que deixei de postar aqui durante o período de férias.  E a procrastinação voltou forte.   Então aqui vamos nós com uma tentativa de voltar a escrever semanalmente.

Hoje, fazendo um troubleshooting the um serviço que não funcionava como devia (um scan de aplicação com o OWASP ZAP), descobri que meus containers em docker não estavam acessando a rede.   O que mudou?   Minha máquina de trabalho é um Ubuntu 18.04.  O repositório bionic-update trouxe uma versão nova do docker que reiniciou o daemon, mas... a parte de rede não funcionando.  E só percebi isso hoje.

root@dell-latitude-7480 /u/local# apt show docker.io
Package: docker.io
Version: 20.10.7-0ubuntu1~18.04.1
Built-Using: glibc (= 2.27-3ubuntu1.2), golang-1.13 (= 1.13.8-1ubuntu1~18.04.3)
Priority: optional
Section: universe/admin
Origin: Ubuntu
Maintainer: Ubuntu Developers <Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo.>
Original-Maintainer: Paul Tagliamonte <Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo.>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 193 MB
Depends: adduser, containerd (>= 1.2.6-0ubuntu1~), iptables, debconf (>= 0.5) | debconf-2.0, libc6 (>= 2.8), libdevmapper1.02.1 (>= 2:1.02.97), libsystemd0 (>= 209~)
Recommends: ca-certificates, git, pigz, ubuntu-fan, xz-utils, apparmor
Suggests: aufs-tools, btrfs-progs, cgroupfs-mount | cgroup-lite, debootstrap, docker-doc, rinse, zfs-fuse | zfsutils
Breaks: docker (<< 1.5~)
Replaces: docker (<< 1.5~)
Homepage: https://www.docker.com/community-edition
Download-Size: 36.9 MB
APT-Manual-Installed: yes
APT-Sources: mirror://mirrors.ubuntu.com/mirrors.txt bionic-updates/universe amd64 Packages
Description: Linux container runtime
 Docker complements kernel namespacing with a high-level API which operates at
 the process level. It runs unix processes with strong guarantees of isolation
 and repeatability across servers.
 .
 Docker is a great building block for automating distributed systems:
 large-scale web deployments, database clusters, continuous deployment systems,
 private PaaS, service-oriented architectures, etc.
 .
 This package contains the daemon and client. Using docker.io on non-amd64 hosts
 is not supported at this time. Please be careful when using it on anything
 besides amd64.
 .
 Also, note that kernel version 3.8 or above is required for proper operation of
 the daemon process, and that any lower versions may have subtle and/or glaring
 issues.

N: There is 1 additional record. Please use the '-a' switch to see it

Primeira coisa que tentei foi reiniciar o docker mesmo.

root@dell-latitude-7480 /u/local# systemctl restart --no-block docker; journalctl -u docker -f
[...]
Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.203367946+02:00" level=info msg="Firewalld: docker zone already exists, returning"
Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.549158535+02:00" level=warning msg="could not create bridge network for id 88bd200
b5bb27d3fd10d9e8bf86b1947b2190cf7be36cd7243eec55ac8089dc6 bridge name docker0 while booting up from persistent state: Failed to program NAT chain:
ZONE_CONFLICT: 'docker0' already bound to a zone" Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.596805407+02:00" level=info msg="stopping event stream following graceful shutdown"
error="" module=libcontainerd namespace=moby Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: time="2021-08-12T10:29:25.596994440+02:00" level=info msg="stopping event stream following graceful shutdown"
error="context canceled" module=libcontainerd namespace=plugins.moby Aug 12 10:29:25 dell-latitude-7480 dockerd[446605]: failed to start daemon: Error initializing network controller: Error creating default "bridge" network:
Failed to program NAT chain: ZONE_CONFLICT: 'docker0' already bound to a zone Aug 12 10:29:25 dell-latitude-7480 systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE Aug 12 10:29:25 dell-latitude-7480 systemd[1]: docker.service: Failed with result 'exit-code'. Aug 12 10:29:25 dell-latitude-7480 systemd[1]: Failed to start Docker Application Container Engine. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Service hold-off time over, scheduling restart. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Scheduled restart job, restart counter is at 3. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: Stopped Docker Application Container Engine. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Start request repeated too quickly. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: docker.service: Failed with result 'exit-code'. Aug 12 10:29:27 dell-latitude-7480 systemd[1]: Failed to start Docker Application Container Engine.

As linhas estão editadas pra facilitar a visualização uma vez que o systemd usa linhas bem maiores que 120 colunas.  Mas o resultado foi... falha.

Parando o firewalld e somente reiniciando docker levava a uma condição em que o daemon iniciava, mas ao iniciar o container, novamente ficava sem acesso à rede.

root@dell-latitude-7480 /u/local# docker run -it --rm --init ubuntu:20.04 bash
root@f45dcbb1ecaa:/# ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
^C
--- 1.1.1.1 ping statistics ---
6 packets transmitted, 0 received, 100% packet loss, time 5153ms

root@f45dcbb1ecaa:/# exit

Olhando somente as regras do firewall eu pude ver que realmente o docker estava carregando a regra correta sem o firewalld:

root@dell-latitude-7480 /u/local# systemctl stop firewalld.service 
root@dell-latitude-7480 /u/local# iptables -L -n -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
root@dell-latitude-7480 /u/local# systemctl restart docker
root@dell-latitude-7480 /u/local# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2021-08-12 12:01:12 CEST; 4s ago
     Docs: https://docs.docker.com
 Main PID: 484649 (dockerd)
    Tasks: 27
   CGroup: /system.slice/docker.service
           └─484649 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061383466+02:00" level=warning msg="Your kernel does not support swap 
 memory limit"
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061414030+02:00" level=warning msg="Your kernel does not support CPU
 realtime scheduler"
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061421558+02:00" level=warning msg="Your kernel does not support cgroup
 blkio weight"
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061427194+02:00" level=warning msg="Your kernel does not support cgroup
 blkio weight_device"
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.061796106+02:00" level=info msg="Loading containers: start."
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.531851162+02:00" level=info msg="Loading containers: done."
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.549979768+02:00" level=info msg="Docker daemon"
 commit="20.10.7-0ubuntu1~18.04.1" graphdriver(s)=overlay2 version=20.10.7
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.550057275+02:00" level=info msg="Daemon has completed initialization"
Aug 12 12:01:12 dell-latitude-7480 dockerd[484649]: time="2021-08-12T12:01:12.558188106+02:00" level=info msg="API listen on /var/run/docker.sock"
Aug 12 12:01:12 dell-latitude-7480 systemd[1]: Started Docker Application Container Engine.
root@dell-latitude-7480 /u/local# iptables -L -n -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.16.0.0/24       0.0.0.0/0           

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           

Claramente existia uma regra de MASQUERADE vinda da rede do docker (172.16.0.0/24).  E o firewalld estava sumindo com essa regra ao ser ativado (pra ficar menos poluído com várias regras peguei só a saída da cadeia do POSTROUTING.

root@dell-latitude-7480 /u/local# systemctl start firewalld.service 
root@dell-latitude-7480 /u/local# iptables -L POSTROUTING -n -t nat 
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
POSTROUTING_direct  all  --  0.0.0.0/0            0.0.0.0/0           
POSTROUTING_ZONES_SOURCE  all  --  0.0.0.0/0            0.0.0.0/0           
POSTROUTING_ZONES  all  --  0.0.0.0/0            0.0.0.0/0

A minha primeira ideia: inserir à força uma regra de MASQUERADE direto na cadeia de POSTROUTING.

root@dell-latitude-7480 /u/local# iptables -I  POSTROUTING 1 -s 172.16.0.0/24 -j MASQUERADE -t nat
root@dell-latitude-7480 /u/local# iptables -L POSTROUTING --line-numbers -t nat
Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination         
1    MASQUERADE  all  --  172.16.0.0/24       anywhere            
2    POSTROUTING_direct  all  --  anywhere             anywhere            
3    POSTROUTING_ZONES_SOURCE  all  --  anywhere             anywhere            
4    POSTROUTING_ZONES  all  --  anywhere             anywhere            

E, claro, não deu certo.

Depois de procurar na Internet sobre docker e firewalld, encontrei o próprio site do Docker explicando como fazer isso em https://docs.docker.com/network/iptables/ com o seguinte comando:

# Please substitute the appropriate zone and docker interface
$ firewall-cmd --zone=trusted --remove-interface=docker0 --permanent
$ firewall-cmd --reload

Beleza.  Agora não teria como dar errado. E...

root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0
public
root@dell-latitude-7480 /u/local# firewall-cmd --zone=public --remove-interface=docker0 --permanent
The interface is under control of NetworkManager and already bound to the default zone
The interface is under control of NetworkManager, setting zone to default.
success
root@dell-latitude-7480 /u/local# systemctl start docker
Job for docker.service failed because the control process exited with error code.
See "systemctl status docker.service" and "journalctl -xe" for details.

Caramba... algo de errado não estava certo.  Bom... se tivesse funcionado de primeira, eu provavelmente não teria escrito esse artigo.

Então vamos rever em qual zona está a interface docker0, remover essa interface dessa zona e adicionar na zona do docker.

root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0
public
root@dell-latitude-7480 /u/local# firewall-cmd --zone=public --remove-interface=docker0 --permanent
The interface is under control of NetworkManager and already bound to the default zone
The interface is under control of NetworkManager, setting zone to default.
success
root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0
public
root@dell-latitude-7480 /u/local# firewall-cmd --reload
success
root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0
public

Mas que catzo... esse foi problema que encontrei.  Por mais que eu removesse ou tentasse remover a interface docker0 da zone public, sempre voltava.

Foram algumas horas nesse vai e vem, procurando na Internet o que fazer, lendo documentação do firewalld, até que finalmente acertei.

root@dell-latitude-7480 /u/local# firewall-cmd --zone=docker --add-interface=docker0 --permanent
The interface is under control of NetworkManager, setting zone to 'docker'.
success
root@dell-latitude-7480 /u/local# firewall-cmd --get-zone-of-interface=docker0
docker
root@dell-latitude-7480 /u/local# firewall-cmd --reload
success
root@dell-latitude-7480 /u/local# systemctl start docker

Então não precisava do comando pra remover.  Apenas adicionar diretamente na zona desejada.

root@dell-latitude-7480 /u/local# docker run -it --rm --init ubuntu:20.04 bash
root@e5d78d7f081b:/# ping -c 5 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=58 time=1.95 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=58 time=2.02 ms
64 bytes from 1.1.1.1: icmp_seq=3 ttl=58 time=1.68 ms
64 bytes from 1.1.1.1: icmp_seq=4 ttl=58 time=1.62 ms
64 bytes from 1.1.1.1: icmp_seq=5 ttl=58 time=1.76 ms

--- 1.1.1.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4003ms
rtt min/avg/max/mdev = 1.621/1.808/2.026/0.162 ms
root@e5d78d7f081b:/# exit

via GIPHY

Limpando entradas de um disco removido do LVM

Categoria: Linux Publicado: Segunda, 24 Maio 2021 Escrito por Helio Loureiro

E minhas aventuras com LVM continuam.  Um dos HDDs começou a chiar.  Mas chiar mesmo, fazendo barulho de marteladas e resets.   Olhando nos logs eu vi que já tinha dado o que tinha que dar.  E justamente o HDD que fazia parte do mirroring em que estão os jogos da steam.

Então procedi com o comando pra terminar a montagem em mirroring.

lvconvert -m 0 diskspace/steam /dev/sdb3

Infelizmente eu dei um reboot em seguida e não salvei nada pra poder postar aqui.  Mas o mais importante foi depois de removido HDD e instalado um novo (também de 2 TB) que tinha parado aqui.  Como eu esqueci de remover o /dev/sdb3 do LVM, claro que subiu com vários erros. Particionei o novo HDD (/dev/sdb) com uma só partição LVM. E fui adicionar quando...

root@goosfraba ~# pvcreate /dev/sdb1
  WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
  WARNING: VG diskspace is missing PV CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa (last written to /dev/sdb3).
  Physical volume "/dev/sdb1" successfully created.

Epa!  Que catzo de CvlXC4-não-sei-lá-o-que é esse?  Sim, o disco que removi fisicamente e não tirei logicamente do LVM.  Com "vgdisplay" pude ver que realmente o problema estava lá.

root@goosfraba ~# vgdisplay 
  WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
  WARNING: VG diskspace is missing PV CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa (last written to /dev/sdb3).
  --- Volume group ---
  VG Name               diskspace
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  230
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                9
  Open LV               2
  Max PV                0
  Cur PV                2
  Act PV                1
  VG Size               5.45 TiB
  PE Size               4.00 MiB
  Total PE              1429493
  Alloc PE / Size       518912 /

Resolver não foi nada complicado.  Bastou o seguinte comando:

root@goosfraba ~# vgreduce  --removemissing diskspace
  WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
  WARNING: VG diskspace is missing PV CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa (last written to /dev/sdb3).
  WARNING: Couldn't find device with uuid CvlXC4-LiEI-mr0c-vSky-oryk-Khrl-J1dyBa.
  Wrote out consistent volume group diskspace.

Pronto!  Metadados do disco antigo removidos.

root@goosfraba ~# vgdisplay 
  --- Volume group ---
  VG Name               diskspace
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  231
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                9
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size

Agora o trabalho seguinte foi adicionar o espaço novo dentro do VG (diskspace é o nome):

root@goosfraba ~# vgextend diskspace /dev/sdb1
  Volume group "diskspace" successfully extended
root@goosfraba ~# vgdisplay 
  --- Volume group ---
  VG Name               diskspace
  System ID             
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  232
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                9
  Open LV               2
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               

Em seguida colocar o disco novo como mirroring novamente:

root@goosfraba ~# lvconvert -m 1 /dev/diskspace/steam /dev/sdb1
Are you sure you want to convert linear LV diskspace/steam to raid1 with 2 images enhancing resilience? [y/n]: y
  Logical volume diskspace/steam successfully converted.

E pronto.  Acabou.  Isso mesmo.  Foi fácil assim.  Agora é só ir monitorando o progresso da cópia no disco novo.

root@goosfraba ~# lvs
  LV       VG        Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  home     diskspace -wi-a----- 500.00g                                                    
  opt      diskspace -wi-a-----   2.00g                                                    
  root     diskspace -wi-ao----  10.00g                                                    
  steam    diskspace rwi-a-r--- 750.00g                                    0.88            
  swap     diskspace -wi-a-----  15.00g                                                    
  tmp      diskspace -wi-a-----   5.00g                                                    
  usr      diskspace -wi-ao----  95.00g                                                    
  usrlocal diskspace -wi-a----- 600.00g                                                    
  var      diskspace -wi-a-----  50.00g                                                    
root@goosfraba ~# lvs
  LV       VG        Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  home     diskspace -wi-a----- 500.00g                                                    
  opt      diskspace -wi-a-----   2.00g                                                    
  root     diskspace -wi-ao----  10.00g                                                    
  steam    diskspace rwi-a-r--- 750.00g                                    1.10            
  swap     diskspace -wi-a-----  15.00g                                                    
  tmp      diskspace -wi-a-----   5.00g                                                    
  usr      diskspace -wi-ao----  95.00g                                                    
  usrlocal diskspace -wi-a----- 600.00g                                                    
  var      diskspace -wi-a-----  50.00g                                                    

Como são 750 GB a coisa demora um pouco.  Mas com LVM, tudo é muito fácil.

Happy hacking :)

Migrando seus dados pra um disco novo

Categoria: Linux Publicado: Quinta, 01 Abril 2021 Escrito por Helio Loureiro

Essa dúvida surgiu no grupo de Ubuntu do Telegram (https://t.me/ulboficial) e ao invés de responder lá eu preferi escrever um artigo sobre isso.

O método que uso, principalmente em laptops novos, é basicamente o mesmo desde... acho que aprendi a fazer isso por volta dos anos 2000.  Era um capítulo do venerado livro "Unix Power Tools", a bíblia dos scripts pra sysadmins.

Livro Unix Power Tools
Livro Unix Power Tools

Eu primeiramente insiro o HD novo e particiono.  Não vou usar o exemplo de LVM, que é mais simples, mas o mais cabeludo que é quando se cria todas as partições que vai usar.  Então digamos que eu adicione um novo HD que aparecerá para mim como /dev/sdb (pra facilitar).  Supondo que o particionamento seja o seguinte:

  • /dev/sdb1: /boot
  • /dev/sdb5: /
  • /dev/sdb6: /home

O passo seguinte, após formatar as partições no formato desejado (eu geralmente uso XFS), é montar essas partições em um local onde possa copiar os dados.  Eu normalmente uso o /mnt pra essa finalidade.

root@goosfraba ~# mount /dev/sdb5 /mnt
root@goosfraba ~# mount /dev/sdb1 /mnt/boot
root@goosfraba ~# mount /dev/sdb6 /mnt/home

Uma vez com tudo montado é fazer cópia de um lado pro outro evitando copiar o /mnt, que já está em uso com o disco novo, e diretórios como /proc, /dev ou /sys, que são criados durante o boot.  E pra isso eu uso o comando tar.

root@goosfraba ~# tar cvf - --exclude=./proc --exclude=./sys --exclude=./dev --exclude=./mnt -C / . | tar xf -C /mnt

Com isso o disco já está todo pronto em /mnt.  Resta corrigir o boot.  Pra isso eu uso chroot pra acessar o disco novo a partir de /mnt e montando tanto /proc quanto /dev pro grub localizar o disco corretamente.

root@goosfraba ~# mkdir /mnt/{proc, dev}
root@goosfraba ~# mount --bind /proc /mnt/proc
root@goosfraba ~# mount --bind /dev /mnt/dev
root@goosfraba ~# chroot /mnt
root@chroot ~$ grub-install /dev/sdb 

Assumindo que vá remover o primeiro disco, /dev/sda, e sdb será o novo sda, nada mais é preciso.  Agora caso vá ficar com sdb como disco principal, então é preciso modificar as entradas do /etc/fstab pra conter os dados corretos do novo disco.

Ao terminar e antes de rebootar, tenha com você o disco de instalação em mãos pra utilizar o modo de recuperação em caso de algum problema (e não precisar ficar colocando o disco antigo).

Reconhecimento de palavras em arquivos PDF com OCR

Categoria: Linux Publicado: Quarta, 27 Janeiro 2021 Escrito por Helio Loureiro

Não sei já descrevi isso aqui ao longo desses 20 anos de blog/site, mas a maioria das coisas que publico são pra mim mesmo.  Como tenho certeza que não vou lembrar o que fiz daqui algum tempo (provavelmente dias), eu deixo aqui registrado como bloco de notas pra referência.  E, claro, espero que isso também ajude mais pessoas além de mim.

Como moro fora do Brasil já faz algum tempo, e ainda não domino a língua (na verdade só apanho dela), eu preciso de tempos em tempos pegar documentos e ler.  A forma que encontrei é passar o documento em um scanner, se for papel, mas ter a certeza de ter o documento digitalizado em formato PDF no final.  Esse foi o formato que mais facilitou o uso de outro software pra OCR (Optical Character Recognition), ou reconhecimento ótico de caracter.

Também percebi que o melhor é ter o documento separado em vários PDFs se possível, um pra cada página.  Isso facilita pro software de OCR de reconhecer cada página corretamente.

Pra usar como exemplo aqui vou adotar um documento sobre o imposto de renda sueco.  Ele está disponível na página do que seria equivalente à receita federa: https://www.skatteverket.se

O documento será esse aqui: https://www.skatteverket.se/download/18.7eada0316ed67d7282aedd/1582550479006/dags-att-deklarera-skv325-utgava41.pdf

Pra começar, apenas baixar o documento usando o curl:

curl -o report.pdf https://www.skatteverket.se/download/18.7eada0316ed67d7282aedd/1582550479006/dags-att-deklarera-skv325-utgava41.pdf

Em seguida serão necessários os seguintes pacotes de software (assumindo um sistema ubuntu ou debian): curl, ghostscript, imagemagick, tesseract-ocr e tesseract-<língua>.  Como no caso eu pego os documentos em sueco, uso então tesseract-swe.

sudo apt install curl ghostscript imagemagic tesseract-ocr tesseract-swe

O arquivo então será baixado e salvo como "report.pdf".  Ao abrir o documento eu vejo quantas páginas são, o que poderia ser feito de alguma outra forma mais automática, mas a visualização assim é fácil até pra detectar logo se tem alguma página pra pular com imagens.  Esse documento tem 8 páginas.

Então pra separar o arquivo em PDF baixado em páginas separadas, que depois vai facilitar o trabalho de tradução, eu uso o seguinte comando:

for i in $(seq 1 8)
    do
    gs -sDEVICE=pdfwrite -q -dNOPAUSE -dBATCH -sOutputFile=report-$i.pdf -dFirstPage=$i -dLastPage=$i report.pdf
done

Com as páginas criadas separadamente em formato PDF e com os nomes como report-1.pdf, report-2.pdf, report-3.pdf, etc e o próximo passo é converter cada uma no formato TIFF, que é o formato onde o reconhecimento de caracteres funciona melhor.  O programa "convert" que faz isso é parte do pacote imagemagick

for i in $(seq 1 8)
    do
    convert report-$i.pdf report-$i.tiff
done

Isso gera então as sequências report-1.tiff, report-2.tiff, etc.

Agora finalmente o passo final pra ter os textos em plain text.

for i in (seq 1 8)
do tesseract report-$i.tiff report-$i -l swe done

E isso cria os documentos com extensão "txt".  Esse documento que escolhi não foi muito feliz na detecção de caracteres.  O arquivo que mais foi reproduzido de forma satisfatória foi a página 7:

helio@xps13ubuntu:exemplo$ cat report-7.txt
Har du skatt att betala på din
preliminära skatteuträkning?

    

Tabellen på sidan 6 visar när skatten senast ska vara
betald. Fram till dess kan du göra delbetalningar.
Du kan när som helst betala in pengar till ditt
skattekonto. Du kan betala antingen med Swish
BIS

Om duvill betala med Swish loggar du in på Mina
sidor och följer instruktionerna där, Du kan också
enkelt betala din kvarskatt med Swish i samband
med att du deklarerar med edegitimation i
tjänsten. Du kan betala skatt med maximalt
15.000 kronor per dygn med Swish.

Om du betalar genom att göra en inbetalning till
BARR Sr ehe
angeditt OCR-nummer som du hittar i din preli-
PST RR NAS TT NT
www.skatteverketse/ocr.

Läs mer på wwwskotteverket.se/betalokvarskatt.

O passo seguinte é copiar essas páginas e colocar no google translator.  Eu não automatizo esse passo e uso o simple copy&paste no browser.

E esse seria o resultado do trecho acima:

Você tem imposto a pagar sobre o seu
cálculo preliminar do imposto?

    

A tabela na página 6 mostra quando o imposto deve durar
pago. Até então, você pode parcelar.
Você pode depositar dinheiro no seu a qualquer momento
conta fiscal. Você pode pagar com Swish
BIS

Se você quiser pagar com Swish, faça login no Mina
páginas e siga as instruções lá, você também pode
pague facilmente seu imposto residual com o Swish em conjunto
com isso você declara com edegitimação em
o serviço. Você pode pagar impostos com um máximo
SEK 15.000 por dia com Swish.

Se você pagar fazendo outro pagamento
BARR Sr ehe
número OCR especificado que você encontra na sua
PST RR NAS TT NT
www.skatteverketse / ocr.

Leia mais em wwwskotteverket.se/betalokvarskatt. 

Resolvendo a baleiada do docker com exit code 139

Categoria: Linux Publicado: Segunda, 21 Setembro 2020 Escrito por Helio Loureiro

Esses dias precisei fazer uma migração de uma mediawiki que usamos na empresa de uma máquina que rodava CentOS 6.8 pra um Ubuntu 18.04.

Para garantir seu funcionamento, primeiro eu queria testar os upgrades necessários em minha máquina.  Nada melhor que copiar os arquivos e rodar a versão exata do site remoto com containers em docker.

Mas ao rodar o container... ele simplesmente saia com código de erro 139.  Mais nada.  Sem logs, sem describe, sem nada que pudesse ajudar.

~ > docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS                  NAMES
8ffcdf12d761        centos:6.8          "bash"              52 minutes ago      Up 52 minutes                 0.0.0.0:8080->80/tcp   elated_ganguly
7bd9374248fd        centos:6.8          "bash"              56 minutes ago      Exited (139) 56 minutes ago                          dreamy_fermat
80ff07e9b84e        centos:6.8          "bash"              10 hours ago        Exited (139) 10 hours ago                            romantic_hertz
3db1d6c1f68b        centos:6            "bash"              10 hours ago        Exited (139) 10 hours ago                            bold_kilby

Olhando pela Internet, descobri em alguns sites pessoas relatando o mesmo problema.  É algo relacionado com a versão da glibc do container com a versão do kernel que estou rodando, que é muito mais novo:

 ~ > uname -a
Linux elxa7r5lmh2 5.9.0-rc5-helio #10 SMP Sat Sep 19 12:04:57 CEST 2020 x86_64 x86_64 x86_64 OSI/Linux

A solução é adicionar um parâmetro a mais no grub a opção "vsyscall=emulate":

~ > grep GRUB_CMDLINE_LINUX /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0 pcie_aspm=off pci=nomsi vsyscall=emulate" 

e fazer um update no próprio grub.

~ > sudo update-grub2 && sudo reboot -f

Após um reboot os containers funcionaram sem problemas.

~ > docker run -it --rm=true centos:6.8 bash
[root@224aecaba978 /]# hostname
224aecaba978
[root@224aecaba978 /]# exit
exit
 ~ > docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                   PORTS               NAMES
ca68add255bd        ubuntu:18.04        "bash"              2 hours ago         Exited (1) 2 hours ago                       festive_galois
 ~ > 

Eu tentei configurar diretamente no kernel através da interface em /sys, mas eu só consegui com isso gerar um kernel panic.  O jeito mais fácil e seguro foi mesmo rebootando meu laptop.