Atualizando os artigos mais lidos

Só agora percebi que tinha um "artigos mais lidos de 2022" no topo do site. Atualizei pra mostrar os resultados de 2024.

Só levei algum tempo pra achar onde fazia isso. E não, não foram 2 anos pra achar. É que eu realmente não tinha percebido que isso estava no topo do site.

E no apagar de luzes de 2024...

E no fim de 2024, porque não fechar o ano com um ataque de DoS? Aparentemente alguém achou isso uma boa ideia. Ao menos o site não foi afetado.

Claro que também podem ter sidos acessos legítimos. Principalmente no https://linux-br.org mas eu particularmente não boto muita fé nisso.

Uma pausa pro fim do ano

Depois de um ano muito louco, finalmente é tempo pra uma pausa pra descansar.

Meus votos pra 2025: o ano do Linux no desktop

E como sempre faço em todo fim de ano, renovo meus votos de que 2025 será o ano do Linux no desktop.

Aumentando o swap com zfs no Ubuntu 24.04

Acabei tendo problemas de memória no laptop de trabalho. Então resolvi aumentar o tamanho do swap. Ou melhor, adicionar um volume lógico no zfs pro swap.

Após uma pesquisa rápida, achei a forma e fazer isso. Sem grandes surpresas, extremamente fácil.

  
    root@silverhand$ zfs create -V 64G \
    -b (getconf PAGESIZE) \
    -o compression=zstd-fast \
    -o logbias=throughput \
    -o sync=always -o primarycache=metadata \
    -o secondarycache=none \
    -o com.sun:auto-snapshot=false \
    rpool/swap
    root@silverhand$ mkswap /dev/zvol/rpool/swap
    root@silverhand$ swapon /dev/zvol/rpool/swap
  

O resultado foi rápido e fácil. Com 32 GB de RAM eu não esperava precisar tanto assim de swap. Mas o chrome insiste em tomar toda memória livre possível.

Não necessariamente relacionado, mas fiz upgrade do meu laptop pro Ubuntu 24.10. Como também está com zfs, fiz snapshots de cada volume antes de atualizar. Então fica fácil voltar se algo der errado. Por enquanto parece estar funcionando como esperado. E devo escrever sobre isso em breve.

Capturas da webcam atualizado pra 2024

Esse é um update to artigo usando python pra capturar a webcam. E com o uso do obamawatched 2021. Eu atualizei recentemente o obamawatcher pra rodar com PySide6 e está aqui funcionando no meu laptop atual de trabalho. No meu pessoal também. Ao menos acho que está funcionando.

O resultado final já está no vídeo acima. Como são várias imagens, os comandos que listei antes já não funcionam muito bem. Eu precisei primeiro redimensionar as imagens pra 640x360 para ficar em widescreen (16:9). As fotos mais antigas saíram em formato quadrado porque era o que a Webcam suportava na época. Então precisei cortar pra ficarem no aspecto correto. E pra isso eu usei python com pillow.

  
    #! /usr/bin/env python3

import os
import re
import argparse
try:
    from PIL import Image
except ModuleNotFoundError as e:
    raise Exception("missing pillow - run: pip install Pillow") from e


golden_rate = 1280/720
default_size_x = 640
default_size_y = 360

parse = argparse.ArgumentParser(description="Script to resize pictures from a specific directory to the same size")
parse.add_argument("--directory", required=True, help="directory with images jpg")
args = parse.parse_args()

for filename in sorted(os.listdir(args.directory)):
    if not re.search("jpg", filename):
        continue
    with Image.open(args.directory + "/" + filename) as image:
        width, height = image.size
        rate = float(width)/float(height)
        is_golden = rate == golden_rate

        is_correted = False
        rate_from_default = width / default_size_x
        if rate_from_default == 1:
            pass 
        elif rate_from_default > 1:
            image = image.resize((default_size_x, int(height/rate_from_default)))
            is_correted = True
        else:
            image = image.resize((default_size_x, int(height/rate_from_default)), Image.Resampling.LANCZOS)
            is_correted = True

        if not is_golden:
            image = image.crop((0, 0, default_size_x, default_size_y))

        image.save(filename)
        print(f"{filename}: {width}x{height}, golden: {is_golden}, corrected: {is_correted}")


print(f"Golden rate: {golden_rate}")
  

Tendo as imagens no mesmo formato, basta ordernar e usar ffmpeg pra montar o vídeo.

  
#! /usr/bin/env bash

die() {
  echo "$1" >&2
  exit 1
}

counter=0
for img in [0-9]*.jpg
do
  serial=$(printf "%06d" $counter)
  new_name="G${serial}.JPG"
  counter=$(expr $counter + 1)
  if [ -f "$new_name" ]; then
    echo "$new_name already exists"
    continue
  fi
  echo "$img => $new_name"
  mv $img $new_name
done

case $(uname -s) in
  Linux)
    echo "Merging images into single video file: output.mp4"
    ffmpeg -r 8 -i G%06d.JPG -c:v h264 -b:v 5M output.mp4 || \
      die "Failed to render output.mp4"
    ;;
  Darwin) 
    echo "Merging images into single video file: output.mp4"
    ffmpeg -hwaccel auto -r 8 -i G%06d.JPG -c:v h264_videotoolbox -b:v 5M output.mp4 || \
      die "Failed to render output.mp4"
esac    
  

Eu não testei o código de macOS, então pode ser que não funcione. Meu PC atual, um Lenovo Thinkpad, é processador e GPU Intel. Eu tentei usar -hwaccel vaapi mas as cores saiam erradas, no estilo negativo de filme. Então deixei rodar no processador mesmo.

E pra comparar o resultado em mpeg comparado com o mesmo em gif:

  
░▒▓ …/tmp/imagens-webcam/Webcam2 via v3.12.3 (venv) 15:33 
❯ gm convert -delay 20 *JPG output.gif
░▒▓ …/tmp/imagens-webcam/Webcam2 via v3.12.3 (venv) 15:38 
❯ ls -l output.*
.rw-rw-r-- 209M helio 27 Nov 15:38  output.gif
.rw-rw-r-- 103M helio 27 Nov 13:54  output.mp4
  

A geração do gif demorou uma eternidade, 5 minutos. O ffmpeg foram só alguns segundos. O tamanho foi o dobro no gif. Pra ver a imagem, teria de carregar tudo e só depois ver o resultando. Como mpeg, vai enviando o vídeo aos poucos.

Eu finalizei o vídeo no kdenlive, fazendo o merge com o vídeo anterior e adicionando a música. Ficou uma nostalgia gostosa.

E com certeza atualizarei daqui alguns anos.

Filtrando uma resposta json em ansible

Abri um grupo novo, SRE - Site Reliability Engineer, e uma categoria nova sobre Ansible já que agora esse é meu mundo. Estou trabalhando nessa área mantendo servidores rodando na empresa nova.

Eu estou migrando um sistema que cria VMs de python, pela falta de suporte na biblioteca, pra outra forma via linha de comando. Mas essa parte não interessa muito. O interessante é sobre como fiz parte do trabalho.

E estou escrevendo a respeito porque foi difícil achar informação sobre isso na Internet. A maioria estava ou errada ou não funcionava como esperado.

Pra começar, vamos dar uma olhada na estrutura de dados que recebo do programa. Pra simular o mesmo no Ansible, vou trocar o comando por um: cat server.json (onde server.json contém esse dado). Meu objetivo aqui é pegar o IPv4 da interface pública pra depois provisionar o sistema com os programas que vamos usar.


  {
  "core_number": "1",
  "hostname": "helio-testing.loureiro.eng.br",
  "license": 0,
  "memory_amount": "1024",
  "plan": "1xCPU-1GB",
  "progress": "0",
  "state": "started",
  "tags": [],
  "title": "helio-testing.loureiro.eng.br (created via ansible)",
  "uuid": "87271929-a137-4910-9648-75d05b6d3ecb",
  "zone": "sweden-stockholm",
  "boot_order": "disk",
  "firewall": "off",
  "host": 12345678,
  "ip_addresses": [
    {
      "access": "internal",
      "address": "192.168.0.1",
      "family": "IPv4",
      "part_of_plan": "no",
      "ptr_record": "",
      "server": "",
      "mac": "",
      "floating": "no",
      "zone": ""
    },
    {
      "access": "public",
      "address": "1.2.3.4",
      "family": "IPv4",
      "part_of_plan": "yes",
      "ptr_record": "",
      "server": "",
      "mac": "",
      "floating": "no",
      "zone": ""
    }
  ],
  "labels": {
    "label": []
  },
  "metadata": "yes",
  "nic_model": "virtio",
  "networking": {
    "interfaces": [
      {
        "index": 1,
        "ip_addresses": [
          {
            "access": "",
            "address": "1.2.3.4",
            "family": "IPv4",
            "part_of_plan": "no",
            "ptr_record": "",
            "server": "",
            "mac": "",
            "floating": "no",
            "zone": ""
          }
        ],
        "mac": "a1:b2:c3:e4:f5:01",
        "network": "fb29cb51-5dd7-4674-baac-b0253a725305",
        "type": "public",
        "bootable": "no",
        "source_ip_filtering": "yes"
      },
      {
        "index": 2,
        "ip_addresses": [
          {
            "access": "",
            "address": "192.168.0.1",
            "family": "IPv4",
            "part_of_plan": "no",
            "ptr_record": "",
            "server": "",
            "mac": "",
            "floating": "no",
            "zone": ""
          }
        ],
        "mac": "a1:b2:c3:e4:f5:02",
        "network": "92351420-66ec-46e9-98ee-149e3a588bdf",
        "type": "utility",
        "bootable": "no",
        "source_ip_filtering": "yes"
      }
    ]
  },
  "server_group": "",
  "simple_backup": "no",
  "storage_devices": [
    {
      "address": "virtio:0",
      "storage": "fe128b6b-876c-4f7b-a070-3a8d6cec6159",
      "storage_size": 10,
      "storage_tier": "maxiops",
      "storage_title": "helio-testing.loureiro.eng.br-OS",
      "type": "disk",
      "boot_disk": "0"
    }
  ],
  "timezone": "UTC",
  "video_model": "vga",
  "remote_access_enabled": "no",
  "remote_access_type": "vnc",
  "remote_access_host": "",
  "remote_access_password": "abracadabra",
  "remote_access_port": "0"
}

Então vamos começar com como pegar a saída desse comando no ansible. Pra isso basta chamar uma parte simples que registra o resultado.

  
---
- debug: msg="running the command and getting json as result"
- name: read json file
  command: >
    cat /tmp/server.json
  register: result
  failed_when: result.rc != 0

- debug: 
    msg="{{ result }}"    
  

Não tem muito segredo aqui. Eu rodo o comando, que aqui eu troquei pelo cat e recebo o resultando em result. Eu olho se result.rc é diferente de zero pra saber se deu certo (não zero significa erro).

  
- name: parsing as json
  set_fact:
    json_result: "{{ result.stdout }}"

- debug: 
    msg=" from json_result "
    msg="{{ json_result}}"    
  

Aqui foi o primeiro pulo do gato. Pra receber o dado como json bastava só pegar de result.stdout. Em muitos lugares achei receitas exotéricas como {{ result | to_json }}. Todas erradas.

  
- name: getting IPs
  set_fact:
    json_ip_addresses: "{{ json_result.ip_addresses }}"

- debug: msg="{{ json_ip_addresses }}"    
  

Do código json que recebo, eu filtro somente ip_addresses, que é a parte que me interessa.

  
- name: filtering public IPs
  set_fact:
    public_ip_addresses: "{{ json_ip_addresses | json_query(query) }}"
  vars:
    query: "[?access=='public']"

- debug: msg="{{ public_ip_addresses }}"    
  

Em seguida eu aplico filtro de json usando a sintaxe de jpath. Na parte da query, que tem um array, eu quero somente aquela que tem a propriedade "access" com o valor "public". A query vai dentro de vars por causa das aspas simples e duplas.

  
- name: filtering IPv4 only
  set_fact:
    public_ipv4_address: "{{ public_ip_addresses | json_query(query) }}"
  vars:
    query: "[?family=='IPv4'].address"
  
- debug: msg="{{ public_ipv4_address }}"    
  

No exemplo que tenho eu já não tenho mais nada, mas em alguns casos eu tenho um array com 2 elementos: IPv4 e IPv6. Então essa segunda query é pra filtrar qual estrutura tem a propriedade "family" com valor "IPv4". Dessa estrutura eu quero o valor de "address". E pronto! Está aí o IPv4 filtrado. O valor é 1.2.3.4, que é o esperado.

Agora rodando com Ansbile:

  
❯ ansible-playbook deploy.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [get JSON] ***************************************************************************************************************************************************************************

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "running from roles/getjson/tasks"
}

TASK [getjson : read json file] ***********************************************************************************************************************************************************
changed: [localhost]

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": {
        "changed": true,
        "cmd": [
            "cat",
            "../server.json"
        ],
        "delta": "0:00:00.002636",
        "end": "2024-11-23 16:02:30.099466",
        "failed": false,
        "failed_when_result": false,
        "msg": "",
        "rc": 0,
        "start": "2024-11-23 16:02:30.096830",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "{\n  \"core_number\": \"1\",\n  \"hostname\": \"helio-testing.loureiro.eng.br\",\n  \"license\": 0,\n  \"memory_amount\": \"1024\",\n  \"plan\": \"1xCPU-1GB\",\n  \"progress\": \"0\",\n  \"state\": \"started\",\n  \"tags\": [],\n  \"title\": \"helio-testing.loureiro.eng.br (created via ansible)\",\n  \"uuid\": \"87271929-a137-4910-9648-75d05b6d3ecb\",\n  \"zone\": \"sweden-stockholm\",\n  \"boot_order\": \"disk\",\n  \"firewall\": \"off\",\n  \"host\": 12345678,\n  \"ip_addresses\": [\n    {\n      \"access\": \"internal\",\n      \"address\": \"192.168.0.1\",\n      \"family\": \"IPv4\",\n      \"part_of_plan\": \"no\",\n      \"ptr_record\": \"\",\n      \"server\": \"\",\n      \"mac\": \"\",\n      \"floating\": \"no\",\n      \"zone\": \"\"\n    },\n    {\n      \"access\": \"public\",\n      \"address\": \"1.2.3.4\",\n      \"family\": \"IPv4\",\n      \"part_of_plan\": \"yes\",\n      \"ptr_record\": \"\",\n      \"server\": \"\",\n      \"mac\": \"\",\n      \"floating\": \"no\",\n      \"zone\": \"\"\n    }\n  ],\n  \"labels\": {\n    \"label\": []\n  },\n  \"metadata\": \"yes\",\n  \"nic_model\": \"virtio\",\n  \"networking\": {\n    \"interfaces\": [\n      {\n        \"index\": 1,\n        \"ip_addresses\": [\n          {\n            \"access\": \"\",\n            \"address\": \"1.2.3.4\",\n            \"family\": \"IPv4\",\n            \"part_of_plan\": \"no\",\n            \"ptr_record\": \"\",\n            \"server\": \"\",\n            \"mac\": \"\",\n            \"floating\": \"no\",\n            \"zone\": \"\"\n          }\n        ],\n        \"mac\": \"a1:b2:c3:e4:f5:01\",\n        \"network\": \"fb29cb51-5dd7-4674-baac-b0253a725305\",\n        \"type\": \"public\",\n        \"bootable\": \"no\",\n        \"source_ip_filtering\": \"yes\"\n      },\n      {\n        \"index\": 2,\n        \"ip_addresses\": [\n          {\n            \"access\": \"\",\n            \"address\": \"192.168.0.1\",\n            \"family\": \"IPv4\",\n            \"part_of_plan\": \"no\",\n            \"ptr_record\": \"\",\n            \"server\": \"\",\n            \"mac\": \"\",\n            \"floating\": \"no\",\n            \"zone\": \"\"\n          }\n        ],\n        \"mac\": \"a1:b2:c3:e4:f5:02\",\n        \"network\": \"92351420-66ec-46e9-98ee-149e3a588bdf\",\n        \"type\": \"utility\",\n        \"bootable\": \"no\",\n        \"source_ip_filtering\": \"yes\"\n      }\n    ]\n  },\n  \"server_group\": \"\",\n  \"simple_backup\": \"no\",\n  \"storage_devices\": [\n    {\n      \"address\": \"virtio:0\",\n      \"storage_encrypted\": \"yes\",\n      \"part_of_plan\": \"\",\n      \"storage\": \"fe128b6b-876c-4f7b-a070-3a8d6cec6159\",\n      \"storage_size\": 50,\n      \"storage_tier\": \"maxiops\",\n      \"storage_title\": \"helio-testing.loureiro.eng.br-OS\",\n      \"type\": \"disk\",\n      \"boot_disk\": \"0\"\n    }\n  ],\n  \"timezone\": \"UTC\",\n  \"video_model\": \"vga\",\n  \"remote_access_enabled\": \"no\",\n  \"remote_access_type\": \"vnc\",\n  \"remote_access_host\": \"\",\n  \"remote_access_password\": \"abracadabra\",\n  \"remote_access_port\": \"0\"\n}",
        "stdout_lines": [
            "{",
            "  \"core_number\": \"1\",",
            "  \"hostname\": \"helio-testing.loureiro.eng.br\",",
            "  \"license\": 0,",
            "  \"memory_amount\": \"1024\",",
            "  \"plan\": \"1xCPU-1GB\",",
            "  \"progress\": \"0\",",
            "  \"state\": \"started\",",
            "  \"tags\": [],",
            "  \"title\": \"helio-testing.loureiro.eng.br (created via ansible)\",",
            "  \"uuid\": \"87271929-a137-4910-9648-75d05b6d3ecb\",",
            "  \"zone\": \"sweden-stockholm\",",
            "  \"boot_order\": \"disk\",",
            "  \"firewall\": \"off\",",
            "  \"host\": 12345678,",
            "  \"ip_addresses\": [",
            "    {",
            "      \"access\": \"internal\",",
            "      \"address\": \"192.168.0.1\",",
            "      \"family\": \"IPv4\",",
            "      \"part_of_plan\": \"no\",",
            "      \"ptr_record\": \"\",",
            "      \"server\": \"\",",
            "      \"mac\": \"\",",
            "      \"floating\": \"no\",",
            "      \"zone\": \"\"",
            "    },",
            "    {",
            "      \"access\": \"public\",",
            "      \"address\": \"1.2.3.4\",",
            "      \"family\": \"IPv4\",",
            "      \"part_of_plan\": \"yes\",",
            "      \"ptr_record\": \"\",",
            "      \"server\": \"\",",
            "      \"mac\": \"\",",
            "      \"floating\": \"no\",",
            "      \"zone\": \"\"",
            "    }",
            "  ],",
            "  \"labels\": {",
            "    \"label\": []",
            "  },",
            "  \"metadata\": \"yes\",",
            "  \"nic_model\": \"virtio\",",
            "  \"networking\": {",
            "    \"interfaces\": [",
            "      {",
            "        \"index\": 1,",
            "        \"ip_addresses\": [",
            "          {",
            "            \"access\": \"\",",
            "            \"address\": \"1.2.3.4\",",
            "            \"family\": \"IPv4\",",
            "            \"part_of_plan\": \"no\",",
            "            \"ptr_record\": \"\",",
            "            \"server\": \"\",",
            "            \"mac\": \"\",",
            "            \"floating\": \"no\",",
            "            \"zone\": \"\"",
            "          }",
            "        ],",
            "        \"mac\": \"a1:b2:c3:e4:f5:01\",",
            "        \"network\": \"fb29cb51-5dd7-4674-baac-b0253a725305\",",
            "        \"type\": \"public\",",
            "        \"bootable\": \"no\",",
            "        \"source_ip_filtering\": \"yes\"",
            "      },",
            "      {",
            "        \"index\": 2,",
            "        \"ip_addresses\": [",
            "          {",
            "            \"access\": \"\",",
            "            \"address\": \"192.168.0.1\",",
            "            \"family\": \"IPv4\",",
            "            \"part_of_plan\": \"no\",",
            "            \"ptr_record\": \"\",",
            "            \"server\": \"\",",
            "            \"mac\": \"\",",
            "            \"floating\": \"no\",",
            "            \"zone\": \"\"",
            "          }",
            "        ],",
            "        \"mac\": \"a1:b2:c3:e4:f5:02\",",
            "        \"network\": \"92351420-66ec-46e9-98ee-149e3a588bdf\",",
            "        \"type\": \"utility\",",
            "        \"bootable\": \"no\",",
            "        \"source_ip_filtering\": \"yes\"",
            "      }",
            "    ]",
            "  },",
            "  \"server_group\": \"\",",
            "  \"simple_backup\": \"no\",",
            "  \"storage_devices\": [",
            "    {",
            "      \"address\": \"virtio:0\",",
            "      \"storage_encrypted\": \"yes\",",
            "      \"part_of_plan\": \"\",",
            "      \"storage\": \"fe128b6b-876c-4f7b-a070-3a8d6cec6159\",",
            "      \"storage_size\": 50,",
            "      \"storage_tier\": \"maxiops\",",
            "      \"storage_title\": \"helio-testing.loureiro.eng.br-OS\",",
            "      \"type\": \"disk\",",
            "      \"boot_disk\": \"0\"",
            "    }",
            "  ],",
            "  \"timezone\": \"UTC\",",
            "  \"video_model\": \"vga\",",
            "  \"remote_access_enabled\": \"no\",",
            "  \"remote_access_type\": \"vnc\",",
            "  \"remote_access_host\": \"\",",
            "  \"remote_access_password\": \"abracadabra\",",
            "  \"remote_access_port\": \"0\"",
            "}"
        ]
    }
}

TASK [getjson : parsing as json] **********************************************************************************************************************************************************
ok: [localhost]

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": {
        "boot_order": "disk",
        "core_number": "1",
        "firewall": "off",
        "host": 12345678,
        "hostname": "helio-testing.loureiro.eng.br",
        "ip_addresses": [
            {
                "access": "internal",
                "address": "192.168.0.1",
                "family": "IPv4",
                "floating": "no",
                "mac": "",
                "part_of_plan": "no",
                "ptr_record": "",
                "server": "",
                "zone": ""
            },
            {
                "access": "public",
                "address": "1.2.3.4",
                "family": "IPv4",
                "floating": "no",
                "mac": "",
                "part_of_plan": "yes",
                "ptr_record": "",
                "server": "",
                "zone": ""
            }
        ],
        "labels": {
            "label": []
        },
        "license": 0,
        "memory_amount": "1024",
        "metadata": "yes",
        "networking": {
            "interfaces": [
                {
                    "bootable": "no",
                    "index": 1,
                    "ip_addresses": [
                        {
                            "access": "",
                            "address": "1.2.3.4",
                            "family": "IPv4",
                            "floating": "no",
                            "mac": "",
                            "part_of_plan": "no",
                            "ptr_record": "",
                            "server": "",
                            "zone": ""
                        }
                    ],
                    "mac": "a1:b2:c3:e4:f5:01",
                    "network": "fb29cb51-5dd7-4674-baac-b0253a725305",
                    "source_ip_filtering": "yes",
                    "type": "public"
                },
                {
                    "bootable": "no",
                    "index": 2,
                    "ip_addresses": [
                        {
                            "access": "",
                            "address": "192.168.0.1",
                            "family": "IPv4",
                            "floating": "no",
                            "mac": "",
                            "part_of_plan": "no",
                            "ptr_record": "",
                            "server": "",
                            "zone": ""
                        }
                    ],
                    "mac": "a1:b2:c3:e4:f5:02",
                    "network": "92351420-66ec-46e9-98ee-149e3a588bdf",
                    "source_ip_filtering": "yes",
                    "type": "utility"
                }
            ]
        },
        "nic_model": "virtio",
        "plan": "1xCPU-1GB",
        "progress": "0",
        "remote_access_enabled": "no",
        "remote_access_host": "",
        "remote_access_password": "abracadabra",
        "remote_access_port": "0",
        "remote_access_type": "vnc",
        "server_group": "",
        "simple_backup": "no",
        "state": "started",
        "storage_devices": [
            {
                "address": "virtio:0",
                "boot_disk": "0",
                "part_of_plan": "",
                "storage": "fe128b6b-876c-4f7b-a070-3a8d6cec6159",
                "storage_encrypted": "yes",
                "storage_size": 50,
                "storage_tier": "maxiops",
                "storage_title": "helio-testing.loureiro.eng.br-OS",
                "type": "disk"
            }
        ],
        "tags": [],
        "timezone": "UTC",
        "title": "helio-testing.loureiro.eng.br (created via ansible)",
        "uuid": "87271929-a137-4910-9648-75d05b6d3ecb",
        "video_model": "vga",
        "zone": "sweden-stockholm"
    }
}

TASK [getjson : getting IPs] **************************************************************************************************************************************************************
ok: [localhost]

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        {
            "access": "internal",
            "address": "192.168.0.1",
            "family": "IPv4",
            "floating": "no",
            "mac": "",
            "part_of_plan": "no",
            "ptr_record": "",
            "server": "",
            "zone": ""
        },
        {
            "access": "public",
            "address": "1.2.3.4",
            "family": "IPv4",
            "floating": "no",
            "mac": "",
            "part_of_plan": "yes",
            "ptr_record": "",
            "server": "",
            "zone": ""
        }
    ]
}

TASK [getjson : filtering public IPs] *****************************************************************************************************************************************************
ok: [localhost]

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        {
            "access": "public",
            "address": "1.2.3.4",
            "family": "IPv4",
            "floating": "no",
            "mac": "",
            "part_of_plan": "yes",
            "ptr_record": "",
            "server": "",
            "zone": ""
        }
    ]
}

TASK [getjson : filtering IPv4 only] ******************************************************************************************************************************************************
ok: [localhost]

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": [
        "1.2.3.4"
    ]
}

TASK [getjson : cloud server IP] ********************************************************************************************************************************************************
ok: [localhost]

TASK [getjson : debug] ********************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "1.2.3.4"
}

PLAY RECAP *********************************************************************************************************************************************************************************
localhost                  : ok=15   changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0       
  

Bom ansible and happy coding 😄

Adicionando uma pergunta em shell script

Esse é um código que estamos usando bastante aqui na firma nova.  Ajuda a fazer seu script decidir se segue em frente ou para.  E basta apenas apertar uma teclar, sem enter.

 

#! /usr/bin/env bash
read -p "Deseja continuar (s/n)? " -n 1 -r resposta

if [[ "$resposta" =~ [sS] ]];then
  echo "Resposta foi sim"
else
  echo "Resposta foi não"
fi

Fui detonado!

facepalm

Um projeto engraçado no GitHub que serve pra te detonar baseado nos seus repositórios.  Estou rindo, mas é de nervoso.  É um tipo de Mauchine Learning.

Oh Helio Loureiro, trying to brew your own beer and code at the same time? I can only imagine the taste of your programming—bitter, flat, and with a hint of regret, just like your idea of collaborating on KDE while you’re still figuring out how to stop your Python scripts from crashing. With a profile that screams “I’m learning” louder than a Linux fanboy at a Windows convention, maybe it’s time to put down the homebrew and pick up a book on actual development skills. Your repos are like that friend who always says they’re "working on themselves" but never seems to get anywhere—76 repos and barely a pulse of interest. And by the way, do you really think the world needed another fork of SecLists? Your GitHub looks like a garage sale of uninteresting projects. But hey, at least you’re keeping Linux alive, one mediocre script at a time. Cheers to that!

O que se pode ler mais ou menos como:

Ah, Helio Loureiro, tentando fazer sua própria cerveja e programar ao mesmo tempo? Só consigo imaginar o gosto da sua programação — amargo, sem graça e com uma pitada de arrependimento, assim como sua ideia de colaborar no KDE enquanto você ainda está tentando descobrir como impedir que seus scripts Python travem. Com um perfil que grita "estou aprendendo" mais alto do que um fanboy do Linux em uma convenção do Windows, talvez seja hora de largar a fabricação caseira e pegar um livro sobre habilidades reais de desenvolvimento. Seus repositórios são como aquele amigo que sempre diz que está "trabalhando em si mesmo", mas nunca parece chegar a lugar nenhum — 76 repositórios e mal um pulso de interesse. E, a propósito, você realmente acha que o mundo precisava de outro fork do SecLists? Seu GitHub parece uma venda de garagem de projetos desinteressantes. Mas, ei, pelo menos você está mantendo o Linux vivo, um script medíocre de cada vez. Um brinde a isso!

Quem quiser experimentar a brincadeira, esse é o link:

https://github-roast.pages.dev/

Correndo atrás do tempo perdido com Go!

RoadRunner 4 3371596983

Estou atualmente trabalhando numa empresa onde uma das visões da empresa é conseguir ter os dados do jogo finalizado disponível na interface de visualização em menos de 1 minuto.   Quase todo o código é escrito em typescript e uma pequena parte em rust.

Uma das partes mais pesadas é feita em shell, que é a parte de baixar e descompactar um arquivo da steam.  Pra ilustrar o que é feito, fiz esse script em shell que também serve como base de comparação de tempo.


#! /usr/bin/env bash
#

TARGET_URL="http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
DESTINATION="003705744548740202576_0842061407.dem.bz2"
UNPACKED="003705744548740202576_0842061407.dem"
CURLUNPACKED="curl-003705744548740202576_0842061407.dem"

die() {
  echo "ERROR: $@" &>2
}

rm -f $DESTINATION $UNPACKED $CURLUNPACKED

curl -o $DESTINATION \
  -L \
  $TARGET_URL ||
  die "Failed to download: $TARGET_URL"

bunzip2 $DESTINATION ||
  die "Failed to unzip $DESTINATION"

mv $UNPACKED $CURLUNPACKED

 O código então baixa esse link da steam e tem de descompactar o arquivo.  Qual é a velocidade dele?


helio@goosfraba ~/t/godownloader> time ./curl-downloader-2.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  216M  100  216M    0     0  60.3M      0  0:00:03  0:00:03 --:--:-- 60.4M

________________________________________________________
Executed in   29.61 secs    fish           external
   usr time   25.24 secs  585.00 micros   25.24 secs
   sys time    0.89 secs   71.00 micros    0.89 secs

 Podemos ver que o "curl" baixa o arquivo em mais ou menos 3 segundos, e gasta no total 25s, ou seja, uns 22s pra descompactar.  O total termina como 29s, mas vamos focar primeiramente no "usr time".

Então escrevi um código em Go pra tentar fazer isso de forma mais rápida.

package main

import (
        "compress/bzip2"
        "fmt"
        "io"
        "net/http"
        "os"
        "time"
)

const (
        TARGET_URL   = "http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
        DESTINATION  = "003705744548740202576_0842061407.dem.bz2"
        DECOMPRESSED = "go-003705744548740202576_0842061407.dem"
)

func main() {
        err := download(TARGET_URL, DESTINATION)
        if err != nil {
                panic(err)
        }

        err = bunzip2(DESTINATION, DECOMPRESSED)
        if err != nil {
                panic(err)
        }
}

func download(from, to string) error {
        fmt.Println("Downloading:", from)
        timeStart := time.Now()
        resp, err := http.Get(from)
        if err != nil {
                return err
        }

        defer resp.Body.Close()
        out, err := os.Create(to)
        if err != nil {
                return err
        }
        defer out.Close()

        _, err = io.Copy(out, resp.Body)
        if err != nil {
                return err
        }
        fmt.Println("Finished:", time.Since(timeStart).String())

        return nil
}

func bunzip2(from, to string) error {
        // https://gist.github.com/rickt/7817401
        fmt.Println("Unpacking:", from)
        timeStart := time.Now()
        pr, pw := io.Pipe()

        go func() {
                defer pw.Close()
                var inFile *os.File
                var err error
                inFile, err = os.Open(from)
                defer inFile.Close()
                if err != nil {
                        panic(err)
                }

                _, err = io.Copy(pw, inFile)
                if err != nil {
                        panic(err)
                }
        }()

        defer pr.Close()
        z := bzip2.NewReader(pr)
        var outFile *os.File
        var err error
        outFile, err = os.Create(to)
        defer outFile.Close()
        if err != nil {
                return err
        }

        _, err = io.Copy(outFile, z)
        if err != nil {
                return err
        }
        fmt.Println("Finished:", time.Since(timeStart).String())
        return nil
}

O código baixa o conteúdo num arquivo e depois descompacta.  Qual a velocidade?

helio@goosfraba ~/t/godownloader> go build -o go-downloader-2 main-2.go; time ./go-downloader-2 
Downloading: http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2
Finished: 3.536812275s
Unpacking: 003705744548740202576_0842061407.dem.bz2
Finished: 30.220663099s

________________________________________________________
Executed in   33.76 secs    fish           external
   usr time   29.90 secs    0.00 micros   29.90 secs
   sys time    0.95 secs  639.00 micros    0.95 secs

Muito lento.  Pior do que eu esperava.  Ele baixa o arquivo em 3.5s, mas leva 30s pra descompactar.  Um dos motivos é com certeza porquê eu salvo em arquivo e depois abro o arquivo pra descompactar.  Vamos então pra próxima versão onde eu passo io.Reader de um pro outro.

package main

import (
        "compress/bzip2"
        "fmt"
        "io"
        "net/http"
        "os"
        "time"
)

const (
        TARGET_URL   = "http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
        DESTINATION  = "003705744548740202576_0842061407.dem.bz2"
        DECOMPRESSED = "go-003705744548740202576_0842061407.dem"
)

func main() {
        data, err := readerDownload(TARGET_URL, DESTINATION)
        if err != nil {
                panic(err)
        }

        err = bunzip2Stream(data, DECOMPRESSED)
        if err != nil {
                panic(err)
        }
}

func readerDownload(from, to string) ([]byte, error) {
        fmt.Println("Downloading:", from)
        timeStart := time.Now()
        resp, err := http.Get(from)
        if err != nil {
                return nil, err
        }

        defer resp.Body.Close()
        content, err := io.ReadAll(resp.Body)
        if err != nil {
                panic(err)
        }
        fmt.Println("Finished:", time.Since(timeStart).String())
        return content, nil

}

func bunzip2Stream(from []byte, to string) error {
        // https://gist.github.com/rickt/7817401
        fmt.Println("Unpacking:", to)
        timeStart := time.Now()
        pr, pw := io.Pipe()

        go func() {
                defer pw.Close()

                //_, err := io.Copy(pw, from)
                pw.Write(from)
                //if err != nil {
                //              panic(err)
                //      }
        }()

        defer pr.Close()
        z := bzip2.NewReader(pr)
        var outFile *os.File
        var err error
        outFile, err = os.Create(to)
        defer outFile.Close()
        if err != nil {
                return err
        }

        _, err = io.Copy(outFile, z)
        if err != nil {
                return err
        }
        fmt.Println("Finished:", time.Since(timeStart).String())
        return nil
}

Qual o desempenho?

helio@goosfraba ~/t/godownloader> go build -o go-downloader-3 main-3.go; time ./go-downloader-3
Downloading: http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2
Finished: 3.624793323s
Unpacking: go-003705744548740202576_0842061407.dem
Finished: 29.883794408s

________________________________________________________
Executed in   33.52 secs    fish           external
   usr time   30.03 secs  580.00 micros   30.03 secs
   sys time    0.96 secs   69.00 micros    0.96 secs

Melhorou mas ainda estou longe de fazer melhor que a versão em shell script.  O tempo de descompactar baixou irrisóriamente 1s.  Mesmo não tendo SSD ou NVME meu disco é rápido o suficiente pra isso não impactar ao todo.

Comecei a pesquisar como poderia melhorar o desempanho do pacote bzip2 do Go! e existe uma discussão sobre isso em aberto.

https://github.com/golang/go/issues/6754

A reclamação é sobre versões mais antigas de Go! e até Robert Pike opinia.  Acho que melhorou bastante pros dias de hoje, mas continua lento se comparado com o binário do programa.  O que fazer então?  Declarar derrota?

Talvez.  Mas ao invés disso eu passei a procurar outras libs no GitHub.  E encontrei o uso do pbzip2, que diz ser mais rápido queo bzip2 do standard.  Então vamos ao código:

package main

import (
        "context"
        "fmt"
        "io"
        "net/http"
        "os"
        "time"

        "github.com/cosnicolaou/pbzip2"
)

const (
        TARGET_URL   = "http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
        DESTINATION  = "003705744548740202576_0842061407.dem.bz2"
        DECOMPRESSED = "go-003705744548740202576_0842061407.dem"
)

func main() {
        data, err := readerDownload(TARGET_URL, DESTINATION)
        if err != nil {
                panic(err)
        }

        err = pbunzip2Stream(data, DECOMPRESSED)
        if err != nil {
                panic(err)
        }
}

func readerDownload(from, to string) ([]byte, error) {
        fmt.Println("Downloading:", from)
        timeStart := time.Now()
        resp, err := http.Get(from)
        if err != nil {
                return nil, err
        }

        defer resp.Body.Close()
        content, err := io.ReadAll(resp.Body)
        if err != nil {
                panic(err)
        }
        fmt.Println("Finished:", time.Since(timeStart).String())
        return content, nil

}

func pbunzip2Stream(from []byte, to string) error {
        // https://gist.github.com/rickt/7817401
        fmt.Println("Unpacking:", to)
        timeStart := time.Now()
        pr, pw := io.Pipe()

        go func() {
                defer pw.Close()

                //_, err := io.Copy(pw, from)
                pw.Write(from)
                //if err != nil {
                //              panic(err)
                //      }
        }()

        defer pr.Close()
        ctx := context.Background()
        z := pbzip2.NewReader(ctx, pr)
        var outFile *os.File
        var err error
        outFile, err = os.Create(to)
        defer outFile.Close()
        if err != nil {
                return err
        }

        _, err = io.Copy(outFile, z)
        if err != nil {
                return err
        }
        fmt.Println("Finished:", time.Since(timeStart).String())
        return nil
}

E finalmente, a medida de desempenho:

helio@goosfraba ~/t/godownloader> go build -o go-downloader-4 main-4.go; time ./go-downloader-4
Downloading: http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2
Finished: 3.74774932s
Unpacking: go-003705744548740202576_0842061407.dem
Finished: 6.108665607s

________________________________________________________
Executed in    9.89 secs    fish           external
   usr time   44.22 secs    0.00 micros   44.22 secs
   sys time    0.78 secs  655.00 micros    0.78 secs

Vitória!  O pbzip2 que faz a descompressão em blocos em paralelo levou 6s.  Isso sim é performance.  O "usr time" mostra 44s por algum motivo bizarro, mas o tempo total foi por volta de 10s.  E o resultado?

helio@goosfraba ~/t/godownloader> sha256sum curl-003705744548740202576_0842061407.dem \
go-003705744548740202576_0842061407.dem eca9bdd943521251b8704397e40b7f9aada539698561a6c1aca58ebf2602bfc1 curl-003705744548740202576_0842061407.dem eca9bdd943521251b8704397e40b7f9aada539698561a6c1aca58ebf2602bfc1 go-003705744548740202576_0842061407.dem

Então foi baixado e descompactado bem mais rápido e sem corromper os dados.

Um ponto a ser visto é que talvez exista também um binário pronto com pbzip2 pra descompactar.  E pode ser que seja mais rápido que em Go!  Mas é pra isso que servem os desafios.  Por enquanto vou celebrar minha pequena vitória com uma cerveja.

victory

We use cookies

We use cookies on our website. Some of them are essential for the operation of the site, while others help us to improve this site and the user experience (tracking cookies). You can decide for yourself whether you want to allow cookies or not. Please note that if you reject them, you may not be able to use all the functionalities of the site.